织女星开发板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库函数的实现,我们可以了解到以下几个寄存器的功能:

寄存器名称全称读/写含义
TVALTimer Value Register读/写设置定时器初值
CVALCurrent Timer Value只读获取当前计数值
SETENSet Timer Enable Register读写定时器使能控制
CLRTENClear Timer Enable Register只写清除计数值
MCRModule Control Register读写定时器时钟使能控制
MSRModule 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图片

VEGA_LOGO2.bmp

  • OLED实际显示效果:

VEGA_DRIVER_IIC_OLED.jpg

总结

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

参考资料

历史精选


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

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

%E6%B1%82%E5%85%B3%E6%B3%A8.jpg

转载于:https://www.cnblogs.com/whik/p/11105346.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值