5 projects · C · STM32F4 · github.com/YouCanCallMeYoussef/messing-with-STM-32 · notes by pwnewi
#include "stm32f4xx.h" RCC->AHB1ENR |= (1 << 6); GPIOG->MODER &= ~((3<<26)|(3<<28)); GPIOG->MODER |= ((1<<26)|(1<<28)); while (1) { GPIOG->ODR ^= (1<<13) | (1<<14); delay(500000); }
#include <stdint.h> #define STM32F429xx #include "stm32f4xx.h" void delay(volatile uint32_t count) { while (count--); } int main(void) { RCC->AHB1ENR |= (1 << 6); GPIOG->MODER &= ~((3<<26)|(3<<28)); GPIOG->MODER |= ((1<<26)|(1<<28)); GPIOG->OTYPER &= ~((1<<13)|(1<<14)); GPIOG->OSPEEDR &= ~((3<<26)|(3<<28)); GPIOG->PUPDR &= ~((3<<26)|(3<<28)); GPIOG->ODR &= ~((1<<13)|(1<<14)); while (1) { GPIOG->ODR ^= (1<<13) | (1<<14); delay(500000); } }
| Register | Operation | Effect |
|---|---|---|
| RCC→AHB1ENR | |= (1<<6) | Enable GPIOG clock |
| GPIOG→MODER | bits [29:26] = 0101 | PG13/PG14 → output mode |
| GPIOG→ODR | ^= (1<<13)|(1<<14) | Toggle both LEDs |
volatile uint8_t on_off = 0; RCC->APB2ENR |= (1<<14); SYSCFG->EXTICR[0] &= ~(0xF<<0); EXTI->IMR |= (1<<0); EXTI->RTSR |= (1<<0); NVIC_EnableIRQ(EXTI0_IRQn); if (EXTI->PR & (1<<0)) { on_off ^= 1; EXTI->PR |= (1<<0); } if (on_off) { GPIOG->ODR ^= (1<<13) | (1<<14); delay(500000); }
#include <stdint.h> #define STM32F429xx #include "stm32f4xx.h" volatile uint8_t on_off = 0; void delay(volatile uint32_t count) { while (count--); } void GPIO_Init(void) { RCC->AHB1ENR |= (1<<0) | (1<<6); GPIOA->MODER &= ~(3<<0); GPIOA->PUPDR &= ~(3<<0); GPIOG->MODER &= ~((3<<26)|(3<<28)); GPIOG->MODER |= ((1<<26)|(1<<28)); } void EXTI_Init(void) { RCC->APB2ENR |= (1<<14); SYSCFG->EXTICR[0] &= ~(0xF<<0); EXTI->IMR |= (1<<0); EXTI->RTSR |= (1<<0); EXTI->FTSR &= ~(1<<0); NVIC_SetPriority(EXTI0_IRQn, 1); NVIC_EnableIRQ(EXTI0_IRQn); } void EXTI0_IRQHandler(void) { if (EXTI->PR & (1<<0)) { on_off ^= 1; EXTI->PR |= (1<<0); } } int main(void) { GPIO_Init(); EXTI_Init(); while (1) { if (on_off) { GPIOG->ODR ^= (1<<13) | (1<<14); delay(500000); } else { GPIOG->ODR &= ~((1<<13)|(1<<14)); } } }
| Register | Operation | Effect |
|---|---|---|
| RCC→APB2ENR | |= (1<<14) | Enable SYSCFG clock |
| SYSCFG→EXTICR[0] | &= ~0xF | Route EXTI0 to PA0 |
| EXTI→IMR / RTSR | |= (1<<0) | Unmask + rising edge trigger |
| EXTI→PR | |= (1<<0) | Clear pending flag (write-1-to-clear) |
RCC->APB1ENR |= (1<<0); TIM2->PSC = 15999; TIM2->ARR = 999; TIM2->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM2_IRQn); TIM2->CR1 |= TIM_CR1_CEN; if (TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; GPIOG->ODR ^= (1<<13); }
#include <stdint.h> #define STM32F429xx #include "stm32f4xx.h" void GPIO_Init(void) { RCC->AHB1ENR |= (1<<6); GPIOG->MODER &= ~(3<<26); GPIOG->MODER |= (1<<26); } void TIM2_Init(void) { RCC->APB1ENR |= (1<<0); TIM2->PSC = 15999; TIM2->ARR = 999; TIM2->CNT = 0; TIM2->DIER |= TIM_DIER_UIE; NVIC_SetPriority(TIM2_IRQn, 1); NVIC_EnableIRQ(TIM2_IRQn); TIM2->CR1 |= TIM_CR1_CEN; } void TIM2_IRQHandler(void) { if (TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; GPIOG->ODR ^= (1<<13); } } int main(void) { GPIO_Init(); TIM2_Init(); while (1) { /* timer runs autonomously */ } }
| Register | Value | Effect |
|---|---|---|
| TIM2→PSC | 15999 | 16 MHz → 1 kHz tick |
| TIM2→ARR | 999 | 1000 ticks → 1 s period |
| TIM2→DIER UIE | set | Fire IRQ on overflow |
| TIM2→SR UIF | cleared in ISR | Must clear or IRQ re-fires immediately |
RCC->AHB1ENR |= (1<<3); GPIOD->MODER |= ((2<<24)|(2<<26)|(2<<28)|(2<<30)); GPIOD->AFR[1] |= (0x22220000); RCC->APB1ENR |= (1<<2); TIM4->PSC = 15; TIM4->ARR = 999; TIM4->CCMR1 |= (6<<4)|TIM_CCMR1_OC1PE; TIM4->CCER |= TIM_CCER_CC1E; TIM4->CCR1 = 100; TIM4->CCMR1 |= (6<<12)|TIM_CCMR1_OC2PE; TIM4->CCER |= TIM_CCER_CC2E; TIM4->CCR2 = 200; TIM4->CCMR2 |= (6<<4)|TIM_CCMR2_OC3PE; TIM4->CCER |= TIM_CCER_CC3E; TIM4->CCR3 = 700; TIM4->CCMR2 |= (6<<12)|TIM_CCMR2_OC4PE; TIM4->CCER |= TIM_CCER_CC4E; TIM4->CCR4 = 900; TIM4->CR1 |= TIM_CR1_ARPE; TIM4->EGR |= TIM_EGR_UG; TIM4->CR1 |= TIM_CR1_CEN;
#include <stdint.h> #define STM32F429xx #include "stm32f4xx.h" void GPIO_Config(void) { RCC->AHB1ENR |= (1<<3); GPIOD->MODER &= ~((3<<24)|(3<<26)|(3<<28)|(3<<30)); GPIOD->MODER |= ((2<<24)|(2<<26)|(2<<28)|(2<<30)); GPIOD->AFR[1] &= ~(0xFFFF0000); GPIOD->AFR[1] |= (0x22220000); } void TIM_Config(void) { RCC->APB1ENR |= (1<<2); TIM4->PSC = 15; TIM4->ARR = 999; TIM4->CCMR1 |= (6<<4)|TIM_CCMR1_OC1PE; TIM4->CCER |= TIM_CCER_CC1E; TIM4->CCR1 = 100; TIM4->CCMR1 |= (6<<12)|TIM_CCMR1_OC2PE; TIM4->CCER |= TIM_CCER_CC2E; TIM4->CCR2 = 200; TIM4->CCMR2 |= (6<<4)|TIM_CCMR2_OC3PE; TIM4->CCER |= TIM_CCER_CC3E; TIM4->CCR3 = 700; TIM4->CCMR2 |= (6<<12)|TIM_CCMR2_OC4PE; TIM4->CCER |= TIM_CCER_CC4E; TIM4->CCR4 = 900; TIM4->CR1 |= TIM_CR1_ARPE; TIM4->EGR |= TIM_EGR_UG; TIM4->CR1 |= TIM_CR1_CEN; } int main(void) { GPIO_Config(); TIM_Config(); while (1) { /* PWM runs in hardware */ } }
#define PWM_STEPS 100 #define STEP_SIZE 10 volatile uint8_t duty = 0, pwm_counter = 0; void TIM2_IRQHandler(void) { if (TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; pwm_counter = (pwm_counter + 1) % PWM_STEPS; if (pwm_counter < duty) GPIOG->ODR |= ((1<<13)|(1<<14)); else GPIOG->ODR &= ~((1<<13)|(1<<14)); } } void EXTI0_IRQHandler(void) { if (EXTI->PR & (1<<0)) { duty = (duty + STEP_SIZE) % (PWM_STEPS + STEP_SIZE); EXTI->PR |= (1<<0); } } int main(void) { GPIO_Init(); EXTI_Init(); TIM2_Init(); while (1) { __WFI(); } }
| Concept | Implementation | Note |
|---|---|---|
| PWM period | 100 TIM2 overflows | pwm_counter cycles 0→99 |
| Duty cycle | counter < duty | ON for first N ticks of 100 |
| Brightness step | duty += 10 | 10 steps 0%→100% |
| CPU idle | __WFI() | Sleep until next interrupt |
ICE3 — Drivers & Embedded Systems · Faculté des Sciences de Tunis · 2025–2026 · notes by pwnewi
This report documents the bare metal implementation of UART communication on the STM32F429I-DISC1, covering character transmission and reception using USART1 with direct register programming — no HAL, no abstraction layer.
Instead of a dedicated USB-to-TTL adapter, an Arduino Uno was used as a serial bridge between the STM32 and the PC, using its SoftwareSerial library to avoid conflicts on its hardware UART pins D0/D1.
The STM32F429I-DISC1 onboard LCD uses many GPIO pins including PA2/PA3 (which would normally carry USART2). After verifying pin availability, USART1 on PA9/PA10 was selected as the only UART with both TX and RX freely accessible.
| STM32 Pin | Direction | Arduino Pin | Purpose |
|---|---|---|---|
| PA9 | → | D2 (SoftwareSerial RX) | USART1 TX |
| PA10 | ← | D3 (SoftwareSerial TX) | USART1 RX |
| GND | — | GND | Common ground |
Configure USART1 and transmit A, B, C followed by CR/LF periodically. Three register groups must be configured: RCC clocks, GPIO alternate function, and USART baud rate + enable.
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; GPIOA->MODER |= (2U << (9*2)); GPIOA->AFR[1] |= (7U << ((9-8)*4)); USART1->BRR = 0x0683; USART1->CR1 = USART_CR1_UE | USART_CR1_TE; while(!(USART1->SR & USART_SR_TXE)); USART1->DR = (ch & 0xFF); while(!(USART1->SR & USART_SR_TC)); UART1_SendChar('A'); UART1_SendChar('B'); UART1_SendChar('C');
#define STM32F429xx #include "stm32f4xx.h" void delay(volatile uint32_t count) { while(count--); } void USART1_Init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; GPIOA->MODER &= ~(3U << (9*2)); GPIOA->MODER |= (2U << (9*2)); GPIOA->AFR[1] &= ~(0xF << ((9-8)*4)); GPIOA->AFR[1] |= (7U << ((9-8)*4)); USART1->BRR = 0x0683; USART1->CR1 = USART_CR1_UE | USART_CR1_TE; } void UART1_SendChar(int ch) { while(!(USART1->SR & USART_SR_TXE)); USART1->DR = (ch & 0xFF); while(!(USART1->SR & USART_SR_TC)); } int main(void) { USART1_Init(); while (1) { UART1_SendChar('A'); UART1_SendChar('B'); UART1_SendChar('C'); UART1_SendChar(' '); UART1_SendChar(' '); delay(4000000); } }
#include <SoftwareSerial.h> SoftwareSerial stm32Serial(2, 3); void setup() { Serial.begin(9600); stm32Serial.begin(9600); } if (stm32Serial.available()) Serial.write(stm32Serial.read()); if (Serial.available()) stm32Serial.write(Serial.read());
#include <SoftwareSerial.h> SoftwareSerial stm32Serial(2, 3); // RX=D2, TX=D3 void setup() { Serial.begin(9600); stm32Serial.begin(9600); } void loop() { if (stm32Serial.available()) Serial.write(stm32Serial.read()); if (Serial.available()) stm32Serial.write(Serial.read()); }
Extend the driver to receive a character and react to it. Typing 'A' in Serial Monitor toggles the green LED on PG13. Requires enabling PA10 as AF7 (USART1_RX), setting RE in CR1, and polling RXNE.
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; GPIOA->MODER |= (2U<<(9*2)); GPIOA->AFR[1] |= (7U<<((9-8)*4)); GPIOA->MODER |= (2U<<(10*2)); GPIOA->AFR[1] |= (7U<<((10-8)*4)); USART1->BRR = 0x0683; USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; while(!(USART1->SR & USART_SR_RXNE)); return (unsigned char)(USART1->DR); if (c == 'A') GPIOG->ODR ^= (1U<<13);
#define STM32F429xx #include "stm32f4xx.h" void USART1_Init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // PA9 TX GPIOA->MODER &= ~(3U<<(9*2)); GPIOA->MODER |= (2U<<(9*2)); GPIOA->AFR[1] &= ~(0xF<<((9-8)*4)); GPIOA->AFR[1] |= (7U<<((9-8)*4)); // PA10 RX GPIOA->MODER &= ~(3U<<(10*2)); GPIOA->MODER |= (2U<<(10*2)); GPIOA->AFR[1] &= ~(0xF<<((10-8)*4)); GPIOA->AFR[1] |= (7U<<((10-8)*4)); // PG13 LED output GPIOG->MODER &= ~(3U<<(13*2)); GPIOG->MODER |= (1U<<(13*2)); USART1->BRR = 0x0683; USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } unsigned char UART1_GetChar(void) { while(!(USART1->SR & USART_SR_RXNE)); return (unsigned char)(USART1->DR); } int main(void) { USART1_Init(); while (1) { unsigned char c = UART1_GetChar(); if (c == 'A') GPIOG->ODR ^= (1U<<13); } }
| Register | Bit / Field | Role |
|---|---|---|
| USART1→CR1 | RE (bit 2) | Enable the receiver |
| USART1→SR | RXNE (bit 5) | Set when a byte is ready |
| USART1→DR | [7:0] | Received byte — reading clears RXNE |
| GPIOG→ODR | bit 13 | Green LED PG13 toggle |
The TD references PA2/PA3 (USART2), valid on STM32F407. On STM32F429I-DISC1 those pins are routed to the onboard SDRAM and LCD controller. USART1 on PA9/PA10 was selected instead.
Using hardware UART pins D0/D1 caused bus contention with the ATmega16U2 USB chip. The solution was SoftwareSerial on D2/D3 — dedicated pins with no shared silicon.
The project required an explicit #define STM32F429xx before including stm32f4xx.h, because compiler flags provided -DSTM32F429ZITx rather than the family-level define the header checks for.
Every remaining exercise is built on the same primitive — writing a byte into USART→DR and waiting for a flag in USART→SR. The complexity shifts from the driver layer to the application layer.
| Exercise | Objective | UART role |
|---|---|---|
| 3 — String TX/RX | Send and receive full strings, echo loop | Loop over SendChar(), buffer chars until CR/LF |
| 4 — SIM808 Init | Send AT commands to configure GSM module | SendString("AT+CMGF=1 ") on a second UART |
| 5 — Send SMS | Button press triggers an SMS | AT+CMGS, wait > prompt via GetChar(), send Ctrl+Z |
| 6 — Voice Call | Button press triggers a phone call | SendString("ATD+num; ") then "ATH " |
| 7 — GSM Frame RX | Detect +CLIP: caller ID in stream | Read chars into buffer, strstr() for keyword |
| 8 — GPS Parsing | Read +CGPSINF: frame, extract lat/lon | Send AT+CGPSINF=0, read line, tokenize with strtok() |
// Layer 3 — Application (ex 4–8) configSIM808() GSM_SendSms() GSM_MakeCall() GPS_readInfo() // Layer 2 — String (ex 3) SendString(char *str) ReceiveString(char *buf) // Layer 1 — Character (ex 1 & 2) ← built in this report SendChar(char ch) GetChar() // Layer 0 — Registers (always the same) USART->DR USART->SR&TXE USART->SR&RXNE USART->SR&TC
// Layer 3 — Application (ex 4–8) configSIM808() GSM_SendSms() GSM_MakeCall() GPS_readInfo() // Layer 2 — String (ex 3) SendString(char *str) ReceiveString(char *buf) // Layer 1 — Character (ex 1 & 2) ← built in this report SendChar(char ch) GetChar() // Layer 0 — Registers (always the same) USART->DR USART->SR&TXE USART->SR&RXNE USART->SR&TC