Fixing STM32 Register-Level C Code: GPIO, Clock, ADC, Timer and UART Setup
main.h
// main.h
#ifndef MAIN_H_
#define MAIN_H_
// Libraries
#include <stdio.h>
#include <stdint.h>
// Register declaration example (GPIOs)
typedef struct {
volatile uint32_t CRL;
volatile uint32_t CRH;
volatile uint32_t IDR;
volatile uint32_t ODR;
volatile uint32_t BSRR;
volatile uint32_t BRR;
volatile uint32_t LCKR;
} GPIO_TypeDef;
// Address definitions
#define RCC_BASE 0x40021000UL // RCC base address
#define GPIOA_BASE 0x40010800UL // GPIO Port A base address
#define GPIOB_BASE 0x40010C00UL // GPIO Port B base address
#define GPIOC_BASE 0x40011000UL // GPIO Port C base address
#define TIM2_BASE 0x40000000UL // TIM2 timer base address
#define ADC1_BASE 0x40012400UL // ADC1 base address
#define USART1_BASE 0x40013800UL // USART1 base address
#define FLASH_BASE 0x40022000UL // FLASH base address
// Register pointers
#define RCC ((RCC_TypeDef *) RCC_BASE)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define TIM2 ((TIM_TypeDef *) TIM2_BASE)
#define ADC1 ((ADC_TypeDef *) ADC1_BASE)
#define USART1 ((USART_TypeDef *) USART1_BASE)
#define FLASH ((FLASH_TypeDef *) FLASH_BASE)
#define RCC_APB2ENR_IOPAEN (0x1UL << 2U)
#define RCC_APB2ENR_IOPBEN (0x1UL << 3U)
#define RCC_APB2ENR_IOPCEN (0x1UL << 4U)
#define RCC_APB1ENR_TIM2EN (0x1UL << 0U)
#define RCC_APB2ENR_ADC1EN (0x1UL << 9U)
#define RCC_CFGR_ADCPRE (0x3UL << 14U)
#define RCC_APB2ENR_USART1EN (0x1UL << 14U)
// Functions
void USER_RCC_ClockEnable(void); // Initialize clock
void USER_GPIO_Init(void); // Initialize GPIOs
#endif
main.c
// main.c
// Libraries and header files
#include "main.h"
#include "timer.h"
#include "ADC.h"
#include "uart.h"
#include "matrixkeyboard.h"
#include "lcd.h"
#include <math.h>
// Initialization Functions
void USER_RCC_ClockEnable(void);
void USER_GPIO_Init(void);
// Function to initialize clocks
void USER_RCC_ClockEnable(void) {
// Clock for Port A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// Clock for Port B
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
// Clock for Port C
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
// Clock for TIM2 (Timer)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// Clock for the potentiometer
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->CFGR |= RCC_CFGR_ADCPRE;
// Clock for UART protocol
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// Set system clock to 64 MHz
FLASH->ACR &= ~(0x5UL << 0U);
FLASH->ACR |= (0x2UL << 0U);
// PLL HSI clock /2 selected as PLL input clock
RCC->CFGR &= ~(0x1UL << 16U)
& ~(0x7UL << 11U)
& ~(0x3UL << 8U)
& ~(0xFUL << 4U);
// PLL input clock x 16 (PLLMUL bits)
RCC->CFGR |= (0xFUL << 18U)
| (0x4UL << 8U);
RCC->CR |= (0x1UL << 24U);
// Wait until PLL is locked
while (!(RCC->CR & ~(0x1UL << 25U)));
// PLL used as system clock (SW bits)
RCC->CFGR &= ~(0x1UL << 0U);
RCC->CFGR |= (0x2UL << 0U);
// Wait until PLL is switched
while (0x8UL != (RCC->CFGR & 0xCUL));
}
// Function to initialize the GPIOs
void USER_GPIO_Init(void) {
// Set PA0 as analog input
GPIOA->CRL &= ~(GPIO_CRL_MODE0)
& ~(GPIO_CRL_CNF0);
// Set PA9 as output push-pull
GPIOA->CRH &= ~(GPIO_CRH_CNF9_0)
& ~(GPIO_CRH_MODE9);
GPIOA->CRH |= (GPIO_CRH_CNF9)
| (GPIO_CRH_MODE9_0);
// Set PB10 as output
GPIOB->CRH &= ~(GPIO_CRH_CNF10)
& ~(GPIO_CRH_MODE10_0);
GPIOB->CRH |= (GPIO_CRH_MODE10_1);
GPIOB->ODR |= (GPIO_ODR_10);
}
// Main function
int main(void) {
// Initialize clock, GPIOs, Timer, ADC, UART and the LCD
USER_RCC_ClockEnable();
USER_GPIO_Init();
USER_TIM2_Init();
USER_ADC1_Init();
USER_USART1_Init();
LCD_Init();
// Initialize Engine model
EngTrModel_initialize();
// Loop
for (;;) {
}
}
ADC.h
// ADC.h
#ifndef ADC_H_
#define ADC_H_
#define ADC_CR1_DUALMOD (0xFUL << 16U)
#define ADC_CR2_CONT (0x1UL << 1U)
#define ADC_CR2_ALIGN (0x1UL << 11U)
#define ADC_SMPR2_SMP0 (0x7UL << 0U)
#define ADC_SQR1_L (0xFUL << 20U)
#define ADC_SQR3_SQ1 (0x1FUL << 0U)
#define ADC_CR2_ADON (0x1UL << 0U)
#define ADC_CR2_CAL (0x1UL << 2U)
#define ADC_SR_EOC (0x1UL << 1U)
void USER_ADC1_Init(void);
uint16_t USER_ADC1_Convert(void);
float scaleVoltageValue(float, float, float);
#endif
ADC.c
// ADC.c
// Libraries and header files
#include "main.h"
#include "ADC.h"
void USER_ADC1_Init(void) {
ADC1->CR1 &= ~(ADC_CR1_DUALMOD); // Step 1 - Select independent mode
ADC1->CR2 |= ADC_CR2_CONT; // Step 2 - Set conversion mode
ADC1->CR2 &= ~(ADC_CR2_ALIGN); // and format for ADC result
ADC1->SMPR2 &= ~(ADC_SMPR2_SMP0); // Step 3 - Select sample time for the ADC channel
ADC1->SQR1 &= ~(ADC_SQR1_L); // Step 4 - Select the sequence and number of
// conversions for the ADC regular channels
ADC1->SQR3 &= ~(ADC_SQR3_SQ1); // Step 5 - Select channel for the first ADC conversion
ADC1->CR2 |= ADC_CR2_ADON; // Step 6 - Enable the ADC module
ADC1->CR2 |= ADC_CR2_CAL; // Step 7 - Calibrate after each power-up
while (ADC1->CR2 & ADC_CR2_CAL); // Step 8 - Wait for reset
ADC1->CR2 |= ADC_CR2_ADON; // Step 9 - Start conversion
}
// Function to convert the analog reading to an integer number
uint16_t USER_ADC1_Convert(void) {
while (!(ADC1->SR & ADC_SR_EOC));
return ADC1->DR;
}
// Function to normalize the voltage value
float scaleVoltageValue(float voltageValue, float min, float max) {
float normalizedVoltageValue = (voltageValue - min) / (max - min) * 100;
return normalizedVoltageValue;
}
timer.h
// timer.h
#ifndef TIMER_H_
#define TIMER_H_
// General timer configuration register bits
#define TIM_SMCR_SMS (0x7UL << 0U)
#define TIM_CR1_UDIS (0x1UL << 1U)
#define TIM_CR1_DIR (0x1UL << 4U)
#define TIM_CR1_CMS (0x3UL << 5U)
#define TIM_SR_UIF (0x1UL << 0U)
#define TIM_CR1_CEN (0x1UL << 0U)
// Prescaler and initial count value for the 500 ms timer
#define TIM_PSC_500MS 0x1E8 // 488
#define TIM_CNT_500MS 0x60 // 96
// Timer functions
void USER_TIM2_Init(void); // Initialize timer
void USER_TIM2_Delay(uint16_t, uint16_t); // Delay via TIM2 timer
#endif
timer.c
// timer.c
// Libraries and header files
#include "main.h"
#include "timer.h"
// Initialize TIM2 (Timer)
void USER_TIM2_Init(void) {
TIM2->SMCR &= ~(TIM_SMCR_SMS); // Enable internal clock source
TIM2->CR1 &= ~(TIM_CR1_UDIS); // Enable UEV
TIM2->CR1 &= ~(TIM_CR1_DIR); // Set counter as upcounter
TIM2->CR1 &= ~(TIM_CR1_CMS); // Set Edge-aligned mode
TIM2->SR &= ~(TIM_SR_UIF); // Clear UIF
}
// Function to delay with TIM2 based on the function's input parameters
void USER_TIM2_Delay(uint16_t TIM_PSC, uint16_t TIM_CNT) {
TIM2->PSC = TIM_PSC; // Prescaler value for the delay
TIM2->CNT = TIM_CNT; // Initial count for the delay
TIM2->CR1 |= TIM_CR1_CEN; // Enable TIM2
while (!(TIM2->SR & TIM_SR_UIF)); // Wait for UIF
TIM2->CR1 &= ~(TIM_CR1_CEN); // Stop TIM2 timer
TIM2->SR &= ~(TIM_SR_UIF); // Clear UIF
}
--------------
High level ODR[0-15] equivalent 0 - 15
Low level ODR[0-15] equivalent 16 - 31
Low level ODR[0-15] equivalent 16 - 31
uart.h
// uart.h
#ifndef UART_H_
#define UART_H_
// Define a USART protocol configuration
#define USART_CR1_UE (0x1UL << 13U) // Enable USART protocol
#define USART_CR1_M (0x1UL << 12U) // Set word length of 8 bits
#define USART_CR2_STOP (0x3UL << 12U) // Set 1 stop bit
#define USARTDIV 0x22B // Set baud rate to 115200 bps with a frequency of 64 MHz
// USARTDIV 0x45 at normal frequency (8 MHz)
#define USART_CR1_TE (0x1UL << 3U) // Enable transmitter
#define USART_SR_TXE (0x1UL << 7U) // Transmit data register empty flag
// Functions
void USER_USART1_Init(void); // Initialize USART1
int _write(int, char*, int); // Redirect standard output to USART1 and print it in the serial monitor
#endif
uart.c
// uart.c
// Libraries and header files
#include "main.h"
#include "uart.h"
// Function to initialize UART protocol
void USER_USART1_Init(void) {
USART1->CR1 |= USART_CR1_UE; // Enable USART
USART1->CR1 &= ~USART_CR1_M; // Set data width to 8 bits
USART1->CR2 &= ~USART_CR2_STOP; // Set 1 stop bit
USART1->BRR = USARTDIV; // Baud rate
USART1->CR1 |= USART_CR1_TE; // Enable transmitter
}
// Function to redirect output to USART protocol and then print it on a serial monitor
int _write(int file, char *ptr, int len) {
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++) {
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = *ptr++;
}
return len;
}
Toggle LED State with Button
// Toggle LED state with button
/* Superloop structure */
int main(void) {
/* Declarations and Initializations */
USER_RCC_ClockEnable();
USER_GPIO_Init();
/* Repetitive block */
for (;;) {
if (!(GPIOA->IDR & (0x1UL << 10U)))
GPIOA->ODR ^= (0x1UL << 5U);
}
}
void USER_RCC_ClockEnable(void) {
// RCC_APB2ENR modified to IO port A clock enable
RCC->APB2ENR = RCC->APB2ENR // RCC_APB2ENR actual value
| // to set
(0x1UL << 2U); // (mask) IOPAEN bit
}