Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Generating Phase-Shifted GPIO Pulses Using STM32 Timer Interrupts

Notes May 8 3

Cross-File Variable Sharing via UART

To dynamically control the pulse width of GPIOA, pulse width of GPIOB, and the interval between them via serial commands, declare the configuration variables in the UART source file:

int pulseWidthA = 20;
int pulseWidthB = 20;
int pulseGap = 20;

Access these variables in other modules, such as the GPIO driver, by using the extern keyword:

extern int pulseWidthA;
extern int pulseWidthB;
extern int pulseGap;

Within the UART receive callback, parse the incoming buffer to update these values. Combining stdlib.h and atoi() allows extracting integers, including negative values, from the buffer payload. By offsetting the buffer pointer, specific command payloads can be isolated.

if (rxBuf[0] == 'P' && rxBuf[1] == 'I' && rxBuf[2] == '=') {
    int val = atoi(rxBuf + 3);
    pulseGap = val;
    printf("OK_GAP");
}

Interrupt Priority Configuration

If HAL_Delay() is invoked inside an interrupt service routine (ISR), the SysTick interrupt must be assigned the highest priority (0). However, HAL_Delay() is blocking and lacks high precision, often resulting in slightly longer delays than requested. For strict timing requirements, hardware timers are superior.

When an external interrupt triggers a timer to manage GPIO states, the timer's priority must be higher (lower numeric value) than the external interrupt. Otherwise, the timer ISR cannot preempt the external ISR, causing the system to stall. Additionally, timers used purely for polling delays do not require their interrupt lines to be enabled.

External Interrupt Setup

Configure the GPIO pin for the triggering button by selecting the appropriate trigger edge (rising or falling) and internal pull-up/pull-down resistors based on the hardware design. Assigning a user label to the pin in the configuration tool generates a macro in the header file, improving code readability.

Hardware Timer Interrupts

When initializing the timer, the period parameter can often be left at its default if it will be dynamically modified during runtime. Upon timer expiration, the HAL executes the HAL_TIM_PeriodElapsedCallback.

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim == &htimPulseA) {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
        HAL_TIM_Base_Stop_IT(&htimPulseA);
    } else if (htim == &htimPulseB) {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
        HAL_TIM_Base_Stop_IT(&htimPulseB);
    }
}

Prior to starting the timer with HAL_TIM_Base_Start_IT, clear any pending update flags using __HAL_TIM_CLEAR_IT. Failing to do so may cause the callback to execute immediately upon starting the timer, skipping the desired delay.

Prescaler and Period Calculations

The prescaler register divides the incoming APB timer clock. With a 72 MHz clock and a prescaler of 7199, the timer increments at 10 KHz (0.1 ms per tick). A 10 ms delay requires 100 ticks. This relationship allows for precise polling-based delays without using interrupts:

void custom_delay_ms(uint16_t ms) {
    __HAL_TIM_SetCounter(&htimDelay, 0);
    __HAL_TIM_ENABLE(&htimDelay);
    while (__HAL_TIM_GetCounter(&htimDelay) < (10 * ms));
    __HAL_TIM_DISABLE(&htimDelay);
}

Given the 16-bit counter limit, the maximum achievable delay with this 10 KHz configuration is roughly 6553 ms.

Non-Blocking Phase-Shifted Pulse Generation

Initially, generating phase-shifted pulses using blocking delays inside an ISR prevents achieving overlapping or reverse-phase relationships. Pulse A will always complete before Pulse B starts.

To resolve this, delegate the pulse termination to timer interrupts. The external interrupt only handles setting the GPIO pins high and starting the respective one-shot timers. For instance, generating Pulse B involves setting the pin and starting the timer:

void triggerPulseB(uint32_t ms) {
    MX_TIM_PulseB_Init(ms * 10); // Reinitialize period
    __HAL_TIM_CLEAR_IT(&htimPulseB, TIM_IT_UPDATE);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
    HAL_TIM_Base_Start_IT(&htimPulseB);
}

The timer ISR then automatically pulls the pin low after the specified duration. This non-blocking architecture allows the external interrupt callback to sequence the rising edges in any order based on the interval parameter, while the hardware timers independently manage the falling edges.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.