当前位置: 首页 > news >正文

STM32中微秒延时的实现方式

STM32中微秒延时的实现方式

  • 0.前言
  • 一、裸机实现方式
  • 二、FreeRTOS实现方式
  • 三、定时器实现(通用)
  • 4、总结


0.前言

  最近在STM32驱动移植过程中需要用到微秒延时来实现一些外设的时序,由于网上找到的驱动方法良莠不齐,笔者在实现时序过程中也浪费了不少时间。这里就将笔者觉得比较好的几种方式记录一下,方便后续使用,也可以作为参考。

一、裸机实现方式

  在STM32的裸机程序中,实现微秒延时比较简单,通过SysTick计时即可。关于SysTick的相关知识可以站内搜索,这里就不再过多赘述了,相关的delay_us函数参考了正点原子例程中的实现方式:

// 注意:nus的值,不要大于798915us(最大值即2^24/fac_us {fac_us=21})
void delay_us(uint32_t nus)
{
    uint32_t temp;
    SysTick->LOAD = nus * fac_us; // 时间加载,fac_us与时钟频率有关,例如:168Mhz时
                                  // fac_us=168,168/168M=1us
    SysTick->VAL = 0x00;                      // 清空计数器
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 开始倒数
    do
    {
        temp = SysTick->CTRL; // 读取控制及状态寄存器的值

    } while ((temp & 0x01) && !(temp & (1 << 16))); // 等待时间到达,使能且位16为0(未计到0)
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;      // 关闭计数器
    SysTick->VAL = 0X00;                            // 清空计数器
}

步骤:
1、根据延时时间和定时器所选时钟频率,计算出定时器要计数的时间数值;
2、将该数值加载到重装载寄存器中;
3、将当前值寄存器清零,打开定时器开始计数;
4、等待控制及状态寄存器的位16变为1;
5、关闭定时器,退出。

二、FreeRTOS实现方式

在FreeRTOS中,SysTick定时器的重装载值是固定的,且定时器是一直在工作的。因此像裸机编程中将时间数值加载到重装载寄存器的方法不再适用。经过多方的查找和修改,最终使用的以下的实现方式:

#include "main.h"
#include "FreeRTOSConfig.h"

extern void vPortSetupTimerInterrupt( void );

void delay_us(uint32_t nus)
{
    uint32_t ticks;
    uint32_t told, tnow, reload, tcnt = 0;
    if ((0x0001 & (SysTick->CTRL)) == 0) { // 定时器未工作
        vPortSetupTimerInterrupt();      // 初始化定时器
    }

    reload = SysTick->LOAD;                    // 获取重装载寄存器值
    ticks = nus * (SystemCoreClock / 1000000); // 计数时间值
    told = SysTick->VAL;                       // 获取当前数值寄存器值(开始时数值)

    while (1)
    {
        tnow = SysTick->VAL; // 获取当前数值寄存器值
        if (tnow != told)    // 当前值不等于开始值说明已在计数
        {
            if (tnow < told) {        // 当前值小于开始数值,说明未计到0
                tcnt += told - tnow; // 计数值=开始值-当前值
            } else {                              // 当前值大于开始数值,说明已计到0并重新计数
                tcnt += reload - tnow + told; // 计数值=重装载值-当前值+开始值  (已从开始值计到0)
            }

            told = tnow; // 更新开始值
            if (tcnt >= ticks) {
                break; // 时间超过/等于要延迟的时间,则退出.
            }
        }
    }
}

// SystemCoreClock为系统时钟(system_stmf1xx.c中),通常选择该时钟作为systick定时器时钟,根据具体情况更改

步骤:
1、根据延时时间和定时器所选时钟频率,计算出定时器要计数的时间数值;
2、获取当前数值寄存器的数值;
3、以当前数值为基准开始计数;
4、当所计数值等于(大于)需要延时的时间数值时退出。
注:由于笔者使用STM32CubeIDE中通过CMSIS封装后的FreeRTOS API,所以需要额外extern引入与初始化定时器相关的API。如果使用原生FreeRTOS API,则需要根据情况引入。

三、定时器实现(通用)

  这种方式的优点就是简单且易用,直接使用一个闲置定时器分频后即可。在CubeMX中配制定时器,笔者选择基本定时器 TIM7,预分频PSC设置为(TIM总线时钟 - 1),分频后频率为1MHz,即每次计数累加1/1000000秒 = 1us,计数区间65535,单次定时最长65535微秒,计数模式为up 向上计数,是否自动重新加载选disable,一次计数满后不再重新计数。
在这里插入图片描述

void  delay_us(uint16_t nus)
{
	__HAL_TIM_SetCounter(&htim7,0);
	__HAL_TIM_ENABLE(&htim7);
	
	while(__HAL_TIM_GetCounter(&htim7)<nus);
	
	__HAL_TIM_DISABLE(&htim7);
}

步骤:
1、首先对定时器中的计数器进行置0;
2、使能定时器;
3、循环等待;
4、关闭定时器退出;

4、总结

  由于微秒延时间隔较短,所以在FreeRTOS中使用时最好避免内核对该线程进行调度,否则调度到其他线程可能要等到下一个时间片才能恢复运行。笔者对这部分还没有仔细了解,如有条件最好还是对相关线程添加关闭和恢复线程调度的操作。

相关文章:

  • 如何使用ps制作ico图标文件
  • Day39 QTableWidget类的使用
  • 基于EPICS stream模块的直流电源的IOC控制程序实例
  • Vue使用epubjs电子书
  • docker部署elk
  • Node.js 中解析 HTML 的方法介绍
  • 月报总结|Moonbeam 10月份大事一览
  • 网际控制报文协议ICMP
  • Spring Boot整合Swagger
  • 【SOC基础】单片机学习案例汇总 Part2:蜂鸣器、数码管显示
  • 【C++心愿便利店】No.11---C++之string语法指南
  • Vscode LinuxC++环境配置
  • ElasticSearch集群架构实战及其原理剖析
  • 好物周刊#29:项目管理软件
  • 概念解析 | Retinex理论
  • 玩了一下 Jenkins,最新版本 + JDK11
  • 项目实战:分页功能实战
  • Java 正则表达式数字篇
  • 1047. 删除字符串中的所有相邻重复项
  • 线性代数 第二章 矩阵
  • 中方是否支持或参加俄乌谈判?外交部:支持一切有利于和平的努力
  • 【社论】进一步拧紧过紧日子的制度螺栓
  • 推开“房间”的门:一部“生命存在的舞台” 史
  • 当“小铁人”遇上青浦,看00后如何玩转长三角铁三
  • 光明日报社副总编辑薄洁萍调任求是杂志社副总编辑
  • 菲律宾选举委员会公布中期选举结果,马科斯阵营选情未达预期