Add stm32f429i-disc1 target and pw_dumb_io Adds both the stm32f429i-disc1 target and a pw_dumb_io uart-based backend. Change-Id: If24379118fc2877485653d61ca66ceeb59ef920a
diff --git a/pw_dumb_io_baremetal_stm32f429/BUILD.gn b/pw_dumb_io_baremetal_stm32f429/BUILD.gn new file mode 100644 index 0000000..d467a41 --- /dev/null +++ b/pw_dumb_io_baremetal_stm32f429/BUILD.gn
@@ -0,0 +1,40 @@ +# 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. + +config("linker_script_config") { + _linker_script = "stm32f429.ld" + inputs = [ + _linker_script, + ] + ldflags = [ "-T" + rebase_path("$_linker_script") ] +} + +source_set("linker_script") { + public_configs = [ ":linker_script_config" ] +} + +source_set("pw_dumb_io_baremetal_stm32f429") { + public_configs = [ "$dir_pw_build:pw_default_cpp" ] + public_deps = [ + ":linker_script", + ] + deps = [ + "$dir_pw_dumb_io:facade", + "$dir_pw_preprocessor", + ] + sources = [ + "core_init.c", + "dumb_io_baremetal.cc", + ] +}
diff --git a/pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty b/pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty new file mode 100644 index 0000000..30e5cfa --- /dev/null +++ b/pw_dumb_io_baremetal_stm32f429/bloaty_config.bloaty
@@ -0,0 +1,29 @@ +# 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. + +source_filter: "^SEG .+$" + +custom_data_source: { + name: "segment_names" + base_data_source: "segments" + + rewrite: { + pattern: "LOAD #0" + replacement: "SEG FLASH" + } + rewrite: { + pattern: "LOAD #1" + replacement: "SEG RAM" + } +}
diff --git a/pw_dumb_io_baremetal_stm32f429/core_init.c b/pw_dumb_io_baremetal_stm32f429/core_init.c new file mode 100644 index 0000000..69bc16e --- /dev/null +++ b/pw_dumb_io_baremetal_stm32f429/core_init.c
@@ -0,0 +1,131 @@ +// 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. + +// !!!WARNING!!! +// +// Some of the code in this file is run without static initialization expected +// by C/C++. Any accesses to statically initialized objects/variables before +// memory is initialized will result in undefined values and violates the C +// specification. Only code run after memory initialization is complete will be +// compliant and truly safe to run. In general, make early initialization code +// run AFTER memory initialization has complete unless it is ABSOLUTELY +// NECESSARY to modify the way memory is initialized. +// +// This file is similar to a traditional assembly startup file. It turns out +// that everything typically done in ARMv7-M assembly startup can be done +// straight from C code. This makes startup code easier to maintain, modify, +// and read. +// +// Core initialization is comprised of two primary parts: +// +// 1. Initialize ARMv7-M Vector Table: The ARMv7-M vector table (See ARMv7-M +// Architecture Reference Manual DDI 0403E.b section B1.5) dictates the +// starting program counter (PC) and stack pointer (SP) when the SoC powers +// on. The vector table also contains a number of other vectors to handle +// different exceptions. This file omits many of the vectors and only +// configures the four most important ones. +// +// 2. Initialize Memory: When execution begins due to SoC power-on (or the +// device is reset), memory must be initialized to ensure it contains the +// expected values when code begins to run. The SoC doesn't inherently have a +// notion of how to do this, so before ANYTHING else the memory must be +// initialized. This is done at the beginning of pw_FirmwareInit(). +// +// +// The simple flow is as follows: +// Power on -> PC and SP set (from vector_table by SoC) -> pw_FirmwareInit() +// +// In pw_FirmwareInit(): +// Initialize memory -> initialize board (pre-main init) -> main() + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include "pw_preprocessor/compiler.h" + +// Extern symbols referenced in the vector table. +extern const uint8_t _stack_end[]; +extern uint8_t _static_init_ram_start[]; +extern uint8_t _static_init_ram_end[]; +extern uint8_t _static_init_flash_start[]; +extern uint8_t _zero_init_ram_start[]; +extern uint8_t _zero_init_ram_end[]; + +// Functions called as part of firmware initialization. +void __libc_init_array(void); +void pw_BoardInit(void); +int main(void); + +void DefaultFaultHandler(void) { + while (true) { + // Wait for debugger to attach. + } +} + +// WARNING: This code is run immediately upon boot, and performs initialization +// of RAM. Note that code running before this function finishes memory +// initialization will violate the C spec (Section 6.7.8, paragraph 10 for +// example, which requires uninitialized static values to be zero-initialized). +// Be EXTREMELY careful when running code before this function finishes RAM +// initialization. +// +// This function runs immediately at boot because it is at index 1 of the +// interrupt vector table. +PW_NO_PROLOGUE void pw_FirmwareInit() { + // Begin memory initialization. + // Static-init RAM. + memcpy(_static_init_ram_start, + _static_init_flash_start, + _static_init_ram_end - _static_init_ram_start); + + // Zero-init RAM. + memset(_zero_init_ram_start, 0, _zero_init_ram_end - _zero_init_ram_start); + + // Call static constructors. + __libc_init_array(); + + // End memory initialization. + + // Do any necessary board init. + pw_BoardInit(); + + // Run main. + main(); + + // In case main() returns, just sit here until the device is reset. + while (true) { + } +} + +// This is the device's interrupt vector table. It's not referenced in any +// code because the platform (STM32F4xx) expects this table to be present at the +// beginning of flash. +// +// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b +// section B1.5.3. +PW_KEEP_IN_SECTION(".vector_table") +const uint32_t vector_table[] = { + // The starting location of the stack pointer. + [0] = (uint32_t)_stack_end, + + // Reset handler, dictates how to handle reset interrupt. This is also run + // at boot. + [1] = (uint32_t)pw_FirmwareInit, + + // NMI handler. + [2] = (uint32_t)DefaultFaultHandler, + // HardFault handler. + [3] = (uint32_t)DefaultFaultHandler, +};
diff --git a/pw_dumb_io_baremetal_stm32f429/docs.rst b/pw_dumb_io_baremetal_stm32f429/docs.rst new file mode 100644 index 0000000..5385e90 --- /dev/null +++ b/pw_dumb_io_baremetal_stm32f429/docs.rst
@@ -0,0 +1,47 @@ +.. _chapter-dumb-io-baremetal-stm32f429: + +.. default-domain:: cpp + +.. highlight:: sh + +--------------------------- +STM32F429 baremetal dumb IO +--------------------------- +The STM32F429 baremetal dumb IO backend provides device startup code and a UART +driver to layer that allows applications built against the ``pw_dumb_io`` +interface to run on a STM32F429 chip and do simple input/output via UART. +The code is optimized for the STM32F429I-DISC1, using USART1 (which is connected +to the virtual COM port on the embedded ST-LINKv2 chip). However, this should +work with all STM32F429 variations (and even some STM32F4xx chips). + +This backend has no configuration options. The point of it is to provide bare- +minimum platform code needed to do UART reads/writes. + +Setup +===== +This module requires relatively minimal setup: + + 1. Write code against the ``pw_dumb_io`` facade. + 2. Specify the ``dir_pw_dumb_io_backend`` GN gloabal variable to point to this + backend. + 3. Build an executable with a main() function using a toolchain that + supports Cortex-M4. + +.. note:: + This module provides early firmware init and a linker script, so it will + conflict with other modules that do any early device init or provide a linker + script. + +Module usage +============ +After building a an executable that utilizes this backend, flash the +produced .elf binary to the development board. Then, using a serial +communication terminal (like minicom or screen), connect to the device at a +baud rate of 115200 (8N1). If you're not using a STM32F429I-DISC1 development +board, manually connect a USB TLL adapter to pins `A9` (TX) and +`A10` (RX). + +Dependencies +============ + * pw_dumb_io facade + * pw_preprocessor module
diff --git a/pw_dumb_io_baremetal_stm32f429/dumb_io_baremetal.cc b/pw_dumb_io_baremetal_stm32f429/dumb_io_baremetal.cc new file mode 100644 index 0000000..110e44c --- /dev/null +++ b/pw_dumb_io_baremetal_stm32f429/dumb_io_baremetal.cc
@@ -0,0 +1,193 @@ +// 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_dumb_io/dumb_io.h" +#include "pw_preprocessor/compiler.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 kGpioPortModeMask = 0x3u; +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 kGpioPortSpeedMask = 0x3u; +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 kGpioPullTypeMask = 0x3u; +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 kGpioAltModeMask = 0x3u; +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 sane values and we don't need to change them. +constexpr uint32_t kReceiveEnable = 0x1 << 2; +constexpr uint32_t kTransmitEnable = 0x1 << 3; +constexpr uint32_t kReadDataReady = 0x1u << 5; +constexpr uint32_t kEnableUsart = 0x1 << 13; + +// 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_BoardInit() { + // 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::dumb_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 GetByte(std::byte* dest) { + while (true) { + if (usart1.status & kReadDataReady) { + *dest = static_cast<std::byte>(usart1.data_register); + } + } + return Status::OK; +} + +// 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 PutByte(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 Status::OK; +} + +} // namespace pw::dumb_io
diff --git a/pw_dumb_io_baremetal_stm32f429/stm32f429.ld b/pw_dumb_io_baremetal_stm32f429/stm32f429.ld new file mode 100644 index 0000000..fbdbe77 --- /dev/null +++ b/pw_dumb_io_baremetal_stm32f429/stm32f429.ld
@@ -0,0 +1,127 @@ +/** + * 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. + */ + +HIDDEN(_min_stack_size = 1K); + +/* Note: This technically doesn't set the firmware's entry point. Setting the + * firmware entry point is done by setting vector_table[1] in core_init.c. + * However, this DOES tell the compiler how to optimize when --gc-sections + * is enabled. + */ +ENTRY(pw_FirmwareInit) + +MEMORY +{ + /* Internal Flash */ + FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 512K + /* Internal SRAM */ + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 192K +} + +SECTIONS +{ + + /* Main executable code. */ + .code : ALIGN(8) + { + /* STM32F4xx expects the vector table to be at the beginning of flash. */ + KEEP(*(.vector_table)) + + . = ALIGN(8); + /* Application code. */ + *(.text) + *(.text*) + KEEP(*(.init)) + KEEP(*(.fini)) + + . = ALIGN(8); + /* Constants.*/ + *(.rodata) + *(.rodata*) + + /* .preinit_array, .init_array, .fini_array are used by libc. + * Each section is a list of function pointers that are called pre-main and + * post-exit for object initialization and tear-down. + * Since the region isn't explicitly referenced, specify KEEP to prevent + * link-time garbage collection. SORT is used for sections that have strict + * init/de-init ordering requirements. */ + . = ALIGN(8); + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* Used by unwind-arm/ */ + .ARM : ALIGN(8) { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + /* Explicitly initialized global and static data. (.data)*/ + .static_init_ram : ALIGN(8) + { + *(.data) + *(.data*) + . = ALIGN(8); + } >RAM AT> FLASH + + /* Zero initialized global/static data. (.bss) + * This section is zero initialized in pw_FirmwareInit(). */ + .zero_init_ram : ALIGN(8) + { + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(8); + } >RAM + + /* Link-time check for stack overlaps. */ + .stack (NOLOAD) : ALIGN(8) + { + HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .); + ASSERT(_stack_size >= _min_stack_size, "Error: Not enough RAM for stack."); + . = . + _stack_size; + } >RAM +} + +/* Symbols used by core_init.c: */ +/* Top of stack to set stack pointer. */ +_stack_end = ORIGIN(RAM) + LENGTH(RAM); + +/* Start of .static_init_ram in FLASH. */ +_static_init_flash_start = LOADADDR(.static_init_ram); + +/* Region of .static_init_ram in RAM. */ +_static_init_ram_start = ADDR(.static_init_ram); +_static_init_ram_end = _static_init_ram_start + SIZEOF(.static_init_ram); + +/* Region of .zero_init_ram. */ +_zero_init_ram_start = ADDR(.zero_init_ram); +_zero_init_ram_end = _zero_init_ram_start + SIZEOF(.zero_init_ram); + +/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */ +PROVIDE (end = _zero_init_ram_end);