In these projects, I implement common or interesting functions using
an STM32F439ZI MCU on a Nucleo-144 board, from the ground up. I do not
use any Hardware Abstraction Layer or pre-written drivers. I do use
header files containing register addresses and macros that execute
inline assembly that cannot be generated by GCC, such as for disabling
interrupts. And, depending on the project I use elements of the Newlib
standard library, for example for its string formatting features. I also
use an auto-generated linker script and startup assembly file which
zero-fills the bss
segment and which I modified to call my
initialization procedure.
My hand-rolled board initialization procedure can be found in /Src/init.c
Stopwatch starts counting time when the user presses the button, with a resolution of 1/100th of a second, and displays the current time on a 4-digit display. The stopwatch can be stopped and started again with the button, or reset with the reset button.
All of Stopwatch is interrupt-driven. The button interrupt handler is responsible
for starting and stopping the timer counting seconds, TIM5
, which is set to a
frequency of 100Hz.
// interrupts.c
extern volatile uint16_t centi_seconds;
void EXTI15_10_IRQHandler(void)
{
EXTI->PR |= EXTI_PR_PR13;
if (timer5_counting)
{
stop_timer(TIM5);
}
else
{
start_timer(TIM5);
}
}
void TIM5_IRQHandler(void)
{
TIM5->SR &= (~1UL);
centi_seconds++;
}
Timer helper functions in /Src/timers.c start and stop the requested timer.
main
starts TIM2
with a frequency of 1200Hz. The 4-digit display used in
this project can only display one unique digit at a time (or multiple identical
digits simultaneously). So in order to display arbitrary 4-digit numbers, Stopwatch
renders one digit for 1/1200th of a second before continuing to display the next
digit, and so on. Thus the entire 4 digits are displayed once every 1/300th of
a second, creating the appearance of all digits being displayed all the time.
// interrupts.c
void TIM2_IRQHandler(void)
{
TIM2->SR &= (~1UL);
render_display();
}
// display.c
static uint8_t current_digit = 0;
static uint16_t temp_number = 0;
static uint16_t digits[4] = {
FIRST_DIGIT,
SECOND_DIGIT,
THIRD_DIGIT,
FOURTH_DIGIT
};
static uint16_t digit_symbols[10] = {
DIGIT_ZERO,
DIGIT_ONE,
DIGIT_TWO,
DIGIT_THREE,
DIGIT_FOUR,
DIGIT_FIVE,
DIGIT_SIX,
DIGIT_SEVEN,
DIGIT_EIGHT,
DIGIT_NINE
};
static void display_digit(uint16_t val, uint16_t decimal_mask)
{
GPIOE->ODR = digit_symbols[val] | (DECIMAL_POINT & decimal_mask);
}
void render_display()
{
if (current_digit == 0) // finish rendering the current number before updating it
temp_number = centi_seconds;
uint16_t digit = temp_number % 10;
temp_number /= 10;
GPIOA->ODR = digits[3 - current_digit]; // select digit to display
display_digit(digit, current_digit == 2 ? 0xFFFFU : 0x0U); // place decimal on 2nd digit
current_digit = (current_digit + 1) & 0x3U;
}
main
does nothing except initialize the required peripherals:
int main(void)
{
// timer2: render timer
// 1200Hz / 4 digits = 300Hz overall
initialize_TIM2_5_stopwatch(TIM2, 1200, TIM2_IRQ_PRIO);
// timer5: 100Hz
initialize_TIM2_5_stopwatch(TIM5, 100, TIM5_IRQ_PRIO);
// gpio pins for display & buttons
initialize_IO();
// button interrupt
enable_ext_intr(50);
// start rendering display
start_timer(TIM2);
while (1)
{
__WFI();
}
}