jonah-saltzman / bare-metal-stopwatch

Bare-metal interrupt-driven stopwatch on STM32F439ZI

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bare-metal STM32 Project 1: Stopwatch

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

stopwatch stopwatch in action

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.

YouTube link

Counting time

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.

Displaying current time

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();
	}
}

About

Bare-metal interrupt-driven stopwatch on STM32F439ZI


Languages

Language:C 98.8%Language:Assembly 1.2%