为什么Arduino这么慢,该怎么办

LOGO


很久以前,我碰到了一篇很棒的文章( tyk )-作者非常清楚地表明了使用Arduino函数和使用寄存器之间的区别。 已经写了很多文章,称赞arduino并认为这都是轻浮的,并且通常是针对儿童的,因此我们不再赘述,而是尝试找出导致该文章作者获得结果的原因。 而且,同样重要的是,我们将考虑可以做什么。 任何有兴趣的人,我都会在猫下问。


第1部分“问题”


引用本文的作者:


在这种情况下,结果是性能下降-28次。 当然,这并不意味着arduino的工作速度要慢28倍,但是我认为为清楚起见,这是Arduino不喜欢的最好的例子。

由于本文刚刚开始,因此我们尚不了解,但将忽略第二句话,并假设控制器速度大约等于引脚开关频率。 即 我们面临着使发电机具有最高频率的任务。 首先,让我们看看一切有多糟。


我们将为arduino写一个简单的程序(实际上,只是复制眨眼)。


void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1); // turn the LED on (HIGH is the voltage level) digitalWrite(13, 0); // turn the LED off by making the voltage LOW } 

缝入控制器。 由于我没有示波器,而只有中文逻辑分析仪,因此需要正确配置。 分析仪的最大频率为24 MHz;因此,必须与控制器频率保持平衡-设置为16 MHz。 我们看...


测试_1


...很长一段时间。 我们试图记住控制器的速度取决于什么-确切地说是频率。 我们在arduino.cc查看 。 时钟速度为16 MHz,此处为145.5 kHz。 怎么办 让我们尝试在额头上解决它。 在同一个arduino.cc上,我们查看董事会的其余部分:



可以假设,如果将控制器的频率增加2倍,则LED的闪烁频率也将增加2倍,如果增加5,则增加5倍。


测试_2


我们没有得到理想的结果。 发电机越来越不像弯路了。 我们再想一想-现在,语言可能很糟糕。 看来c,c ++确实如此,但这很困难(根据Dunning-Krueger效应,我们无法意识到我们已经在用c ++编写),因此我们正在寻找替代方案。 简短的搜索将我们带到BASCOM-AVR( 在这里的描述还不错),把它写成下面的代码:


 $Regfile="m328pdef.dat" $Crystal=16000000 Config Portb.5 = Output Do Toggle Portb.5 Loop 

我们得到:


Test_3


结果好得多,此外,我们得到了完美的曲折,但是... BASIC在2018年,认真吗? 也许我们会过去。


第2部分“答案”


看来是时候停止傻瓜了,开始理解(并且还记住si和汇编程序)。 只需复制循环()开头提到的文章中的“有用”代码即可。


我相信这里需要一个解释:所有代码都将在arduino项目中编写,但是在Atmel Studio 7.0环境(那里有一个方便的反汇编程序)中,屏幕快照将来自于此。


 void setup() { DDRB |= (1 << 5); // PB5 } void loop() { PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON } 

结果:


Test_4


在这里! 几乎您所需要的。 只是形式与曲折和频率并不特别相似,尽管它已经比较接近,但仍然不相同。 我们还尝试放大并每毫秒找到信号中的间隙。


Test_5


这是由于负责Millis()的计时器触发了中断。 因此,我们要做的就是断开连接。 我们正在寻找ISR(中断处理函数)。 我们发现:


 ISR(TIMER0_OVF_vect) { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; nsigned char f = timer0_fract; m += MILLIS_INC; f += FRACT_INC; if (f >= FRACT_MAX) { f -= FRACT_MAX; m += 1; } timer0_fract = f; timer0_millis = m; timer0_overflow_count++; } 

对我们来说,很多无用的代码。 您可以更改计时器操作模式或禁用中断,但这对于我们而言是不必要的,因此只需使用cli()命令禁用所有中断即可。 看看我们的代码:


 PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON 

过多的操作员,减少为一项任务。


 PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON 

是的,切换到loop()需要很多命令,因为这是主循环中的一个附加功能。


 int main(void) { init(); // ... setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; } 

因此,只需在setup()中进行一个无限循环。 我们得到以下内容:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON } } 

Test_6


61 ns是与控制器频率相对应的最大值。 有可能更快吗? 剧透-不 让我们尝试了解原因-为此,我们反汇编代码:


Code_asm_1


从屏幕上可以看出,为了写入端口1或0,正好花费了1个时钟周期,但是有一个过渡无法在不到一个时钟周期内完成(RJMP在两个时钟周期内执行,例如,JMP在三个时钟周期内执行) ) 而且我们几乎到了-要蜿蜒而行,您需要增加两次测度给出0的时间。 为此两个汇编器nop命令添加命令,它们只需要1个时钟周期就什么都不做:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF asm("nop"); asm("nop"); PORTB = 0b11111111; //ON } } 

测试结束


第3部分“结论”


不幸的是,从实际的角度来看,我们所做的一切绝对是无用的,因为我们无法再执行任何代码。 同样,在99.9%的情况下,端口切换频率足以满足任何目的。 是的,如果我们确实需要产生平滑的曲折,则可以将stm32与dma或NE555等外部定时器芯片配合使用。 本文对于总体了解mega328p和arduino设备很有用。


但是,写入8位值的寄存器PORTB = 0b11111111;digitalWrite(13, 1);快得多digitalWrite(13, 1); 但是您将不得不为无法将代码转移到其他电路板而为此付出代价,因为寄存器的名称可能不同。


只剩下一个问题:为什么使用更快的宝石没有效果? 答案很简单-在复杂的系统中,gpio频率低于核心频率。 但是,可以在特定控制器的数据表中始终看到更低的值以及如何设置它。


该出版物引用了以下文章:



Source: https://habr.com/ru/post/zh-CN422177/


All Articles