/*
* ----------------------------------------------------------------------------
* Project : LED Blink on STM32 (PA5) Without Using HAL
* Author : Daniel Lizarazo
* Date : 2025-04-24
* Description : This project configures GPIO pin PA5 as a digital output and
* toggles its state in an infinite loop to blink an LED.
*
* The goal is to understand what happens behind the scenes when
* using STM32 microcontrollers. While many developers rely on the
* HAL (Hardware Abstraction Layer), it's important to learn how to
* work directly with registers. Doing so builds a deeper understanding
* of the microcontroller and makes it easier to transition to other
* platforms or architectures in the future.
*
* Target MCU : STM32C031C6
* Clock : Default internal clock
* Status : In progress
* LinkedIn : https://www.linkedin.com/in/daniellizarazoo/
* Connect with me in LinkedIn!
* ----------------------------------------------------------------------------
*/
#include "stm32c0xx.h"
#include <stdint.h> // typedef unsigned int uint...
#include <stdio.h>
//-------Macros------------------------------------------------------------------------------
/*This is the way in which HAL define them, so I took them in the same way*/
#define _IO volatile /* Read/Write permissions */
#define _I volatile const /* Read only */
#define _O volatile /* Write only */
#define GPIO_PIN_SET (1)
#define GPIO_PIN_RESET (0)
//-------GPIOs config------------------------------------------------------------------------
/*
* If you were asking why the values within struct are incrementing
* by 0x04; each value is being configure as
* volatile uint32_t which means that they take 32 bits or 4 bytes
* in memory.
*/
typedef struct {
__IO uint32_t MODER; // 0x00: Mode register
__IO uint32_t OTYPER; // 0x04: Output type register
__IO uint32_t OSPEEDR; // 0x08: Output speed register
__IO uint32_t PUPDR; // 0x0C: Pull-up/pull-down register
__IO uint32_t IDR; // 0x10: Input data register
__IO uint32_t ODR; // 0x14: Output data register
__IO uint32_t BSRR; // 0x18: Bit set/reset register
__IO uint32_t LCKR; // 0x1C: Configuration lock register
__IO uint32_t AFR[2]; // 0x20, 0x24: Alternate function registers (Low/High)
__IO uint32_t BRR; // 0x28: Bit reset register
} GPIO_TypeDef2;
typedef enum {
GPIO_MODE_INPUT2 = 0x00,
GPIO_MODE_OUTPUT2 = 0x01,
GPIO_MODE_ALTERNATE2 = 0x02,
GPIO_MODE_ANALOG2 = 0x03
} GPIO_ModeTypeDef2;
/*
* Read Chapter 2 https://www.st.com/resource/en/reference_manual/rm0490-stm32c0x1-advanced-armbased-64bit-mcus-stmicroelectronics.pdf
* Dereferences ((pointer to a struct type) address)
*/
#define GPIOA ((GPIO_TypeDef2 *) 0x50000000) /*GPIOA base is 0x50000000.*/
#define GPIOC ((GPIO_TypeDef2 *) 0x50000800)
#define GPIOB ((GPIO_TypeDef2 *) 0x50000400)
//----------RCC Config-------------------------------------------------------------------------
/*
* Here I created the structure for RCC and define the base address for it
*/
typedef struct {
__IO uint32_t CR; // Clock control register
__IO uint32_t ICFGR; // Internal clock configuration register
__IO uint32_t CFGR; // Clock configuration register
__IO uint32_t PLLCFGR; // PLL configuration register
__IO uint32_t RESERVED0; // Reserved
__IO uint32_t CIER; // Clock interrupt enable register
__IO uint32_t CIFR; // Clock interrupt flag register
__IO uint32_t CICR; // Clock interrupt clear register
__IO uint32_t IOPRSTR; // IO port reset register
__IO uint32_t AHBRSTR; // AHB peripheral reset register
__IO uint32_t APBRSTR1; // APB peripheral reset register 1
__IO uint32_t IOPENR; // IO port clock enable register
__IO uint32_t AHBENR; // AHB peripheral clock enable register
__IO uint32_t APBENR1; // APB peripheral clock enable register 1
__IO uint32_t CCIPR; // Peripherals independent clock configuration register
__IO uint32_t BDCR; // Backup domain control register
__IO uint32_t CSR; // Control/status register
} RCC_TypeDef2;
#define RCC ((RCC_TypeDef2*)0x40021000)
//------FUNCTION PROTOTYPES------------------------------------------------------
//If you want to organize much better your code, use function prototypes or create header files
void DumbDelay(int delay_number);
//-------------------------------------------------------------------------------
//------------------GPIO HANDLER HELPERS ---------------------------------------
void GPIO_SetMode (GPIO_TypeDef2 *GPIOx,uint8_t GPIO_Pin,GPIO_ModeTypeDef2 Mode) {
/* Reset value */
GPIOx->MODER &= ~( 0b11 <<(GPIO_Pin * 2));
/*Set value*/
GPIOx->MODER |= (Mode << (GPIO_Pin * 2));
}
static inline void GPIO_WritePin (GPIO_TypeDef2 *GPIOx, uint8_t GPIO_Pin,uint8_t set) {
/* Use BRR to reset pin. Avoid reading ODR, and use atomic updates*/
if (set) { // If set is 1
GPIOx->BSRR = (1 << GPIO_Pin); //Places a 1 in the GPIO_Pin desired
} else {
GPIOx->BSRR = (1 << GPIO_Pin) << 16;
}
}
static inline uint8_t GPIO_ReadBool (GPIO_TypeDef2 *GPIOx, uint8_t GPIO_Pin) {
// Here you can write this in one line using conditional (ternary)
return ( (GPIOx->IDR & (1 << GPIO_Pin))) ? 1 : 0;
}
//-------------------------------------------------------------------------------
//-----------INITIAL STM32 CONFIGURATION-----------------------------------------
#define SystemCoreClock 48000000UL
void SysTick_Init(uint32_t ticks) {
// Set the reload register
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
// Set the SysTick priority (optional)
NVIC_SetPriority(SysTick_IRQn, 0);
// Reset the current value register
SysTick->VAL = 0;
// Select processor clock (HCLK) as SysTick clock source
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
void _INIT_CONFIG() {
/*Enabling cloks for peripherals*/
//RCC->IOPENR &= ~(1<<0);
DumbDelay(20);
RCC->IOPENR |= (1<<0); //Put a 1 in the position 0 of IOPENR (Enable GPIOA)
/*Set GPIOS*/
GPIO_SetMode(GPIOA,5,GPIO_MODE_OUTPUT2);
DumbDelay(50);
GPIO_SetMode(GPIOC,6,GPIO_MODE_INPUT2); //Input
GPIOC->PUPDR &= ~(0b11 << (6 * 2)); // Clear
GPIOC->PUPDR |= (0b10 << (6 * 2)); // Set Pull-Up
DumbDelay(50);
GPIO_SetMode(GPIOB,9,GPIO_MODE_OUTPUT2); //PB9 as output
/*SysTick configuration
* SysTick is a 24-bit timer based counter based on documentation
* https://developer.arm.com/documentation/107706/0100/System-exceptions/SysTick/Using-the-SysTick-timer-for-timing-measurement
* VAL has the actual value of the counter, and it decrements,
* when Val gets to zero it has finished counting, and restarts again
*/
SysTick_Init(1000);
}
//---------DELAY FUNCTIONS----------------------------------------------
/*Based on documentation, the interruption is handled
* witht SysTick_Handler function
*/
volatile static uint32_t volatile_uSeconds = 0; // Variables that are handled by interrupts, must use volatiel
void SysTick_Handler(void) { // Function interruption
volatile_uSeconds++;
}
void delay_uSeconds (uint32_t uSeconds) {
uint32_t start = volatile_uSeconds;
while ((volatile_uSeconds - start) < uSeconds);
}
void delay_mSeconds (uint32_t mSeconds) {
delay_uSeconds(mSeconds * 1000);
}
//----------------------------------------------------------------------
//----------MAIN-----------------------------------------------------------
int main(void) {
_INIT_CONFIG(); // Config GPIOS
DumbDelay(60000); // Wait
while(1) {
uint8_t valuePinC6 = GPIO_ReadBool(GPIOC, 6);
/*If you want to test, GPIO input and output, uncomment this code*/
// if (valuePinC6 == 1) {
// GPIO_WritePin(GPIOA, 5, GPIO_PIN_SET); // Turn LED ON
// } else {
// GPIO_WritePin(GPIOA, 5, GPIO_PIN_RESET); // Turn LED OFF
// }
/*If you want to test delays uncomment this code*/
GPIO_WritePin(GPIOB, 9, GPIO_PIN_RESET);
delay_mSeconds(200);
GPIO_WritePin(GPIOB, 9, GPIO_PIN_SET);
delay_mSeconds(200);
}
}
void DumbDelay (int delay_number){
// MAX DELAY COUNT 2^16 - 1 = 65536 - 1
for (volatile uint16_t i = 0; i <delay_number;i++){
__asm__("nop");
__asm__("nop");
}
}