C语言精确延时

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言精确延时相关的知识,希望对你有一定的参考价值。

C语言延时0。5秒的程序怎么写
SLEEP和DELAY的区别在哪?
还想问一下有没有延时的同时不影响程序执行的语句;
谢谢啦
sleep()的单位不是毫秒吧,我在WIN-TC环境下

void delay500ms(void)

unsigned char i,j,k;

for(i=15;i>0;i--) for(j=202;j>0;

j--) for(k=81;k>0;k--);


产生的汇编:
C:0x0800 7F0F MOV R7,#0x0F 7ECA MOV R6,#0xCA

7D51 MOV R5,#0x51

DDFE DJNZ R5,C:0806

DEFA DJNZ R6,C:0804

DFF6 DJNZ R7,C:0802 C:0x080C 22 RET

程序共有三层循环 一层循环n:R5*2 = 81*2 = 162us DJNZ 2us 二层循环m:R6*(n+3) = 202*165 = 33330us DJNZ 2us + R5赋值 1us = 3us 三层循环: R7*(m+3) = 15*33333 = 499995us DJNZ 2us + R6赋值 1us = 3us 循环外: 5us 子程序调用 2us + 子程序返回 2us + R7赋值 1us = 5us 延时总时间 = 三层循环 + 循环外 = 499995+5 = 500000us =500ms 计算公式:延时时间= [(2*R5+3)*R6+3]*R7+5
二. 200ms延时子程序 程序:

void delay200ms(void)

unsigned char i,j,k;

for(i=5;i>0;i--) for(j=132;j>0;j--)

for(k=150;k>0;k--);


产生的汇编

C:0x0800 7F05 MOV R7,#0x05

C:0x0802 7E84 MOV R6,#0x84

C:0x0804 7D96 MOV R5,#0x96 DDFE DJNZ R5,C:0806

DEFA DJNZ R6,C:0804

DFF6 DJNZ R7,C:0802

22 RET

三. 10ms延时子程序 程序:

void delay10ms(void)

unsigned char i,j,k;

for(i=5;i>0;i--)

for(j=4;j>0;j--)

for(k=248;k>0;k--);

产生的汇编 C:0x0800 7F05 MOV R7,#0x05 7E04 MOV R6,#0x04

7DF8 MOV R5,#0xF8

C:0x0806 DDFE DJNZ R5,C:0806 DEFA DJNZ R6,C:0804

DFF6 DJNZ R7,C:0802

C:0x080C 22 RET

四. 1s延时子程序 程序:

void delay1s(void)

unsigned char h,i,j,k;

for(h=5;h>0;h--)

for(i=4;i>0;i--)

for(j=116;j>0;j--)

for(k=214;k>0;k--);

产生的汇编

C:0x0800 7F05 MOV R7,#0x05 7E04 MOV R6,#0x04

7D74 MOV R5,#0x74

7CD6 MOV R4,#0xD6

DCFE DJNZ R4,C:0808

DDFA DJNZ R5,C:0806

DEF6 DJNZ R6,C:0804

DFF2 DJNZ R7,C:0802

22 RET 在精确延时的计算当中,最容易让人忽略的是计算循环外的那部分延时,在对时间要求不高的场合,这部分对程序不会造成影响.
参考技术A delay()是循环等待,该进程还在运行,占用处理器。
sleep()不同,它会被挂起,把处理器让给其他的进程。

sleep()参数指定暂停时间,单位是s
delay()参数指定暂停时间,单位是ms本回答被提问者采纳
参考技术B delay在c语言中不可用
sleep(500)延迟当前线程500毫秒

如果想不影响程序执行
创建线程,在线程中延迟
参考技术C Sleep至少在Windows下是毫秒为单位的,它是一个系统API

WINBASEAPI VOID WINAPI Sleep(DWORD dwMilliseconds );

织女星开发板RISC-V内核实现微秒级精确延时

前言

