Some time ago, I wanted to learn assembler, and after reading the relevant literature, it was time to practice. Actually, it will be discussed further. The first time I practiced on the Arduino Uno (Atmega328p), now I decided to move on and took up the STM32. I got into the hands of the STM32F103C8 itself on it and will undergo further experiments.
I used the following tools:
- Notepad ++ - for writing code
- GNU Assembler - compiler
- STM32 ST-LINK Utility + ST-LINK V2 - for flashing microcontroller code and debugging
The main purpose of programming in assembler for me is learning. Since you never know where you stumble upon another interesting problem, it was decided to write everything from scratch. The primary task was to understand how the interrupt vector works. Unlike Atmega, in STM32, the interrupt vector does not contain transition instructions:
Specific addresses are written in it, and during an interrupt, the processor itself inserts the address written in the vector into the PC register. Here is an example of my interrupt vector:
.org 0x00000000 SP: .word STACKINIT RESET: .word main NMI_HANDLER: .word nmi_fault HARD_FAULT: .word hard_fault MEMORY_FAULT: .word memory_fault BUS_FAULT: .word bus_fault USAGE_FAULT: .word usage_fault .org 0x000000B0 TIMER2_INTERRUPT: .word timer2_interupt + 1
I want to draw the reader’s attention that the first line is not a reset vector, but the values that will be initialized by the stack. Right after it comes the reset vector followed by 5 mandatory interrupt vectors (NMI_HANDLER - USAGE_FAULT).
The first thing I was stuck with was an ARM assembler syntax. While studying the interrupt vector, I met the mention that ARM has 2 types of Thumb and not Thumb instructions. And what the Cortex-M3 (STM32F103C8 is the Cortex-M3) supports only a set of Thumb instructions. I wrote the instructions strictly on the documentation, but the assembler cursed them for some reason.
unshifted register required
It turned out that at the beginning of the program it is necessary to put
This tells the assembler that you can use Thumb and not Thumb instructions at the same time.
The next thing I encountered was disconnected GPOI ports by default. In order for them to work, among other things, it is necessary to set the corresponding values in the RCC (reset and clock control) registers. I used PORT C, you can enable it by setting bit 4 (the numbering of bits comes from scratch) in RCC_APB2ENR (peripheral clock enable register 2).
Further blinking LED. First of all, as in Arduino, you need to set the pin for the record. This is done via GPIOx_CRL (control register low) or GPIOx_CRH (control register high). Here it is necessary to cancel that 4 bits in one of these registers are responsible for each pin (32-bit registers). 2 bits (MODEy) define the maximum data transfer rate and 2 bits (CNF) pin configuration. I used PORT C pin 14, for this I set the bits in the GPIOx_CRH register [25:24] = 10 and the bits [27:26] = 00.
In order for the diode to burn, you need to set the corresponding bit in GPIOx_ODR (output data register). In my case, bit 14. This would end this simple example by making the delay function and putting it all in a loop, but I could not do it. I decided to set up a timer interrupt ... As it turned out, it was in vain, primarily because the timers are too fast for this type of task.
I will not describe in detail the setting of the timer, who are interested in the code is on Github
. The idea was simple, in a cycle to send the processor to Idle, to time out from Idle to turn on / turn off the LED and again to Idle. But the timer worked much faster than I could do all of the above because of what I had to enter an additional counter.
The counter is a 32 bit variable that should have been in SRAM. And here I was waited by the next rake. When I was programming on Atmega to put a variable in the SRAM, I through .org specified the address of the beginning of the memory where the data block actually was placed. Now, after reading a little about memory initialization, I'm not sure that it was correct, but it worked. And I decided to crank the same with STM32. The memory start address in STM32F103C8 is 0x20000000. And when I made .org at this address, I received a 512 MB binary. This sent me a couple of evenings to smoke manuals. I'm still not 100% aware of how this works, but as far as I understand the .data section puts the values with which to initialize the variables to the executable file, but at runtime the programmer must initialize the values of the variables in memory. Please correct me if I'm wrong. As a result, I created a variable like this:
.section .bss .offset 0x20000000 flash_counter: .word
Initialized it at the beginning of the main function and the LED blinked. I hope this article will help someone. If you have questions I will be glad to answer them.