用Source Insight追踪Arduino的setup和loop

查看一些大型的开源项目自然少不了一款神器——Source Insight。呵呵,不扯了

一般情况下,在ArduinoIDE中编写的程序非常简单,只有setup和loop这两个核心函数。一般的C语言程序都有一个main函数的入口点,而Arduino是基于avr-gcc的二次封装,把这些底层的东西都屏蔽掉了。其实查看Arduino IDE的源码,会发现在其\hardware\arduino\avr\cores\arduino目录下有一个main.cpp文件,这就是Arduino程序的入口点,main.cpp的内容如下:

#include "Arduino.h"
int main( void )
{
	// Initialize watchdog
	watchdogSetup();
	init();
	initVariant();
	delay(1);
#if defined(USBCON)
	USBDevice.attach();
#endif
	setup();
	for (;;)
	{
		loop();
		if (serialEventRun) serialEventRun();
	}
	return 0;
}

再扯一句,有一个网站做的不错, 极客工坊知识库,总结了一个Arduino的核心文件,查阅起来也比较方便。

上面的源文件中include了一个Arduino.h的头文件,其实查看一下这个头文件的源码,就会发现在这个头文件中引入了部分C语言的标准库函数,比如说stdlib.h、string.h、以及一些数学函数库;还有在这个头文件中声明了Arduino的标准库函数的原型,比如说,pinMode、digitalWrite、analogRead、delay等函数的原型。

其中init()函数就是为了实现设置中断、定时器、初始化寄存器等功能,这个函数的实现是直接用avr-gcc写的;initVariant()函数的定义如下:

voidinitVariant() __attribute__((weak));

__attribute_的weak属性的作用是弱符号:若有两个或两个以上全局符号(函数或变量名)的名字一样,而其中之一声明为weak symbol(弱符号),则这些全局符号不会引发重定义错误。链接器会忽略弱符号,去使用普通的全局符号来解析所有对这些符号的引用,但当普通的全局符号不可用时,链接器会使用弱符号。

接下来的一句是USBDevice.attach();其中USBDevice是用avr-gcc写的一个类,调用attach方法就是为了让USB的功能可用。

然后就是setup()和loop(),这两个函数需要开发者调用那些已经被高度封装的API来自己实现。

if (serialEventRun) serialEventRun();当串口有数据过来的时候就调用serialEventRun这个函数,处理串口事件。总之,Arduino里面的每一个库函数几乎都是对avr-gcc的高度封装。比如说以控制13号引脚LED的亮灭为例子。Arduino的代码就是下面几行:

void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

用工具跟踪一下这个文件实际的运行过程,先进入pinMode的定义,进入cores\arduino\Wiring_digital.c源文件,就有下面这几行代码:

void pinMode(uint8_t pin, uint8_t mode)
{
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *reg, *out;
	if (port == NOT_A_PIN) return;
	// JWS: can I let the optimizer do this?
	reg = portModeRegister(port);
	out = portOutputRegister(port);
	if (mode == INPUT) { 
		uint8_t oldSREG = SREG;
                cli();
		*reg &= ~bit;
		*out &= ~bit;
		SREG = oldSREG;
	} else if (mode == INPUT_PULLUP) {
		uint8_t oldSREG = SREG;
                cli();
		*reg &= ~bit;
		*out |= bit;
		SREG = oldSREG;
	} else {
		uint8_t oldSREG = SREG;
                cli();
		*reg |= bit;
		SREG = oldSREG;
	}
}

继续跟踪digitalPinToBitMask这个函数,结果找到了下面几个函数宏:

#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )  
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )  
#define portInputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_input_PGM + (P))) )  
#define portModeRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_mode_PGM + (P))) )  

其中,pgm_read_byte和pgm_read_word是avr-gcc中标准的访问 FLASH 程序存储器的函数。而宏内传进去的参数digital_pin_to_port_PGM、digital_pin_to_bit_mask_PGM、port_to_input_PGM、port_to_mode_PGM,跟踪到hardware\arduino\variants\leonardo\Pins_arduino.h文件中,结果发现这是一个数组,数组里面按一定的顺序存放了各个引脚的值,他这么做就相当于事先把各个芯片的端口和引脚的映射关系都定义成几个数组,相当于几个表格,这几个表格再把引脚所在的位置作为索引,在使用的时候通过引脚位置,通过直接查表的方式就可以得到对应的功能寄存器。

所以想让13号引脚的LED亮灭,只需要设置输入输出模式,设置输入输出电平就可以了。对于开发者来说这个引脚在哪个端口上,是PORTA还是PORTB。

要是想用avr-gcc来实现,上面的各个方面都要考虑:

#include 
int main( void )
{
unsigned char i, j, k,led=0;
DDRB=0xFF;
while (1)
{
if(led)
PORTB|=0X01;
else
PORTB&=0XFE;
led=!led;
//延时
for (i=0; i<255; i++)
for(j=0; j<255;j++)
k++;
}
}

总之,Arduino IDE就是对avr-gcc的高度封装,屏蔽掉了很多硬件层的东西。


你可能感兴趣的:(瞎搞)