收到VEGA织女星开发板也有一段时间了,好久没玩了,想驱动个OLED屏,但是首先要实现IIC协议,而实现IIC协议,最基本的就是需要一个精确的延时函数,所以研究了一下如何来写一个精确的延时函数。众所周知,ARM Cortex-M内核都有一个24位的SysTick系统节拍定时器,它是一个简易的周期定时器,用于提供时基,多为操作系统所使用。RV32M1的RISC-V内核也有一个SysTick定时器,只不过它不属于内核,而是使用的一个外部通用定时器,即LPIT0( low power periodic interval timer)定时器的通道0来实现的,我们可以从system_RV32M1_ri5cy.c文件中获得一些信息:


/* Use LIPT0 channel 0 for systick. */
#define SYSTICK_LPIT LPIT0
#define SYSTICK_LPIT_CH 0
#define SYSTICK_LPIT_IRQn LPIT0_IRQn

/* Leverage LPIT0 to provide Systick */
void SystemSetupSystick(uint32_t tickRateHz, uint32_t intPriority)

    /* Init pit module */
    CLOCK_EnableClock(kCLOCK_Lpit0);

    /* Reset the timer channels and registers except the MCR register */
    SYSTICK_LPIT->MCR |= LPIT_MCR_SW_RST_MASK;
    SYSTICK_LPIT->MCR &= ~LPIT_MCR_SW_RST_MASK;

    /* Setup timer operation in debug and doze modes and enable the module */
    SYSTICK_LPIT->MCR = LPIT_MCR_DBG_EN_MASK | LPIT_MCR_DOZE_EN_MASK | LPIT_MCR_M_CEN_MASK;

    /* Set timer period for channel 0 */
    SYSTICK_LPIT->CHANNEL[SYSTICK_LPIT_CH].TVAL = (CLOCK_GetIpFreq(kCLOCK_Lpit0) / tickRateHz) - 1;

    /* Enable timer interrupts for channel 0 */
    SYSTICK_LPIT->MIER |= (1U << SYSTICK_LPIT_CH);

    /* Set interrupt priority. */
    EVENT_SetIRQPriority(SYSTICK_LPIT_IRQn, intPriority);

    /* Enable interrupt at the EVENT unit */
    EnableIRQ(SYSTICK_LPIT_IRQn);

    /* Start channel 0 */
    SYSTICK_LPIT->SETTEN |= (LPIT_SETTEN_SET_T_EN_0_MASK << SYSTICK_LPIT_CH);


void SystemClearSystickFlag(void)

    /* Channel 0. */
    SYSTICK_LPIT->MSR = (1U << SYSTICK_LPIT_CH);

system_RV32M1_ri5cy.h文件中的SysTick中断服务函数:


#define SysTick_Handler LPIT0_IRQHandler

关于LPIT0

LPIT0的每个通道都包含一个32位的计数器,加载计数值之后开始倒数,当倒数到0时,中断标志位被置1,通过向中断标志位写1来清除中断。为了尽量减少执行函数所消耗的时间,delay延时函数的采用了直接操作寄存器的方式来实现。通过阅读RV32M1参考手册【Chapter 50 Low Power Interrupt Timer (LPIT)】章节,和fsl_lpit.h库函数的实现,我们可以了解到以下几个寄存器的功能:

寄存器名称 全称 读/写 含义
TVAL Timer Value Register 读/写 设置定时器初值
CVAL Current Timer Value 只读 获取当前计数值
SETEN Set Timer Enable Register 读写 定时器使能控制
CLRTEN Clear Timer Enable Register 只写 清除计数值
MCR Module Control Register 读写 定时器时钟使能控制
MSR Module Status Register 读写 溢出中断标志,写1清除中断

通过上面参考手册相关寄存器的介绍,我们有两种方式来获取定时器是否溢出:

  • 获取当前计数值是否为0,即CVAL寄存器的值
  • 获取寄存器状态是否溢出,即MSR寄存器的值。

这几个寄存器都是32位的,具体每一位的含义,可以查阅RV32M1参考手册

delay.c文件


#include "delay.h"

static uint8_t  fac_us=0;                           //us延时倍乘数
static uint16_t fac_ms=0;                           //ms延时倍乘数,在ucos下,代表每个节拍的ms数

