手上有一个嘉立创的梁山派开发板,最近打算用做一个小东西,需要用到iic协议,而如果用软件i2c的话,精确延迟就免不了。下面记录一下我的开发过程。
下面的代码参考了正点原子的精确延迟的函数
首先这里用的是systick的库函数,systick是芯片自带的时钟,与timer定时器不一样,更容易移植,而且不产生中断,不会导致程序跑飞,缺点是属于堵塞延迟,长时间使用会占用系统资源,不过用于us级别的延迟正合适,使用完关闭就可以。
下面是systick的官方说明:
/*SysTick 定时器
1、CTRL该寄存器中有四个位有效,分别是:
第16位: COUNTFLAG,Systick 计数结束标志,如果在上次读取本寄存器后, SysTick 已经计到
了 0,则该位置 1,如果读取该位,该位将自动清零。用在查询方式上,确定计时结束,并开始新的计数。
第2位: 时钟源选择位,=0 时 AHB/8,=1 时 处理器时钟AHB
第1位: =1时,SysTick 倒数计数到 0 时产生 SysTick 异常请求;=0时,数到 0 时无动作,即中断使能位。(也可以通过读取 COUNTFLAG 标志位来确定计数器是否递减到0。)
第0位: SysTick 定时器的使能位,使用SysTick开始计时时需要将它置1
2、LOAD SysTick 重装载数值寄存器
Systick是一个递减的定时器,当定时器递减至0时,重装载寄存器中的值就会被重装载,继续开始递减。STK_LOAD 重装载寄存器是个24位的寄存器,最大计数 0xFFFFFF。
3、VAL SysTick 当前数值寄存器
当前数值寄存器也是个24位的寄存器,读取时返回当前倒计数的值。写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。
4、CALIB SysTick 校准数值寄存器*/
使用systick前需要先引入必要的头文件
#include "gd32f4xx_misc.h"
然后进行必要的初始化。
void delay_init(unsigned char SYSCLK){ //SYSCLK为systick的时钟
systick_clksource_set(SYSTICK_CLKSOURCE_HCLK_DIV8); //设置systick的时钟源为AHB/8,梁山派的AHB = __SYSTEM_CLOCK_ = 200mhz,也就是systick为200/8=25hmz
SysTick->CTRL&=0xfffffffb; // 系统时钟控制及状态寄存器,这个fffffffb换算成二进制代表bit2被置零,时钟源=AHB( HCLK)/8
fac_us = SYSCLK;
//fac_ms =(unsigned short int)fac_us*1000;
}
上面的SYSCLK的值要与AHB的频率的八分频结果一致才能精确到1us,AHB的频率我查了很久,发现它与系统时钟是一致的,而系统时钟的值__SYSTEM_CLOCK_可以在system_gd32f4xx.c里面找到。我这边是200mhz,所以使用的时候就是200/8=25 :delay_init(25)。
也就是说,systick在200mhz的主频下,systick的频率为25mhz,所以相对的,它每计数25次,就刚好是一微秒。
接下来进行us延迟的编写。
void delay_us(unsigned int nus)
{
unsigned int temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL=0x01 ; //开始倒数
do
{
temp=SysTick->CTRL;
}
while(temp&0x01&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
这段代码比较难理解的是while(temp&0x01&&!(temp&(1<<16)))这一段,其实就是将CTRL的值传给temp,然后检查第一位和第十六位,第一位表示计数正在进行,第十六位就是计数完成。如果计数没有完成,就反复检查CTRL的值,直到计数完成为止。
然后就是ms延迟的编写。
void delay_ms(unsigned int nms)
{
unsigned int i;
for(i=0;i<nms;i++) delay_us(1000);
}
之所以ms延迟要用for而不像us使用更精确的systick,是因为systick计数器是有上限的,在200m的速率下,671ms就已经满了,这个时候再装入更高的值是没有意义的,为了避免使用毫秒函数的时候装超过671的值而导致计数不准确,就直接用for循环来完成。