先给出头文件

#ifndef _DELAY_H_
#define _DELAY_H_

#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"

/*自定义延时函数宏开关*/
#define SUPPORT_FreeRTOS 0

void Delay_Init(void);//SysTick延时函数初始化(1ms)
void Delay_ms(uint32_t ms);//毫秒级延时函数
uint32_t GetTick(void);//获取系统的当前时间(ms)
uint64_t GetUs(void);//获取系统的当前时间(us)
void Delay_us(uint32_t us);//微秒级延时函数

#endif

接下来是源文件

#include "delay.h"

__IO uint32_t ulTicks;
extern void xPortSysTickHandler(void);
static uint8_t delay_initialized_flag = 0;
static float us_per_mini_tick;


/*初始化1ms的SysTick延时函数*/
void Delay_Init(void)
{
	if(!delay_initialized_flag)//防止重复初始化
	{
		delay_initialized_flag = 1;
		
		RCC_ClocksTypeDef clockinfo = {0};//初始化时钟参数结构体
		uint32_t tmp;
		
		SysTick->CTRL &= ~SysTick_CTRL_ENABLE;//关闭SysTick

		ulTicks = 0;

		RCC_GetClocksFreq(&clockinfo);//根据实际情况自动填充时钟参数结构体

		SysTick->CTRL |= SysTick_CTRL_TICKINT;//使能SysTick中断

		SCB->SHP[7] = 0;//将SysTick中断优先级设置为0,即最高优先级

		// 设置自动重装值以保证1ms的时钟
		tmp =  clockinfo.HCLK_Frequency/1000;//将时钟频率的时间单位从s转化为ms
		if(tmp > 0x00ffffff)//即systick溢出也计不到1ms
		{
			tmp = tmp / 8;
			SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE;//时钟源频率过高,则8分频
		}
		else
		{
			SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;//时钟源频率合适,则不分频
		}
        
		SysTick->LOAD = tmp - 1;//1ms延时对应的计数值

		SysTick->CTRL |= SysTick_CTRL_ENABLE;//开启SysTick
		
		us_per_mini_tick = 1000.0/((SysTick->LOAD & 0x00ffffff) + 1);//计算SysTick每计一个数需要的us数
	}
}

/*毫秒级延时*/
void Delay_ms(uint32_t Delay)
{
    #if SUPPORT_FreeRTOS
        uint64_t expiredTime = xTaskGetTickCount() + Delay;
        while(xTaskGetTickCount() <  expiredTime){}
    #else
        uint64_t expiredTime = ulTicks + Delay;
        while(ulTicks <  expiredTime){}
    #endif
}

/*获取当前毫秒级时间*/
uint32_t GetTick(void)
{
    #if SUPPORT_FreeRTOS
        return xTaskGetTickCount();
    #else
        return ulTicks;
    #endif
}

/*获取当前微秒级时间*/
uint64_t GetUs(void)
{
    #if SUPPORT_FreeRTOS
        uint64_t temp;
        uint64_t tick;
        uint32_t mini_tick;
        us_per_mini_tick = 1000.0/((SysTick->LOAD & 0x00ffffff) + 1);

        __disable_irq();//关闭中断
        
        while(1)
        {
            temp = xTaskGetTickCount();//读取毫秒值
            mini_tick = SysTick->VAL;//读取SysTick当前计数值寄存器的值
            /*
            这里要解决的是读取ulTicks和读取SysTick->VAL之间发生两种情况
            1.被其他中断打断,导致二者不匹配,但是没有发生溢出;
                ---方法:关中断
            2.没有被其他中断打断,但是两句代码恰好卡在SysTick溢出点前后;
                ---方法:判断是否发生中断,如果发生则更新temp值并重新计算当前微秒*/
            if(SysTick->CTRL & SysTick_CTRL_COUNTFLAG)
            {
                temp++;
            }
            else
            {
                break;
            }
        }
        
        __enable_irq();//开启中断
        
        // 换算成微秒
        tick = temp;
        tick *= 1000; // 毫秒部分乘以1000
        tick += (uint32_t)((SysTick->LOAD - mini_tick) * us_per_mini_tick);//小数部分折算成微秒
        
        return tick;
    #else
        uint64_t tick;
        uint32_t mini_tick;

        __disable_irq();//关闭中断
        
        while(1)
        {
            tick = ulTicks;//读取毫秒值
            mini_tick = SysTick->VAL;//读取SysTick当前计数值寄存器的值
            /*
            这里要解决的是读取ulTicks和读取SysTick->VAL之间发生两种情况
            1.被其他中断打断,导致二者不匹配,但是没有发生溢出;
                ---方法:关中断
            2.没有被其他中断打断,但是两句代码恰好卡在SysTick溢出点前后;
                ---方法:判断是否发生中断,如果发生则更新ulTicks值并重新计算当前微秒*/
            if(SysTick->CTRL & SysTick_CTRL_COUNTFLAG)
            {
                ulTicks++;
            }
            else
            {
                break;
            }
        }
        
        __enable_irq();//开启中断
        
        // 换算成微秒
        tick *= 1000; // 毫秒部分乘以1000
        tick += (uint32_t)((SysTick->LOAD - mini_tick) * us_per_mini_tick);//小数部分折算成微秒
        
        return tick;
    #endif
}

/*微秒级延时*/
void Delay_us(uint32_t us)
{
	//当前微秒的计算过程可能存在精度损失,因此需要+1确保足够的延时时间
	uint64_t expiredTime = GetUs() + us + 1;
	
	while(GetUs() < expiredTime);
}

/*SysTick中断函数*/
void SysTick_Handler(void)
{
    #if SUPPORT_FreeRTOS
        if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
        {
            xPortSysTickHandler();
        }
    #else
        if(SysTick->CTRL & SysTick_CTRL_COUNTFLAG)
        {
            ulTicks++;//记录从程序启动开始的累积运行时间,单位ms
        }
    #endif
}

程序思路

这里的delay函数同样是使用systick系统滴答定时器,当在RTOS中使用时,需要调用xTaskGetTickCount()函数获取当前时间片,并不能直接读取xTickCount,因为这是静态变量外部无法直接读取;如果在裸机环境下使用,直接在systick中断服务函数中实现全局变量ulTicks++即可;
关于us延时函数需要注意的点已经写在注释里了;

Tip

#define xPortPendSVHandler                              PendSV_Handler
#define vPortSVCHandler                                 SVC_Handler

RTOSConfig文件在移植后需要增补中断服务函数的宏定义,在正点原子的版本中只定义了上面两个,但是在我找的标准库版本RTOS移植例程中还定义了下面这个systick系统滴答定时器中断;

#define xPortSysTickHandler                                 SysTick_Handler

经过分析,原因是:这种函数名的宏的目的是为了两个函数名共用同一个函数体,以上定义会让中断向量表中的中断服务函数直接使用RTOS中定义的函数体,这样就免去了编写提到的中断服务函数的函数体的问题;但是我的和正点原子的代码里SysTick_Handler并没有直接使用RTOS中xPortSysTickHandler的函数体,因此不能使用这个宏定义;

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