aboutsummaryrefslogtreecommitdiff
path: root/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
blob: 4475a0851fad8ebe1eaba54e76445862e9f7fcd9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Copyright 2019 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include <cinttypes>

#include "pw_preprocessor/compiler.h"
#include "pw_sys_io/sys_io.h"

namespace {

// Default core clock. This is technically not a constant, but since this app
// doesn't change the system clock a constant will suffice.
constexpr uint32_t kSystemCoreClock = 16000000;

// Base address for everything peripheral-related on the STM32F4xx.
constexpr uint32_t kPeripheralBaseAddr = 0x40000000u;
// Base address for everything AHB1-related on the STM32F4xx.
constexpr uint32_t kAhb1PeripheralBase = kPeripheralBaseAddr + 0x00020000U;
// Base address for everything APB2-related on the STM32F4xx.
constexpr uint32_t kApb2PeripheralBase = kPeripheralBaseAddr + 0x00010000U;

// Reset/clock configuration block (RCC).
// `reserved` fields are unimplemented features, and are present to ensure
// proper alignment of registers that are in use.
PW_PACKED(struct) RccBlock {
  uint32_t reserved1[12];
  uint32_t ahb1_config;
  uint32_t reserved2[4];
  uint32_t apb2_config;
};

// Mask for ahb1_config (AHB1ENR) to enable the "A" GPIO pins.
constexpr uint32_t kGpioAEnable = 0x1u;

// Mask for apb2_config (APB2ENR) to enable USART1.
constexpr uint32_t kUsart1Enable = 0x1u << 4;

// GPIO register block definition.
PW_PACKED(struct) GpioBlock {
  uint32_t modes;
  uint32_t out_type;
  uint32_t out_speed;
  uint32_t pull_up_down;
  uint32_t input_data;
  uint32_t output_data;
  uint32_t gpio_bit_set;
  uint32_t port_config_lock;
  uint32_t alt_low;
  uint32_t alt_high;
};

// Constants related to GPIO mode register masks.
constexpr uint32_t kGpio9PortModePos = 18;
constexpr uint32_t kGpio10PortModePos = 20;
constexpr uint32_t kGpioPortModeAlternate = 2;

// Constants related to GPIO port speed register masks.
constexpr uint32_t kGpio9PortSpeedPos = 18;
constexpr uint32_t kGpio10PortSpeedPos = 20;
constexpr uint32_t kGpioSpeedVeryHigh = 3;

// Constants related to GPIO pull up/down resistor type masks.
constexpr uint32_t kGpio9PullTypePos = 18;
constexpr uint32_t kGpio10PullTypePos = 20;
constexpr uint32_t kPullTypePullUp = 1;

// Constants related to GPIO port speed register masks.
constexpr uint32_t kGpio9AltModeHighPos = 4;
constexpr uint32_t kGpio10AltModeHighPos = 8;

// Alternate function for pins A9 and A10 that enable USART1.
constexpr uint8_t kGpioAlternateFunctionUsart1 = 0x07u;

// USART status flags.
constexpr uint32_t kTxRegisterEmpty = 0x1u << 7;

// USART configuration flags for config1 register.
// Note: a large number of configuration flags have been omitted as they default
// to reasonable values and we don't need to change them.
constexpr uint32_t kReceiveEnable = 0x1 << 2;
constexpr uint32_t kTransmitEnable = 0x1 << 3;
constexpr uint32_t kEnableUsart = 0x1 << 13;

// USART configuration flags for status register.
constexpr uint32_t kReadDataReady = 0x1u << 5;

// Layout of memory mapped registers for USART blocks.
PW_PACKED(struct) UsartBlock {
  uint32_t status;
  // Only the lower 8 bits are valid. Read for RX data, write to TX data.
  uint32_t data_register;
  uint32_t baud_rate;
  uint32_t config1;
  uint32_t config2;
  uint32_t config3;
  uint32_t config4;
};

// Sets the UART baud register using the peripheral clock and target baud rate.
// These calculations are specific to the default oversample by 16 mode.
// TODO(amontanez): Document magic calculations in full UART implementation.
uint32_t CalcBaudRegister(uint32_t clock, uint32_t target_baud) {
  uint32_t div_fac = (clock * 25) / (4 * target_baud);
  uint32_t mantissa = div_fac / 100;
  uint32_t fraction = ((div_fac - mantissa * 100) * 16 + 50) / 100;

  return (mantissa << 4) + (fraction & 0xFFu);
}

// Declare a reference to the memory mapped RCC block.
volatile RccBlock& platform_rcc =
    *reinterpret_cast<volatile RccBlock*>(kAhb1PeripheralBase + 0x3800U);

// Declare a reference to the 'A' GPIO memory mapped block.
volatile GpioBlock& gpio_a =
    *reinterpret_cast<volatile GpioBlock*>(kAhb1PeripheralBase + 0x0000U);

// Declare a reference to the memory mapped block for USART1.
volatile UsartBlock& usart1 =
    *reinterpret_cast<volatile UsartBlock*>(kApb2PeripheralBase + 0x1000U);

}  // namespace

