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

Home 论坛 开发和更新问题 织女星开发板RISC-V内核实现微秒级精确延时

标签: ,

正在查看 5 帖子:1-5 (共 8 个帖子)
  • 作者
    帖子
  • #1726
    chao wangwhik
    参与者

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

    前言

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

    systemRV32M1ri5cy.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实际显示效果:

    总结

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

    参考资料

    #1777
    HowardHoward
    参与者

    @whik 这个不错。这才是织女星开发板真正的玩家,能把自己做的东西分享出来大家一起学习。赞一个!

    VEGALite也可以通过Arduino标准接口接SPI的LCD屏。网上有卖的。

    #1811
    123123
    参与者

    赞赞赞!

    #1818
    123123
    参与者

    请问是怎么把图片传上来的啊?还有另一个帖子的动态图,是用了什么插件吗?我也想发图片,但是不知道怎么操作呢。。。

    #1819
    chao wangwhik
    参与者

    这个论坛没有存放图片的位置,所以你需要把你的图片上传到一个地方,然后获取到这个图片的地址,使用html的方式插入图片,并选择文本模式:

正在查看 5 帖子:1-5 (共 8 个帖子)
  • 抱歉,回复话题必需登录。