...
This time, we'll dedicate a hardware timer entirely to toggling the LED and forego the ability to run on x86. We'll also hardcode everything.
Code Block | ||||
---|---|---|---|---|
| ||||
#include "stm32f0xx.h" static void prv_init_gpio(void) { // Enable GPIO clock - RM0091 6.4.6 RCC->AHBENR |= RCC_AHBENR_GPIOCEN; // Set up GPIO as output - RM0091 8.3.10 // Be sure to understand where these values are from and how they're calculated! // Understanding registers and how to read a datasheet is critical for debugging. // Set state first so we don't have a period where we're unsure of the output value // Use atomic bit set/reset register to reset the pin's output - RM0091 8.4.7 // Prefer using BSRR over ODR - ODR requires read-modify-write GPIOC->BSRR = GPIO_BSRR_BR_9; // Mode: Output (0b01) - RM0091 8.4.1 GPIOC->MODER &= GPIO_MODER_MODER9; GPIOC->MODER |= GPIO_MODER_MODER9_0; // Type: Push-pull (0b0) - RM0091 8.4.2 GPIOC->OTYPER &= GPIO_OTYPER_OT_9; // Speed: Low (0b00) - RM0091 8.4.3 GPIOC->OSPEEDR &= GPIO_OSPEEDER_OSPEEDR9; // Pull-up/pull-down: None (0b00) - RM0091 8.4.4 GPIOC->PUPDR &= GPIO_PUPDR_PUPDR9; } static void prv_init_timer(void) { // Enable TIM2 clock - RM0091 6.4.5 // Look at the clock tree to determine what clock peripherals are attached to (Fig 10, 11) RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // CR1: keep disabled (0b0), no clock division (0b0), up counter (0b0) - RM0091 18.4.2 // See also 18.3.2 TIM2->CR1 = 0; // Clock prescaler: Divider = PSC + 1 - RM0091 18.4.11 // Assume TIM2 is connected to a 48MHz clock, divide by 48 to get a 1MHz clock TIM2->PSC = 48 - 1; // Auto reload: 0 to 999999 = 1000000 ticks - RM0091 18.4.12 TIM2->ARR = 1000000 - 1; // Counter: reset to 0 - RM0091 18.4.10 TIM2->CNT = 0; // Force an update event immediately - RM0091 18.4.6 TIM2->EGR = TIM_EGR_UG; // Clear interrupt flag: Update (0b0) - RM0091 18.4.5 // We clear the flag first so it isn't triggered by accident when we enable the interrupt source TIM2->SR &= ~TIM_SR_UIF; // Enable SYSCFG clock RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // Set NVIC priority to highest NVIC_EnableIRQ(TIM2_IRQn); NVIC_SetPriority(TIM2_IRQn, 0); // Enable TIM2 TIM2->CR1 |= TIM_CR1_CEN; // Enable interrupt source: Update enabled (0b1) - RM0091 18.4.4 // We enable it after enabling the timer clock to make sure we don't get an update event // from the forced update TIM2->DIER |= TIM_DIER_UIE; } int main(void) { prv_init_gpio(); prv_init_timer(); while (1) { __WFI(); } return 0; } void TIM2_IRQHandler(void) { // TODO return 0; } Toggle ODR value - Could also store a static variable or use BSRR GPIOC->ODR ^= GPIO_ODR_9; // Clear interrupt flags TIM2->SR = 0; } |
Try to understand how this works! It's always useful to understand roughly how our HAL works. It also gives you an appreciation for how easy our HAL makes using these peripherals!
If you're working with other ICs, you'll likely need to read datasheets to understand how to communicate with them and what the chips are capable of. The STM32F0 reference manual is just a very long, very complex example for a very powerful chip. Personally, I find this part to be the most fulfilling and the most useful, especially when it comes to figuring out why things aren't working the way they should.
I'd recommend playing around with this example and comparing it to our HAL implementations. For example, try making it easy to change the LED or adjust the timing! Maybe add another LED or play with the timer peripheral modes.