這篇文章以Andes Andino M1為例,探索Arduino的系統組成,揭開Arduino的神秘面紗。
歡迎各位多多指教~
因此我們由這個Arduino系統核心程式碼main.cpp開始看起
class HardwareSerial與main.cpp一樣定義在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino路徑下
此class的繼承關係為:
class HardwareSerial : public Stream
class Stream : public Print
class Print
再來看到main()中,其中Line 10 setup()與Line 14 loop()就是我們撰寫Ardunio Sketch的那兩大function
這也就是為什麼每次寫一個新的Sketch需要連著系統核心程式碼一起編譯連結
回到開頭的Line 1,Arduino.h將串起了整個Ardunio系統,其內容包含:
歡迎各位多多指教~
順帶一提
使用SSD的朋友們,為了減少占用C槽的空間,可以將你的Arduino IDE在安裝時選擇裝到D:下
然而,preference設定檔位置似乎無法修改路徑,在安裝新的板子時,toolchain等檔案會佔用不少C槽空間,因此可以在Windows建立以下連結,將檔案存放到D:再link回來歐~
mklink /d "C:\Users\使用者\AppData\Local\Arduino15" "D:\Program Files\Arduino\Arduino15"
另外,以下貼程式碼部分使用的是Google Code Prettify,有興趣的人自己Google啦~
另外,以下貼程式碼部分使用的是Google Code Prettify,有興趣的人自己Google啦~
直攻核心
以下用${P}代表preference設定檔位置的省略(preference設定檔位置在前一篇介紹過)
在Andino M1的board support package,其架構分為:
- ${P}\packages\Andino\hardware有nds32\1.6.5資料夾存放與板子相關的Arduino系統核心程式碼
- ${P}\packages\Andino\tools有m2c_burner\0.0.1與nds32le-elf-mculib-v3m\4.0.3分別為燒錄程式與toolchain
因此我們由這個Arduino系統核心程式碼main.cpp開始看起
#include "Arduino.h" HardwareSerial Serial(UART0); HardwareSerial Serial1(UART1); int main( void ) { System_init(); TIMER1_init(); setup(); for (;;) { loop(); } return 0; }在以上程式碼中,Line 3,4看到class HardwareSerial兩個在global的instance: UART0、UART1
class HardwareSerial與main.cpp一樣定義在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino路徑下
此class的繼承關係為:
class HardwareSerial : public Stream
class Stream : public Print
class Print
再來看到main()中,其中Line 10 setup()與Line 14 loop()就是我們撰寫Ardunio Sketch的那兩大function
這也就是為什麼每次寫一個新的Sketch需要連著系統核心程式碼一起編譯連結
回到開頭的Line 1,Arduino.h將串起了整個Ardunio系統,其內容包含:
- include與MCU相關的Header,如: m2c8001.h, m2c8001_sys.h, m2c8001_int.h, m2c8001_uart.h,定義各模組的init、enable/disable等函數(這些Header定義在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino\sys\inc)
- include標準C函式庫,如:stdint.h, stdlib.h, string.h, math.h, stdbool.h...等(這些Header定義在${P}\packages\Andino\tools\nds32le-elf-mculib-v3m\4.0.3\lib\gcc\nds32le-elf\4.9.2\include)
- include與CPU架構相關的Header,如: nds32_intrinsic.h(定義在${P}\packages\Andino\tools\nds32le-elf-mculib-v3m\4.0.3\lib\gcc\nds32le-elf\4.9.2\include)
- 定義來自Ardunio sketch的兩大函數extern void setup( void )與extern void loop( void )
- 以define macro方式轉換Ardunio sketch中的常數與函式,如:lowByte/highByte, bitRead/bitWrite, INPUT/OUTPUT/INPUT_PULLUP
- include提供Ardunio sketch的一些補強類別與函式,如:WString.h字串class、WCharacter.h字元函式、HardwareSerial.h、WMath.h
- 定義Ardunio sketch中使用的Digital I/O(pinMode, digitalWrite, digitalRead)、Analog I/O(analogReference, analogRead, analogWrite...)、Advanced I/O(pulseIn, shiftIn...)、External interrupt(attachInterrupt, detachInterrupt)、Time function(delay, delayMicroseconds...)、Ardunio觀點的GPIO pin numbers(D0~D13, A0~A5),以上除了GPIO pin numbers是以enum定義之外,其他都在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino目錄下實作function,經由轉換後再呼叫MCU相關header的function操作
如:以GPIO為例,
digitalWrite()定義在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino\Arduino.h,
實作在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino\wiring_digital.c並呼叫GPIO_SetOutput(),
uint32_t pin_dmux[14]={20,19,23,24,3,18,7,10,1,2,28,25,26,27}; void digitalWrite( int pin_m, uint8_t value ) { uint32_t pin = pin_dmux[pin_m]; GPIO_SetOutput(pin, value); }
而GPIO_SetOutput()定義在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino\sys\inc\m2c8001_gpio.h,
實作在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino\sys\driver\m2c8001_gpio.cvoid GPIO_SetOutput(u8 u8Idx, u8 u8Val) { if (u8Val == GPIO_OUT_HIGH) GPIO->DOUT |=(1 <<u8idx); else GPIO->DOUT &=(1 <<u8idx); }
在以上這段code中,最神奇的是底層的code看起來竟然如此向物件導向,追根究柢發現原來GPIO是define的macro,是用struct來操作memory map I/O所對應到的那塊memory,以下節錄部分code(定義在${P}\packages\Andino\hardware\nds32\1.6.5\cores\arduino\sys\inc\m2c8001.h)出來:#define GPIO_BASE (0x00F07000) #define GPIO ((GPIO_TypeDef*)GPIO_BASE) typedef struct { __RW u32 DOUT; /* Offset 0x00 GPIO data output register*/ //以下省略 }GPIO_TypeDef;
所以說根本該做的事、該寫的code可是一行都不少呢!!
只是Arduino幫你先寫好了,而且包的漂漂亮亮der~
讓我們回顧一下,傳統使用Keil C操作8051系列時的寫法:#include <reg52.h> sbit LED = P2^0; // Defining LED pin, port 2 bit 0 void main (void) { while(1) // infinite loop { LED = 0; // LED ON Delay(); LED = 1; // LED OFF Delay(); } }
然後在reg52.h中定義的port 2(P2)為
//... sfr P2 = 0xA0; //...
是不是瞬間變難懂許多了呢~真的是code越包越多層,提供越高的abstraction人越容易懂,但是效能是不是也讓步了一點呢?
What's Next
有興趣探討以上所提到的詳細實作細節,除了自己trace code以外,也可以看看"Arduino 底層原始碼解析心得(http://www.slideshare.net/roboard/arduino-385580)"~
留言
張貼留言