xref: /aosp_15_r20/external/pigweed/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2019 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include <cinttypes>
16 
17 #include "pw_preprocessor/compiler.h"
18 #include "pw_sys_io/sys_io.h"
19 
20 namespace {
21 
22 // Default core clock. This is technically not a constant, but since this app
23 // doesn't change the system clock a constant will suffice.
24 constexpr uint32_t kSystemCoreClock = 16000000;
25 
26 // Base address for everything peripheral-related on the STM32F4xx.
27 constexpr uint32_t kPeripheralBaseAddr = 0x40000000u;
28 // Base address for everything AHB1-related on the STM32F4xx.
29 constexpr uint32_t kAhb1PeripheralBase = kPeripheralBaseAddr + 0x00020000U;
30 // Base address for everything APB2-related on the STM32F4xx.
31 constexpr uint32_t kApb2PeripheralBase = kPeripheralBaseAddr + 0x00010000U;
32 
33 // Reset/clock configuration block (RCC).
34 // `reserved` fields are unimplemented features, and are present to ensure
35 // proper alignment of registers that are in use.
PW_PACKED(struct)36 PW_PACKED(struct) RccBlock {
37   uint32_t reserved1[12];
38   uint32_t ahb1_config;
39   uint32_t reserved2[4];
40   uint32_t apb2_config;
41 };
42 
43 // Mask for ahb1_config (AHB1ENR) to enable the "A" GPIO pins.
44 constexpr uint32_t kGpioAEnable = 0x1u;
45 
46 // Mask for apb2_config (APB2ENR) to enable USART1.
47 constexpr uint32_t kUsart1Enable = 0x1u << 4;
48 
49 // GPIO register block definition.
PW_PACKED(struct)50 PW_PACKED(struct) GpioBlock {
51   uint32_t modes;
52   uint32_t out_type;
53   uint32_t out_speed;
54   uint32_t pull_up_down;
55   uint32_t input_data;
56   uint32_t output_data;
57   uint32_t gpio_bit_set;
58   uint32_t port_config_lock;
59   uint32_t alt_low;
60   uint32_t alt_high;
61 };
62 
63 // Constants related to GPIO mode register masks.
64 constexpr uint32_t kGpio9PortModePos = 18;
65 constexpr uint32_t kGpio10PortModePos = 20;
66 constexpr uint32_t kGpioPortModeAlternate = 2;
67 
68 // Constants related to GPIO port speed register masks.
69 constexpr uint32_t kGpio9PortSpeedPos = 18;
70 constexpr uint32_t kGpio10PortSpeedPos = 20;
71 constexpr uint32_t kGpioSpeedVeryHigh = 3;
72 
73 // Constants related to GPIO pull up/down resistor type masks.
74 constexpr uint32_t kGpio9PullTypePos = 18;
75 constexpr uint32_t kGpio10PullTypePos = 20;
76 constexpr uint32_t kPullTypePullUp = 1;
77 
78 // Constants related to GPIO port speed register masks.
79 constexpr uint32_t kGpio9AltModeHighPos = 4;
80 constexpr uint32_t kGpio10AltModeHighPos = 8;
81 
82 // Alternate function for pins A9 and A10 that enable USART1.
83 constexpr uint8_t kGpioAlternateFunctionUsart1 = 0x07u;
84 
85 // USART status flags.
86 constexpr uint32_t kTxRegisterEmpty = 0x1u << 7;
87 
88 // USART configuration flags for config1 register.
89 // Note: a large number of configuration flags have been omitted as they default
90 // to reasonable values and we don't need to change them.
91 constexpr uint32_t kReceiveEnable = 0x1 << 2;
92 constexpr uint32_t kTransmitEnable = 0x1 << 3;
93 constexpr uint32_t kEnableUsart = 0x1 << 13;
94 
95 // USART configuration flags for status register.
96 constexpr uint32_t kReadDataReady = 0x1u << 5;
97 
98 // Layout of memory mapped registers for USART blocks.
PW_PACKED(struct)99 PW_PACKED(struct) UsartBlock {
100   uint32_t status;
101   // Only the lower 8 bits are valid. Read for RX data, write to TX data.
102   uint32_t data_register;
103   uint32_t baud_rate;
104   uint32_t config1;
105   uint32_t config2;
106   uint32_t config3;
107   uint32_t config4;
108 };
109 
110 // Sets the UART baud register using the peripheral clock and target baud rate.
111 // These calculations are specific to the default oversample by 16 mode.
112 // TODO(amontanez): Document magic calculations in full UART implementation.
CalcBaudRegister(uint32_t clock,uint32_t target_baud)113 uint32_t CalcBaudRegister(uint32_t clock, uint32_t target_baud) {
114   uint32_t div_fac = (clock * 25) / (4 * target_baud);
115   uint32_t mantissa = div_fac / 100;
116   uint32_t fraction = ((div_fac - mantissa * 100) * 16 + 50) / 100;
117 
118   return (mantissa << 4) + (fraction & 0xFFu);
119 }
120 
121 // Declare a reference to the memory mapped RCC block.
122 volatile RccBlock& platform_rcc =
123     *reinterpret_cast<volatile RccBlock*>(kAhb1PeripheralBase + 0x3800U);
124 
125 // Declare a reference to the 'A' GPIO memory mapped block.
126 volatile GpioBlock& gpio_a =
127     *reinterpret_cast<volatile GpioBlock*>(kAhb1PeripheralBase + 0x0000U);
128 
129 // Declare a reference to the memory mapped block for USART1.
130 volatile UsartBlock& usart1 =
131     *reinterpret_cast<volatile UsartBlock*>(kApb2PeripheralBase + 0x1000U);
132 
133 }  // namespace
134 
pw_sys_io_stm32f429_Init()135 extern "C" void pw_sys_io_stm32f429_Init() {
136   // Enable 'A' GIPO clocks.
137   platform_rcc.ahb1_config = platform_rcc.ahb1_config | kGpioAEnable;
138 
139   // Enable Uart TX pin.
140   // Output type defaults to push-pull (rather than open/drain).
141   gpio_a.modes = gpio_a.modes | (kGpioPortModeAlternate << kGpio9PortModePos);
142   gpio_a.out_speed =
143       gpio_a.out_speed | (kGpioSpeedVeryHigh << kGpio9PortSpeedPos);
144   gpio_a.pull_up_down =
145       gpio_a.pull_up_down | (kPullTypePullUp << kGpio9PullTypePos);
146   gpio_a.alt_high =
147       gpio_a.alt_high | (kGpioAlternateFunctionUsart1 << kGpio9AltModeHighPos);
148 
149   // Enable Uart RX pin.
150   // Output type defaults to push-pull (rather than open/drain).
151   gpio_a.modes = gpio_a.modes | (kGpioPortModeAlternate << kGpio10PortModePos);
152   gpio_a.out_speed =
153       gpio_a.out_speed | (kGpioSpeedVeryHigh << kGpio10PortSpeedPos);
154   gpio_a.pull_up_down =
155       gpio_a.pull_up_down | (kPullTypePullUp << kGpio10PullTypePos);
156   gpio_a.alt_high =
157       gpio_a.alt_high | (kGpioAlternateFunctionUsart1 << kGpio10AltModeHighPos);
158 
159   // Initialize USART1. Initialized to 8N1 at the specified baud rate.
160   platform_rcc.apb2_config = platform_rcc.apb2_config | (kUsart1Enable);
161 
162   // Warning: Normally the baud rate register calculation is based off
163   // peripheral 2 clock. For this code, the peripheral clock defaults to
164   // the system core clock so it can be used directly.
165   usart1.baud_rate = CalcBaudRegister(kSystemCoreClock, /*target_baud=*/115200);
166 
167   usart1.config1 = kEnableUsart | kReceiveEnable | kTransmitEnable;
168 }
169 
170 namespace pw::sys_io {
171 
172 // Wait for a byte to read on USART1. This blocks until a byte is read. This is
173 // extremely inefficient as it requires the target to burn CPU cycles polling to
174 // see if a byte is ready yet.
ReadByte(std::byte * dest)175 Status ReadByte(std::byte* dest) {
176   while (true) {
177     if (TryReadByte(dest).ok()) {
178       return OkStatus();
179     }
180   }
181 }
182 
183 // Wait for a byte to read on USART1. This blocks until a byte is read. This is
184 // extremely inefficient as it requires the target to burn CPU cycles polling to
185 // see if a byte is ready yet.
TryReadByte(std::byte * dest)186 Status TryReadByte(std::byte* dest) {
187   if (!(usart1.status & kReadDataReady)) {
188     return Status::Unavailable();
189   }
190   *dest = static_cast<std::byte>(usart1.data_register);
191   return OkStatus();
192 }
193 
194 // Send a byte over USART1. Since this blocks on every byte, it's rather
195 // inefficient. At the default baud rate of 115200, one byte blocks the CPU for
196 // ~87 micro seconds. This means it takes only 10 bytes to block the CPU for
197 // 1ms!
WriteByte(std::byte b)198 Status WriteByte(std::byte b) {
199   // Wait for TX buffer to be empty. When the buffer is empty, we can write
200   // a value to be dumped out of UART.
201   while (!(usart1.status & kTxRegisterEmpty)) {
202   }
203   usart1.data_register = static_cast<uint32_t>(b);
204   return OkStatus();
205 }
206 
207 // Writes a string using pw::sys_io, and add newline characters at the end.
WriteLine(std::string_view s)208 StatusWithSize WriteLine(std::string_view s) {
209   size_t chars_written = 0;
210   StatusWithSize result = WriteBytes(as_bytes(span(s)));
211   if (!result.ok()) {
212     return result;
213   }
214   chars_written += result.size();
215 
216   // Write trailing newline.
217   result = WriteBytes(as_bytes(span("\r\n", 2)));
218   chars_written += result.size();
219 
220   return StatusWithSize(result.status(), chars_written);
221 }
222 
223 }  // namespace pw::sys_io
224