extern "C" void pw_sys_io_stm32f429_Init() {
  // Enable 'A' GIPO clocks.
  platform_rcc.ahb1_config |= kGpioAEnable;

  // Enable Uart TX pin.
  // Output type defaults to push-pull (rather than open/drain).
  gpio_a.modes |= kGpioPortModeAlternate << kGpio9PortModePos;
  gpio_a.out_speed |= kGpioSpeedVeryHigh << kGpio9PortSpeedPos;
  gpio_a.pull_up_down |= kPullTypePullUp << kGpio9PullTypePos;
  gpio_a.alt_high |= kGpioAlternateFunctionUsart1 << kGpio9AltModeHighPos;

  // Enable Uart RX pin.
  // Output type defaults to push-pull (rather than open/drain).
  gpio_a.modes |= kGpioPortModeAlternate << kGpio10PortModePos;
  gpio_a.out_speed |= kGpioSpeedVeryHigh << kGpio10PortSpeedPos;
  gpio_a.pull_up_down |= kPullTypePullUp << kGpio10PullTypePos;
  gpio_a.alt_high |= kGpioAlternateFunctionUsart1 << kGpio10AltModeHighPos;

  // Initialize USART1. Initialized to 8N1 at the specified baud rate.
  platform_rcc.apb2_config |= kUsart1Enable;

  // Warning: Normally the baud rate register calculation is based off
  // peripheral 2 clock. For this code, the peripheral clock defaults to
  // the system core clock so it can be used directly.
  usart1.baud_rate = CalcBaudRegister(kSystemCoreClock, /*target_baud=*/115200);

  usart1.config1 = kEnableUsart | kReceiveEnable | kTransmitEnable;
}

namespace pw::sys_io {

// Wait for a byte to read on USART1. This blocks until a byte is read. This is
// extremely inefficient as it requires the target to burn CPU cycles polling to
// see if a byte is ready yet.
Status ReadByte(std::byte* dest) {
  while (true) {
    if (TryReadByte(dest).ok()) {
      return OkStatus();
    }
  }
}

// Wait for a byte to read on USART1. This blocks until a byte is read. This is
// extremely inefficient as it requires the target to burn CPU cycles polling to
// see if a byte is ready yet.
Status TryReadByte(std::byte* dest) {
  if (!(usart1.status & kReadDataReady)) {
    return Status::Unavailable();
  }
  *dest = static_cast<std::byte>(usart1.data_register);
  return OkStatus();
}

// Send a byte over USART1. Since this blocks on every byte, it's rather
// inefficient. At the default baud rate of 115200, one byte blocks the CPU for
// ~87 micro seconds. This means it takes only 10 bytes to block the CPU for
// 1ms!
Status WriteByte(std::byte b) {
  // Wait for TX buffer to be empty. When the buffer is empty, we can write
  // a value to be dumped out of UART.
  while (!(usart1.status & kTxRegisterEmpty)) {
  }
  usart1.data_register = static_cast<uint32_t>(b);
  return OkStatus();
}

// Writes a string using pw::sys_io, and add newline characters at the end.
StatusWithSize WriteLine(const std::string_view& s) {
  size_t chars_written = 0;
  StatusWithSize result = WriteBytes(as_bytes(span(s)));
  if (!result.ok()) {
    return result;
  }
  chars_written += result.size();

  // Write trailing newline.
  result = WriteBytes(as_bytes(span("\r\n", 2)));
  chars_written += result.size();

  return StatusWithSize(result.status(), chars_written);
}

}  // namespace pw::sys_io