【龙芯1c库】封装软件延时接口和使用示例
来源:互联网 发布:淘宝退款被拒绝怎么办 编辑:程序博客网 时间:2024/06/16 15:34
龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库。Git地址:http://git.oschina.NET/caogos/OpenLoongsonLib1c
程序中难免会用到延时函数,一般通过执行n个nop指令实现延时。为此封装了delay_us(i), delay_ms(i), delay_s(i)三个函数,分别延时ius, ims, is。并测试了几个函数的延时精度,除了延时时间为几微秒时,精度稍微差一些之外,其它延时时间长度,误差都尽量控制在了一两个单位之内。
本文先讲解封装的延时接口函数如何使用,再展示测试的效果,再分析延时函数的源码,最后再尝试着优化并给出优化后的代码和测试结果。
龙芯1c库中软件延时接口使用示例
软件延时接口简介
共提供三个接口,微秒,毫秒,秒三个级别的延时各一个,如下
/* * 延时指定时间,单位ms * @j 延时时间,单位ms */void delay_ms(int j);/* * 延时指定时间,单位us * @n 延时时间,单位us */void delay_us(int n);/* * 延时指定时间,单位s * @i 延时时间,单位s */void delay_s(int i);
比如,延时50us,调用语句为delay_us(50);
延时50ms,调用语句为delay_ms(50);
延时1s,调用语句为delay_s(1);
为每个接口设计了一个测试用例,通过延时指定时间将gpio拉低拉高,用示波器观察波形的方式来测试,微秒和毫秒的用例里面依次产生占空比为0.5,周期为2个单位,10个单位,100个单位和500个单位的pwm波形。秒的测试用例里只产生周期2s和10s的pwm。
测试delay_ms()
测试代码
// 测试延时函数delay_1ms()void test_delay_1ms(void){ unsigned int gpio = 6; int time = 0; gpio_init(gpio, gpio_mode_output); gpio_set(gpio, gpio_level_high); // 产生不同宽度的高低电平,用示波器观察高低电平宽度是否正确 while (1) { // 2ms time = 2/2; delay_ms(time); gpio_set(gpio, gpio_level_low); delay_ms(time); gpio_set(gpio, gpio_level_high); // 10ms time = 10/2; delay_ms(time); gpio_set(gpio, gpio_level_low); delay_ms(time); gpio_set(gpio, gpio_level_high); // 100ms time = 100/2; delay_ms(time); gpio_set(gpio, gpio_level_low); delay_ms(time); gpio_set(gpio, gpio_level_high); // 500ms time = 500/2; delay_ms(time); gpio_set(gpio, gpio_level_low); delay_ms(time); gpio_set(gpio, gpio_level_high); }}
测试结果
每格200ms时,可以看到好几个完整的波形
周期为500ms的,实际测量结果为497ms
周期为100ms的,实际测量结果为98.9ms
周期为10ms的,实际测量结果为9.91ms
最后再来看看,周期为2ms的,实际测量结果为1.98ms。
如果觉得这个精度不够,还可以微调,给代码中k_max一个补偿值(代码在后面)。
测试delay_us()
测试代码
// 测试延时函数delay_1us()void test_delay_1us(void){ unsigned int gpio = 6; int time; gpio_init(gpio, gpio_mode_output); gpio_set(gpio, gpio_level_high); // 产生不同宽度的高低电平,用示波器观察高低电平宽度是否正确 while (1) { // 2us time = 2/2; delay_us(time); gpio_set(gpio, gpio_level_low); delay_us(time); gpio_set(gpio, gpio_level_high); // 10us time = 10/2; delay_us(time); gpio_set(gpio, gpio_level_low); delay_us(time); gpio_set(gpio, gpio_level_high); // 50us time = 50/2; delay_us(time); gpio_set(gpio, gpio_level_low); delay_us(time); gpio_set(gpio, gpio_level_high); // 100us time = 100/2; delay_us(time); gpio_set(gpio, gpio_level_low); delay_us(time); gpio_set(gpio, gpio_level_high); // 500us time = 500/2; delay_us(time); gpio_set(gpio, gpio_level_low); delay_us(time); gpio_set(gpio, gpio_level_high); }}
这里多增加了一个延时时间50us。因为源码中将1到1000us分为三段(1-10,10-100,100-1000),分别优化,以尽量提高延时精度,不论延时时间长度是多少。
测试结果
每格为200us时,看到的整个波形
周期为500us的波形,实际测量值为501us。
周期为100us的波形,实际测量值为98.7us
程序不变,按复位键复位后,相邻两次测量可能有1us左右的偏差。
周期为50us的波形,实际测量为51.9us
周期为10us的波形,实际测量值为10.4us
周期为2us的波形,实际测量值为5.46us。
从这个测试结果看来,当延时时间只有几微秒时,误差比较大。当然应该可以再继续优化达到更好效果,我这里就不优化,感兴趣的可以自己优化试试。
测试delay_s()
测试代码
// 测试延时函数delay_1s()void test_delay_1s(void){ unsigned int gpio = 6; int time; gpio_init(gpio, gpio_mode_output); gpio_set(gpio, gpio_level_high); while (1) { // 2s time = 2/2; delay_s(time); gpio_set(gpio, gpio_level_low); delay_s(time); gpio_set(gpio, gpio_level_high); // 10s time = 10/2; delay_s(time); gpio_set(gpio, gpio_level_low); delay_s(time); gpio_set(gpio, gpio_level_high); }}
测试结果
周期太长,示波器都不能完整显示一个周期的。
周期10s的波形,周期太长,只能看到一部分,这里测量了一下高电平部分的宽度。实际测量结果为4.99s
最后来看看周期为2s的实际结果,实际测量值为1.99s。
封装延时函数接口
要点
处理器有nop汇编指令,就是执行空操作,什么也不做,就占用一点时间而已。既然是执行n个nop指令,理论上可以通过控制nop指令执行的次数n实现“精确”延时,基于这点理论基础,本文尽可能的提高延时精度。
如果编译选项开启了-O2优化选项的话,不论是while还是for循环执行空操作很容易被编译器优化掉。避免被编译器优化掉,在for循环中使用__asm__ ("nop")代替“;”执行空操作。
当延时时间很短时,比如几us,可能需要考虑GPIO反转速度,除了for循环之外函数内其它指令占用的时间等等。
有必要的话可以使用命令“mipsel-linux-objdump -S OpenLoongsonLib1c > objdump.txt”反汇编,可以查看到更多细节。
源码清单
delay_ms()
/* * 延时指定时间,单位ms * @j 延时时间,单位ms */void delay_ms(int j){ int k_max = clk_get_cpu_rate()/1000/3; // 除以1000表示ms,除以3为测试所得的经验(可以理解为最内层循环执行一次需要的时钟个数) int k = k_max; for ( ; j > 0; j--) { for (k = k_max; k > 0; k--) { __asm__ ("nop"); // 注意,这里必须用内联汇编,否则会被优化掉 } } return ;}
代码很简单,应该能看懂。最里层for循环延时1ms,外面一层for循环根据入参j的值,控制执行多少个延时1ms,实现延时jms。
重点分析一下最里层的for循环,总共执行k_max个nop指令,变量k_max值的大小指定决定延时时间长度。为了便于移植,这里选择读取cpu的频率,如果没有流水线的话,通常一个cpu时钟执行一个nop指令,但龙芯1c是有流水线的,再加上最里层的for循环出来nop指令之外,至少还有判断K>0和k--。所以最里层的for循环执行一次需要的时间最好是通过反汇编来看,这里选择了通过测量来获得一个经验值,经过测量3个cpu时钟执行一次最里层的for循环,这也是k_max初始值里面需要cpu频率除以3的原因。把cpu频率除以1000就是1ms内cpu时钟个数,再除以3(一个最里层for循环执行需要3个cpu时钟)就是for循环执行次数。
延时1ms,外层for循环执行一次,延时n毫秒,外层for循环执行n次。外层for循环这几条汇编指令需要的时间和1ms比起来,可以忽略不计,所以ms级延时精度应该可以比较高的。
delay_us()
/* * 延时指定时间,单位us * @n 延时时间,单位us */void delay_us(int n){ int count_1us = clk_get_cpu_rate() / 1000000 / 3; // 延时1us的循环次数 int count_max; // 延时n微秒的循环次数 int tmp; // 根据延时长短微调(注意,这里是手动优化的,cpu频率改变了可能需要重新优化,此时cpu频率为252Mhz) if (10 >= n) // <=10us { count_1us -= 35; } else if (100 >= n) // <= 100us { count_1us -= 6; } else // > 100us { count_1us -= 1; } count_max = n * count_1us; // 延时 for (tmp = count_max; tmp > 0; tmp--) { __asm__ ("nop"); // 注意,这里必须用内联汇编,否则会被优化掉 } return ;}
和延时1ms的代码类似,都是通过控制nop指令执行次数来控制延时时间。除了相同点之外,也有两个不同点
1,将1us到1000us整个区间,分为三段,目的是尽可能的提高每段的延时精度,减小误差,如果你问我为什么要分为三段,我只能说是测试经验告诉我,如果不分段,则很难将整个区间的精度都控制好,经验所得。2,把两层for循环改为一层for循环。按照前面delay_1ms()的思路,这里也应该用两次for循环,通用经过实际测试,发现两层for循环很难将整个区间的误差控制好,因为延时时间越低,除最里层for循环之外的代码执行时间占整个延时时间的比重越大,也就是误差越大。为了尽量减少除最里层for循环的代码,这里选择了只用一层for循环的方法。
count_1us = clk_get_cpu_rate() / 1000000 / 3 = 252000000 / 1000000 / 3 = 84,即理论上当cpu频率为252Mhz时,延时1us需要执行for循环的次数为84次。
延时n微秒需要执行的次数count_max = n * count_1us = n * 84;源码中在计算循环次数之前,会微调一下变量count_1us的值,具体微调多少是根据测试效果来判断的,所以代码中微调的值都是经验值,如果cpu频率改变了,也应该相应改变,这可能会带来移植问题,但没办法。
delay_s()
/* * 延时指定时间,单位s * @i 延时时间,单位s */void delay_s(int i){ for ( ; i > 0; i--) { delay_ms(1000); } return ;}
考虑到通常需要延时1s的地方都不是需要非常高的精度,所以这里延时1s是通过延时1000ms实现的,即前面的ms级延时精度直接影响这里延时1s的精度。
感谢耐心看完,谢谢!
- 【龙芯1c库】封装软件延时接口和使用示例
- 【龙芯1c库】封装gpio接口和使用示例
- 【龙芯1c库】封装时钟接口和使用示例
- 【龙芯1c库】封装硬件pwm接口和使用示例
- 【龙芯1c库】封装引脚复用接口和使用示例
- 【龙芯1c库】封装模拟I2C接口和使用示例
- 【龙芯1c库】封装硬件定时器接口和使用示例
- 【龙芯1c库】封装硬件I2C接口和使用示例
- 【龙芯1c库】封装硬件SPI接口和使用示例
- STM32上使用UCOSII--软件定时器和任务延时
- STM32上使用UCOSII--软件定时器和任务延时
- Windows Touch示例-操作和延时示例
- C/S架构移动网络Socket API接口 Socket网络库 xNet框架介绍(C++封装) Demo演示示例
- 接口和父类的使用示例
- C语言动态封装库的建立和使用
- 使用接口封装变化
- gsoap库的使用技巧(使用类封装和接口的区别)
- 封装和接口思想
- jQuery基础-样式篇
- html里制作简单导航栏
- 牛腩总结(一)
- SpringMVC异步上传多文件
- 数据库连接到ireport简单示例
- 【龙芯1c库】封装软件延时接口和使用示例
- linux下禁止用户使用密码方式登陆,而只使用密钥方式登陆
- angularJs中事件处理
- 《UNIX网络编程 卷1》 笔记: 广播
- Markdown基本语法
- Maven入门教程(内含实例)
- Error:Execution failed for task ':app:processDebugManifest'. > Manifest merger failed with multiple
- hook_导出表eat
- 主流深度学习开源框架