Arduino代码机制-main.cpp

打开ArduinoIDE,会看到只有setup和loop函数,还有几句注释,提示我们把运行一遍的程序放在setup中,把重复运行的部分放在loop中。但是,main函数呢?程序运行入口就是main函数,怎么可能没有main函数呢?

原来,Arduino将main函数写好了,放在main.cpp文件中。为什么不让用户自己写main函数呢?

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

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++;
}

serialEventRun

在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

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值