STM32 利用SysTick实现高精度计时

STM32 HAL库利用ARM Cortex-M内核自带的24位递减计数器SysTick(系统节拍),它属于 NVIC的一部分,且可以产生 SysTick 异常(异常类型#15)。通过读取并判断计数值来实现精确延时,从0xFFFFFF向下计数到0。可以用作I2C、SPI通信中的时序控制,RTOS环境中作为心跳时钟。

目录

[一、微秒级延时函数 udelay](#一、微秒级延时函数 udelay)

[二、毫秒级延时函数 mdelay](#二、毫秒级延时函数 mdelay)

三、获取系统时间函数

四、函数调用


一、微秒级延时函数 udelay

udelay 函数用于实现微秒级的延时。它利用了 STM32 的 SysTick 定时器,这是一个 24 位的递减计数器。函数首先记录 SysTick 定时器当前的计数值 told 作为计时起点,并获取 SysTick 的重装载值 load。根据 LOAD + 1 个时钟对应 1ms 的关系,计算出输入的微秒数 us 对应的时钟滴答数 ticks。在一个无限循环中,不断获取当前的计数值 tnow,并根据计数值是否溢出计算已经过去的时钟滴答数 cnt。当 cnt 达到或超过 ticks 时,退出循环,延时结束。

cpp 复制代码
/**********************************************************************
 * 函数名称: udelay
 * 功能描述: us 级别的延时函数
 * 输入参数: us - 延时多少 us
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 ***********************************************************************/
void udelay(int us)
{

  // 记录 SysTick 定时器当前的计数值,作为计时起点
    uint32_t told = SysTick->VAL;
    uint32_t tnow;

    // 获取 SysTick 定时器的重装载值,用于后续计算
    uint32_t load = SysTick->LOAD;

    /* LOAD+1 个时钟对应 1ms
     * n us 对应 n*(load+1)/1000 个时钟
     */
    // 根据输入的 us 数,计算需要的时钟滴答数
    uint32_t ticks = us*(load+1)/1000;

    // 用于累计已经过去的时钟滴答数
    uint32_t cnt = 0;

    // 进入循环,开始计时
    while (1)
    {
        // 获取 SysTick 定时器当前的计数值
        tnow = SysTick->VAL;
        if (told >= tnow)
            // 如果计数值没有溢出,直接计算两次计数值的差值并累加到 cnt 中
            cnt += told - tnow;
        else
            // 如果计数值溢出了,需要加上重装载值来正确计算经过的滴答数
            cnt += told + load + 1 - tnow;

        // 更新计时起点为当前计数值
        told = tnow;
        if (cnt >= ticks)
            // 当累计的滴答数达到需要的滴答数时,退出循环,延时结束
            break;
    }	
}

二、毫秒级延时函数 mdelay

mdelay 函数用于实现毫秒级的延时。它通过循环调用 udelay 函数,每次延时 1000 微秒(即 1 毫秒),从而实现指定毫秒数的延时。这种设计利用了已实现的微秒级延时函数,提高了代码的复用性。

cpp 复制代码
/**********************************************************************
 * 函数名称: mdelay
 * 功能描述: ms 级别的延时函数
 * 输入参数: ms - 延时多少 ms
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 ***********************************************************************/
void mdelay(int ms)
{
    // 通过循环多次调用 udelay 函数,每次延时 1000us(即 1ms),实现 ms 级别的延时
    for (int i = 0; i < ms; i++)
        udelay(1000);
}

三、获取系统时间函数

system_get_ns 函数用于获取系统时间,单位为纳秒。它首先获取 SysTick 定时器的重装载值 reload 和系统启动后经过的滴答数(单位为毫秒)ns。然后获取 SysTick 定时器当前的计数值 cnt,将 ns 转换为纳秒,并计算 SysTick 定时器从上次重装载后到现在经过的时间(单位为纳秒),累加到 ns 中,最后返回系统当前的时间。

cpp 复制代码
/**********************************************************************
 * 函数名称: system_get_ns
 * 功能描述: 获得系统时间(单位 ns)
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 系统时间(单位 ns)
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------*/

uint64_t system_get_ns(void)
{
    // 用于存储 SysTick 定时器当前计数值和计算过程中的中间结果
    uint64_t cnt;
    // 获取 SysTick 定时器的重装载值
    uint32_t reload = SysTick->LOAD;
    // 获取系统启动后经过的滴答数,单位是 ms
    uint64_t ns = HAL_GetTick();

    // 获取 SysTick 定时器当前的计数值
    cnt = SysTick->VAL;

    // 将系统启动后经过的时间从 ms 转换为 ns
    ns *= 1000000;
    // 计算 SysTick 定时器从上次重装载后到现在经过的时间(单位 ns),并累加到 ns 中
    ns += (reload - cnt) * 1000000 / reload;
    // 返回系统当前的时间,单位为 ns
    return ns;
}

四、函数调用

在主循环中,代码实现了一个周期性的操作。首先,将 GPIOE 引脚 5 置高电平,点亮连接在该引脚上的设备。然后记录开始时间 start_time,调用 mdelay 函数延时 500 毫秒,再记录结束时间 end_time。通过计算 end_timestart_time 的差值,得到 mdelay(500) 函数执行所花费的时间 elapsed_time,并使用 printf 函数将其打印输出。最后,将 GPIOE 引脚 5 置低电平,熄灭连接的设备,再次延时 500 毫秒,完成一个周期的操作。

cpp 复制代码
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
		// 点亮 GPIOE 引脚 5 连接的设备
		HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);
		// 记录开始时间
		start_time = system_get_ns();
		// 延时 500 毫秒
		mdelay(500);
		// 记录结束时间
		end_time = system_get_ns();
		// 计算从计时起始点到结束点所经过的时间,即代码在 mdelay(500) 执行期间所花费的时间
    // 将结果存储在 elapsed_time 变量中,单位为纳秒
		//elapsed_time = end_time - start_time;
		// 打印延时耗时
		printf("elapsed_time: %"PRIu64"\r\n", elapsed_time);
		// 熄灭 GPIOE 引脚 5 连接的设备
		HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
		// 再延时 500 毫秒
		mdelay(500);
 
  }

五、运行结果

调用500ms延时测的时间是约500337532 ns

综上所述,这段代码通过合理利用 STM32 的 SysTick 定时器,实现了微秒和毫秒级的延时,以及系统时间的获取和代码执行时间的测量,控制 LED 闪烁。

相关推荐
kaikaile19952 小时前
GY-BMP280压强传感器完整工程stm32控制
stm32·单片机·嵌入式硬件
GodKK老神灭3 小时前
STM32 定时器(互补输出+刹车)
stm32·单片机·嵌入式硬件
lingzhilab6 小时前
零知开源——基于STM32F407VET6实现ULN2003AN驱动28BYJ-48步进电机控制系统
stm32·单片机·嵌入式硬件
JasmineX-17 小时前
直流电机驱动与TB6612
c语言·stm32·单片机·嵌入式硬件
hahaha601618 小时前
模拟电路中什么时候适合使用电流传递信号,什么时候合适使用电压传递信号
stm32·单片机·嵌入式硬件
小小少年12319 小时前
基于蓝牙的stm32智能火灾烟雾报警系统设计
stm32·单片机·嵌入式硬件
点灯小铭21 小时前
基于51单片机红外避障车辆高速汽车测速仪表设计
单片机·嵌入式硬件·汽车·毕业设计·51单片机·课程设计
猫猫的小茶馆1 天前
【STM32】将 FreeRTOS移植到STM32F103RCT6 详细流程
stm32·单片机·嵌入式硬件·mcu·智能硬件
智驾1 天前
MCU平台化实践方案
单片机·嵌入式硬件·mcu·嵌入式
日更嵌入式的打工仔1 天前
uC/OS-III 队列相关接口
单片机