赞
踩
打开ArduinoIDE,会看到只有setup和loop函数,还有几句注释,提示我们把运行一遍的程序放在setup中,把重复运行的部分放在loop中。但是,main函数呢?程序运行入口就是main函数,怎么可能没有main函数呢?
原来,Arduino将main函数写好了,放在main.cpp文件中。为什么不让用户自己写main函数呢?
在main函数中还做了一些重要的初始化工作,初始化之后有些函数才能正常工作,比如millis,PWM输出等。
int main(void) { init(); initVariant(); #if defined(USBCON) USBDevice.attach(); #endif setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; }
进入main函数之后,先进行了两项初始化工作。init和initVariant。开发者将所有单片机共同的初始化部分放在init中,将不同型号单片机的初始化部分放在initVariant中,目前initVariant函数是空函数,是为以后可能的升级预留的。
init函数初始化了所有定时计数器,还初始化了AD转换模块,最后将串口0于bootloader断开,这样0,1号引脚能作为普通数字引脚使用。
init函数中代码量太多,还包含了大量的预编译命令来判断设备中是否有某个硬件,从而决定是否初始化。
在init函数中,将所有定时计数器的频率都设为cpu频率的64分频。将0号定时计数器用来记录系统运行时间,并对其他依赖时间的函数提供支持,比如millis,micros,delay等函数。将其他定时计数器全都设定为相位修正(phase correct)PWM模式。
Arduino是如何通过定时计数器0来精确计时的呢?
arduino用两个变量来记录时间,用timer0_millis来记录毫秒数,用timer0_fract来记录微秒,这里比较巧妙后面解释。假设每过a毫秒b微秒0号计数器溢出中断,则将a毫秒累加到timer0_millis上,b微妙累加到timer0_fract上,当timer0_fract累加到1毫秒时,则将timer0_millis加1。
timer0_millis是unsigned long类型的,4个字节,最多能记录4294967295毫秒,49天。而timer0_fract是unsigned char类型,只有1个字节,并不能记录下1000个微秒,怎么办呢?实际上,timer0_fract记录的是微秒数除以8。因为如果晶振为16MHz的话,那么每1毫秒24微妙中断一次,晶振为8MHz的话,每2毫秒48微妙中断一次,将微秒数除以8并不会损失精度。
中断函数实现为:
#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ISR(TIM0_OVF_vect) #else ISR(TIMER0_OVF_vect) #endif { unsigned long m = timer0_millis; unsigned 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++; }
在main函数的for循环中还看到了这样一句话
if (serialEventRun) serialEventRun();
这是串口事件,关于serialEventRun函数的定义如下
void serialEventRun(void)
{
#if defined(HAVE_HWSERIAL0)//如果有串口0,在预编译时期则会将下面代码包含进来,否则会去掉这句代码。这样做是为了在编译时期不会因使用了未定义的东西而报错,下面也是这样的。
if (Serial0_available && serialEvent && Serial0_available()) serialEvent();
#endif
#if defined(HAVE_HWSERIAL1)
if (Serial1_available && serialEvent1 && Serial1_available()) serialEvent1();
#endif
#if defined(HAVE_HWSERIAL2)
if (Serial2_available && serialEvent2 && Serial2_available()) serialEvent2();
#endif
#if defined(HAVE_HWSERIAL3)
if (Serial3_available && serialEvent3 && Serial3_available()) serialEvent3();
#endif
}
每个if中,先判断串口缓冲区是否有数据,再判断用户是否定义了SerialEvent函数,如果都为真的话,则调用用户定义的SerialEvent函数。
SerialEvent函数的属性为:
void serialEvent() __attribute__((weak));
也就是可以定义也可以不定义他,如果不定义这个函数,编译器将会给这个函数指针赋0。
以Serial0为例。假如Serial0_available()函数定义了的话,那么在第三个判断条件中调用这个函数不会有问题,如果未定义的话,那么不会进行第三个判断,不会调用这个函数,也不会有问题。对于SerialEvent函数,如果用户未定义它,那么判断条件为假,不会调用。只有用户定义了它才会调用。
对于SerialEvent,ArduinoIDE中有个Example,可以看看。
从main函数中可以看到,SerialEvent不是中断实现的,仅仅是在loop后面调用的一个函数,因此要使用它的话,就不能在loop中再写一个死循环了!SerialEvent要等到每次loop执行完了才会执行,因此速度会比较慢,尤其是在loop中用delay函数延迟了几秒的,这么长时间的延迟可能会使得串口的接收缓冲区填满。使用它的时候要小心。
源文件参考:
hardware\arduino\avr\cores\arduino\main.cpp
hardware\arduino\avr\cores\arduino\wiring.h
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。