Firmware Codes

Return Home
STM32L432KC: Bare-Metal UART2 Configuration
// Enable USART2 and GPIOA Clocks
RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN;
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;

// Configure PA2 as Alternate Function (TX)
GPIOA->MODER &= ~(3U << (2 * 2));
GPIOA->MODER |= (2U << (2 * 2));

// Set Alternate Function 7 (AF7) for PA2 which maps to USART2_TX
GPIOA->AFR[0] &= ~(0xFU << (2 * 4));
GPIOA->AFR[0] |= (7U << (2 * 4));

// Assuming a 4MHz system clock, configure baud rate to 9600
USART2->BRR = 4000000 / 9600;

// Enable USART, Transmitter, and Receiver
USART2->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;

// Transmit a character
while(!(USART2->ISR & USART_ISR_TXE));
USART2->TDR = 'A';
Execution Theory Setting up UART without HAL requires routing the internal USART peripheral to a physical pin using the Alternate Function Register (AFR). We first enable the APB1 clock for USART2 and AHB2 for GPIOA. We configure PA2 into Alternate Function mode and specifically assign it AF7, which is the hardware map for USART2_TX. We then calculate the baud rate by dividing our core clock frequency (4MHz) by our desired baud rate (9600) and inserting it into the BRR register. Finally, we enable the transmitter (TE) and the peripheral (UE). To send data, we wait for the Transmit Data Register Empty (TXE) flag before loading a character into TDR.
STM32L432KC: Bare-Metal SPI1 Master Initialization
// Enable SPI1 Clock via APB2
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

// Reset Control Register 1
SPI1->CR1 = 0; 

// Configure SPI1: Master Mode, Baud Rate = fPCLK/16
SPI1->CR1 |= SPI_CR1_MSTR | (3 << SPI_CR1_BR_Pos); 

// Configure CR2: 8-bit data size, Software Slave Management Output Enable
SPI1->CR2 = 0;
SPI1->CR2 |= (7 << SPI_CR2_DS_Pos) | SPI_CR2_SSOE;

// Enable Software Slave Management and set Internal Slave Select high
SPI1->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;

// Enable the SPI peripheral
SPI1->CR1 |= SPI_CR1_SPE;
Execution Theory Initializing the SPI peripheral in Master mode. We first enable the clock to SPI1 via APB2. We set the Master Mode (`MSTR`) bit and configure the Baud Rate (`BR`) prescaler to divide the peripheral clock by 16. We set the Data Size (`DS`) to 8-bit (which is value 7 in the register). For flexibility, we use Software Slave Management (`SSM`) and set the Internal Slave Select (`SSI`) bit high, allowing us to manually control the Chip Select pin via a standard GPIO rather than hardware control. Finally, we enable the SPI peripheral (`SPE`).
STM32L432KC: Bare-Metal I2C1 Master Configuration
// Enable I2C1 Clock
RCC->APB1ENR1 |= RCC_APB1ENR1_I2C1EN;

// Disable I2C peripheral before modifying timing registers
I2C1->CR1 &= ~I2C_CR1_PE;

// Set timing for 100kHz Standard Mode at 4MHz Core Clock
// TIMINGR register configuration is highly specific (Prescaler, SCLDEL, SDAHD, etc)
I2C1->TIMINGR = 0x00200C28; 

// Re-enable I2C peripheral
I2C1->CR1 |= I2C_CR1_PE;

// To transmit: set slave address, byte count, and send START condition
I2C1->CR2 = (SlaveAddress << 1) | (1 << I2C_CR2_NBYTES_Pos);
I2C1->CR2 |= I2C_CR2_START;
Execution Theory Configuring I2C in bare-metal requires strictly disabling the peripheral (PE=0) before setting the timing register. The 32-bit hex value placed in TIMINGR is calculated based on the system clock frequency (4MHz) and defines the exact SCL high/low periods, data setup/hold times, and clock prescaler to achieve a standard 100kHz I2C bus speed. To initiate communication, we load the 7-bit slave address shifted left by 1, set the number of bytes to transfer, and trigger the START condition in CR2.
STM32L432KC: ADC Single Conversion Polling
// Wake ADC and enable voltage regulator
RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;
ADC1->CR &= ~ADC_CR_DEEPPWD;
ADC1->CR |= ADC_CR_ADVREGEN;
for(volatile int i=0; i<1000; i++); // Wait for regulator to stabilize

// Run Calibration
ADC1->CR |= ADC_CR_ADCAL;
while(ADC1->CR & ADC_CR_ADCAL);

// Enable ADC
ADC1->CR |= ADC_CR_ADEN;
while(!(ADC1->ISR & ADC_ISR_ADRDY));

// Read Channel 5
ADC1->SQR1 = (5 << ADC_SQR1_SQ1_Pos);
ADC1->CR |= ADC_CR_ADSTART;
while(!(ADC1->ISR & ADC_ISR_EOC));
uint16_t result = ADC1->DR;
Execution Theory Analog to Digital initialization on the STM32 requires waking the ADC from deep power-down mode and enabling its voltage regulator. We then run an automatic internal calibration (ADCAL). Once calibrated and enabled (ADEN), we configure the regular sequence register (SQR1) to sample channel 5. Setting ADSTART begins the conversion, and we block until the End of Conversion (EOC) flag is thrown before reading the Data Register (DR).
STM32L432KC: Bare-Metal GPIO Blink
// Enable GPIOB Clock (via RCC_AHB2ENR register)
RCC->AHB2ENR |= (1U << 1);

// Set PB3 to General Purpose Output Mode
// Clear the two mode bits first, then set bit 0
GPIOB->MODER &= ~(3U << (3 * 2));
GPIOB->MODER |= (1U << (3 * 2));

while(1) {
    // XOR the Output Data Register to toggle LED state
    GPIOB->ODR ^= (1U << 3); 
    
    // Blocking Delay
    for(volatile int i = 0; i < 100000; i++); 
}