void Delay_Init(void)

    CLOCK_SetIpSrc(kCLOCK_Lpit0, kCLOCK_IpSrcFircAsync);    //设置定时器时钟48MHz
    LOG("LPIT0: %ld \r\n", CLOCK_GetIpFreq(kCLOCK_Lpit0));  //输出LPIT0时钟

    CLOCK_EnableClock(kCLOCK_Lpit0);    //使能时钟
    LPIT_Reset(LPIT0);                  //复位定时器
    LPIT0->MCR = LPIT_MCR_M_CEN_MASK;   //使能定时器

    fac_us = CLOCK_GetIpFreq(kCLOCK_Lpit0)/1000000;
    fac_ms = fac_us*1000;

//基于SysTick即LPIT0实现的延时微秒函数
void Delay_us(uint32_t Nus)

    LPIT0->CHANNEL[kLPIT_Chnl_0].TVAL =  48 * Nus - 1;                  //加载时间
    LPIT0->SETTEN |= (LPIT_SETTEN_SET_T_EN_0_MASK << kLPIT_Chnl_0);     //启动定时器
    while(LPIT0->CHANNEL[kLPIT_Chnl_0].CVAL);                           //等待计数值到0
//  while((LPIT0->MSR & 0x0001) != 0x01);                               //等待溢出
//  LPIT0->MSR |= (1U << kLPIT_Chnl_0);                                 //写1,清除中断
    LPIT0->CLRTEN |= (LPIT_CLRTEN_CLR_T_EN_0_MASK << kLPIT_Chnl_0);     //清除计数器


//基于SysTick即LPIT0实现的延时毫秒函数
void Delay_ms(uint32_t Nms)

    LPIT0->CHANNEL[kLPIT_Chnl_0].TVAL = Nms * fac_ms  - 1;          //加载时间
    LPIT0->SETTEN |= (LPIT_SETTEN_SET_T_EN_0_MASK << kLPIT_Chnl_0); //启动定时器
    while(LPIT0->CHANNEL[kLPIT_Chnl_0].CVAL);                       //等待计数到0
//  while((LPIT0->MSR & 0x0001) != 0x0001);                         //等待产生中断
//  LPIT0->MSR |= (1U << kLPIT_Chnl_0);                             //向中断标志位写1,清除中断
    LPIT0->CLRTEN |= (LPIT_CLRTEN_CLR_T_EN_0_MASK << kLPIT_Chnl_0); //清除计数器

delay.h文件


#ifndef __DELAY_H__
#define __DELAY_H__

#include "fsl_lpit.h"
#include "fsl_lpit.h"
#include "fsl_debug_console.h"
#include "sys.h"

void Delay_Init(void);      //SysTick定时器,即LPIT0,时钟可设置
void Delay_ms(uint32_t Nms);
void Delay_us(uint32_t Nus);

#endif

实际验证


...

#include "delay.h"
...

int main(void)

    ...
    Delay_Init();
    ...
    while(1)
    
        GPIOA->PTOR = 1 << 24;  //寄存器方式操作,减小误差
        Delay_ms(100);  //延时微秒函数验证
//      Delay_us(5);    //延时微秒函数验证              
    


通过实际示波器测试,发现Delay_us微秒级延时函数,无论延时多少时间都有2us左右的误差,不知道是这为什么,不过实现IIC协议驱动OLED屏并没有什么影响。

驱动IIC接口OLED

  • 社区首页的LOGO图片

技术图片

  • OLED实际显示效果:

技术图片

总结

既然精确延时函数实现了,那么各种协议的传感器、显示模块、通信模块的驱动都可以轻松实现了,希望分享的本篇帖子能给社区的朋友一些帮助,也希望大家能多多发帖,互相交流学习。

参考资料

历史精选


欢迎关注我的个人博客www.wangchaochao.top

或微信扫码关注我的公众号

技术图片

以上是关于C语言精确延时的主要内容,如果未能解决你的问题,请参考以下文章

非常精确的C语言延时子程序表

相比于汇编语言的准确性c语言延时精确度如何提升

C语言delay函数延时计算

单片机求一用C编写的延时1小时的子程序,要精确到秒级别

如何让C语言sleep()函数等待时间小于1秒

求一个C语言的微秒级延时函数