diff --git a/docs/11_toolchain.html b/docs/11_toolchain.html new file mode 100644 index 0000000..a23e676 --- /dev/null +++ b/docs/11_toolchain.html @@ -0,0 +1,288 @@ + + +
+ ++By default each release installs to its own directory instead of upgrading the +previous one. This way several releases can coexist and you can select which +one you use for a specific project. One downside to this is that the directory +and filename convention is heavy. For practical use, you need to configure an +IDE or encapsulate those paths and names in Makefile variables. + +
+### Build environment selection + +GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" + +BINPFX = $(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc + +### Build rules + +.PHONY: version + +version: + $(CC) --version ++
+$ make +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-gcc --version +arm-none-eabi-gcc.exe (Arm GNU Toolchain 13.3.Rel1 (Build arm-13.24)) 13.3.1 202 +40614 +Copyright (C) 2023 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ +
+GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi ++ +By selecting the path based on the development environment, there is no need +to make changes while switching between OS. Gmake has the built-in +variable MAKE_HOST that can be tested for this. + +
+### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +endif + +BINPFX = $(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc + +### Build rules + +.PHONY: version + +version: + $(CC) --version ++ +I use the path prefix $(HOME)/Packages instead of ~/Packages when +defining GCCDIR as some sub-processes called by gmake may have +issues with ~ expansion (in this case ld). This way gmake +will handle the expansion before calling the sub-processes. + +
+$ touch empty.c + +$ make empty.o +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-gcc -c -o empty.o empty.c ++Compilation is succesful and empty.o file is generated. +
+There are sample link scripts that come with the toolchain, they are located +in the subfolder share/gcc-arm-none-eabi/samples/ldscripts. For now I can +use the simplest script: mem.ld. + +
+### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +endif + +BINPFX = $(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc +LD = $(BINPFX)ld + +LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld + +### Build rules + +%.elf: %.o + $(LD) -T$(LD_SCRIPT) -o $@ $< ++ +
+$ make empty.elf +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-ld -T"D:/Program Files (x86)/GNU +Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld -o empty.elf empty.o ++Link terminates successfully and creates empty.elf. +
+### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +endif + +BINPFX = $(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc +LD = $(BINPFX)ld +OBJCOPY = $(BINPFX)objcopy + +LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld + +### Build rules + +%.elf: %.o + $(LD) -T$(LD_SCRIPT) -o $@ $< + +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +%.hex: %.elf + $(OBJCOPY) -O ihex $< $@ ++ +Now, if I start in a directory that contains only this Makefile and an +empty empty.c file, I can successfully build. + +
+$ make empty.bin empty.hex +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-gcc -c -o empty.o empty.c +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-ld -T"D:/Program Files (x86)/GNU +Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld -o empty.elf empty.o +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-objcopy -O binary empty.elf empty +.bin +"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m +ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-objcopy -O ihex empty.elf empty.h +ex +rm empty.o empty.elf ++ +Notice that gmake automatically removes the intermediary .o and +.elf files on completion. +
+The generated empty.bin is empty. + +
+### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +endif + +BINPFX = @$(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc +LD = $(BINPFX)ld +OBJCOPY = $(BINPFX)objcopy + +LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld + +### Build rules + +.PHONY: clean + +clean: + @echo CLEAN + @rm -f *.o *.elf *.bin *.hex + +%.elf: %.o + @echo $@ + $(LD) -T$(LD_SCRIPT) -o $@ $< + +%.bin: %.elf + @echo $@ + $(OBJCOPY) -O binary $< $@ + +%.hex: %.elf + @echo $@ + $(OBJCOPY) -O ihex $< $@ ++ +
+$ make clean +CLEAN + +$ make empty.bin empty.hex +empty.elf +empty.bin +empty.hex +rm empty.o empty.elf ++ +
+Next, I will select a micro-controller +from the STM32 family and generate a binary file that it can execute. + +
+/* Linker script to configure memory regions. + * Need modifying for a specific board. + * FLASH.ORIGIN: starting address of flash + * FLASH.LENGTH: length of flash + * RAM.ORIGIN: starting address of RAM bank 0 + * RAM.LENGTH: length of RAM bank 0 + */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x0, LENGTH = 0x20000 /* 128K */ + RAM (rwx) : ORIGIN = 0x10000000, LENGTH = 0x2000 /* 8K */ +} ++ +It needs to be modified with actual flash and ram locations and sizes. +
+Also this link script does not contain any information for the linker to know +how to locate the output of the C compiler. Code, constant data and initial +value of variables need to be located in flash, variables and stack need to be +located in ram. I need a better link script that specify all of that. +nokeep.ld in the sample scripts folder is the one I need. + +
+/* Linker script to configure memory regions. + * Need modifying for a specific board. + * FLASH.ORIGIN: starting address of flash + * FLASH.LENGTH: length of flash + * RAM.ORIGIN: starting address of RAM bank 0 + * RAM.LENGTH: length of RAM bank 0 + */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x0, LENGTH = 0x20000 /* 128K */ + RAM (rwx) : ORIGIN = 0x10000000, LENGTH = 0x2000 /* 8K */ +} + +/* Linker script to place sections and symbol values. Should be used together + * with other linker script that defines memory regions FLASH and RAM. + * It references following symbols, which must be defined in code: + * Reset_Handler : Entry of reset handler + * + * It defines following symbols, which code can use without definition: + * __exidx_start + * __exidx_end +... +... + * __StackTop + * __stack + */ +ENTRY(Reset_Handler) + +SECTIONS +{ +... +... ++ +From this snippet I can see that not only flash and ram parameters but also +the entry point for code execution, Reset_Handler, needs to be provided. +
+As a check, let’s change the link script to nokeep.ld in Makefile +and generate an executable .elf from the empty source code file +empty.c: + +
+LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/nokeep.ld ++ +
+$ make empty.elf +empty.elf +D:\Program Files (x86)\GNU Arm Embedded Toolchain\arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi\bin\arm-none-eabi-ld.exe: warning: cannot find entry +symbol Reset_Handler; defaulting to 00000000 +rm empty.o ++ +The linker gives a warning and fallback on a default address as entry point. +
+So let’s create boot.c with an idle loop as Reset_Handler: +
+void Reset_Handler( void) { + for( ;;) ; +} ++ +In order to better understand the output of the link phase, I make the +following changes to the Makefile: +
+OBJDUMP = $(BINPFX)objdump +SIZE = $(BINPFX)size + +CFLAGS = -g + +clean: + @echo CLEAN + @rm -f *.o *.elf *.map *.lst *.bin *.hex + +%.elf: %.o + @echo $@ + $(LD) -T$(LD_SCRIPT) -Map=$*.map -cref -o $@ $< + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $*.lst ++ +
+$ make boot.elf +boot.elf + text data bss dec hex filename + 12 0 0 12 c boot.elf +rm boot.o ++ +I can see that this build results in 12 bytes of code in the text section. +More details can be found in the boot.map and boot.lst files. + +
+MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 4K +} ++ +The Makefile needs the following changes: +
+CPU = -mthumb -mcpu=cortex-m0 +CFLAGS = $(CPU) -g -Wall -Wextra -Os +LD_SCRIPT = f030f4.ld ++ +At boot time, the Arm core fetches the initial address of the stack pointer +and the address where to start execution from the first two entries of its +interrupt routine table. I have to modify boot.c to initialize such a +table in accord with the symbols defined in the link script. + +
+/* Memory locations defined by linker script */ +extern long __StackTop ; /* &__StackTop points after end of stack */ +void Reset_Handler( void) ; /* Entry point for execution */ + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * NN Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 2] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler +} ; + +void Reset_Handler( void) { + for( ;;) ; +} ++ +__StackTop is defined by the linker script and is located after the end +of the RAM. I use the GNU C extension __attribute__() to name the section +where I want the interrupt vector to be included. If you check the linker +script you will see that it places .isr_vector at the start of the +text area which is located at the beginning of the flash memory. I chose +to name the interrupt vector table isr_vector to match the section name +.isr_vector, but it is really the section name that matters here. + +
+$ make boot.hex +boot.elf + text data bss dec hex filename + 10 0 0 10 a boot.elf +boot.hex +rm boot.elf boot.o ++ +A build produces 10 bytes of code, I can check the disassembly in the +boot.lst file. + +
+Disassembly of section .text: + +08000000 <isr_vector>: + 8000000: 00 10 00 20 09 00 00 08 ... .... + +08000008 <Reset_Handler>: +/* System Exceptions */ + Reset_Handler +} ; + +void Reset_Handler( void) { + for( ;;) ; + 8000008: e7fe b.n 8000008 <Reset_Handler> ++
+Next, I will take a board with a STM32F030F4P6 and +check if the code generated behaves as expected. +
+Below is the Makefile for reference. If you happen to cut&paste from this +web page to a file, remember that gmake expects rules to be tab +indented. + +
+### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +endif + +BINPFX = @$(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc +LD = $(BINPFX)ld +OBJCOPY = $(BINPFX)objcopy +OBJDUMP = $(BINPFX)objdump +SIZE = $(BINPFX)size + +CPU = -mthumb -mcpu=cortex-m0 +CFLAGS = $(CPU) -g -Wall -Wextra -Os +LD_SCRIPT = f030f4.ld + +### Build rules + +.PHONY: clean + +clean: + @echo CLEAN + @rm -f *.o *.elf *.map *.lst *.bin *.hex + +%.elf: %.o + @echo $@ + $(LD) -T$(LD_SCRIPT) -Map=$*.map -cref -o $@ $< + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $*.lst + +%.bin: %.elf + @echo $@ + $(OBJCOPY) -O binary $< $@ + +%.hex: %.elf + @echo $@ + $(OBJCOPY) -O ihex $< $@ ++ +
+ + +
+ 14:37:31 : STM32CubeProgrammer API v2.17.0 | Windows-64Bits + 14:37:38 : UR connection mode is defined with the HWrst reset mode + 14:37:38 : ST-LINK SN : 55FF6B065177495619420887 + 14:37:38 : ST-LINK FW : V2J45S7 + 14:37:38 : Board : -- + 14:37:38 : Voltage : 3.27V + 14:37:38 : SWD freq : 4000 KHz + 14:37:38 : Connect mode: Hot Plug + 14:37:38 : Reset mode : Software reset + 14:37:38 : Device ID : 0x444 + 14:37:38 : Revision ID : Rev 1.0 + 14:37:38 : Debug in Low Power mode is not supported for this device. + 14:37:39 : UPLOADING OPTION BYTES DATA ... + 14:37:39 : Bank : 0x00 + 14:37:39 : Address : 0x1ffff800 + 14:37:39 : Size : 16 Bytes + 14:37:39 : UPLOADING ... + 14:37:39 : Size : 1024 Bytes + 14:37:39 : Address : 0x8000000 + 14:37:39 : Read progress: + 14:37:39 : Data read successfully + 14:37:39 : Time elapsed during the read operation is: 00:00:00.007 ++ +Then program and verify the bootstrap code. Either binary, Intel hex or +Motorola S rec format are supported. Our Makefile as rules for binary +and Intel hex, objcopy also support Motorola S record as an output +format. Last build produced boot.hex. + +
+ 14:40:24 : Memory Programming ... + 14:40:24 : Opening and parsing file: boot.hex + 14:40:24 : File : boot.hex + 14:40:24 : Size : 10.00 B + 14:40:24 : Address : 0x08000000 + 14:40:24 : Erasing memory corresponding to segment 0: + 14:40:24 : Erasing internal memory sector 0 + 14:40:24 : Download in Progress: + 14:40:24 : File download complete + 14:40:24 : Time elapsed during download operation: 00:00:00.130 + 14:40:24 : Verifying ... + 14:40:24 : Read progress: + 14:40:24 : Download verified successfully + 14:40:24 : RUNNING Program ... + 14:40:24 : Address: : 0x08000000 + 14:40:24 : Application is running, Please Hold on... + 14:40:24 : Start operation achieved successfully ++Finally check the registers in the MCU Core Panel: + +
+MSP: 0x20001000 +PC: 0x8000008 ++ +After reset, the stack pointer has been initialized and the program +counter is on the idle loop under execution. +
+If I check the Programming Manual + +PM0215 STM32F0 series Cortex-M0 programming manual, I can +read the following about the registers MSP and PC: + +
+Stack pointer (SP) register R13 + In Thread mode, bit[1] of the CONTROL register indicates the stack + pointer to use: + ● 0: Main Stack Pointer (MSP)(reset value). On reset, the processor + loads the MSP with the value from address 0x00000000. + ● 1: Process Stack Pointer (PSP). + +Program counter (PC) register R15 + Contains the current program address. On reset, the processor loads the PC + with the value of the reset vector, which is at address 0x00000004. + Bit[0] of the value is loaded into the EPSR T-bit at reset and must be 1. ++ +- According to this, initial values for MSP and PC registers are +fetched from address 0x00000000 and 0x00000004 respectively, but +I have located the isr table at the beginning of the Flash memory at +address 0x08000000! This works because the memory space at address 0 +is a mirror of another memory area. Which area is mirrored depends of +the state of the BOOT0 pin. On the board I am testing, there is a +jumper to select either Flash or System memory by setting the state of +the BOOT0 pin to high or low. +
+- The ESPR T-bit, mentioned in the description of the PC +register is the Thumb bit. As I highlighted before when I checked the +output of our first build, bit 0 of the second entry in our isr table is +set to 1 as requested by this specification. + +
+Next, I will provide feedback of execution directly +through the board. + +
+ +
+This board is based on the micro-controller STM32F030F4P6, so let's +learn about its implementation of GPIOs. + +
+Diving in the reference manual + +RM0360, +I find the layout of the GPIO B registers and their initial state plus +the info that peripheral clocks need to be enabled through the Reset and +Clock Controller (RCC) connected on the AHB1 bus. So I need to activate +the clocks of GPIO B through the RCC before I can access its registers. +
+To turn the user LED on, I need to +
+/* Memory locations defined by linker script */ +extern long __StackTop ; /* &__StackTop points after end of stack */ +void Reset_Handler( void) ; /* Entry point for execution */ + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * NN Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 2] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler +} ; + +#define RCC ((volatile long *) 0x40021000) +#define RCC_AHBENR RCC[ 5] +#define RCC_AHBENR_IOPBEN 0x00040000 /* 18: I/O port B clock enable */ + +#define GPIOB ((volatile long *) 0x48000400) +#define GPIOB_MODER GPIOB[ 0] + +void Reset_Handler( void) { +/* User LED ON */ + RCC_AHBENR |= RCC_AHBENR_IOPBEN ; /* Enable IOPB periph */ + GPIOB_MODER |= 1 << (1 * 2) ; /* PB1 Output [01], over default 00 */ + /* OTYPER Push-Pull by default */ + /* PB1 output default LOW at reset */ + for( ;;) ; +} ++ +- I use the C preprocessor to specify the mapping of the peripheral +registers. +
+- The naming convention is from the Reference Manual, the address +locations from the Data Sheet. +
+- Registers are indicated as volatile as they may change out of the code +control, this way the compiler will avoid optimizations based on known states. +
+To build I just request the format I need, either .bin or .hex. + +
+$ make ledon.hex +ledon.elf + text data bss dec hex filename + 40 0 0 40 28 ledon.elf +ledon.hex +rm ledon.elf ledon.o ++ +
+ + +
+Next, I will implement the classic blinking +LED. + +
+As I already manage to turn the LED on, making it blink is quite +straightforward. + +
+/* Memory locations defined by linker script */ +extern long __StackTop ; /* &__StackTop points after end of stack */ +void Reset_Handler( void) ; /* Entry point for execution */ + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * NN Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 2] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler +} ; + +#define RCC ((volatile long *) 0x40021000) +#define RCC_AHBENR RCC[ 5] +#define RCC_AHBENR_IOPBEN 0x00040000 /* 18: I/O port B clock enable */ + +#define GPIOB ((volatile long *) 0x48000400) +#define GPIOB_MODER GPIOB[ 0] +#define GPIOB_ODR GPIOB[ 5] + +void Reset_Handler( void) { + int delay ; + +/* User LED ON */ + RCC_AHBENR |= RCC_AHBENR_IOPBEN ; /* Enable IOPB periph */ + GPIOB_MODER |= 1 << (1 * 2) ; /* PB1 Output [01], over default 00 */ + /* OTYPER Push-Pull by default */ + /* PB1 output default LOW at reset */ + +/* User LED blink */ + for( ;;) { + for( delay = 1000000 ; delay ; delay--) ; /* delay between toggling */ + GPIOB_ODR ^= 1 << 1 ; /* toggle PB1 (User LED) */ + } +} ++ +I set the value of the delay counter at one million. By default the +internal clock is set to 8MHz at reset, which means the delay will still +be less than one second. + +
+$ make blink.hex +blink.elf + text data bss dec hex filename + 68 0 0 68 44 blink.elf +blink.hex +rm blink.o blink.elf ++ +
+Next, interrupt driven blinking, because +active wait delay is just not cool. + +
+In previous iteration, I made the user LED blink using an active delay +loop. I have two issues with this implementation: +
+What the System Tick does is very similar to my active delay loop as +can be seen from the following pseudo-code. + +
+while( enabled) { + if( --current_value == 0) { + current_value = reload_value ; + countflag = true ; + if( interrupt_enabled) + SysTick_Handler() ; + } +} ++ +It’s an auto decremented counter that reloads and sets a flag when +reaching zero. It can trigger a system interrupt if requested to. Its +default clock is the processor clock and can be switched to external +clock. Details can be found in the Programming Manual as this is part of +Arm Core. + +
+/* Memory locations defined by linker script */ +extern long __StackTop ; /* &__StackTop points after end of stack */ +void Reset_Handler( void) ; /* Entry point for execution */ + +void SysTick_Handler( void) ; + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * NN Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 16] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + SysTick_Handler +} ; + +#define SYSTICK ((volatile long *) 0xE000E010) +#define SYSTICK_CSR SYSTICK[ 0] +#define SYSTICK_RVR SYSTICK[ 1] +#define SYSTICK_CVR SYSTICK[ 2] + +#define RCC ((volatile long *) 0x40021000) +#define RCC_AHBENR RCC[ 5] +#define RCC_AHBENR_IOPBEN 0x00040000 /* 18: I/O port B clock enable */ + +#define GPIOB ((volatile long *) 0x48000400) +#define GPIOB_MODER GPIOB[ 0] +#define GPIOB_ODR GPIOB[ 5] + +void Reset_Handler( void) { +/* By default SYSCLK == HSI [8MHZ] */ + +/* SYSTICK */ + SYSTICK_RVR = 1000000 - 1 ; /* HBA / 8 */ + SYSTICK_CVR = 0 ; + SYSTICK_CSR = 3 ; /* HBA / 8, Interrupt ON, Enable */ + /* SysTick_Handler will execute every 1s from now on */ + +/* User LED ON */ + RCC_AHBENR |= RCC_AHBENR_IOPBEN ; /* Enable IOPB periph */ + GPIOB_MODER |= 1 << (1 * 2) ; /* PB1 Output [01], over default 00 */ + /* OTYPER Push-Pull by default */ + /* PB1 output default LOW at reset */ + + for( ;;) + __asm( "WFI") ; /* Wait for interrupt */ +} + +void SysTick_Handler( void) { + GPIOB_ODR ^= 1 << 1 ; /* Toggle PB1 (User LED) */ +} ++ +I didn’t initialize the GPIO B before enabling the SysTick as I have a +whole second before the first interrupt will tick in. +
+Build is straightforward. + +
+$ make ledtick.hex +ledtick.elf + text data bss dec hex filename + 148 0 0 148 94 ledtick.elf +ledtick.hex +rm ledtick.o ledtick.elf ++ +If I compare with blink.hex, 56 bytes of the 80 bytes code increase are +due to the expansion of the interrupt vector. +
+Once flashed in the board I can see that the LED changes state every second. + +
+Code size has been growing steadily since the first bootstrap. On the +other hand, except for the stack, I have not used RAM memory so far. + +
+│ │ text │ data │ bss │ +├────────┼──────┼───────┼───────┤ +│boot │ 10 │ 0 │ 0 │ +│ledon │ 40 │ 0 │ 0 │ +│blink │ 68 │ 0 │ 0 │ +│ledtick │ 148 │ 0 │ 0 │ ++ +Next, I will focus on RAM initialization. + +
+│ Section │ Description │ +├─────────┼──────────────────────────────────────────────┤ +│ data │ static initialized, initial values in flash │ +│ bss │ static unassigned, cleared │ +│ heap │ dynamically allocated, user managed │ +│ stack │ automatically allocated, stack frame managed │ ++My bootstrap since the first boot.c already initializes the stack. I +need now to copy the initial values from flash to the data section and +clear the bss section. +
+You can check your understanding of the C memory model by looking at the +C test code below and figuring where the linker will allocate the +variables. + +
+/** Test code: main.c *********************************************************/ + +const char hexa[] = "0123456789abcdef" ; +long first = 1 ; +long i ; + +int main( void) { + static char c = 'a' ; + char *cp = &c ; + + *cp += i ; + i += hexa[ 13] - c + first++ ; + return 0 ; +} ++
first
and c
, for a
+total of 8 bytes as sections are word aligned.
+i
for a total of 4 bytes.
+hexa
with all the const data
+located after the code. As it is a zero terminated string, it occupies 17 bytes
+and is padded with 3 zero for word alignment.
+first
and
+c
for a total of 8 bytes located after the const data.
+cp
, it is dynamically managed by
+the code generated by the C compiler.
+main()
, hexa
and c
+are unchanged, first
has the value 2, i
has the
+value 4 and cp
has been deallocated.
++I add the symbols defined by the linker script: +
__etext
, start of initial value copy in FLASH.
+__data_start
, start of initialized data in RAM.
+__bss_start
, start of unitialized data in RAM, it is the same
+location as __data_end
.
+__bss_end
, first location after the bss section.
+Reset_handler()
to:
+main()
C function.
+
+main()
has been executed.
++/* Memory locations defined by linker script */ +extern long __StackTop ; /* &__StackTop points after end of stack */ +void Reset_Handler( void) ; /* Entry point for execution */ +extern const long __etext[] ; /* start of initialized data copy in flash */ +extern long __data_start__[] ; +extern long __bss_start__[] ; +extern long __bss_end__ ; /* &__bss_end__ points after end of bss */ + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * NN Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 2] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler +} ; + + +extern int main( void) ; + +void Reset_Handler( void) { + const long *f ; /* from, source constant data from FLASH */ + long *t ; /* to, destination in RAM */ + +/* Assume: +** __bss_start__ == __data_end__ +** All sections are 4 bytes aligned +*/ + f = __etext ; + for( t = __data_start__ ; t < __bss_start__ ; t += 1) + *t = *f++ ; + + while( t < &__bss_end__) + *t++ = 0 ; + + main() ; + for( ;;) ; +} + +/** Test code: main.c *********************************************************/ + +const char hexa[] = "0123456789abcdef" ; +long first = 1 ; +long i ; + +int main( void) { + static char c = 'a' ; + char *cp = &c ; + + *cp += i ; + i += hexa[ 13] - c + first++ ; + return 0 ; +} ++ +
+$ make cstartup.bin +cstartup.elf + text data bss dec hex filename + 121 8 4 133 85 cstartup.elf +cstartup.bin +rm cstartup.o cstartup.elf ++ +If I look further in the cstartup.map generated by the linker. + +
+.text 0x08000000 0x79 + *(.isr_vector) + .isr_vector 0x08000000 0x8 cstartup.o + 0x08000000 isr_vector + *(.text*) + .text 0x08000008 0x34 cstartup.o + 0x08000008 Reset_Handler + .text.startup 0x0800003c 0x2c cstartup.o + 0x0800003c main + *(.rodata*) + .rodata 0x08000068 0x11 cstartup.o + 0x08000068 hexa +.data 0x20000000 0x8 load address 0x0800007c + 0x20000000 __data_start__ = . + *(.data*) + .data 0x20000000 0x8 cstartup.o + 0x20000004 first + 0x20000008 . = ALIGN (0x4) + 0x20000008 __data_end__ = . +.bss 0x20000008 0x4 load address 0x08000084 + 0x20000008 . = ALIGN (0x4) + 0x20000008 __bss_start__ = . + *(.bss*) + .bss 0x20000008 0x0 cstartup.o + *(COMMON) + COMMON 0x20000008 0x4 cstartup.o + 0x20000008 i + 0x2000000c . = ALIGN (0x4) + 0x2000000c __bss_end__ = . + *(.stack*) + 0x20001000 __StackTop = (ORIGIN (RAM) + LENGTH (RAM)) ++
hexa
is located in .rodata at 0x08000068
+
+first
is located in .data at 0x20000004
+
+i
is located in .bss at 0x20000008
+
+c
is not listed as it doesn’t have global scope, but I can
+guess it’s located at 0x20000000.
+
+c
is at offset 0x7c, which also means that c
has
+been located at 0x20000000.
+
++$ hexdump -C cstartup.bin +00000000 00 10 00 20 09 00 00 08 10 b5 08 4a 08 4b 09 49 |... .......J.K.I| +00000010 8b 42 06 d3 00 21 08 4a 93 42 05 d3 00 f0 0e f8 |.B...!.J.B......| +00000020 fe e7 01 ca 01 c3 f3 e7 02 c3 f5 e7 7c 00 00 08 |............|...| +00000030 00 00 00 20 08 00 00 20 0c 00 00 20 30 b5 08 49 |... ... ... 0..I| +00000040 08 48 0a 78 04 68 4b 68 12 19 d2 b2 5d 1c 9b 1a |.H.x.hKh....]...| +00000050 64 33 1b 19 4d 60 03 60 0a 70 00 20 30 bd c0 46 |d3..M`.`.p. 0..F| +00000060 00 00 00 20 08 00 00 20 30 31 32 33 34 35 36 37 |... ... 01234567| +00000070 38 39 61 62 63 64 65 66 00 00 00 00 61 00 00 00 |89abcdef....a...| +00000080 01 00 00 00 |....| +00000084 ++ +
main()
has been
+executed.
++ + +
+Next, I will merge the C startup initialization with +the ledtick code. + +
init()
function.
+main()
C function will be the focus of the last file and
+it should be written in a subset of standard C. As an example I will use
+success.c.
++/* success.c -- success does nothing, successfully */ +#include <stdlib.h> + +int main( void) { + return EXIT_SUCCESS ; +} ++ +
Reset_Handler()
that calls
+init()
and main()
, I have created stubs for all the
+System Exceptions listed in the Programming Manual. For those, if there is no
+implementation avalable in init.c, the linker will use the
+Default_Handler()
provided with the __attribute__()
+Gnu C extension.
+
++/* Memory locations defined by linker script */ +extern long __StackTop ; /* &__StackTop points after end of stack */ +void Reset_Handler( void) ; /* Entry point for execution */ +extern const long __etext[] ; /* start of initialized data copy in flash */ +extern long __data_start__[] ; +extern long __bss_start__[] ; +extern long __bss_end__ ; /* &__bss_end__ points after end of bss */ + +/* Stubs for System Exception Handler */ +void Default_Handler( void) ; +#define dflt_hndlr( fun) void fun##_Handler( void) \ + __attribute__((weak,alias("Default_Handler"))) +dflt_hndlr( NMI) ; +dflt_hndlr( HardFault) ; +dflt_hndlr( SVCall) ; +dflt_hndlr( PendSV) ; +dflt_hndlr( SysTick) ; + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * NN Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 16] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler, + NMI_Handler, + HardFault_Handler, + 0, 0, 0, 0, 0, 0, 0, + SVCall_Handler, + 0, 0, + PendSV_Handler, + SysTick_Handler +} ; + +extern int init( void) ; +extern int main( void) ; + +void Reset_Handler( void) { + const long *f ; /* from, source constant data from FLASH */ + long *t ; /* to, destination in RAM */ + +/* Assume: +** __bss_start__ == __data_end__ +** All sections are 4 bytes aligned +*/ + f = __etext ; + for( t = __data_start__ ; t < __bss_start__ ; t += 1) + *t = *f++ ; + + while( t < &__bss_end__) + *t++ = 0 ; + + if( init() == 0) + main() ; + + for( ;;) + __asm( "WFI") ; /* Wait for interrupt */ +} + +void Default_Handler( void) { + for( ;;) ; +} ++ +Except for the future addition of stubs to handle the device specific +interrupts, this file will not grow much anymore. + +
+#define SYSTICK ((volatile long *) 0xE000E010) +#define SYSTICK_CSR SYSTICK[ 0] +#define SYSTICK_RVR SYSTICK[ 1] +#define SYSTICK_CVR SYSTICK[ 2] + +#define RCC ((volatile long *) 0x40021000) +#define RCC_AHBENR RCC[ 5] +#define RCC_AHBENR_IOPBEN 0x00040000 /* 18: I/O port B clock enable */ + +#define GPIOB ((volatile long *) 0x48000400) +#define GPIOB_MODER GPIOB[ 0] +#define GPIOB_ODR GPIOB[ 5] + +int init( void) { +/* By default SYSCLK == HSI [8MHZ] */ + +/* SYSTICK */ + SYSTICK_RVR = 1000000 - 1 ; /* HBA / 8 */ + SYSTICK_CVR = 0 ; + SYSTICK_CSR = 3 ; /* HBA / 8, Interrupt ON, Enable */ + /* SysTick_Handler will execute every 1s from now on */ + +/* User LED ON */ + RCC_AHBENR |= RCC_AHBENR_IOPBEN ; /* Enable IOPB periph */ + GPIOB_MODER |= 1 << (1 * 2) ; /* PB1 Output [01], over default 00 */ + /* OTYPER Push-Pull by default */ + /* PB1 output default LOW at reset */ + + return 0 ; +} + +void SysTick_Handler( void) { + GPIOB_ODR ^= 1 << 1 ; /* Toggle PB1 (User LED) */ +} ++ +
SRCS
lines. Single file
+steps can be build explicitly (make ledon.hex
) or implicitly
+(make
) after removing the comment on the corresponding
+SRCS
line. Multiple file steps can only be build implicitly when
+their SRCS
line is uncommented.
+
++### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi" +endif + +BINPFX = @$(GCCDIR)/bin/arm-none-eabi- +CC = $(BINPFX)gcc +LD = $(BINPFX)ld +OBJCOPY = $(BINPFX)objcopy +OBJDUMP = $(BINPFX)objdump +SIZE = $(BINPFX)size + +### STM32F030F4P6 based board + +PROJECT = f030f4 +#SRCS = boot.c +#SRCS = ledon.c +#SRCS = blink.c +#SRCS = ledtick.c +#SRCS = cstartup.c +SRCS = startup.c init.c success.c +OBJS = $(SRCS:.c=.o) +CPU = -mthumb -mcpu=cortex-m0 +CFLAGS = $(CPU) -g -Wall -Wextra -Os +LD_SCRIPT = $(PROJECT).ld + +### Build rules + +.PHONY: clean all + +all: $(PROJECT).hex $(PROJECT).bin + +clean: + @echo CLEAN + @rm -f *.o *.elf *.map *.lst *.bin *.hex + +$(PROJECT).elf: $(OBJS) + @echo $@ + $(LD) -T$(LD_SCRIPT) -Map=$(PROJECT).map -cref -o $@ $(OBJS) + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $(PROJECT).lst + +%.elf: %.o + @echo $@ + $(LD) -T$(LD_SCRIPT) -Map=$*.map -cref -o $@ $< + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $*.lst + +%.bin: %.elf + @echo $@ + $(OBJCOPY) -O binary $< $@ + +%.hex: %.elf + @echo $@ + $(OBJCOPY) -O ihex $< $@ ++ +A successful build will generate the files f030f4.hex, f030f4.bin, +f030f4.map, f030f4.lst. + +
EXIT_SUCCESS
from that header is used. Furthermore, default
+location of the header files is derived by the compiler from the location of
+gcc.
+
++$ make +f030f4.elf + text data bss dec hex filename + 216 0 0 216 d8 f030f4.elf +f030f4.hex +f030f4.bin ++ +Building shows an increase in code, still no data. +
+Once f030f4.hex is loaded into the board, the behavior is the same
+as ledtick.hex. The new file structure and data initialization
+didn’t introduce any bugs changes, just code overhead.
+
+
+Next, I will make the code available in a +public git repository. + +
+I chose MIT license as open source license for the code. +
+The only rework of the C sources is the addition of a copyright notice +and conversion of tabs to spaces for portability. +
+Next, I will start working with USART +peripherals: select an USB adapter, flash the board using the UART +communication and say hello to the world. + +
+Boards sold online often have dedicated pre-soldered pins for UART +connectivity similar to what I have seen before for the SWD interface. +The VCC-GND board I used previously doesn’t have such dedicated pins but the +functionality is wired on the pins PA9 (TX) and PA10 (RX). +
+I will use a board with dedicated pins (GND, TX, RX, VCC 3.3V). Board +specifications can be found + +here. +
+ + +
+I use an adapter based on Silicon Labs CP2102 chipset. +Windows has USB driver available for Silicon Labs CP210x chipset family. +The adapter enumerates as COM4 on my Windows PC. +
+I connect the adapter to the board to provide 3.3V and make sure to cross +RX and TX wires (STM32 RX <-> Adapter TX, STM32 TX <-> Adapter RX). + +
+ + +
+Next, I will make sure the code I wrote so far is +working on the new board. + +
+The new board uses PA4 to turn the LED ON when high. Also, by +taking off a jumper, PA4 can be used for other purposes. +
+The first board uses PB1 to turn the LED ON when low as I have +seen previously here. +
+I can adapt the code by adding the base address of GPIOA and +shifting pin location accordingly, but this type of variations is so +common that I want to make sure adaptations can be done easily without +errors. + +
+For the new board: +
+#define LED_IOP A +#define LED_PIN 4 +#define LED_ON 1 ++ +And for the vcc-gnd board: +
+#define LED_IOP B +#define LED_PIN 1 +#define LED_ON 0 ++ +
+As I have several GPIO peripheral GPIOA .. GPIOF, I switch notation,
+instead of writing, say, GPIOA_MODER
, I will write either
+GPIOA[ MODER]
or GPIO( A)[ MODER]
. This way I could
+refer directly to GPIO( LED_IOP)[ MODER]
.
+
+I use conditional compilation based on LED_ON
. If
+LED_ON
is high, I need an extra step during initialization
+compare to LED_ON
low. On the other hand, if LED_ON
+is undefined, the code would be removed for a board that doesn’t have a user
+LED.
+
+
+#define SYSTICK ((volatile long *) 0xE000E010) +#define SYSTICK_CSR SYSTICK[ 0] +#define SYSTICK_RVR SYSTICK[ 1] +#define SYSTICK_CVR SYSTICK[ 2] + +#define CAT( a, b) a##b +#define HEXA( a) CAT( 0x, a) +#define RCC ((volatile long *) 0x40021000) +#define RCC_AHBENR RCC[ 5] +#define RCC_AHBENR_IOP( h) (1 << (17 + HEXA( h) - 0xA)) + +#define GPIOA ((volatile long *) 0x48000000) +#define GPIOB ((volatile long *) 0x48000400) +#define GPIO( x) CAT( GPIO, x) +#define MODER 0 +#define ODR 5 + +/* user LED ON when PA4 is high */ +#define LED_IOP A +#define LED_PIN 4 +#define LED_ON 1 + +void SysTick_Handler( void) { +#ifdef LED_ON + GPIO( LED_IOP)[ ODR] ^= 1 << LED_PIN ; /* Toggle User LED */ +#endif +} + +int init( void) { +/* By default SYSCLK == HSI [8MHZ] */ + +/* SYSTICK */ + SYSTICK_RVR = 1000000 - 1 ; /* HBA / 8 */ + SYSTICK_CVR = 0 ; + SYSTICK_CSR = 3 ; /* HBA / 8, Interrupt ON, Enable */ + /* SysTick_Handler will execute every 1s from now on */ + +#ifdef LED_ON +/* User LED ON */ + RCC_AHBENR |= RCC_AHBENR_IOP( LED_IOP) ; /* Enable IOPx periph */ + GPIO( LED_IOP)[ MODER] |= 1 << (LED_PIN * 2) ; /* LED_IO Output [01], + ** over default 00 */ + /* OTYPER Push-Pull by default */ + /* Pxn output default LOW at reset */ +# if LED_ON + SysTick_Handler() ; +# endif +#endif + return 0 ; +} ++ +
SRCS
definition in Makefile.
+
+SRCS = startup.c board.c success.c+ +and build. + +
+$ make +f030f4.elf + text data bss dec hex filename + 224 0 0 224 e0 f030f4.elf +f030f4.hex +f030f4.bin ++ +Once I have flashed the board with this new binary, I put back the BOOT0 +jumper and press reset. This board user LED is red. 😎 + +
+Next, I will do some serial transmission. + +
+In order to make a first transmission, the peripherals have to be +initialized. As the TX/RX of USART1 are mapped on pin PA9 and PA10, I +need to configure GPIOA first. +
+/* USART1 9600 8N1 */ + RCC_AHBENR |= RCC_AHBENR_IOP( A) ; /* Enable GPIOA periph */ + GPIOA[ MODER] |= 0x0A << (9 * 2) ; /* PA9-10 ALT 10, over default 00 */ + GPIOA[ AFRH] |= 0x110 ; /* PA9-10 AF1 0001, over default 0000 */ + RCC_APB2ENR |= RCC_APB2ENR_USART1EN ; + USART1[ BRR] = 8000000 / 9600 ; /* PCLK [8MHz] */ + USART1[ CR1] |= USART_CR1_UE | USART_CR1_TE ; /* Enable USART & Tx */ ++ +Sending data is done by writing in the Transmission Data Register (TDR). +To check if it is ready for transmission you must check the state of the +TX Empty (TXE) bit in the Interrupt & Status Register (ISR). +
+I write a basic kputc()
function that does busy waiting if the
+TDR is not empty and insures that LF are mapped to CR LF. The ‘k’ in
+kputc refer to ‘kernel’, as kputc is a low level function that will be used
+mostly for debugging. With the busy wait and the recursive code this
+implementation is definitively not optimal, but it’s functional and
+that’s what matter most at this stage.
+
+
+void kputc( unsigned char c) { + static unsigned char lastc ; + + if( c == '\n' && lastc != '\r') + kputc( '\r') ; + +/* Active wait while transmit register is full */ + while( (USART1[ ISR] & USART_ISR_TXE) == 0) ; + + USART1[ TDR] = c ; + lastc = c ; +} ++ +The high level C function I need for this simple test is
puts()
.
+I make my own implementation but I keep the same declaration as the standard
+header that come with the C compiler.
+
++int puts( const char *s) { + while( *s) + kputc( *s++) ; + + kputc( '\n') ; + return 0 ; +} ++ +Finally I use a standard C implementation for hello.c. + +
+/* hello.c -- hello there */ +#include <stdio.h> +#include <stdlib.h> + +int main( void) { + puts( "hello, world") ; + return EXIT_SUCCESS ; +} ++ +
SRCS
line.
+
+SRCS = startup.c usart1tx.c hello.c+ +Calling make, I can see that there is now some variable in BSS section +of the RAM. It is
lastchar
local to kputc()
. Because
+of word alignment BSS
occupies 4 bytes.
+
++$ make +f030f4.elf + text data bss dec hex filename + 413 0 4 417 1a1 f030f4.elf +f030f4.hex +f030f4.bin ++ +
+On Windows PC, if I use PuTTY or Arduino IDE to open COM4 at 9600 +baud, every time I press and release the reset button I can see ‘hello, +world’ displayed on a new line in the terminal window. +
+On Linux, when I plug in the USB to UART adapter, it enumerates as +/dev/ttyUSB0, so it is compatible with the USB driver for serial +ports. If I try to open it with Arduino IDE, I get an error message as I +need to belong to dialout group to open that TTY for reading and +writing. + +
sudo usermod -a -G dialout $USER+ +Once added to dialout, I can open /dev/ttyUSB0 at 9600 baud in +Arduino IDE, each time I press and release the board RESET button, I can see +‘hello, world’ displayed on a new line in the Serial Monitor window. + +
puts()
, but I will add
+support for other stdio functions when needed.
++Next, I will switch to an open source tool +for flashing over serial connection that works on both Windows and Linux. + +
+I clone the repository from Sourceforge in my Projects folder. + +
+$ cd ~/Projects +$ git clone https://git.code.sf.net/p/stm32flash/code stm32flash-code +Cloning into 'stm32flash-code'... +remote: Enumerating objects: 1357, done. +remote: Counting objects: 100% (1357/1357), done. +remote: Compressing objects: 100% (682/682), done. +remote: Total 1357 (delta 912), reused 996 (delta 671) +Receiving objects: 100% (1357/1357), 1.04 MiB | 74.00 KiB/s, done. +Resolving deltas: 100% (912/912), done. ++ +Build on Linux doesn’t show any warnings. + +
+$ cd stm32flash-code +$ make +cc -Wall -g -c -o dev_table.o dev_table.c +cc -Wall -g -c -o i2c.o i2c.c +cc -Wall -g -c -o init.o init.c +cc -Wall -g -c -o main.o main.c +cc -Wall -g -c -o port.o port.c +cc -Wall -g -c -o serial_common.o serial_common.c +cc -Wall -g -c -o serial_platform.o serial_platform.c +cc -Wall -g -c -o stm32.o stm32.c +cc -Wall -g -c -o utils.o utils.c +cd parsers && make parsers.a +make[1]: Entering directory '~/Projects/stm32flash-code/parsers' +cc -Wall -g -c -o binary.o binary.c +cc -Wall -g -c -o hex.o hex.c +ar rc parsers.a binary.o hex.o +make[1]: Leaving directory '~/Projects/stm32flash-code/parsers' +cc -o stm32flash dev_table.o i2c.o init.o main.o port.o serial_common.o serial_ +platform.o stm32.o utils.o parsers/parsers.a ++ +I test the newly compiled command first by calling it without argument +
./stm32flah
then with the serial port where the USB to UART
+adapter is plugged in.
+
+./stm32flash
gives a detailed help of the command.
+
+Calling it with the serial port argument where the board is plugged in +and set in bootloader mode gives a description of the chipset detected. + +
+$ ./stm32flash /dev/ttyUSB0 +stm32flash 0.7 + +http://stm32flash.sourceforge.net/ + +Interface serial_posix: 57600 8E1 +Version : 0x31 +Option 1 : 0x00 +Option 2 : 0x00 +Device ID : 0x0444 (STM32F03xx4/6) +- RAM : Up to 4KiB (2048b reserved by bootloader) +- Flash : Up to 32KiB (size first sector: 4x1024) +- Option RAM : 16b +- System RAM : 3KiB ++ +I install the command by moving the executable to my local bin directory. + +
$ mv stm32flash ~/bin+ +If everything goes well, I will later
strip
and compress (with
+upx
) the executable.
+
+$ stm32flash -r read.bin -S 0x08000000:1024 /dev/ttyUSB0+ +Writing the executable in hex format. + +
$ stm32flash -w f030f4.hex /dev/ttyUSB0+ +Comparing the memory read-out using
od
, there is no difference.
+
++The build phase gave more warnings than the Linux version, this is +mostly due to stricter warnings in the GCC compiler version. +
+Usage of stm32flash only differs in the name of the serial device, in my +case COM4 instead of /dev/ttyUSB0. + +
+Next, I will write an application which make +better use of transmission than hello. + +
uptime
command.
+
++$ man -k uptime +uptime (1) - Tell how long the system has been running. ++ +I am going to make a quick prototype first to validate the concept. + +
+volatile unsigned uptime ; /* seconds elapsed since boot */ + +#ifdef LED_ON +static void userLEDtoggle( void) { + GPIO( LED_IOP)[ ODR] ^= 1 << LED_PIN ; /* Toggle User LED */ +} +#endif + +void SysTick_Handler( void) { + uptime += 1 ; +#ifdef LED_ON + userLEDtoggle() ; +#endif +} ++ +The global variable
uptime
is marked volatile
, the
+compiler needs this information to avoid optimization as the value changes
+concurrently when an interrupt is triggered.
+
+I move the user LED toggling code to a dedicated local function
+userLEDtoggle()
as this is not the only task of
+SysTick_Handler()
anymore and a call to toggle the LED is needed
+during initialization. I adjust the initialization code accordingly.
+
+I write a first uptime.1.c to print the count of seconds every time
+the uptime
counter value changes.
+
+
+/* uptime.1.c -- tells how long the system has been running */ +#include <stdio.h> + +extern volatile unsigned uptime ; +extern void kputc( unsigned char c) ; + +void kputu( unsigned u) { + unsigned r = u % 10 ; + u /= 10 ; + if( u) + kputu( u) ; + + kputc( '0' + r) ; +} + +int main( void) { + static unsigned last ; + + for( ;;) + if( last != uptime) { + last = uptime ; + kputu( last) ; + puts( " sec") ; + } else + __asm( "WFI") ; /* Wait for System Tick Interrupt */ +} ++ +As before for
kputc()
, the implementation of kputu()
+to print an unsigned integer in decimal format is not optimal but still
+functional.
+
+SRCS = startup.c uplow.1.c uptime.1.c+ +Unfortunately, when I try to build an executable, the link phase fails. + +
+$ make +f030f4.elf +D:\Program Files (x86)\GNU Arm Embedded Toolchain\arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi\bin\arm-none-eabi-ld.exe: uptime.1.o: in function `kp +utu': +D:\Projects\stm32bringup\docs/uptime.1.c:13:(.text+0x6): undefined reference to +`__aeabi_uidivmod' +D:\Program Files (x86)\GNU Arm Embedded Toolchain\arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi\bin\arm-none-eabi-ld.exe: D:\Projects\stm32bringup\do +cs/uptime.1.c:14:(.text+0x14): undefined reference to `__aeabi_uidiv' +make: *** [Makefile:45: f030f4.elf] Error 1 ++ +The compiler has generated code that references two functions +
__aeabi_uidivmod
and __aeabi_uidiv
when compiling
+the lines 13 and 14 of uptime.1.c.
+
++ unsigned r = u % 10 ; + u /= 10 ; ++ +This happens because the compiler generates code for Cortex-M0 which has +no integer division support. So integer division needs to be implemented +by code as it is not supported by hardware. + +I need to pass the linker a reference to GNU Arm Embedded Toolchain +library for Cortex-M0. The library file is libggc.a, the option -l and +-L of the linker tell what the library name is (-lgcc => libgcc.a) and +where to look for it. + +
+LIBDIR = $(GCCDIR)/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp +LIB_PATHS = -L$(LIBDIR) +LIBS = -lgcc + +$(PROJECT).elf: $(OBJS) + @echo $@ + $(LD) -T$(LD_SCRIPT) $(LIB_PATHS) -Map=$(PROJECT).map -cref -o $@ $^ $(LIBS) + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $(PROJECT).lst ++ +Once the Makefile has been updated, the build finish successfully. + +
+$ make +f030f4.elf + text data bss dec hex filename + 769 0 8 777 309 f030f4.elf +f030f4.hex +f030f4.bin ++ +Checking the linker produced map file, f030f4.map, I can see which +library (libgcc.a) but also which modules in the library ( +_udivsi3.o and _dvmd_tls.o) have been used to resolve the +symbols (
__aeabi_uidiv
and __aeabi_idiv0
).
+
++Archive member included to satisfy reference by file (symbol) + +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a +(_udivsi3.o) + uptime.1.o (__aeabi_uidiv) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a +(_dvmd_tls.o) + D:/Program Files (x86)/GNU Arm Embedded Toolchain/ +arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/1 +3.3.1/thumb/v6-m/nofp\libgcc.a(_udivsi3.o) (__aeabi_idiv0) ++ +
+ + +
ar
command distributed by the GNU Arm embedded toolchain is
+the same GNU ar as the Linux or Cygwin and MSYS2 distributions on
+Windows. So I use my native environment implementation for convenience.
+This is true for the utility commands (ar
, objcopy
,
+objdump
and size
) but not for gcc
and
+ld
.
+
+uptime
and found an extra
+dependency to Gnu Arm Embedded Toolchain: some modules included in
+libgcc.a have to be included at link time as the chipset I am using has
+no support for integer division. At this stage I will reuse the library as it
+is, but I know where to look in the map file generated by the linker to find
+which modules are included. If I ever need a better control of the link phase,
+I can use ar
to extract locally those modules from the library.
+
+Next, I will write uptime
with a
+better structure.
+
+
uptime
. There is several things I want to straighten up in
+uptime.1.c:
+main()
is using assembly code to wait for interrupt.
+Definitively not high level C.
+
+kputu( last) ; puts( ” sec”) ;
.
+I should have printf( “%u sec\n”, last) ;
instead.
+
+kputc()
function prototype and the external variable
+declaration for uptime
should be included as a C header file.
++/* system.h -- system services */ + +extern volatile unsigned uptime ; /* seconds elapsed since boot */ + +int init( void) ; /* System initialization, called once at startup */ + +void kputc( unsigned char c) ; /* character output */ +int kputs( const char s[]) ; /* string output */ +void yield( void) ; /* give way */ ++ +Next, I make a revision of uplow.1.c by making a copy into +uplow.2.c. +
+I include system.h which is the interface that uplow.2.c +implements. I will have several implementations of the same interface, +so system.h is not just the interface published by uplow.2.c, +it’s uplow.2.c which is an implementation of system.h. + +
+#include "system.h" /* implements system.h */ ++ +I extract the code for puts() as it is a library function that doesn’t +really belong to the system. +
+I add the implementation of kputs() and yield(). + +
+int kputs( const char s[]) { /* string output */ + int cnt = 0 ; + int c ; + + while( (c = *s++) != 0) { + kputc( c) ; + cnt += 1 ; + } + + return cnt ; +} + +void yield( void) { /* give way */ + __asm( "WFI") ; /* Wait for System Tick Interrupt */ +} ++ +
printf()
in printf.c.
+kputu()
+version by adding characters at the beginning of a string.
+
+kputu()
takes one additional divider parameter, so it can be
+used to print unsigned integer in various format like octal, decimal and
+hexadecimal. Current implementation will work for base 8 to 16, it won’t
+work for binary or base 36.
+
+kputi()
outputs signed integer.
+
+printf()
implements a subset of the format interpreter: %%,
+%c, %d, %i, %o, %s, %u, %x, %X.
++/* printf.c -- format and print data */ +#include <stdarg.h> +#include <stdio.h> +#include "system.h" /* kputc(), kputs() */ + +static int kputu( unsigned u, unsigned d) { + char s[ 12] ; /* room for 11 octal digit + EOS */ + char *p = &s[ sizeof s - 1] ; /* point to last byte */ + + *p = 0 ; /* null terminated string */ + do { + unsigned r = u % d ; + u /= d ; + *--p = "0123456789ABCDEF"[ r] ; + } while( u) ; + + return kputs( p) ; +} + +static int kputi( int i) { + int flag = i < 0 ; + if( flag) { + i = -i ; + kputc( '-') ; + } + + return flag + kputu( i, 10) ; +} + +int printf( const char *fmt, ...) { + va_list ap ; + int cnt = 0 ; + int c ; /* current char in format string */ + va_start( ap, fmt) ; + + while( ( c = *fmt++) != 0) + if( c != '%') { + cnt += 1 ; kputc( c) ; + } else if( ( c = *fmt++) == 0) { + cnt += 1 ; kputc( '%') ; + break ; + } else + switch( c) { + case 'c': + cnt += 1 ; kputc( va_arg( ap, int /* char */)) ; + break ; + case 'o': + cnt += kputu( va_arg( ap, unsigned), 8) ; + break ; + case 'u': + cnt += kputu( va_arg( ap, unsigned), 10) ; + break ; + case 'x': + case 'X': + cnt += kputu( va_arg( ap, unsigned), 16) ; + break ; + case 'i': + case 'd': + cnt += kputi( va_arg( ap, int)) ; + break ; + case 's': + cnt += kputs( va_arg( ap, char *)) ; + break ; + default: + cnt += 1 ; kputc( '%') ; + /* fallthrough */ + case '%': + cnt += 1 ; kputc( c) ; + } + + va_end( ap) ; + return cnt ; +} ++ +
+/* uptime.c -- tells how long the system has been running */ + +#include <stdio.h> +#include "system.h" /* uptime, yield() */ + +static void display( unsigned u, const char *s) { + if( u) + printf( " %d %s%s", u, s, &"s"[ u <= 1]) ; +} + +int main( void) { + static unsigned last ; + + for( ;;) + if( last != uptime) { + unsigned w, d, h, m ,s ; + + last = uptime ; + d = h = m = 0 ; + s = last % 60 ; + w = last / 60 ; + if( w) { + m = w % 60 ; + w /= 60 ; + if( w) { + h = w % 24 ; + w /= 24 ; + if( w) { + d = w % 7 ; + w /= 7 ; + } + } + } + + printf( "up") ; + display( w, "week") ; + display( d, "day") ; + display( h, "hour") ; + display( m, "minute") ; + display( s, "second") ; + printf( "\n") ; + } else + yield() ; /* Wait for System Tick Interrupt */ +} ++ +
SRCS = startup.c uplow.2.c uptime.c printf.c+ +Unfortunately, the build fails at the link phase. + +
+$ make +f030f4.elf +D:\Program Files (x86)\GNU Arm Embedded Toolchain\arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi\bin\arm-none-eabi-ld.exe: uptime.o: in function `main +': +D:\Projects\stm32bringup\docs/uptime.c:41:(.text.startup+0xa4): +undefined reference to `putchar' +make: *** [Makefile:49: f030f4.elf] Error 1 ++ +The linker found a reference to
putchar()
at line 41 of
+uptime.c.
+
++ printf( "\n") ; ++ +I haven’t used
putchar()
in my code and line 41 is a
+printf( "\n")
that can be optimized to a
+putchar( '\n')
. This must be some high level C optimization of gcc.
+
+I add the code for putchar()
in putchar.c as it is a
+standard library function.
+
+
+/* putchar.c -- write a character to stdout */ +#include <stdio.h> +#include "system.h" /* kputc() */ + +int putchar( int c) { + kputc( c) ; + return c ; +} ++ +Updating Makefile by adding
putchar.c
to the composition.
+
+SRCS = startup.c uplow.2.c uptime.c printf.c putchar.c+ +The build now complete successfully. + +
+$ make +f030f4.elf + text data bss dec hex filename + 1797 0 12 1809 711 f030f4.elf +f030f4.hex +f030f4.bin ++ +By checking the map file provided by the linker, I can see that the +number of low level modules referred by the code generated by the +compiler has increased. Both integer and unsigned division but also some +code to handle
switch()
statement are now referenced.
+
++Archive member included to satisfy reference by file (symbol) + +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a(_thumb1_case_sqi.o) + printf.o (__gnu_thumb1_case_sqi) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a(_udivsi3.o) + uptime.o (__aeabi_uidiv) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a(_divsi3.o) + uptime.o (__aeabi_idiv) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a(_dvmd_tls.o) + D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a(_udivsi3.o) (__aeabi_idiv0) ++ +
+ +
+It will take a while to see the days and weeks counts appear, so I will
+need to power the board independently from it’s serial interface. For
+test purpose I fast forward the execution by using a bigger value for
+the increment of uptime
in SysTick_handler()
.
+
+
+I didn’t expect gcc to optimize call to high level C functions,
+replacing printf()
by putchar()
, thus forcing me to
+write additional code. So far I am not concerned by execution speed, so this
+type of optimization is a bit counter productive.
+
+Next, I will make sure that what belongs to the +library category fits in an actual library file. + +
printf()
, putchar()
and puts()
. It’s
+time to bundle them as a library. This will give me more flexibility as I will
+not have to give a full list of the modules to link, the linker will handle
+the missing dependencies by looking into the libraries.
+
+printf()
and putchar()
in
+stand alone modules. As I have removed my previous implementation of
+puts()
from the system, I need to create puts.c.
+
++/* puts.c -- write a string to stdout */ + +#include <stdio.h> +#include "system.h" /* kputc(), kputs() */ + +int puts( const char *s) { + kputs( s) ; + kputc( '\n') ; + return 0 ; +} ++ +
+What’s the name, the content and the rule to maintain the library: + +
+AR = $(BINPFX)ar + +LIBOBJS = printf.o putchar.o puts.o +LIBSTEM = stm32 + +lib$(LIBSTEM).a: $(LIBOBJS) + $(AR) rc $@ $? ++ +Where to look for and which libraries to use in the link phase: + +
+LIBS = -l$(LIBSTEM) -lgcc +LIB_PATHS = -L. -L$(LIBDIR) + +$(PROJECT).elf: $(OBJS) lib$(LIBSTEM).a + @echo $@ from $(OBJS) + $(LD) -T$(LD_SCRIPT) $(LIB_PATHS) -Map=$(PROJECT).map -cref -o $@ $(OBJS) $(LIBS) + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $(PROJECT).lst ++ +Library modules are implicitly part of the composition, so it’s not +necessary to list them anymore. + +
+#SRCS = startup.c uplow.2.c uptime.c printf.c putchar.c +SRCS = startup.c uplow.2.c uptime.c ++ +I include libraries in the list of files to delete when doing a make +clean. + +
+clean: + @echo CLEAN + @rm -f *.o *.elf *.map *.lst *.bin *.hex *.a ++ +
+$ make +f030f4.elf from startup.o uplow.2.o uptime.o + text data bss dec hex filename + 1797 0 12 1809 711 f030f4.elf +f030f4.hex +f030f4.bin ++ +Checking the map produced by the linker I can see that it fetched the +necessary modules for
printf()
and putchar()
from the
+newly created library.
+
++Archive member included to satisfy reference by file (symbol) + +.\libstm32.a(printf.o) uptime.o (printf) +.\libstm32.a(putchar.o) uptime.o (putchar) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a +(_thumb1_case_sqi.o) + .\libstm32.a(printf.o) (__gnu_thumb1_case_sqi) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a +(_udivsi3.o) + uptime.o (__aeabi_uidiv) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a +(_divsi3.o) + uptime.o (__aeabi_idiv) +D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mi +ngw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/13.3.1/thumb/v6-m/nofp\libgcc.a +(_dvmd_tls.o) + D:/Program Files (x86)/GNU Arm Embedded Toolchain/ +arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi/lib/gcc/arm-none-eabi/1 +3.3.1/thumb/v6-m/nofp\libgcc.a(_udivsi3.o) (__aeabi_idiv0) ++ +
SRCS = startup.c uplow.2.c hello.c+ +Build terminates successfully, the changes in size are due to the +difference in the system implementation. + +
+$ make +f030f4.elf from startup.o uplow.2.o hello.o + text data bss dec hex filename + 445 0 8 453 1c5 f030f4.elf +f030f4.hex +f030f4.bin ++ +Checking the map file produced in the link phase, I can see that only +puts.o has been fetched from my local library. + +
+Archive member included to satisfy reference by file (symbol) + +.\libstm32.a(puts.o) hello.o (puts) ++ +
+Next, I will continue on the topic of asynchronous serial +transmission and look into baud rate and clock configuration. + +
“The time has come,” the walrus said, “to talk of many things: Of baud + rates – and clocks – and quartz.”+ +One thing to consider in any kind of transmission is the speed, how fast +or how slowly you can transmit data. I have configured USART1 at +9600 baud, keeping the other settings at default value (8N1), +so how fast is that? + +
+ -- Les huit scaroles -- +
+The thing is, I didn’t program USART1 to transmit at 9600 baud. As my +default clock is 8MHz, I had to write in USART1 baud rate register a +value close to 8000000/9600 or 2500/3, 833 is close enough but my actual +transmission speed is closer to 9604, slightly faster than 9600 baud. +
+The error is small (4/10000) and the transmission works fine. Still +common baud rates are 300, 1200, 2400, 9600, 19200, 38400, 57600, 115200. +It would be better if my clock frequency was 6MHz or 12MHz if I +want to work at higher baud rate. + +
+ +
+The default configuration I have been using so far goes like this. +
+The PLL output frequency must be in the range 16-48 MHz. As I am looking +for a frequency that can be divided by 3 to match most of the baud rate, +I will use 24 MHz. +
+To set a 24 MHz clock with a 8 MHz High Speed External Oscillator (HSE): +
+I expand the board description part by adding HSE
,
+PLL
and BAUD
macro definitions. Based on those I can
+handle four clock configurations: HSI, HSE, PLL HSI and PLL HSE.
+
+
+/* user LED ON when PA4 is high */ +#define LED_IOP A +#define LED_PIN 4 +#define LED_ON 1 +/* 8MHz quartz, configure PLL at 24MHz */ +#define HSE 8000000 +#define PLL 6 +#define BAUD 9600 + +#ifdef PLL +# ifdef HSE +# define CLOCK HSE / 2 * PLL +# else /* HSI */ +# define CLOCK 8000000 / 2 * PLL +# endif +# if CLOCK < 16000000 +# error PLL output below 16MHz +# endif +#else +# ifdef HSE +# define CLOCK HSE +# else /* HSI */ +# define CLOCK 8000000 +# endif +#endif ++ +At compilation time there will be a check if the clock targeted is in +the supported range of the chipset and a warning given if the baud rate +generation is not accurate. + +
+#if CLOCK > 48000000 +# error clock frequency exceeds 48MHz +#endif + +#if CLOCK % BAUD +# warning baud rate not accurate at that clock frequency +#endif ++ +I expand the definition of the Reset and Clock Control (RCC) peripheral +to add the necessary bit fields. + +
+#define CAT( a, b) a##b +#define HEXA( a) CAT( 0x, a) +#define RCC ((volatile long *) 0x40021000) + +#define RCC_CR RCC[ 0] +#define RCC_CR_HSION 0x00000001 /* 1: Internal High Speed clock enable */ +#define RCC_CR_HSEON 0x00010000 /* 16: External High Speed clock enable */ +#define RCC_CR_HSERDY 0x00020000 /* 17: External High Speed clock ready flag$ +#define RCC_CR_PLLON 0x01000000 /* 24: PLL enable */ +#define RCC_CR_PLLRDY 0x02000000 /* 25: PLL clock ready flag */ + +#define RCC_CFGR RCC[ 1] +#define RCC_CFGR_SW_MSK 0x00000003 /* 1-0: System clock SWitch Mask */ +#define RCC_CFGR_SW_HSE 0x00000001 /* 1-0: Switch to HSE as system clock */ +#define RCC_CFGR_SW_PLL 0x00000002 /* 1-0: Switch to PLL as system clock */ +#define RCC_CFGR_SWS_MSK 0x0000000C /* 3-2: System clock SWitch Status Mask$ +#define RCC_CFGR_SWS_HSE 0x00000004 /* 3-2: HSE used as system clock */ +#define RCC_CFGR_SWS_PLL 0x00000008 /* 3-2: PLL used as system clock */ +#define RCC_CFGR_PLLSRC 0x00010000 +#define RCC_CFGR_PLLSRC_HSI 0x00000000 /* HSI / 2 */ +#define RCC_CFGR_PLLSRC_HSE 0x00010000 /* HSE */ +#define RCC_CFGR_PLLXTPRE 0x00020000 +#define RCC_CFGR_PLLXTPRE_DIV1 0x00000000 /* HSE */ +#define RCC_CFGR_PLLXTPRE_DIV2 0x00020000 /* HSE / 2 */ +#define RCC_CFGR_PLLMUL_MSK (0x00F << 18) +#define RCC_CFGR_PLLMUL( v) ((v - 2) << 18) + +#define RCC_AHBENR RCC[ 5] +#define RCC_AHBENR_IOP( h) (1 << (17 + HEXA( h) - 0xA)) + +#define RCC_APB2ENR RCC[ 6] +#define RCC_APB2ENR_USART1EN 0x00004000 /* 14: USART1 clock enable */ ++ +The code to configure the clocks follow the steps I have described +before. The conditional compilation allows the generation of the four +possible cases: HSI, HSE, PLL HSI and PLL HSE. + +
+/* By default SYSCLK == HSI [8MHZ] */ + +#ifdef HSE +/* Start HSE clock (8 MHz external oscillator) */ + RCC_CR |= RCC_CR_HSEON ; +/* Wait for oscillator to stabilize */ + do {} while( (RCC_CR & RCC_CR_HSERDY) == 0) ; +#endif + +#ifdef PLL +/* Setup PLL HSx/2 * 6 [24MHz] */ + /* Default 0: PLL HSI/2 src, PLL MULL * 2 */ +# ifdef HSE + RCC_CFGR = RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_DIV2 ; +# endif + RCC_CFGR |= RCC_CFGR_PLLMUL( PLL) ; + RCC_CR |= RCC_CR_PLLON ; + do {} while( (RCC_CR & RCC_CR_PLLRDY) == 0) ; /* Wait for PLL */ + +/* Switch to PLL as system clock SYSCLK == PLL [24MHz] */ + RCC_CFGR = (RCC_CFGR & ~RCC_CFGR_SW_MSK) | RCC_CFGR_SW_PLL ; + do {} while( (RCC_CFGR & RCC_CFGR_SWS_MSK) != RCC_CFGR_SWS_PLL) ; +#else +# ifdef HSE +/* Switch to HSE as system clock SYSCLK == HSE [8MHz] */ + RCC_CFGR = (RCC_CFGR & ~RCC_CFGR_SW_MSK) | RCC_CFGR_SW_HSE ; + do {} while( (RCC_CFGR & RCC_CFGR_SWS_MSK) != RCC_CFGR_SWS_HSE) ; +# endif +#endif + +#ifdef HSE +/* Switch off HSI */ + RCC_CR &= ~RCC_CR_HSION ; +#endif ++ +Systick reload value is calculated based on
CLOCK
constant value.
+
++ SYSTICK_RVR = CLOCK / 8 - 1 ; /* HBA / 8 */ ++ +Similarly, USART1 baud rate register is calculated based on
CLOCK
+and BAUD
constant value.
+
++ USART1[ BRR] = CLOCK / BAUD ; /* PCLK is default source */ ++ +I add a debug print at the end of
init()
to display which clock
+configuration has been set.
+
++ kputs( +#ifdef PLL + "PLL" +#endif +#ifdef HSE + "HSE" +#else + "HSI" +#endif + "\n") ; ++ +
SRCS = startup.c clocks.c uptime.c+ +Build complete successfully, this is for PLL HSE board configuration. + +
+$ make +f030f4.elf from startup.o clocks.o uptime.o + text data bss dec hex filename + 1901 0 12 1913 779 f030f4.elf +f030f4.hex +f030f4.bin ++ +I use a board with a 8 MHz quartz soldered on and test the four clock +configuration. + +
+Next, I will implement interrupt driven +transmission. + +
kputc()
, remove the
+recursive call to handle CR LF transmission and avoid the busy wait
+loop. USART1 can trigger an interrupt when the Transmit Data Register
+(TDR) is empty which is all I need to implement interrupt driven
+transmission.
+
++I make a copy of startup.c into startup.txeie.c to do the +changes. The name txeie refers to Transmit Data Register Empty Interrupt +Enabled. + +
+/* Stubs for System Exception Handler */ +void Default_Handler( void) ; +#define dflt_hndlr( fun) void fun##_Handler( void) \ + __attribute__((weak,alias("Default_Handler"))) +dflt_hndlr( NMI) ; +dflt_hndlr( HardFault) ; +dflt_hndlr( SVCall) ; +dflt_hndlr( PendSV) ; +dflt_hndlr( SysTick) ; + +dflt_hndlr( WWDG) ; +dflt_hndlr( RTC) ; +dflt_hndlr( FLASH) ; +dflt_hndlr( RCC) ; +dflt_hndlr( EXTI0_1) ; +dflt_hndlr( EXTI2_3) ; +dflt_hndlr( EXTI4_15) ; +dflt_hndlr( DMA_CH1) ; +dflt_hndlr( DMA_CH2_3) ; +dflt_hndlr( DMA_CH4_5) ; +dflt_hndlr( ADC) ; +dflt_hndlr( TIM1_BRK_UP_TRG_COM) ; +dflt_hndlr( TIM1_CC) ; +dflt_hndlr( TIM3) ; +dflt_hndlr( TIM6) ; +dflt_hndlr( TIM14) ; +dflt_hndlr( TIM15) ; +dflt_hndlr( TIM16) ; +dflt_hndlr( TIM17) ; +dflt_hndlr( I2C1) ; +dflt_hndlr( I2C2) ; +dflt_hndlr( SPI1) ; +dflt_hndlr( SPI2) ; +dflt_hndlr( USART1) ; +dflt_hndlr( USART2) ; +dflt_hndlr( USART3_4_5_6) ; +dflt_hndlr( USB) ; + +/* Interrupt vector table: + * 1 Stack Pointer reset value + * 15 System Exceptions + * 32 Device specific Interrupts + */ +typedef void (*isr_p)( void) ; +isr_p const isr_vector[ 16 + 32] __attribute__((section(".isr_vector"))) = { + (isr_p) &__StackTop, +/* System Exceptions */ + Reset_Handler, + NMI_Handler, + HardFault_Handler, + 0, 0, 0, 0, 0, 0, 0, + SVCall_Handler, + 0, 0, + PendSV_Handler, + SysTick_Handler, +/* STM32F030xx specific Interrupts cf RM0360 */ + WWDG_Handler, + 0, + RTC_Handler, + FLASH_Handler, + RCC_Handler, + EXTI0_1_Handler, + EXTI2_3_Handler, + EXTI4_15_Handler, + 0, + DMA_CH1_Handler, + DMA_CH2_3_Handler, + DMA_CH4_5_Handler, + ADC_Handler, + TIM1_BRK_UP_TRG_COM_Handler, + TIM1_CC_Handler, + 0, + TIM3_Handler, + TIM6_Handler, + 0, + TIM14_Handler, + TIM15_Handler, + TIM16_Handler, + TIM17_Handler, + I2C1_Handler, + I2C2_Handler, + SPI1_Handler, + SPI2_Handler, + USART1_Handler, + USART2_Handler, + USART3_4_5_6_Handler, + 0, + USB_Handler +} ; ++ +
+I add the description of the TX Empty Interrupt Enable bit in the +Configuration Register of USART1: + +
#define USART_CR1_TXEIE (1 << 7) /* 7: TDR Empty Interrupt Enable */+ +I use a Round Robin buffer to synchronize
kputc()
and
+USART1_Handler()
making sure they don’t write to the same location.
+
++static unsigned char txbuf[ 8] ; // best if size is a power of 2 for cortex-M0 +#define TXBUF_SIZE (sizeof txbuf / sizeof txbuf[ 0]) +static unsigned char txbufin ; +static volatile unsigned char txbufout ; ++
kputc()
write in txbuf[]
while
+USART1_Handler()
only read from it.
+
+txbufin
is the index of the position where
+kputc()
will insert a character, it’s written by
+kputc()
and read by USART1_Handler()
.
+
+txbufout
is the index of the position where
+USART1_Handler()
will fetch a character, it’s written by
+USART1_Handler()
and read by kputc()
. The value of
+txbufout
will change under interrupt, so it is marked as
+volatile
to make sure the compiler will not optimize the code in a
+conflicting way.
+
+(idx+1) %
+ size
into (idx+1) & (size-1)
, which is more efficient on chipset
+ with no support for integer division.
+
++void USART1_Handler( void) { + if( txbufout == txbufin) { + /* Empty buffer => Disable TXEIE */ + USART1[ CR1] &= ~USART_CR1_TXEIE ; + } else { + static unsigned char lastc ; + unsigned char c ; + + c = txbuf[ txbufout] ; + if( c == '\n' && lastc != '\r') + c = '\r' ; + else + txbufout = (txbufout + 1) % TXBUF_SIZE ; + + USART1[ TDR] = c ; + lastc = c ; + } +} + +void kputc( unsigned char c) { /* character output */ + int nextidx ; + +/* Wait if buffer full */ + nextidx = (txbufin + 1) % TXBUF_SIZE ; + while( nextidx == txbufout) + yield() ; + + txbuf[ txbufin] = c ; + txbufin = nextidx ; +/* Trigger transmission by enabling interrupt */ + USART1[ CR1] |= USART_CR1_TXEIE ; +} ++
kputc()
enables the interrupt generation after a new
+character is inserted in the buffer.
+
+USART1_Handler()
disables the interrupt generation when the
+buffer is empty.
+
+kputc()
now yields when the buffer is full.
++The 32 device specific interrupts are enabled through the Nested +Vectored Interrupt Controller (NVIC). NVIC is a core peripheral so its +description is in the Programming Manual. Enabling is done through the +Interrupt Set-Enable Register (ISER). +
+Set-Enable means writing a 1 enables while writing a 0 does nothing. +Reading reports the current settings. There is a corresponding +Clear-Enable register, to disable interrupts. + +
+#define NVIC ((volatile long *) 0xE000E100) +#define NVIC_ISER NVIC[ 0] +#define unmask_irq( idx) NVIC_ISER = 1 << idx +#define USART1_IRQ_IDX 27 ++ +I add a call to the macro unmask_irq() after USART1 initialization. + +
+/* Unmask USART1 irq */ + unmask_irq( USART1_IRQ_IDX) ; ++ +
SRCS = startup.txeie.c txeie.c uptime.c+ +Build completes successfully + +
+$ make +f030f4.elf from startup.txeie.o txeie.o uptime.o + text data bss dec hex filename + 2097 0 20 2117 845 f030f4.elf +f030f4.hex +f030f4.bin ++ +Checking the map and lst files I can verify that +
kputc()
was using 4 bytes of bss
+to hold lastchar (1 byte). The new version uses 12 bytes to hold the round
+robin buffer (8 bytes), its in and out indexes (2 bytes) and lastchar (1 byte).
+
+% size
to
+bit masking & (size – 1)
as the size 8 is a power of 2.
++Next, I will use an external sensor to do some +measurement. + +
+The io data line when idle need to be at high level, so a pull up +resistor is necessary. +
+I will use 3.3V, connect DHT11 data io pin to STM32 GPIOA0. The small +DHT11 boards I use all have a pull up resistor between vcc and io data. +
+ + +
+This requirement is easy to implement based on uptime 1 second counter. + +
+To request data, the STM32 needs to keep the io data line at low level +for more than 18 ms. +
+Once the STM32 releases the line, the pull up will bring the level back to up +and the DHT11 will assert the beginning of transmission by first pulling it +down for 80 µs then up for 80 µs. +
+The DHT11 will transmit 40 bits (5 bytes), high bit first, encoding a +zero as 50 µs low followed by 26-28 µs high and a one as 50 µs low +followed by 70 µs high. +
+The last bit is followed by 50 µs low to signal the end of transmission +and the return to idle state. +
+To implement this protocol on STM32 side: +
+The sleep granularity is 1 µs, I need to assert the line LOW for at +least 18 ms, so 1 ms or even 10 ms granularity would be fine. +
+I add the following lines to system.h to declare the interface I am +going to implement. + +
+/* GPIOA low level API ********************************************************/ + +typedef enum { + LOW = 0, + HIGH +} iolvl_t ; + +void gpioa_input( int pin) ; /* Configure GPIOA pin as input */ +void gpioa_output( int pin) ; /* Configure GPIOA pin as output */ +iolvl_t gpioa_read( int pin) ; /* Read level of GPIOA pin */ + +void usleep( unsigned usecs) ; /* wait at least usec µs */ ++ +I make a copy of txeie.c into gpioa.c to implement the new API. + +
+#define IDR 4 + +/* GPIOA low level API ********************************************************/ + +void gpioa_input( int pin) { /* Configure GPIOA pin as input */ + GPIOA[ MODER] &= ~(3 << (pin * 2)) ; /* Apin as input [00] */ +} + +void gpioa_output( int pin) { /* Configure GPIOA pin as output */ + GPIOA[ MODER] |= 1 << (pin * 2) ; /* Apin output (over [00]) */ +} + +iolvl_t gpioa_read( int pin) { /* Read level of GPIOA pin */ + return LOW != (GPIOA[ IDR] & (1 << pin)) ; +} ++ +I didn’t use the GPIO Input Data Register (IDR) until now, so I add it +to the registers description. +
+gpioa_output()
implementation is minimal. I know I am switching
+only between input and output mode, so I don’t need to mask the bit field
+first.
+
+I use the System Tick to implement usleep()
.
+
+
+void usleep( unsigned usecs) { /* wait at least usec µs */ +#if CLOCK / 8000000 < 1 +# error HCLOCK below 8 MHz +#endif + usecs = SYSTICK_CVR - (CLOCK / 8000000 * usecs) ; + while( SYSTICK_CVR > usecs) ; +} ++ +The System Tick generates an interrupt every second but I can read the +Current Value Register (CVR) to pause for smaller time period. +
+As I will read the sensor just after a new second count, I know that the +CVR value is close to maximum and I don’t need to care for a roll +over. +
+SysTick input clock is HCLK/8, this implementation will work for +HCLK equal to a multiple of 8 MHz (8, 16, 24, 32, 40, 48). + +
+/* dht11.h -- DHT11 API */ + +typedef enum { + DHT11_SUCCESS, + DHT11_FAIL_TOUT, + DHT11_FAIL_CKSUM +} dht11_retv_t ; + + +/* 5 .. 95 %RH, -20 .. 60 °C */ +extern unsigned char dht11_humid ; /* 5 .. 95 %RH */ +extern signed char dht11_tempc ; /* -20 .. 60 °C */ +extern unsigned char dht11_tempf ; /* .0 .. .9 °C */ + + +void dht11_init( void) ; +dht11_retv_t dht11_read( void) ; ++ +Usage: +
dht11_init()
, once at startup.
+
+dht11_read()
, not more often than every two seconds,
+starting one second after voltage stabilizes.
+
++/* dht11main.c -- sample DHT11 sensor */ +#include <stdio.h> + +#include "system.h" +#include "dht11.h" + +int main() { + static unsigned last ; + + dht11_init() ; + for( ;;) + if( last == uptime) + yield() ; + else { + last = uptime ; + if( last & 1) /* every 2 seconds starting 1 second after boot */ + switch( dht11_read()) { + case DHT11_SUCCESS: + printf( "%u%%RH, %d.%uC\n", dht11_humid, dht11_tempc, + dht11_tempf) ; + break ; + case DHT11_FAIL_TOUT: + puts( "Timeout") ; + break ; + case DHT11_FAIL_CKSUM: + puts( "Cksum error") ; + } + } +} ++ +
+dht11_retv_t dht11_read( void) { + unsigned char values[ 5] ; + +/* Host START: pulls line down for > 18ms then release line, pull-up raises to HIGH */ + dht11_output() ; + usleep( 18000) ; + dht11_input() ; + +/* DHT START: takes line, 80µs low then 80µs high */ + wait_level( LOW) ; /* HIGH -> LOW, starts 80µs low */ + wait_level( HIGH) ; /* LOW -> HIGH, ends 80µs low, starts 80µs high */ + +/* DHT transmits 40 bits, high bit first + * 0 coded as 50µs low then 26~28µs high + * 1 coded as 50µs low then 70µs high + */ + wait_level( LOW) ; /* HIGH -> LOW, ends 80µs high, starts 50µs low */ + + unsigned char sum = 0 ; + unsigned char v = 0 ; + for( int idx = 0 ; idx <= 4 ; idx += 1) { + sum += v ; + v = 0 ; + for( unsigned char curbit = 128 ; curbit ; curbit >>= 1) { + /* Measure duration of HIGH level */ + wait_level( HIGH) ; /* LOW -> HIGH, ends 50µs low, starts timed high */ + wait_level( LOW) ; /* HIGH -> LOW, timed high ends, starts 50µs low */ + /* Set bit based on measured HIGH duration */ + if( duration is 70µs) /* 0 == 26~28µs, 1 == 70µs */ + v |= curbit ; + } + + values[ idx] = v ; + } + +/* DHT STOP: releases line after 50µs, pull-up raises to HIGH */ + wait_level( HIGH) ; /* LOW -> HIGH, ends 50µs low, dht has released the line */ + + if( sum != values[ 4]) + return DHT11_FAIL_CKSUM ; + + dht11_humid = values[ 0] ; + dht11_tempc = values[ 2] ; + dht11_tempf = values[ 3] ; + if( dht11_tempf & 0x80) { + dht11_tempc *= -1 ; + dht11_tempf &= 0x7F ; + } + + return DHT11_SUCCESS ; +} ++ +To turn this pseudocode into real implementation I need to code +
wait_level()
: wait for a line transmission and triggers a
+timeout if there is none.
+
+wait_level()
as a macro, triggering a timeout when the
+number of retries reach a limit. Originally, I set MAX_RETRIES to 999,
+later I tune it to be large enough for the highest frequency supported
+by STM32F030 (48 MHz).
+
++#define MAX_RETRIES 200 /* at 48 MHz, 160 retries for 80 µs HIGH */ +#define is_not_LOW( a) a != LOW +#define is_not_HIGH( a) a == LOW +#define wait_level( lvl) \ + retries = MAX_RETRIES ; \ + while( is_not_##lvl( dht11_bread())) \ + if( retries-- == 0) \ + return DHT11_FAIL_TOUT ++ +
wait_level()
allows me to measure the duration of a wait in retries
+unit. As DHT11 starts transmission by 80µs LOW followed by 80µs HIGH, I
+can measure 80µs in retries unit. This is all I need to calibrate the timing
+measurement.
++I can do this calibration every time the DHT11 starts transmission, this +way I don’t need to update some constant if I change the frequency of my +system clock. + +
+/* DHT transmits 40 bits, high bit first + * 0 coded as 50µs low then 26~28µs high + * 1 coded as 50µs low then 70µs high + */ + wait_level( LOW) ; /* HIGH -> LOW, ends, 80µs high, starts 50µs low */ + int threshold = (MAX_RETRIES + retries) / 2 ; ++ +Based on the measured duration of 80µs, I can define a threshold at +40µs. Later to identify if a bit transmitted was a 0 (26~28µs) or a 1 +(70µs), I will check if its duration is below or higher than the +threshold. + +
+ wait_level( LOW) ; /* HIGH -> LOW, timed high ends, starts 50µs low */ + /* Set bit based on measured HIGH duration */ + if( retries < threshold) /* false == 26~28µs, true == 70µs */ + v |= curbit ; ++ +To finalize
dht11_read()
, I declare retries before the first
+wait_level()
.
+
++/* DHT START: takes line, 80µs low then 80µs high */ + int retries ; /* retry counter */ + wait_level( LOW) ; /* HIGH -> LOW, starts 80µs low */ ++ +There is still a bit of pseudocode left as I need to map +
dht11_input()
, dht11_output()
and
+dht11_bread()
to actual GPIO peripheral, pin and low level
+functions. I am using GPIOA pin 0.
+
++/* dht11.c -- DHT11 humidity and temperature sensor reading */ + +#include "dht11.h" /* implements DHT11 API */ + +#include "system.h" /* usleep(), gpioa_*() */ + + +#define DIO 0 + +#define dht11_input() gpioa_input( DIO) +#define dht11_output() gpioa_output( DIO) +#define dht11_bread() gpioa_read( DIO) + +/* 5 .. 95 %RH, -20 .. 60 °C */ +unsigned char dht11_humid ; /* 5 .. 95 %RH */ + signed char dht11_tempc ; /* -20 .. 60 °C */ +unsigned char dht11_tempf ; /* .0 .. .9 °C */ + +void dht11_init( void) { + dht11_input() ; +} ++ +After adding the includes, global variables declarations and the +implementation of
dht11_init()
, I can build and test.
+
+SRCS = startup.txeie.c gpioa.c dht11main.c dht11.c+ +Build completes successfully + +
+$ make +f030f4.elf from startup.txeie.o gpioa.o dht11main.o dht11.o + text data bss dec hex filename + 1877 0 24 1901 76d f030f4.elf +f030f4.hex +f030f4.bin ++ +Flashing the board and starting execution, I can see a new output every +two seconds. +
+ +
+The humidity value seems off the mark. So I need to investigate what’s +the issue. + +
+Next, I will investigate if the values read are +correct. + +
+Some candidates for investigations: +
+I double checked both code and wiring to be on the safe side. +
+I am confident that the protocol implementation matches the transmission +of DHT11. So next I need recheck if the setup time (one second) and +frequency of reading (every two seconds) are the correct requirement. + +
+I also have an English Aosong DHT11 datasheet that seems outdated. +Aosong doesn’t seem to allow redistribution of their datasheet so most +online vendor have made their own based on that English version. +
+The English datasheet states the temperature range as 0~50℃, the +humidity range to be 20~95%RH and recommend a reading frequency greater +than 5 seconds. +
+The Chinese datasheet states the temperature range as -20~60°C, the +humidity range to be 0~95%RH and recommend a reading frequency greater +than 2 seconds. It also explains the encoding of negative temperature. +
+My implementation is based on the latest Chinese version. I can retest +with a longer interval between readings in case I am using a chip that +follows the older specification (5 seconds interval instead of 2). +
+In dht11main.c I just need to change the test if( 1 & last)
+to if( 2 == (last % 15))
in order to read every 15 seconds after
+a setup time of 2 seconds. If this works better, I can retry for shorter
+intervals by changing the modulo value.
+
+When I test with readings every 15 seconds, I get stable humidity value +around 25~26%RH. As I have changed both interval and setup time, I need +confirm that it is the interval time that matters. +
+With intervals of 5 and 6 seconds, the reading jumps above 37%RH. So +it’s clearly a problem with the interval. +
+I want to make a round number of samples per minute, so I need retest to +check if 10 and 12 seconds work the same as 15, but before I do fine +tuning, I better check if this is not just a problem with that +particular DHT11. + +
+Defects happen! No production line output is 100% perfect, so I may just +have a defective or damaged chip. As this particular product is low +cost, I have several boards on hands I bought from different suppliers, I would +be very unlucky if they end up all coming from the same bad production +batch or all damaged by mishandling. +
+Testing the four DHT11 I have, I find that three of them are working in +their precision range when sampled every five seconds. Understanding +that humidity precision is ±5%RH and temperature precision is ±2℃, there +is still room for quite some variation between readings from different +devices. +
+I select the most accurate chip at my current environment humidity and +temperature to do further test. + +
+I need to use a 5V tolerant GPIO pin for this test, so I switch to GPIOA +13 (SWDIO). By default that pin is configured as ALT SWDIO, floating +input with weak pull-up, similar to the initial state for DHT11 Data IO +pin. +
+The board needs to be powered by its USB connector 5V source instead of +directly by the USB to Serial adapter, I make sure board and adapter are +powered independently, the two being connected only by TX, RX and GND. +I can then select the voltage of DHT11 according to what I want to test, 3.3V +or 5V. +
+There is a difference in measurement, 3.3V giving slightly higher value +than 5V, for this particular test: 2%RH more for humidity and 0.3℃ for +temperature. There is no clear advantage to use 5V over 3.3V. +
+I am not doing precise voltage test as the precision of DHT11 and the +variation between the chips I have would make the interpretation of the +results irrelevant. + +
+I have implemented dht11_read()
accordingly so I just need to
+test at below zero ℃.
+
+From my test, I can see that the values reported are negative but I +found a difference versus the datasheet. +
+According to the datasheet, the temperature values are encoded as 1 byte +for the integer part and 1 byte for the one decimal digit fractional +part, the highest bit of the fractional part indicating the sign. +
+So when temperature crosses zero, I expects to see + +
+0 + 2 => 0.2 +0 + 1 => 0.1 +0 + 0 => 0.0 +0 - 1 => -0.1 +0 - 2 => -0.2 +... +0 - 8 => -0.8 +0 - 9 => -0.9 +1 - 0 => -1.0 +1 - 1 => -1.1 +... ++ +Instead the values transmitted are + +
+0 + 2 => 0.2 +0 + 1 => 0.1 +0 + 0 => 0.0 +0 - 9 => ??? +0 - 8 => ??? +... +0 - 2 +0 - 1 +0 - 0 => !!! +1 - 9 +... ++ +I have to modify my original implementation + +
+ dht11_tempc = values[ 2] ; + dht11_tempf = values[ 3] ; + if( dht11_tempf & 0x80) { + dht11_tempc *= -1 ; + dht11_tempf &= 0x7F ; + } ++ +And retest after the following modification. + +
+ dht11_tempc = values[ 2] ; + dht11_tempf = values[ 3] ; + if( dht11_tempf & 0x80) { + dht11_tempc *= -1 ; + dht11_tempf = 10 - ( dht11_tempf & 0x7F) ; + if( dht11_tempf == 10) { + dht11_tempc -= 1 ; + dht11_tempf = 0 ; + } + } ++ +
+I found some difference between the Chinese datasheet and the behavior +of the chips I have when it come to representation of temperature below +0℃. +
+Cost and one pin transmission interface are the two main advantages of +this chipset. +
+I could use the DHT11 for monitoring temperature and humidity +variations. For fast and accurate measurements, this is not the droid I +am looking for. + +
+Next, I will use another digital thermometer as a +reference. + +
+The io data line when idle need to be at high level, so a pull up +resistor is necessary. The small DS18B20 board I use has a pull up +resistor between vcc and io data. +
+ +
+It is possible to power the chip using data io and gnd only (no vcc) in +Parasitic Power Mode if a two wire only interface is needed. I won’t use +this feature for now. + ++A typical transaction sequence goes like this +
+static ds18b20_retv_t initialization() { +/* Reset */ + output() ; /* Wire LOW */ + usleep( 480) ; + input() ; /* Wire floating, HIGH by pull-up */ + +/* Presence */ + int retries ; + wait_level( HIGH) ; /* Pull-up LOW -> HIGH, T1 */ + wait_level( LOW) ; /* DS18B20 asserts line to LOW, T2, T2 - T1 = 15~60us */ + wait_level( HIGH) ; /* DS18B20 releases lines, Pull-up LOW -> HIGH, T3 + ** T3 - T2 = 60~240us */ + usleep( 405) ; /* 480 = 405 + 15 + 60 */ + + return DS18B20_SUCCESS ; +} ++ +The ROM Command is how the host selects the device for communication. +Writing a ROM Skip command addresses all devices connected. +
+The Function Command is the request to the device selected by the ROM +Command: +
+static void write( unsigned char uc) { +/* Transmit byte, least significant bit first */ + for( unsigned char curbit = 1 ; curbit ; curbit <<= 1) { + /* Transmit a bit takes 60us + 1us between transmit */ + /* Write 1: <15us LOW */ + /* Write 0: 60us LOW */ + unsigned t = uc & curbit ? 13 : 60 ; + output() ; /* Wire LOW */ + usleep( t) ; + input() ; /* Wire floating, HIGH by pull-up */ + usleep( 61 - t) ; + } +} ++ +When the host expects to read some data, it can triggers a 1 bit +transmission from the device by first pulling the line LOW for 1µs then +reading the state asserted by the device. + +
+static iolvl_t poll( void) { + output() ; /* Wire LOW */ + usleep( 1) ; + input() ; /* Wire floating, HIGH by pull-up */ + usleep( 5) ; + iolvl_t bit = bread() ; + usleep( 55) ; + return bit ; +} ++ +Integrity of the data transmitted by the device is guaranteed by 8 bit +Cyclic Redundancy Check (CRC). + +
+static unsigned char read( unsigned char *p, int size) { + unsigned char crc = 0 ; + + while( size--) { + /* Receive byte, least significant bit first */ + unsigned char uc = 0 ; + for( unsigned char curbit = 1 ; curbit ; curbit <<= 1) { + /* read bit */ + int v = poll() ; + if( v) + uc |= curbit ; + + /* update CRC */ + v ^= crc ; + crc >>= 1 ; + if( v & 1) + crc ^= 0x119 >> 1 ; /* reverse POLY = x^8 + x^5 + x^4 + 1 */ + } + + /* store byte */ + *p++ = uc ; + } + + return crc ; +} ++ +Base on this, complex transaction sequences can be coded. +
+The transaction to read the eight byte scratchpad (device memory) plus +CRC: + +
+static ds18b20_retv_t read_scratchpad( unsigned char scratchpad[]) { + ds18b20_retv_t ret = initialization() ; + if( ret != DS18B20_SUCCESS) + return ret ; + + write( 0xCC) ; /* Skip ROM */ + write( 0xBE) ; /* Read Scratchpad */ + return read( scratchpad, 9) ? DS18B20_FAIL_CRC : DS18B20_SUCCESS ; +} ++ +
+The host requests the conversion, waits for the conversion to end, then +fetch the device memory to read the measurement. +
+The host can poll()
the device to check if the conversion is
+finished.
+
+
+/* ds18b20.h -- 1-Wire temperature sensor */ + +typedef enum { + DS18B20_SUCCESS, + DS18B20_FAIL_TOUT, + DS18B20_FAIL_CRC +} ds18b20_retv_t ; + +void ds18b20_init( void) ; +ds18b20_retv_t ds18b20_resolution( unsigned res) ; /* 9..12 bits */ +ds18b20_retv_t ds18b20_convert( void) ; +ds18b20_retv_t ds18b20_fetch( short *deciCtemp) ;/* -550~1250 = -55.0~125.0 C */ +ds18b20_retv_t ds18b20_read( short *deciCtemp) ; /* -550~1250 = -55.0~125.0 C */ ++ +Usage: +
ds18b20_init()
, once at startup.
+
+ds18b20_resolution()
to select the resolution. It can be
+done before starting a conversion. The value will be kept until the ds18b20
+is powered down.
+
+ds18b20_read()
, which will
+start a conversion, wait until it finishes, fetch the value from the device
+memory and deci℃ (1 deci℃ = 0.1 ℃).
+
+ds18b20_read()
, call
+ds18b20_convert()
followed by ds18b20_fetch()
once
+enough time has elapsed to complete the conversion.
++/* ds18b20main.c -- sample temperature using 1-Wire temperature sensor */ + +#include <stdio.h> + +#include "system.h" /* uptime */ +#include "ds18b20.h" /* ds18b20_() */ + +int main( void) { + unsigned last = 0 ; + + ds18b20_init() ; + ds18b20_resolution( 12) ; /* Set highest resolution: 12 bits */ + ds18b20_convert() ; /* start temperature conversion */ + for( ;;) + if( last == uptime) + yield() ; + else { + short val ; + + last = uptime ; + switch( ds18b20_fetch( &val)) { + case DS18B20_SUCCESS: + printf( "%i.%i\n", val / 10, val % 10) ; + break ; + case DS18B20_FAIL_TOUT: + puts( "Timeout") ; + break ; + case DS18B20_FAIL_CRC: + puts( "CRC Error") ; + } + + ds18b20_convert() ; /* start temperature conversion */ + } +} ++ +
+/* ds18b20.c -- 1-Wire digital thermometer */ + +#include "ds18b20.h" /* implements DS18B20 API */ + +#include "system.h" /* gpioa_(), usleep() */ + +#define DIO 13 +#define input() gpioa_input( DIO) +#define output() gpioa_output( DIO) +#define bread() gpioa_read( DIO) + +#define MAX_RETRIES 999 +#define wait_level( lvl) \ + retries = MAX_RETRIES ; \ + while( bread() != lvl) \ + if( retries-- == 0) \ + return DS18B20_FAIL_TOUT + +void ds18b20_init( void) { + input() ; /* Wire floating, HIGH by pull-up */ +} ++ +I add the local functions that are the building block for the +transactions (
initialization()
, write()
,
+poll()
and read()
) and
+the read_scratchpad()
transaction I explained before.
++Start conversion transaction: + +
+ds18b20_retv_t ds18b20_convert( void) { + ds18b20_retv_t ret ; + + ret = initialization() ; + if( ret != DS18B20_SUCCESS) + return ret ; + + write( 0xCC) ; /* Skip ROM */ + write( 0x44) ; /* Convert T */ + return DS18B20_SUCCESS ; +} ++ +Fetch temperature, to be called after conversion is done. + +
+ds18b20_retv_t ds18b20_fetch( short *deciCtemp) { /* -550~1250 = -55.0~125.0 C $ + ds18b20_retv_t ret ; + unsigned char vals[ 9] ; /* scratchpad */ + + ret = read_scratchpad( vals) ; + if( ret != DS18B20_SUCCESS) + return ret ; + + *deciCtemp = *((short *) vals) * 10 / 16 ; + return DS18B20_SUCCESS ; +} ++ +Blocking temperature read, which polls the device for end of conversion. + +
+ds18b20_retv_t ds18b20_read( short *deciCtemp) { /* -550~1250 = -55.0~125.0 C */ + ds18b20_retv_t ret ; + + ret = ds18b20_convert() ; + if( ret != DS18B20_SUCCESS) + return ret ; + + do + usleep( 4000) ; + while( poll() == LOW) ; /* up to 93.75ms for 9 bits, 750ms for 12 bits */ + + return ds18b20_fetch( deciCtemp) ; +} ++ +Set resolution. + +
+ds18b20_retv_t ds18b20_resolution( unsigned res) { /* 9..12 bits */ + ds18b20_retv_t ret ; + unsigned char vals[ 9] ; /* scratchpad */ + unsigned char curres ; + +/* read scratchpad */ + ret = read_scratchpad( vals) ; + if( ret != DS18B20_SUCCESS) + return ret ; + +/* update resolution if current value is different than requested */ + res = (res - 9) & 3 ; + curres = vals[ 4] >> 5 ; + if( curres != res) { + vals[ 4] = (vals[ 4] & 0x1F) | (res << 5) ; + ret = initialization() ; + if( ret != DS18B20_SUCCESS) + return ret ; + + write( 0xCC) ; /* Skip ROM */ + write( 0x4E) ; /* Write Scratchpad */ + write( vals[ 2]) ; + write( vals[ 3]) ; + write( vals[ 4]) ; + } + + return DS18B20_SUCCESS ; +} ++ +There is no error check when writing to the device, so it would make +sense to read back the device memory after the set to make sure there +was no error when writing in the first place. + +
SRCS = startup.txeie.c gpioa.c ds18b20main.c ds18b20.c+ +Build complete successfully. + +
+$ make +f030f4.elf from startup.txeie.o gpioa.o ds18b20main.o ds18b20.o + text data bss dec hex filename + 2530 0 16 2546 9f2 f030f4.elf +f030f4.hex +f030f4.bin ++ +Flashing the board and starting execution, I can see a new output every +second. +
+ + +
+The STM32F030 family has a suffix 6 (STM32F030F4P6) for the ambient +operating temperature: -40℃ to 85℃. It should be fine to use not only +inside rooms but also outdoor (may be not on the dashboard of a car +parked under direct tropical sunlight at noon in summer). + +
+The initialization steps: +
+static void adc_init( void) { +/* Enable ADC peripheral */ + RCC_APB2ENR |= RCC_APB2ENR_ADCEN ; +/* Setup ADC sampling clock */ +#ifdef HSI14 + RCC_CR2 |= RCC_CR2_HSI14ON ; /* Start HSI14 clock */ + do {} while( !( RCC_CR2 & RCC_CR2_HSI14RDY)) ; /* Wait for stable clock */ +/* Select HSI14 as sampling clock for ADC */ +// ADC_CFGR2 &= ~ADC_CFGR2_CKMODE ; /* Default 00 == HSI14 */ +#else +/* Select PCLK/2 as sampling clock for ADC */ + ADC_CFGR2 |= ADC_CFGR2_PCLK2 ; /* 01 PCLK/2 Over default 00 */ +// ADC_CFGR2 |= ADC_CFGR2_PCLK4 ; /* 10 PCLK/4 Over default 00 */ +#endif + +/* Calibration */ + ADC_CR |= ADC_CR_ADCAL ; + do {} while( ADC_CR & ADC_CR_ADCAL) ; /* Wait end of calibration */ + +/* Enable Command (below Work Around from Errata necessary with PCLK/4) */ + do { + ADC_CR |= ADC_CR_ADEN ; + } while( !( ADC_ISR & ADC_ISR_ADRDY)) ; + +/* Select inputs and precision */ + ADC_CHSELR = 3 << 16 ; /* Channel 16: temperature, Channel 17: Vrefint */ + ADC_CCR |= ADC_CCR_TSEN | ADC_CCR_VREFEN ; + ADC_SMPR = 7 ; +/* Select acquisition direction and mode */ +/* Default scan direction (00) is Temperature before Voltage */ +// ADC_CFGR1 &= ~ADC_CFGR1_SCANDIR ; /* Default 0 is low to high */ + ADC_CFGR1 |= ADC_CFGR1_DISCEN ; /* Enable Discontinuous mode */ +} ++ +The ADC characteristics in the STM32F030 datasheet states that the +maximum ADC sampling clock is 14MHz. It is possible to select either an +internal 14 MHz clock (HSI14) or PCLK divided by 2 or 4. I want to check +how the clock affects the readings (HSI14, 28/2, 24/2, 48/4). At first I +couldn’t manage to make PCLK/4 work until I found the note on ADC +calibration work around in the errata +ES0219 2.5.3 +
+After this initialization, I can trigger an ADC conversion by issuing a +command, wait until completion and fetch the result in the ADC Data +Register. + +
+static unsigned adc_convert( void) { +/* Either only one channel in sequence or Discontinuous mode ON */ + ADC_CR |= ADC_CR_ADSTART ; /* Start ADC conversion */ + do {} while( ADC_CR & ADC_CR_ADSTART) ; /* Wait for start command cleared */ + return ADC_DR ; +} ++ +There is two values to fetch, temperature and voltage in that order. + +
+temperature = adc_convert() ; +voltage = adc_convert() ; ++ +The values resulting from an ADC conversion are raw, at the precision I +selected during initialization (12 bits) it gives me a value between 0 +and 4095. + +
+Vmeasured = VDDA * VRAW / 4095 +
+This is why I measure both voltage and temperature. Ideally, VDDA should +be 3.3V, in practice it’s highly dependent of the power supply. +
+Every chip is calibrated in factory during production, ADC conversion is +done at 3.3V and 30℃ and the resulting values are stored in system +memory. +
+ +
+ + +
+/* STM32F030 calibration addresses (at 3.3V and 30C) */ +#define TS_CAL1 ((unsigned short *) 0x1FFFF7B8) +#define VREFINT_CAL ((unsigned short *) 0x1FFFF7BA) ++ +VREFINT is the embedded reference voltage measured on channel 17 of the +ADC. According to the STM32F030 datasheet it is in the range 1.2V to +1.25V. I have a chip whose VREFINT calibration value is 1526, that means +the embedded reference voltage VREFINT is 3.3 * 1526 / 4095, close to +the middle of the range and the given typical value of 1.23V. +
+I can read VREFINT raw data after ADC conversion and I know its factory +calibrated value, so I can calculate VDDA. +
+VREFINT = 3.3 * VCAL / 4095 = VDDA * VRAW / 4095 +
+VDDA = 3.3 * VCAL / VRAW +
+On my chip whose VCAL is 1526, if I measure VRAW at 1534, this will put +VDDA at 3.28V. Less than 1% off the ideal 3.3V voltage. +
+There is only one temperature calibration value stored for STM32F030 +chips. You need two reference values to be able to calculate the +temperature reliably, either the ADC readings taken at two temperatures +(30℃ and 110℃ for other STM32 families) or one ADC reading plus the +value of the temperature slope characteristic for that particular chip. +
+The STM32F030 datasheet gives the range 4~4.6 mV/℃ and a typical value +of 4.3 mV/℃ for the average slope. The reference manual uses 4.3 mV/℃ in +the temperature calculation code example. +
+When it comes to temperature measurement using the temperature sensor of +STM32F030, you need to do your own two point calibration. + +
+typedef enum { + VNT_INIT, + VNT_CAL, + VNT_RAW, + VNT_VNC +} vnt_cmd_t ; + +void adc_vnt( vnt_cmd_t cmd, short *ptrV, short *ptrC) ; ++ +The voltage return value is in centiV (330 == 3.3V), the temperature +return value is in deci℃ (300 == 30℃). +
+I make a copy of gpioa.c into adc.c adding the code I just
+explained for adc_init()
, adc_convert()
, the
+calibration value addresses and the following implementation of
+adc_vnt()
.
+
+
+void adc_vnt( vnt_cmd_t cmd, short *ptrV, short *ptrC) { + if( cmd == VNT_INIT) + adc_init() ; + + if( cmd <= VNT_CAL) { + /* Calibration Values */ + *ptrV = *VREFINT_CAL ; + *ptrC = *TS_CAL1 ; + return ; + } + +/* ADC Conversion */ + *ptrC = adc_convert() ; + *ptrV = adc_convert() ; + + if( cmd == VNT_VNC) { + *ptrC = 300 + (*TS_CAL1 - *ptrC * *VREFINT_CAL / *ptrV) * 10000 / 5336 ; + *ptrV = 330 * *VREFINT_CAL / *ptrV ; + } +} ++ +The calculation for the temperature is based on the code example from +the reference manual (RM0360 A.7.16). +
+The only thing missing is the description of the newly used registers +and bitfields. +
+● The extra RCC bitfields and register for enabling the ADC peripheral and activating HSI14 clock. + +
+#define RCC_APB2ENR_ADCEN 0x00000200 /* 9: ADC clock enable */ + +#define RCC_CR2 RCC[ 13] +#define RCC_CR2_HSI14ON 0x00000001 /* 1: HSI14 clock enable */ +#define RCC_CR2_HSI14RDY 0x00000002 /* 2: HSI14 clock ready */ ++ +● The ADC registers and bitfields. + +
+#define ADC ((volatile long *) 0x40012400) +#define ADC_ISR ADC[ 0] +#define ADC_ISR_ADRDY 1 /* 0: ADC Ready */ +#define ADC_ISR_EOC 4 /* 2: End Of Conversion flag */ + +#define ADC_CR ADC[ 2] +#define ADC_CR_ADEN 1 /* 0: ADc ENable command */ +#define ADC_CR_ADSTART 4 /* 2: ADC Start Conversion command */ +#define ADC_CR_ADCAL (1 << 31) /* 31: ADC Start Calibration cmd */ + +#define ADC_CFGR1 ADC[ 3] /* Configuration Register 1 */ +#define ADC_CFGR1_SCANDIR 4 /* 2: Scan sequence direction */ +#define ADC_CFGR1_DISCEN (1 << 16) /* 16: Enable Discontinuous mode */ + +#define ADC_CFGR2 ADC[ 4] /* Configuration Register 2 */ +#define ADC_CFGR2_CKMODE (3 << 30) /* 31-30: Clock Mode Mask */ + /* 31-30: Default 00 HSI14 */ +#define ADC_CFGR2_PCLK2 (1 << 30) /* 31-30: PCLK/2 */ +#define ADC_CFGR2_PCLK4 (2 << 30) /* 31-30: PCLK/4 */ + +#define ADC_SMPR ADC[ 5] /* Sampling Time Register */ +#define ADC_CHSELR ADC[ 10] /* Channel Selection Register */ +#define ADC_DR ADC[ 16] /* Data Register */ +#define ADC_CCR ADC[ 194] /* Common Configuration Register */ +#define ADC_CCR_VREFEN (1 << 22) /* 22: Vrefint Enable */ +#define ADC_CCR_TSEN (1 << 23) /* 23: Temperature Sensor Enable */ ++ +
+/* adcmain.c -- ADC reading of reference voltage and temperature sensor */ + +#include <stdio.h> +#include "system.h" + +#define RAW + +int main( void) { + unsigned last = 0 ; + short calV, calC ; + +/* Initialize ADC and fetch calibration values */ + adc_vnt( VNT_INIT, &calV, &calC) ; +#ifdef RAW + printf( "%u, %u\n", calV, calC) ; +#endif + + for( ;;) + if( uptime == last) + yield() ; + else { + short Vsample, Csample ; + + last = uptime ; +#ifdef RAW + adc_vnt( VNT_RAW, &Vsample, &Csample) ; + printf( "%i, %i, %i, %i, ", calV, Vsample, calC, Csample) ; + Csample = 300 + (calC - (int) Csample * calV / Vsample) + * 10000 / 5336 ; + Vsample = 330 * calV / Vsample ; +#else + adc_vnt( VNT_VNC, &Vsample, &Csample) ; +#endif + printf( "%i.%i, %i.%i\n", Vsample / 100, Vsample % 100, + Csample / 10, Csample % 10) ; + } +} ++ +
+/* No quartz, configure PLL at 28MHz */ +//#define HSE 8000000 +#define PLL 7 +#define BAUD 9600 +//#define HSI14 1 ++ +I add the composition in Makefile + +
SRCS = startup.txeie.c adc.c adcmain.c+ +The build gives some warning as 28MHz is not a perfect match for a +baudrate of 9600 and the current implementation of
usleep()
. This
+will not affect my application.
+
++$ make +adc.c:155:3: warning: #warning baud rate not accurate at that clock frequency [- +Wcpp] + 155 | # warning baud rate not accurate at that clock frequency + | ^~~~~~~ +adc.c: In function 'usleep': +adc.c:232:3: warning: #warning HCLK is not multiple of 8 MHz [-Wcpp] + 232 | # warning HCLK is not multiple of 8 MHz + | ^~~~~~~ +f030f4.elf from startup.txeie.o adc.o adcmain.o + text data bss dec hex filename + 2464 0 16 2480 9b0 f030f4.elf +f030f4.hex +f030f4.bin ++ +Flashing the board and starting execution, I can see the results of the +ADC conversion and the calculated values. +
+ +
+The temperature readings are roughly 5℃ higher than room temperature. + ++Using the factory calibration data I can convert the raw ADC measurement +into actual Vref. This will help in adjusting the ADC readings. But for +temperature, the provided calibration is insufficient, there is only one +point measured in factory for the STM32F030 family members. +
+Next, I will do temperature calibration, which +means taking two measurements as far apart as possible in the working range I +want to use. + +
+ +
+There is only one point calibration documented and its reference +temperature is known with a precision of ±5℃. That’s not enough to +calculate temperature but it shows that the sensor was tested in +production. +
+Notice that the calibration value name is TS_CAL1, some other STM32 +chipset families do have a second temperature factory calibration point +TS_CAL2. Also some don’t have any factory calibration stored at all. +So you have to refer to the datasheet that matches the chip you are +targeting when you port your code to a new chipset. +
+ +
+The sensor linearity with temperature is at worst ±2℃. So I am curious +to see how it performs over a range of temperature. +
+If you are so lucky that you have picked a STM32F030 chip that has the +typical average slope of 4.3 mV/℃ and was calibrated in factory at +exactly 3.3V and 30℃, then you could use the following formula +to calculate the temperature. +
+T = 30 + (TS_CAL1 * 3.3 – TS_RAW * VDDA) / 4095 / 0.0043 +
+with +
+VDDA = 3.3 * VREFINT_CAL / V_RAW +
+that gives +
+T = 30 + 3.3 * (TS_CAL1 – TS_RAW * V_CAL / V_RAW) / 4095 / 0.0043 +
+If I express the average slope in raw ADC units per ℃ instead of mV/℃ +
+5.336 = 4095 * 0.0043 / 3.3 +
+the final formula is +
+T = 30 + (TS_CAL1 – TS_RAW * V_CAL / V_RAW) * 1000 / 5336 +
+which matches the sample code for temperature computation given in the +reference manual (RM0360 A.7.16). + +
+/* Temperature sensor calibration value address */ +#define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7B8)) +#define VDD_CALIB ((uint32_t) (3300)) +#define VDD_APPLI ((uint32_t) (3000)) +#define AVG_SLOPE ((uint32_t) (5336)) /* AVG_SLOPE in ADC conversion step + (@3.3V)/°C multiplied by 1000 for + precision on the division */ +int32_t temperature; /* will contain the temperature in degrees Celsius */ +temperature = ((uint32_t) *TEMP30_CAL_ADDR + - ((uint32_t) ADC1->DR * VDD_APPLI / VDD_CALIB)) * 1000; +temperature = (temperature / AVG_SLOPE) + 30; ++ +If I use the raw ADC readings from my last run +
+VDDA = 3.3 * 1526 / 1538 = 3.274V +
+t = 30 + (1721 – 1718 * 1526 / 1538) * 1000 / 5336 = 33.07℃ +
+I confirm the voltage with a voltmeter (measured 3.282V versus 3.274V +computed). The computed internal temperature value is roughly 5℃ higher +than the room temperature. + +
+| Content | Start Address | Size | +|---------------|---------------|------| +| System Memory | 0x1FFFEC00 | 3KB | +| Option Bytes | 0x1FFFF800 | 2KB | +| RAM Memory | 0x20000000 | 4KB | ++The calibration data are saved in the last 96 bytes of the System +Memory, starting at address 0x1FFFF7A0. So it’s simple to dump the +content of that zone and compare the values for multiple chips. + +
+$ stm32flash -r - -S 0x1FFFF7a0:96 COM3 2>/dev/null | hexdump -C +00000000 ff ff ff ff 31 00 10 00 ff ff ff ff 1c 00 3a 00 |....1.........:.| +00000010 12 57 34 41 38 32 30 20 b9 06 f6 05 f0 ff ff ff |.W4A820 ........| +00000020 ff ff 11 05 ff ff ff ff fc ff ff ff 10 00 ff ff |................| +00000030 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| +00000040 ff ff ff ff ff ff ff ff f3 ff ff ff ff ff ff ff |................| +00000050 ff ff ff ff ff ff ff ff 68 97 52 ad 3b c4 3f c0 |........h.R.;.?.| +00000060 ++
+| Location | Content | Size | F030 | F0x1/2/8 | +|------------|-----------------|------|------|----------| +| 0x1FFFF7AC | Unique ID | 12 | | √ | +| 0x1FFFF7B8 | TS_CAL1 | 2 | √ | √ | +| 0x1FFFF7BA | VREFINT_CAL | 2 | √ | √ | +| 0x1FFFF7C2 | TS_CAL2 | 2 | | √ | +| 0x1FFFF7CC | Flash size (KB) | 2 | √ | √ | ++This is the same layout as the one documented in RM0091 Reference Manual +STM32F0x1/STM32F0x2/STM32F0x8 which includes the following sample code +for temperature computation. + +
+/* Temperature sensor calibration value address */ +#define TEMP110_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7C2)) +#define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7B8)) +#define VDD_CALIB ((uint16_t) (330)) +#define VDD_APPLI ((uint16_t) (300)) +int32_t temperature; /* will contain the temperature in degrees Celsius */ +temperature = ((int32_t) ADC1->DR * VDD_APPLI / VDD_CALIB) + - (int32_t) *TEMP30_CAL_ADDR; +temperature *= (int32_t)(110 - 30); +temperature /= (int32_t)(*TEMP110_CAL_ADDR - *TEMP30_CAL_ADDR); +temperature += 30; ++ +Factoring in the actual measured voltage, this gives +
+T = 30 + (TS_CAL1 – TS_RAW * V_CAL / V_RAW) * 80 / (TS_CAL1 – TS_CAL2) +
+If I use the raw ADC readings from my last run + +
+TSCAL2_SLOPE = (1721 - 1297) / 80 = 5.3 ADC step/℃ + = 3.3 * 5.3 / 4095 = 4.271 mV/℃ ++ +t = 30 + (1721 – 1718 * 1526 / 1538) * 80 / (1721 – 1297) = 33.09℃ +
+Which is only 0.02℃ higher than the previous result based on the more +generic formula. Because the temperature measured is close to the +calibration temperature, the correction is negligible. For this +particular chip, to see a difference of 0.1℃ between the value computed +by the two formulas, you need a delta of 15℃ from the calibration +temperature. + +
+I update the application to use TS_CAL2 based temperature calculation +and access the tuned reference temperature from the option bytes. + +
+/* adcmain.c -- ADC reading of reference voltage and temperature sensor */ + +#include <stdio.h> +#include "system.h" + +#define RAW + +#define TS_CAL2 ((const short *) 0x1FFFF7C2) +#define USER0 ((const unsigned char *) 0x1FFFF804) + +int main( void) { + unsigned last = 0 ; + short calV, calC ; + +/* Initialize ADC and fetch calibration values */ + adc_vnt( VNT_INIT, &calV, &calC) ; +#ifdef RAW + printf( "%u, %u\n", calV, calC) ; + + int baseC = 300 ; +# ifdef USER0 + if( 0xFF == (USER0[ 0] ^ USER0[ 1])) + baseC = USER0[ 0] * 10 ; +# endif +#endif + + for( ;;) + if( uptime == last) + yield() ; + else { + short Vsample, Csample ; + + last = uptime ; +#ifdef RAW + adc_vnt( VNT_RAW, &Vsample, &Csample) ; + printf( "%i, %i, %i, %i, ", calV, Vsample, calC, Csample) ; + Csample = baseC + (calC - (int) Csample * calV / Vsample) +# ifdef TS_CAL2 + * 800 / (calC - *TS_CAL2) ; +# else + * 10000 / 5336 ; +# endif + Vsample = 330 * calV / Vsample ; +#else + adc_vnt( VNT_VNC, &Vsample, &Csample) ; +#endif + printf( "%i.%i, %i.%i\n", Vsample / 100, Vsample % 100, + Csample / 10, Csample % 10) ; + } +} ++ +
+Next, I will cover the toolchain update that I +made while working on the temperature sensors. + +
+By example, to switch from release 9 update to release 10 major, I made three +changes to Makefile. +
+● Update the Linux base directory location: + +
+#GCCDIR = $(HOME)/Packages/gcc-arm-none-eabi-9-2020-q2-update +GCCDIR = $(HOME)/Packages/gcc-arm-none-eabi-10-2020-q4-major ++ +● Update the Windows base directory location: + +
+#GCCDIR = D:/Program Files (x86)/GNU Arm Embedded Toolchain/9 2020-q2-update +GCCDIR = D:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2020-q4-major ++ +● Update the library subdirectory location: + +
+#LIBDIR = $(GCCDIR)/lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp +LIBDIR = $(GCCDIR)/lib/gcc/arm-none-eabi/10.2.1/thumb/v6-m/nofp ++ +In the case of release 10 major, unfortunately while doing some regression +testing by recompiling the projects so far, I found that the new release +optimizes further the C startup clearing of BSS data by calling +
memset()
from the distribution libraries.
++$ make +f030f4.elf from startup.txeie.o adc.o adcmain.o +D:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2020-q4-major\bin\arm-none- +eabi-ld.exe: startup.txeie.o: in function `Reset_Handler': +D:\Renau\Documents\Projects\stm32bringup\docs/startup.txeie.c:126: undefined ref +erence to `memset' +make: *** [mk8:61: f030f4.elf] Error 1 ++So I had to add libc.a and its location on top of libgcc.a +to the list of libraries. +
+LIBS = -l$(LIBSTEM) -lc -lgcc +LIB_PATHS = -L. -L$(GCCDIR)/arm-none-eabi/lib/thumb/v6-m/nofp -L$(LIBDIR) ++with libc.a the link phase complete successfully. +
+f030f4.elf from startup.txeie.o adc.o adcmain.o + text data bss dec hex filename + 2644 0 16 2660 a64 f030f4.elf ++
+As I don’t want to turn off size optimization and I am not willing to
+always pay the full 180 bytes for a production ready memset()
+when it is called only once at startup to clear a few bytes, I ended up adding
+my own version of memset()
to my local library.
+
+
+#include <string.h> + +void *memset( void *s, int c, size_t n) { + char *p = s ; + while( n--) + *p++ = c ; + + return s ; +} ++
+LIBOBJS = printf.o putchar.o puts.o memset.o ++Link succeed with a reduction of 152 bytes of code. +
+f030f4.elf from startup.txeie.o adc.o adcmain.o + text data bss dec hex filename + 2492 0 16 2508 9cc f030f4.elf ++
+# $(LD) -T$(LD_SCRIPT) $(LIB_PATHS) -Map=$(PROJECT).map -cref -o $@ $(OBJS) $(LIBS) + $(CC) $(CPU) -T$(LD_SCRIPT) -L. -Wl,-Map=$(PROJECT).map,-cref \ + -nostartfiles -o $@ $(OBJS) -l$(LIBSTEM) ++ +As the compiler front end is now controlling the libraries selection it is +possible to give it a hint how to select a better optimized memset(). The +libc library comes in two flavors: regular and nano. + +
+OBJS = $(SRCS:.c=.o) +LIBOBJS = printf.o putchar.o puts.o # memset.o + +CPU = -mthumb -mcpu=cortex-m0 --specs=nano.specs ++ +
memset()
included in the nano version of libc occupies the same
+space as my own implementation.
++f030f4.elf from startup.txeie.o adc.o adcmain.o + text data bss dec hex filename + 2492 0 16 2508 9cc f030f4.elf ++
+### Build environment selection + +ifeq (linux, $(findstring linux, $(MAKE_HOST))) + INSTALLDIR = $(HOME)/Packages +#REVDIR = gcc-arm-none-eabi-10-2020-q4-major + REVDIR = arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi +else + DRIVE = d +ifeq (cygwin, $(findstring cygwin, $(MAKE_HOST))) + OSDRIVE = /cygdrive/$(DRIVE) +else ifeq (msys, $(findstring msys, $(MAKE_HOST))) + OSDRIVE = /$(DRIVE) +else + OSDRIVE = $(DRIVE): +endif + INSTALLDIR = $(OSDRIVE)/Program Files (x86) +#REVDIR = GNU Arm Embedded Toolchain/10 2020-q4-major + REVDIR = GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi +endif + +GCCDIR = $(INSTALLDIR)/$(REVDIR) +export PATH := $(GCCDIR)/bin:$(PATH) + +BINPFX = @arm-none-eabi- +AR = $(BINPFX)ar +CC = $(BINPFX)gcc +OBJCOPY = $(BINPFX)objcopy +OBJDUMP = $(BINPFX)objdump +SIZE = $(BINPFX)size ++Switching back to latest version of the toolchain at the time of writing this, +the link shows further improvement of the code size. The optimization via +
memset()
has been skipped by the compiler.
++f030f4.elf from startup.txeie.o adc.o adcmain.o + text data bss dec hex filename + 2464 0 16 2480 9b0 f030f4.elf ++
+Next, I will (re)build to execute code in RAM +instead of FLASH. + +
+Using stm32flash I can request the bootloader to transfer execution to +the code in flash memory. + +
stm32flash -g 0 COM6+ +With my current code, this works fine as far as I don’t use interrupt +subroutine. ledon and blink both work, but ledtick will +reset once the
SysTick_Handler()
interrupt routine is triggered
+for the first time. This is due to the fact that the system memory is still
+mapped at address 0x0 where my interrupt subroutine vector should be. To
+fix this, I need to insure the flash is mapped at address 0x0 before I
+enable interrupts.
+
+The memory mapping is managed through the System Configuration
+controller SYSCFG, so I need to activate it and reconfigure the mapping
+before my SysTick initialization code in init()
.
+
+
+/* Make sure FLASH Memory is mapped at 0x0 before enabling interrupts */ + RCC_APB2ENR |= RCC_APB2ENR_SYSCFGEN ; /* Enable SYSCFG */ + SYSCFG_CFGR1 &= ~3 ; /* Map FLASH at 0x0 */ ++ +and add the SYSCFG peripheral description. + +
+#define RCC_APB2ENR_SYSCFGEN 0x00000001 /* 1: SYSCFG clock enable */ + +#define SYSCFG ((volatile long *) 0x40010000) +#define SYSCFG_CFGR1 SYSCFG[ 0] ++ +With this in place, I can now switch easily from bootloader to flash +code by sending a go command via stm32flash. + +
+The answer is in the application note AN2606 STM32 microcontroller +system memory boot mode. Section 5 covers STM32F03xx4/6 devices +bootloader and it states in 5.1 Bootloader Configuration: +
2 Kbyte starting from address 0x20000000 are used by the bootloader +firmware.+
+I am using a STM32F030F4P6, which has 4KB RAM and the bootloader +firmware is using the first 2KB. That means I have only 2KB left to use +starting from address 0x20000800. +
+Actually, I have only 2KB left to use until the bootloader firmware +transfer execution to my code in RAM. Once my code executes, I can +reclaim the first 2KB. This is exactly what I have to tell the linker. +
+I just create a new linker script f030f4.ram.ld by copying +f030f4.ld and changing the memory configuration. + +
+/* FLASH means code, read only data and data initialization */ + FLASH (rx) : ORIGIN = 0x20000800, LENGTH = 2K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 2K ++ +I can build ledon or blink with that new linker script and check +the resulting f030f4.map file. +
+stm32flash -w blink.bin -S 0x20000800 COM6 +stm32flash -g 0x20000800 COM6 ++ +This work just fine but of course the executable of ledon or +blink doesn’t use interrupt routines. + +
isr_vector[]
to the beginning of RAM in the
+space reserved by the linker.
++/* Make sure SRAM Memory is mapped at 0x0 before enabling interrupts */ + RCC_APB2ENR |= RCC_APB2ENR_SYSCFGEN ; /* Enable SYSCFG */ + SYSCFG_CFGR1 |= 3 ; /* Map RAM at 0x0 */ ++ +The ISR vector will have at most 16 + 32 entries for STM32F030xx, that +means 192 bytes need to be reserved. I add a new section before .data in +the link script. + +
+ .isrdata : + { + ram_vector = . ; + . = . + 192 ; + } > RAM + + .data : AT (__etext) + { +... ++ +In the startup code, I add the code to copy the
isr_vector[]
to
+the location reserved at the beginning of RAM.
+
++#define ISRV_SIZE (sizeof isr_vector / sizeof *isr_vector) + +extern isr_p ram_vector[] ; + +/* Copy isr vector to beginning of RAM */ + for( unsigned i = 0 ; i < ISRV_SIZE ; i++) + ram_vector[ i] = isr_vector[ i] ; ++ +RAM initialization now consists of +
ram_vector[]
.
+
++.isrdata 0x20000000 0xc0 + 0x20000000 ram_vector = . + 0x200000c0 . = (. + 0xc0) + *fill* 0x20000000 0xc0 + +.data 0x200000c0 0x0 load address 0x20000c88 + 0x200000c0 __data_start__ = . ++ +I can now use stm32flash to write those executables in RAM and request +execution. + +
+| Model | ISRV Location | Load address (word aligned) | +|---------|--------------------|-----------------------------------------| +|BOOTFLASH| Beginning of FLASH | Beginning of FLASH | +| BOOTRAM | Beginning of RAM | Beginning of RAM | +| GOFLASH | Beginning of RAM | In FLASH | +| GORAM | Beginning of RAM | In RAM, after bootloader reserved space | ++
+MEMORY +{ +/* FLASH means code, read only data and data initialization */ + FLASH (rx) : ORIGIN = DEFINED(FLASHSTART) ? FLASHSTART : 0x08000000, + LENGTH = DEFINED(FLASHSIZE) ? FLASHSIZE : 16K + RAM (rwx) : ORIGIN = DEFINED(RAMSTART) ? RAMSTART : 0x20000000, + LENGTH = DEFINED(RAMSIZE) ? RAMSIZE : 4K +} ++ +The Makefile will provide the necessary addresses and sizes information +by passing parameters to the linker:
FLASHSTART
,
+FLASHSIZE
, RAMSTART
, RAMSIZE
.
+
++ /* In RAM isr vector reserved space at beginning of RAM */ + .isrdata (NOLOAD): + { + KEEP(*(.ram_vector)) + } > RAM ++ +The startup code will allocate
ram_vector[]
in .ram_vector
+section if needed.
+
++#if RAMISRV == 2 +# define ISRV_SIZE (sizeof isr_vector / sizeof *isr_vector) +isr_p ram_vector[ ISRV_SIZE] __attribute__((section(".ram_vector"))) ; +#endif + +int main( void) ; + +void Reset_Handler( void) { + const long *f ; /* from, source constant data from FLASH */ + long *t ; /* to, destination in RAM */ + +#if RAMISRV == 2 +/* Copy isr vector to beginning of RAM */ + for( unsigned i = 0 ; i < ISRV_SIZE ; i++) + ram_vector[ i] = isr_vector[ i] ; +#endif + +/* Assume: +** __bss_start__ == __data_end__ +** All sections are 4 bytes aligned +*/ + f = __etext ; + for( t = __data_start__ ; t < __bss_start__ ; t += 1) + *t = *f++ ; + + while( t < &__bss_end__) + *t++ = 0 ; + +/* Make sure active isr vector is mapped at 0x0 before enabling interrupts */ + RCC_APB2ENR |= RCC_APB2ENR_SYSCFGEN ; /* Enable SYSCFG */ +#if RAMISRV + SYSCFG_CFGR1 |= 3 ; /* Map RAM at 0x0 */ +#else + SYSCFG_CFGR1 &= ~3 ; /* Map FLASH at 0x0 */ +#endif + + if( init() == 0) + main() ; + + for( ;;) + __asm( "WFI") ; /* Wait for interrupt */ +} ++ +The SYSCFG controller definition is now included through a chipset +specific header file. This way I can maintain all the chipset +controllers and peripherals in one place. + +
#include "stm32f030xx.h"+ +
+### Memory Models +# By default we use the memory mapping from linker script + +# In RAM Execution, load and start by USART bootloader +# Bootloader uses first 2K of RAM, execution from bootloader +#FLASHSTART = 0x20000800 +#FLASHSIZE = 2K +#RAMSTART = 0x20000000 +#RAMSIZE = 2K + +# In RAM Execution, load and start via SWD +# 4K RAM available, execution via SWD +#FLASHSTART = 0x20000000 +#FLASHSIZE = 3K +#RAMSTART = 0x20000C00 +#RAMSIZE = 1K + +# In Flash Execution +# if FLASHSTART is not at beginning of FLASH: execution via bootloader or SWD +#FLASHSTART = 0x08000000 +#FLASHSIZE = 16K +#RAMSTART = 0x20000000 +#RAMSIZE = 4K + +# ISR vector copied and mapped to RAM when FLASHSTART != 0x08000000 +ifdef FLASHSTART + ifneq ($(FLASHSTART),0x08000000) + ifeq ($(FLASHSTART),0x20000000) + # Map isr vector in RAM + RAMISRV := 1 + else + # Copy and map isr vector in RAM + RAMISRV := 2 + endif + endif + BINLOC = $(FLASHSTART) +else + BINLOC = 0x08000000 +endif ++ +Compiler and linker have different syntax for defining symbols through +command line parameters. + +
+CPU = -mthumb -mcpu=cortex-m0 --specs=nano.specs +ifdef RAMISRV + CDEFINES = -DRAMISRV=$(RAMISRV) +endif +WARNINGS=-pedantic -Wall -Wextra -Wstrict-prototypes +CFLAGS = $(CPU) -g $(WARNINGS) -Os $(CDEFINES) + +LD_SCRIPT = generic.ld +ifdef FLASHSTART + LDOPTS =--defsym FLASHSTART=$(FLASHSTART) --defsym FLASHSIZE=$(FLASHSIZE) + LDOPTS +=--defsym RAMSTART=$(RAMSTART) --defsym RAMSIZE=$(RAMSIZE) +endif +LDOPTS +=-Map=$(subst .elf,.map,$@) -cref --print-memory-usage +comma :=, +space :=$() # one space before the comment +LDFLAGS =-Wl,$(subst $(space),$(comma),$(LDOPTS)) ++ +As I am revising the compilation flags, I have increased the level of +warnings by adding -pedantic, -Wstrict-prototypes. +
+Build rules updated with new symbols for the linker. + +
+$(PROJECT).elf: $(OBJS) libstm32.a +boot.elf: boot.o +ledon.elf: ledon.o +blink.elf: blink.o +ledtick.elf: ledtick.o +cstartup.elf: cstartup.o + +%.elf: + @echo $@ + $(CC) $(CPU) -T$(LD_SCRIPT) $(LDFLAGS) -nostartfiles -o $@ $+ + $(SIZE) $@ + $(OBJDUMP) -hS $@ > $(subst .elf,.lst,$@) ++ +The projects composition need to be updated to use the new startup. + +
SRCS = startup.ram.c txeie.c uptime.1.c+ +Finally, to keep track of the memory model and the load location, I put +the load address in the name of the binary file generated. + +
all: $(PROJECT).$(BINLOC).bin $(PROJECT).hex+ +This way if I build uptime prototype in GORAM memory model + +
+$ make +f030f4.elf + text data bss dec hex filename + 1164 0 20 1184 4a0 f030f4.elf +f030f4.hex +f030f4.0x20000800.bin ++ +The name of the file will remind me where to load the code. + +
+$ stm32flash -w f030f4.0x20000800.bin -S 0x20000800 COM6 +$ stm32flash -g 0x20000800 ++ +
+Until v0.7 is out, I am using my own patched version of stm32flash or +the binary files when I need to test GOFLASH and GORAM memory models. +
+As I branched off my own patched version of stm32flash, I added a
+-x
option to write and execute an intel hex file:
+
+
stm32flash -x file.hex COM#+ +
+Using the USART bootloader, I validate BOOTFLASH, GOFLASH and GORAM with +stm32flash and STM32 Cube Programmer. +
+Using the SWD interface, I validate BOOTFLASH, GOFLASH, BOOTRAM and +GORAM with STM32 Cube Programmer. + +
+The STM32 CRC calculation unit has the following default characteristic: +
+#define RCC_AHBENR_CRCEN (1 << 6) /* 6: CRC clock enable */ ++ +I will make use of the default setup so I only need to refer to the Data +Register and the Control Register. I create all register definitions as +there is a gap in the memory layout. + +
+#define CRC ((volatile unsigned *) 0x40023000) +#define CRC_DR CRC[ 0] +#define CRC_IDR CRC[ 1] +#define CRC_CR CRC[ 2] +#define CRC_INIT CRC[ 4] ++ +
+I use conditional compilation, the build option `CRC32SIGN` will be +defined in the Makefile. +
+The constant variable `crcsum` is a placeholder with the hexadecimal value
+DEADC0DE
in byte order. This value will be overriden by the
+computed CRC value during build. The linker will put `crcsum` at the end of the
+used FLASH.
+
+`check_flash()` use the CRC calculation unit to compute the CRC value +from beginning of FLASH `isr_vector` to end of FLASH `crcsum`. If +`crcsum` value is the correct CRC, the computed result will be 0. + +
+#ifdef CRC32SIGN +const unsigned crcsum __attribute__((section(".crc_chk"))) = 0xDEC0ADDE ; + +static int check_flash( void) { + int ret = 0 ; + +/* Flash CRC validation */ + RCC_AHBENR |= RCC_AHBENR_CRCEN ; /* Enable CRC periph */ + CRC_CR = 1 ; /* Reset */ + if( CRC_DR == 0xFFFFFFFF) { /* CRC periph is alive and resetted */ + const unsigned *wp = (const unsigned *) isr_vector ; + while( wp <= &crcsum) + CRC_DR = *wp++ ; + + ret = CRC_DR == 0 ; + } + + RCC_AHBENR &= ~RCC_AHBENR_CRCEN ; /* Disable CRC periph */ + return ret ; +} +#endif ++ +Flash content is checked before calling `init()`. This means the check +is done using the default clock setup of HSI 8 MHz. + +
+ if( +#ifdef CRC32SIGN + check_flash() && +#endif + init() == 0) + main() ; ++ +
+ .crc __etext + SIZEOF(.data) : + { + KEEP(*(.crc_chk)) + } > FLASH ++ +
+$ touch empty.bin + +$ ./sign32 empty.bin +FFFFFFFF empty.bin: 0, signed.bin: 4 ++ +If the input file is already signed, the output signed.bin is identical +to the input. + +
+$ mv signed.bin FFFFFFFF.bin + +$ ./sign32 FFFFFFFF.bin +00000000 FFFFFFFF.bin: 4, signed.bin: 4 ++ +Padding with null is done on the input to insure the calculation is +DWORD aligned. + +
+$ echo > nl.bin + +$ ./sign32 nl.bin +E88E0BAD nl.bin: 1, signed.bin: 8 + +$ hexdump -C signed.bin +00000000 0a 00 00 00 ad 0b 8e e8 |........| +00000008 + ++ +Calculation stops when the placeholder DEADC0DE is found or the end of +the input file is reached. +
+I create a folder crc32/ for sign32.c and its Makefile. +
+The core of sign32.c is customizable to do CRC calculation bitwise, +unrolled bitwise, tablewise or to generate the CRC table. + +
+# build options +CRC32SIGN := 1 + +ifdef CRC32SIGN + CDEFINES += -DCRC32SIGN=$(CRC32SIGN) +endif + +%.$(BINLOC).bin: %.elf + @echo $@ + $(OBJCOPY) -O binary $< $@ +ifdef CRC32SIGN + crc32/sign32 $@ + mv signed.bin $@ + +%.hex: %.$(BINLOC).bin + @echo $@ + $(OBJCOPY) --change-address=$(BINLOC) -I binary -O ihex $< $@ +endif ++ +
+$ make +f030f4.elf +Memory region Used Size Region Size %age Used + FLASH: 2680 B 16 KB 16.36% + RAM: 24 B 4 KB 0.59% + text data bss dec hex filename + 2673 4 20 2697 a89 f030f4.elf +f030f4.0x08000000.bin +crc32/sign32 f030f4.0x08000000.bin +BC689506 f030f4.0x08000000.bin: 2676, signed.bin: 2680 +mv signed.bin f030f4.0x08000000.bin +f030f4.hex ++ +I can double check that the value at the end of the binary file matches. + +
+$ hexdump -C f030f4.0x08000000.bin | tail +000009f0 01 46 63 46 52 41 5b 10 10 46 01 d3 40 42 00 2b |.FcFRA[..F..@B.+| +00000a00 00 d5 49 42 70 47 63 46 5b 10 00 d3 40 42 01 b5 |..IBpGcF[...@B..| +00000a10 00 20 00 f0 05 f8 02 bd 00 29 f8 d0 16 e7 70 47 |. .......)....pG| +00000a20 70 47 c0 46 50 4c 4c 48 53 49 0a 00 20 25 64 20 |pG.FPLLHSI.. %d | +00000a30 25 73 25 73 00 75 70 00 77 65 65 6b 00 64 61 79 |%s%s.up.week.day| +00000a40 00 68 6f 75 72 00 6d 69 6e 75 74 65 00 73 65 63 |.hour.minute.sec| +00000a50 6f 6e 64 00 30 31 32 33 34 35 36 37 38 39 41 42 |ond.0123456789AB| +00000a60 43 44 45 46 00 00 20 2b 2b 10 0a 02 08 00 00 00 |CDEF.. ++.......| +00000a70 ef 00 00 00 06 95 68 bc |......h.| +00000a78 ++ +I can flash the resulting intel hex file and see that it executes. + +
+$ stm32flash -x f030f4.hex COM3 +stm32flash 0.6-patch-hex + +http://stm32flash.sourceforge.net/ + +Using Parser : Intel HEX +Location : 0x8000000 +Size : 2680 +Interface serial_w32: 57600 8E1 +Version : 0x31 +Option 1 : 0x00 +Option 2 : 0x00 +Device ID : 0x0444 (STM32F03xx4/6) +- RAM : Up to 4KiB (2048b reserved by bootloader) +- Flash : Up to 32KiB (size first sector: 4x1024) +- Option RAM : 16b +- System RAM : 3KiB +Write to memory +Erasing memory +Wrote address 0x08000a78 (100.00%) Done. + +Starting execution at address 0x08000000... done. ++ +I can use stm32flash to compute the CRC-32 checksum on the first 2680 +bytes of FLASH, the result is 0 as this covers both the payload AND +the CRC-32 checksum value. + +
+$ stm32flash -C -S 0x08000000:2680 COM3 +stm32flash 0.6-patch-hex + +http://stm32flash.sourceforge.net/ + +Interface serial_w32: 57600 8E1 +Version : 0x31 +Option 1 : 0x00 +Option 2 : 0x00 +Device ID : 0x0444 (STM32F03xx4/6) +- RAM : Up to 4KiB (2048b reserved by bootloader) +- Flash : Up to 32KiB (size first sector: 4x1024) +- Option RAM : 16b +- System RAM : 3KiB +CRC computation +CRC address 0x08000a78 (100.00%) Done. +CRC(0x08000000-0x08000a78) = 0x00000000 ++ +If I ask stm32flash to compute the CRC-32 checksum on the first 2676 +bytes (payload excluding CRC-32 checksum value), it returns 0xbc689506, +which is the value computed at build time. + +
+$ stm32flash -C -S 0x08000000:2676 COM3 +stm32flash 0.6-patch-hex + +http://stm32flash.sourceforge.net/ + +Interface serial_w32: 57600 8E1 +Version : 0x31 +Option 1 : 0x00 +Option 2 : 0x00 +Device ID : 0x0444 (STM32F03xx4/6) +- RAM : Up to 4KiB (2048b reserved by bootloader) +- Flash : Up to 32KiB (size first sector: 4x1024) +- Option RAM : 16b +- System RAM : 3KiB +CRC computation +CRC address 0x08000a74 (100.00%) Done. +CRC(0x08000000-0x08000a74) = 0xbc689506 ++ +Because STM32F030 USART bootloader is v3.1, it doesn't implement the CRC +checksum command included in v3.3. This means that in this case +stm32flash computes the CRC checksum value on its own. You can check the +sources of stm32flash for its implementation of the CRC-32 calculation. + +
check_flash()
+implementation I just made relying on the default settings for polynomial,
+initial value, polynomial length and shift direction should be common.
++Next, I will use the ADC to read a resistor +value. + +
+Vout = Vin * Rref / (Rref + Rmeasured) + +
+Vout = VADC = VDDA * ADCRAW / 4095 +
+Vin = VDDA +
+~VDDA~ * ADCRAW / 4095 = ~VDDA~ * Rref / (Rref + Rmeasured) +
+ADCRAW * Rmeasured = Rref * (4095 – ADCRAW) +
+Rmeasured = Rref * (4095 – ADCRAW) / ADCRAW +
+Rmeasured = Rref * (4095 / ADCRAW – 1) + +
+#include <limits.h> +int R = ADCraw ? Rref * (4095 / ADCraw - 1) : INT_MAX ; ++ +
int R = ADCraw ? Rref * 4095 / ADCraw - Rref : INT_MAX ;+ +
+$ stm32flash -r - -S 0x1FFFF7a0:96 COM6 2>/dev/null | hexdump -C +00000000 ff ff ff ff 31 00 10 00 ff ff ff ff 14 80 20 00 |....1......... .| +00000010 13 57 33 52 31 37 31 20 ce 06 f5 05 f0 ff ff ff |.W3R171 ........| +00000020 ff ff 27 05 ff ff ff ff fc ff ff ff 10 00 ff ff |................| +00000030 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| +00000040 ff ff ff ff ff ff ff ff f3 ff ff ff ff ff ff ff |................| +00000050 ff ff ff ff ff ff ff ff 68 97 64 9b 3c c3 3f c0 |........h.d.<.?.| +00000060 ++
+| Location | Content | Size | Format | Reference | +|------------|-----------------|------|--------------------------|-------------| +| 0x1FFFF7A0 | Protocol 3 Ver. | 2 | xFFFF => unsupported | | +| 0x1FFFF7A2 | Protocol 2 Ver. | 2 | xFFFF => unsupported | | +| 0x1FFFF7A4 | Protocol 1 Ver. | 2 | x31 => 3.1 | AN2606 4.2 | +| 0x1FFFF7A6 | Bootloader ID | 2 | x10 => 1 USART, 1st ver. | AN2606 4.2 | +| 0x1FFFF7AC | UID.X | 2 | bit signed, x8014 => -20 | | +| 0x1FFFF7AE | UID.Y | 2 | | | +| 0x1FFFF7B0 | UID.WAF_NUM | 1 | unsigned | | +| 0x1FFFF7B1 | UID.LOT_NUM | 7 | ASCII | | +| 0x1FFFF7B8 | TS_CAL1 | 2 | | DS9773 3.10 | +| 0x1FFFF7BA | VREFINT_CAL | 2 | | DS9773 3.10 | +| 0x1FFFF7C2 | TS_CAL2 | 2 | | | +| 0x1FFFF7CC | Flash size (KB) | 2 | x10 => 16 KB | RM0360 27.1 | ++
+| BootID | X | Y | Wafer | Lot | TS_CAL1 | VREFINT_CAL | TS_CAL2 | Flash | TBD | +|--------|-------|-----|-------|-----------|---------|-------------|---------|-------|---------| +| x10 | x8014 | x20 | x13 | ‘W3R171 ‘ | x6CE | x5F5 | x527 | 16 | hd<? | +| x10 | x8011 | x49 | x0E | ‘W3U795 ‘ | x6D1 | x5F0 | x523 | 16 | hbF? | +| x10 | x8015 | x21 | x13 | ‘W4A195 ‘ | x6DA | x5EE | x52A | 16 | h^D? | +| x10 | x1C | x3A | x12 | ‘W4A820 ‘ | x6B9 | x5F6 | x511 | 16 | hR;? | +| x10 | x09 | x4D | x0B | ‘W4C593 ‘ | x6E6 | x5F4 | x53C | 16 | haG? | +| x10 | x1D | x41 | x12 | ‘W4R342 ‘ | x6E4 | x5F1 | x535 | 16 | hZJ? | +| x10 | x10 | x17 | x05 | ‘WAA390 ‘ | x6E9 | x5F8 | x523 | 16 | hY=? | +| x10 | x800A | x41 | x08 | ‘QMY687 ‘ | x6E6 | x5F1 | x53E | 16 | hZE? | +| x10 | xDDC1 |xDDC1| x0D | ‘ ‘ | x703 | x5E9 | x535 | 32 | hNb\xFF | +| x10 | xDDC1 |xDDC1| x0F | ‘ ‘ | x70B | x5EF | x537 | 32 | hHX\xFF | +| x10 | xDDC1 |xDDC1| x10 | ‘ ‘ | x6FE | x5E9 | x539 | 32 | hZa\xFF | +| x21 | x4D | x47 | x0C | ‘QMT476 ‘ | x6D8 | x5F3 | x52D | 64 | hlR\xBF | +| x21 | x36 | x47 | x0A | ‘QRW813 ‘ | x6DF | x5F3 | x539 | 64 | hhF\xFF | +| x21 | x41 | x1E | x12 | ‘SNG712 ‘ | x6EB | x5F0 | x526 | 64 | hjS\xFF | ++ +
+Commercial solutions and mature open source projects are a must if you want to +develop products with some level of quality. Unfortunately their complexity is +high because they have to satisfy complex requirements. Their documentation +and source code when available are often hard to navigate, out of date or just +not addressing what you need to learn. +
+Starting from scratch, on the other hand, is not something often documented and +when it is, it is usually after the fact. So if you want to learn how to do it +you need to catch the opportunity to watch someone going through the steps and +explaining what’s going on. +
+I will try to capture here my own “STM32 bring up” journey using a step by step +approach, writing down the problems faced and decisions taken while evolving +simple projects. + +