In embedded programming, we use mostly libraries, IDEs, extensions or so on. I'm on that way also. This gives us the simplicity and consistency. But even we should do that at projects, I think that every embedded programmer should now what is going on under the hood!
In this post, I wanna share how to write a minimal HAL library (just for LED blinking) for STM32F446RE microcontroller. I just will do simple memory mappings and register operations so that it will be simple, but you will learn the most important stuffs about embedded programming.
Firstly, you have to know exactly two terms: memory mapping and register operations. So embedded programming (nearly) is all about these.
Every microcontroller has a memory layout and this layout is separated into different groups. We know that which address owns what. For example, if I wanna use GPIO peripheral, I should look at memory address of that GPIO from the microcontroller's reference manual.
In STM32F4 series microcontrollers, 0x40000000 is the base address for all peripherals. Below is a part of peripherals' memory layout:
We see the what type of peripheral lays out at which addresses and busses. So we use these addresses when programming embedded device.
Don't forget that I will just write a minimal library that just can blink the LED. But the logic behind it is persistent for other applications.
What exactly is there at the peripheral addresses? The question is registers. Every peripheral has a set of registers that make the peripheral usable. Registers are the main topic in embedded world so that you have to know what is exactly it and how works?
A register is a 32-bit addressed data holder at all and used to do a "work". Every register in a peripheral has a special work and all registers make the peripheral active. We set the bits of these registers so that it is programmed!
Embedded programming is all about the setting up the bits of registers.
So that beside the memory mapping, registers must also be defined in the source code. Below is RCC AHB1 peripheral clock enable register as an example (we'll use this to enable the clock of GPIOA port further):
After giving the core logic behind the embedded programming, let's start writing library. You can use any IDE/extension for compiling, flashing and debugging. I'm gonna use PlatformIO extension within VS Code. I've created main.h and main.c source files. In main.h, I'm gonna define the memory mappings and registers and, in main.c, I will blink the LED. Let's start!
According to reference manual (look at "2.2. Memory organization"):
#define APB1_BASEADDR 0x40000000UL
#define APB2_BASEADDR 0x40010000UL
#define AHB1_BASEADDR 0x40020000UL
#define AHB2_BASEADDR 0x50000000UL
#define AHB3_BASEADDR 0xA0001000UL
#define GPIOA_BASEADDR (AHB1_BASEADDR + 0x0UL)
#define RCC_BASEADDR (AHB1_BASEADDR + 0x00003800UL)
We've defined the bus and GPIO port addresses. As I said before, I just write for LED blinking (connected to PA0 pin of microcontroller) so that we just need the GPIO and RCC peripherals.
By the way, RCC (Reset and Clock Control) is peripheral that is used to reset/clock the microcontroller peripherals. I need to enable the clock of GPIOA port firstly.
After the defined memory mappings, let's define the peripheral registers. As I said before, we have many registers that belong to its peripheral. It is good practice to use struct:
typedef unsigned int uint32_t;
typedef struct {
volatile uint32_t CR; /* RCC clock control */
volatile uint32_t PLLCFGR; /* RCC PLL configuration */
volatile uint32_t CFGR; /* RCC clock configuration */
volatile uint32_t CIR; /* RCC clock interrupt */
volatile uint32_t AHB1RSTR; /* RCC AHB1 peripheral reset */
volatile uint32_t AHB2RSTR; /* RCC AHB2 peripheral reset */
volatile uint32_t AHB3RSTR; /* RCC AHB3 peripheral reset */
volatile uint32_t NA1; /* Reversed */
volatile uint32_t APB1RSTR; /* RCC APB1 peripheral reset */
volatile uint32_t APB2RSTR; /* RCC APB2 peripheral reset */
volatile uint32_t NA2; /* Reversed */
volatile uint32_t NA3; /* Reversed */
volatile uint32_t AHB1ENR; /* RCC AHB1 peripheral clock enable */
volatile uint32_t AHB2ENR; /* RCC AHB2 peripheral clock enable */
volatile uint32_t AHB3ENR; /* RCC AHB3 peripheral clock enable */
volatile uint32_t NA4; /* Reversed */
volatile uint32_t APB1ENR; /* RCC APB1 peripheral clock enable */
volatile uint32_t APB2ENR; /* RCC APB2 peripheral clock enable */
volatile uint32_t NA5; /* Reversed */
volatile uint32_t NA6; /* Reversed */
volatile uint32_t AHB1LPENR; /* RCC AHB1 peripheral clock enable in low power mode */
volatile uint32_t AHB2LPENR; /* RCC AHB2 peripheral clock enable in low power mode */
volatile uint32_t AHB3LPENR; /* RCC AHB3 peripheral clock enable in low power mode */
volatile uint32_t NA7; /* Reversed */
volatile uint32_t APB1LPENR; /* RCC APB1 peripheral clock enable in low power mode */
volatile uint32_t APB2LPENR; /* RCC APB2 peripheral clock enable in low power mode */
volatile uint32_t NA8; /* Reserved */
volatile uint32_t NA9; /* Reserved */
volatile uint32_t BDCR; /* RCC backup domain control */
volatile uint32_t CSR; /* RCC clock control & status */
volatile uint32_t NA10; /* Reserved */
volatile uint32_t NA11; /* Reserved */
volatile uint32_t SSCGR; /* RCC spread spectrum clock generation */
volatile uint32_t PLLI2SCFGR; /* RCC PLLI2S configuration */
volatile uint32_t PLLSAICFGR; /* RCC PLL configuration */
volatile uint32_t DCKCFGR; /* RCC dedicated clock configuration */
volatile uint32_t CKGATENR; /* RCC clocks gated enable */
volatile uint32_t DCKCFGR2; /* RCC dedicated clocks configuration 2 */
} RCC_Handler;
typedef struct {
volatile uint32_t MODER; /* GPIO port mode */
volatile uint32_t OTYPER; /* GPIO port output type */
volatile uint32_t OSPEEDR; /* GPIO port output speed */
volatile uint32_t PUPDR; /* GPIO port pull-up/pull-down */
volatile uint32_t IDR; /* GPIO port input data */
volatile uint32_t ODR; /* GPIO port output data */
volatile uint32_t BSRR; /* GPIO port bit set/reset */
volatile uint32_t LCKR; /* GPIO port configuration lock */
volatile uint32_t AFRL; /* GPIO alternate function low */
volatile uint32_t AFRH; /* GPIO alternate function high */
} GPIO_Handler;
#define RCC ((RCC_Handler *) RCC_BASEADDR)
#define GPIOA ((GPIO_Handler *) GPIOA_BASEADDR)
Please recheck your reference manual and define the correct registers (look at "6.3. RCC Registers" and "7.4. GPIO Registers").
Don't forget that each peripheral register has 32-bit length. Also defining the registers with volatile keyword is good practice. This tells to compiler: "Hey compiler, you don't have to optimize register variables because they will probably be changed quickly".
Another point is that you have to follow the register offsets. For example, MODER register of GPIOA port is at 0x40020000 so the next register, OTYPER, address should be at 0x40020004 (32-bit offset). Generally, registers are queued so that you just put these in struct in order. But sometimes, there can be offset gaps, as being in RCC registers. If you look at carefully at RCC_Handler, there are NAx (Not Applicable) variables. These are not registers. It's the placeholders. Because of at that offset, any register is not defined.
Right now, let's define the main() (entry-point) like this:
void main(void)
{
RCC->AHB1ENR |= (1 << 0); /* Enable GPIOA clock */
GPIOA->MODER |= (1 << 0); /* General purpose output mode */
GPIOA->OTYPER &= ~(1 << 0); /* Output push-pull */
GPIOA->OSPEEDR &= ~(3 << 0); /* Low speed */
GPIOA->PUPDR &= ~(3 << 0);; /* No pull-up, pull-down */
GPIOA->BSRR = (1 << 0); /* Set (1 << 16 for reset) */
while (1);
}
In here, we used the bitwise operations to manipulate the register bits. I assume that you already know it from your programming background.
What exactly bits are manipulated?
- bit of RCC AHB1 register
- and 2. bits of GPIO MODER register
- bit of GPIO OTYPER register
- and 2. bits of GPIO OSPEEDR register
- and 2. bits of GPIO PUPDR register
- bit of GPIO BSRR register
Finally, you can compile and then flash this program to the microcontroller. That's it 🥳🥳🥳.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.