有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 皋陶 于 2020-8-26 15:33 编辑
文章目录
- 前言
- 关于LPIT0
- ZERO核的SysTick定时器
- delay.c文件
- delay.h文件
- 实际验证
- 驱动IIC接口OLED
- 总结
- 参考资料
- 历史精选
前言收到VEGA织女星开发板也有一段时间了,好久没玩了,想驱动个OLED屏,但是首先要实现IIC协议,而实现IIC协议,最基本的就是需要一个精确的延时函数,所以研究了一下如何来写一个精确的延时函数。
众所周知,ARM Cortex-M内核都有一个24位的SysTick系统节拍定时器,它是一个简易的周期定时器,用于提供时基,多为操作系统所使用。
RV32M1的RISC-V内核RI5CY也有一个SysTick定时器,只不过它不属于内核,而是使用的一个外部通用定时器,即LPIT0( low power periodic interval timer)定时器的通道0来实现的。
system_RV32M1_ri5cy.c文件的SysTick定时器:
- /* Use LIPT0 channel 0 for systick. */
- #define SYSTICK_LPIT LPIT0
- #define SYSTICK_LPIT_CH 0
- #define SYSTICK_LPIT_IRQn LPIT0_IRQn
复制代码
system_RV32M1_ri5cy.h文件中的SysTick中断服务函数:
- #define SysTick_Handler LPIT0_IRQHandler
复制代码
system_RV32M1_zero_riscy.c文件中的SysTick定时:
- /* Use LIPT1 channel 0 for systick. */
- #define SYSTICK_LPIT LPIT1
- #define SYSTICK_LPIT_CH 0
- #define SYSTICK_LPIT_IRQn LPIT1_IRQn
复制代码
system_RV32M1_zero_riscy.h文件中的SysTick中断服务函数:
- /
- #define SysTick_Handler LPIT1_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参考手册
ZERO核的SysTick定时器
虽然同样是属于RISC-V内核,ZERO核与RI5CY核使用的SysTick定时器不同,
- ZERO : LPIT1_CH0
- RI5CY: LPIT0_CH0
可以通过预编译指令来进行条件编译,官方的Demo工程通过使用不同的宏定义来区分两个工程。
- ZERO核宏定义:CPU_RV32M1_zero_riscy
- RI5CY核宏定义:CPU_RV32M1_ri5cy
delay.c文件
- #include "delay.h"
- /*
- * ZERO : LPIT1_CH0
- * RI5CY: LPIT0_CH0
- * */
- static uint8_t fac_us=0;
- static uint16_t fac_ms=0;
- #if defined(CPU_RV32M1_zero_riscy)
- /*
- * RISC_V ZERO 使用 LPIT1_CH0作为SysTick,与RI5CY不同
- * */
- void Delay_Init(void)
- {
- CLOCK_SetIpSrc(kCLOCK_Lpit1, kCLOCK_IpSrcFircAsync); //设置定时器时钟48MHz
- LOG("LPIT1时钟: %ld \r\n", CLOCK_GetIpFreq(kCLOCK_Lpit1)); //输出LPIT0时钟
- CLOCK_EnableClock(kCLOCK_Lpit1); //使能时钟
- LPIT_Reset(LPIT1); //复位定时器
- LPIT1->MCR = LPIT_MCR_M_CEN_MASK; //使能定时器
- fac_us = CLOCK_GetIpFreq(kCLOCK_Lpit1)/1000000;
- fac_ms = fac_us*1000;
- }
- void Delay_us(uint32_t Nus)
- {
- LPIT1->CHANNEL[kLPIT_Chnl_0].TVAL = 48 * Nus - 1; //加载时间
- LPIT1->SETTEN |= (LPIT_SETTEN_SET_T_EN_0_MASK << kLPIT_Chnl_0); //启动定时器
- while(LPIT1->CHANNEL[kLPIT_Chnl_0].CVAL); //等待计数值到0
- // while((LPIT1->MSR & 0x0001) != 0x01); //等待溢出
- // LPIT0->MSR |= (1U << kLPIT_Chnl_0); //写1,清除中断
- LPIT1->CLRTEN |= (LPIT_CLRTEN_CLR_T_EN_0_MASK << kLPIT_Chnl_0); //清除计数器
- }
- void Delay_ms(uint32_t Nms)
- {
- LPIT1->CHANNEL[kLPIT_Chnl_0].TVAL = Nms * fac_ms - 1; //加载时间
- LPIT1->SETTEN |= (LPIT_SETTEN_SET_T_EN_0_MASK << kLPIT_Chnl_0); //启动定时器
- while(LPIT1->CHANNEL[kLPIT_Chnl_0].CVAL); //等待计数到0
- // while((LPIT1->MSR & 0x0001) != 0x0001); //等待产生中断
- // LPIT0->MSR |= (1U << kLPIT_Chnl_0); //向中断标志位写1,清除中断
- LPIT1->CLRTEN |= (LPIT_CLRTEN_CLR_T_EN_0_MASK << kLPIT_Chnl_0); //清除计数器
- }
- #elif defined(CPU_RV32M1_ri5cy)
- /*
- * RISC_V RI5CY 使用 LPIT0_CH0作为SysTick,与ZERO不同
- * */
- 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;
- }
- 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); //清除计数器
- }
- 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); //清除计数器
- }
- #endif
复制代码
delay.h文件
- #ifndef __DELAY_H__
- #define __DELAY_H__
- #include "fsl_lpit.h"
- #include "fsl_debug_console.h"
- #include "sys.h"
- /*
- * ZERO : LPIT1_CH0
- * RI5CY: LPIT0_CH0
- * */
- void Delay_Init(void);
- 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
总结
既然精确延时函数实现了,那么各种协议的传感器、显示模块、通信模块的驱动都可以轻松实现了,希望分享的本篇帖子能给社区的朋友一些帮助,也希望大家能多多发帖,互相交流学习。
参考资料历史精选本篇完,感谢关注:RISC-V单片机中文网
|