Arduino 原始碼讀書會 (I) : Overview
Nov 22, 2014
Arduino 原始碼讀書會
(I) : Overview
簡單複習 Arduino
Arduino 硬體配置
開啟 .ino 程式檔
Arduino IDE 的使用
編輯程式
編譯
燒錄到 Arduino 上執行
複習完畢! 收工…(毆)
進入 Arduino 原始碼的世界
Q:
Arduino 0023、1.0.5、1.5.2 原始碼, 要推倒哪一個?
A:
我們將以 Arduino 1.0.5 為主要推倒對象
原始碼目錄介紹
改過的 Processing IDE 原始碼 (Arduino 相關)
Processing IDE 原始碼
存放編譯結果的目錄
Arduino Bootloader, Standard API 原始碼
Arduino Library 原始碼
app → src → processing → app → debug
處理 Arduino 韌體燒錄動作
處理 Arduino 韌體燒錄動作
處理 .ino 編譯動作
今天不談這裡
hardware → arduino
各種版本 bootloader 原始碼
Standard API 原始碼(共用部分)
各種周邊配套的處理器韌體
Standard API 不共用部分
定義 Board 選單及編譯參數
定義 Programmers 選單及燒錄參數
今天只談它
如何編譯 Arduino 原始碼?
http://code.google.com/p/arduino/wiki/BuildingArduino
Windows 下編譯 Arduino 原始碼
下載 JAVA JDK 並完成安裝
下載 cygwin並完成安裝
◦ Linux-like environment for Windows
◦ 安裝 cygwin 過程中, 選擇安裝下列套件
git
make, gcc-mingw, and g++
perl
unzip, zip
Windows 下編譯 Arduino 原始碼
下載 Apache Ant 程式
◦ JAVA base 編譯器
設定 Apache Ant 和 JAVA JDK 的環境變數
執行 cygwin, 使用 git 指令下載 Arduino
最新原始碼
Windows 下編譯 Arduino 原始碼
使用指令 ant/ant run 開始編譯 Arduino
原始碼
Windows 下編譯 Arduino 原始碼
編譯完成的結果會存放在 (DIR)\
\build\windows\work 資料夾內
Linux、Mac 下編譯
Arduino 原始碼
Orz…
目前還沒時間試… (哭~~~)
.ino 程式的編譯原理概觀
.ino: 偽裝過的 C++
一切都是幻覺, 嚇不倒我滴!!
◦ Arduino 會先將 .ino 檔轉換為 .cpp 檔再進行編譯
◦ 可在 .ino 檔中使用 C++ 語法 (但不能使用
C++ standard library 內的某些物件或函式,
例如: cout, cin)
◦ 可使用 avr-gcc 的所有語法
IDE 會將 .ino 轉換成 .cpp
加入 include ” Arduino.h”
加入所有.ino 內函式的原型宣告
加入編譯指示詞#line, 重新定義與原始.ino 檔一致的行號
.ino 的編譯流程概觀
Arduino IDE 會先建立一個暫存目錄
把 .ino 轉成 .cpp, 複製到暫存目錄下並進行編譯
掃描並編譯 .ino 所 include 到的每個
library, 編譯結果輸出到暫存目錄下
所有編譯結果連結成一個 .hex 的韌體燒錄檔
若編譯過程出錯, 會直接停止編譯並 show 出錯誤訊息
進入 Arduino Standard API
(樓還沒歪…無誤)
Standard API 概觀
Digital I/O
◦ pinMode( )
◦ degitalWrite( )
◦ degitalRead( )
Analog I/O
◦ analogReference()
◦ analogRead()
◦ analogWrite()
Standard API 概觀
Advanced I/O
◦ tone ( )
◦ noTone ( )
◦ shiftOut( )
◦ shiftIn( )
◦ pulseln( )
Standard API 概觀
時間函式
◦ millis( )
◦ micros( )
◦ delay( )
◦ delayMicroseconds( )
Standard API 概觀
基本數學函式
◦ min( )
◦ max( )
◦ abs( )
◦ constrain( )
◦ map( )
◦ pow( )
◦ sqrt( )
Standard API 概觀
三角函式
◦ sin( )
◦ cos( )
◦ tan( )
隨機數函式
◦ randomSeed( )
◦ random( )
Standard API 概觀
位元操作
◦ lowByte( )
◦ highByte( )
◦ bitRead( )
◦ bitWrite( )
◦ bitSet( )
◦ bitClear( )
◦ bit( )
Standard API 概觀
中斷相關函式
◦ attachInterrupt( )
◦ detachInterrupt( )
◦ Interrupts( )
◦ noInterrupts( )
Standard API 概觀
串列通訊
◦ serial.begin( )
◦ serial.available( )
◦ serial.read( )
◦ serial.write( )
從進入的觀點看 Standard API
接著重新用嘿客的眼光來分類 Standard
API
直接來自 C/C++ standard library 的函式
◦ pow( )
◦ sqrt( )
◦ sin( )
◦ cos( )
◦ tan( )
從進入的觀點看 Standard API
直接使用 C 語言巨集定義的函式
◦ min( )
◦ max( )
◦ constrain( )
◦ abs( )
Arduino.h
從進入的觀點看 Standard API
直接使用 C 語言巨集定義的函式
◦ lowByte( )
◦ highByte( )
◦ bitRead( )
◦ bitWrite( )
◦ bitSet( )
◦ bitClear( )
◦ bit( )
Arduino.h
從進入的觀點看 Standard API
平台獨立函式
◦ randomSeed( )
◦ random( )
◦ map( )
WMath.cpp
從進入的觀點看 Standard API
硬體相關函式
◦ pinMode( )
◦ degitalWrite( )
◦ degitalRead( )
◦ analogReference()
◦ analogRead()
◦ analogWrite()
◦ tone ( )
◦ noTone ( )
從進入的觀點看 Standard API
硬體相關函式
◦ shiftOut( )
◦ shiftIn( )
◦ pulseln( )
◦ millis( )
◦ micros( )
◦ delay( )
◦ delayMicroseconds( )
從進入的觀點看 Standard API
硬體相關函式
◦ attachInterrupt( )
◦ detachInterrupt( )
◦ Interrupts( )
◦ noInterrupts( )
◦ serial.begin( )
◦ serial.available( )
◦ serial.read( )
◦ serial.write( )
硬體相關函式實作解析
pinMode (pin, mode)
◦ 函式功能:
設定腳位為輸出或輸入模式
◦ 函式實作內容:
1. 由指定的 pin 編號來取得對應的 port
2. 由 port 找到對應的暫存器
3. 依照輸入的 mode, 修改暫存器設定
wiring_digital.c
硬體相關函式實作解析
digitalWrite (pin, value)
◦ 函式功能:
設定腳位輸出電位為 HIGH/LOW
◦ 函式實作內容:
1. 由指定的 pin 編號來取得對應的 port
2. 檢查指定 pin 上的硬體 PWM 是否正在被使用,
如果是則停止它
3. 由 port 找到對應的暫存器
4. 依照輸入的 value, 修改暫存器設定
wiring_digital.c
硬體相關函式實作解析
digitalRead (pin)
◦ 函式功能:
讀取指定腳位的輸入電位, 回傳 HIGH/LOW
◦ 函式實作內容:
1. 由指定的 pin 編號來取得對應的 port
2. 檢查指定 pin 的硬體 PWM 是否正在被使用, 如果是則停止它
3. 由 port 找到對應的暫存器, 讀取並判斷暫存器值, 回傳 HIGH/LOW
wiring_digital.c
硬體相關函式實作解析
analogReference (mode)
◦ 函式功能:
設定 A/D 的參考電壓
◦ 函式實作內容:
把指定的 mode 傳給內部宣告的變數
wiring_analog.c
硬體相關函式實作解析
analogRead (pin)
◦ 函式功能:
讀取 A/D 的電壓數值
◦ 函式實作內容:
1. 依照不同的 Arduino 版本, 由輸入的 pin 編號來取得對應的 analog IN 腳位
2. 設定 analog reference 和 PIN
3. 開始做 A/D 轉換並等待完成
4. 回傳轉換後的值
wiring_analog.c
wiring_analog.c
硬體相關函式實作解析
analogWrite (pin, val)
◦ 函式功能:
使用 Arduino 的 PWM 硬體送出指定 duty 的
PWM
◦ 函式實作內容:
1. 先將指定 pin 切成 OUTPUT
2. 再從指定 pin 編號, 找到相對應的 timer
3. 把 val 設定到 timer 暫存器 (藉由 timer 和
PWM generator 的硬體行為輸出指定 duty 的
PWM)
wiring_analog.c
硬體相關函式實作解析
tone (pin, frequency, duration)
◦ 函式功能:
從指定的 pin 送出 frequency 頻率的 pulse (duty
為 50%) 並持續一段 duration 時間
◦ 函式實作內容:
1. 依據輸入的 pin 編號, 初始化對應的 timer
2. 計算輸入的 frequency 並將結果填入 timer 暫存器
3. 將 duration 與 frequency 換算成 timer counter
4. 開啟 timer 中斷, 在 ISR 中進行/停止 pulse 輸出
Tone.cpp
…
Tone.cpp
硬體相關函式實作解析
noTone (pin)
◦ 函式功能:
停止指定 pin 的 pulse 輸出
◦ 函式實作內容:
1. 依據輸入的 pin 編號, 取得正在使用中的 timer
2. 關閉 timer 中斷
3. 將輸出 pin 的電位設定為 0
Tone.cpp
硬體相關函式實作解析
shiftOut (datapin, clockpin, bitorder, val)
◦ 函式功能:
選用兩個 pin 作為 datapin 與 clockpin, 將 8-bit val
用指定的 bitorder 送出
◦ 函式實作內容:
1. 依照指定的 bitorder 將 val 從 datapin 輸出
2. 改變 clockpin 電位, 使其 high、low 各一次
wiring_shift.c
硬體相關函式實作解析
shiftIn (datapin, clockpin, bitorder)
◦ 函式功能:
選用兩個 pin 作為 datapin 與 clockpin, 用指定的
bitorder 讀取 8-bit 的 value
◦ 函式實作內容:
1. 設定 clockpin 電位為 high
2. 用指定的 bitorder 從 datapin 讀取 1bit value
3. 設定 clockpin 電位為 low
4. 以上三個步驟重複 8 次
wiring_shift.c
硬體相關函式實作解析
PulseIn (pin, state, timeout)
◦ 函式功能:
對指定的 pin 去計算輸入 state (high/low) 的持續時間
◦ 函式實作內容:
1. 由指定的 pin 編號來取得對應的 port
2. 設定 state 的 timeout 時間
3. 等待 pin 的電位變化到非指定的 state
4. 等待 pin 的電位變化到指定的 state 並開始計時
5. 等待 pin 的電位變化到非指定的 state 並停止計時
6. 回傳指定 state 持續的總時間
wiring_pulse.c
硬體相關函式實作解析
millis ( )
◦ 函式功能:
回傳 Arduino 運行後所經過的時間, 單位是
millisencond, resolution 是 1ms
◦ 函式實作內容:
1. 關中斷
2. 讀取目前的 timer0_millis 變數值
3. 開中斷
4. 回傳其值
wiring.c
硬體相關函式實作解析
micros ( )
◦ 函式功能:
回傳 Arduino 運行後所經過的時間, 單位是 microsencond, resolution 是 4us (for 16MHz)
◦ 函式實作內容:
1. 關中斷
2. 先讀取 timer overflow 的次數
3. 判斷此時 timer 是否 overflow, 若是則加一次
4. 讀取目前的 timer counter
5. 開中斷
6. 計算 timer overflow 次數與 counter 值
7. 將計算結果回傳
wiring.c
硬體相關函式實作解析
delay (ms)
◦ 函式功能:
延長一段指定的時間, 單位是 ms, resolution 是
1ms
◦ 函式實作內容:
1. 調用 micros ( ) 得到目前的時間 count 值
2. 每經過 1ms 後把指定的 ms 值減一, 直到 ms
等於 0 為止
wiring.c
硬體相關函式實作解析
delayMicroseconds (us)
◦ 函式功能:
延長一段指定的時間, 單位是 us, resolution 是
1us
◦ 函式實作內容:
1. 依據 CPU 時脈與輸入的 us, 算出需要的
count 總數
2. 將 count 減一, 直到 0 為止
wiring.c
硬體相關函式實作解析
interrupts ( )
◦ 函式功能:
打開 global 中斷
◦ 函式實作內容:
對 SREG 的第 7 bit (Global Interrupt Enable) 填 1
Arduino.h
interrupt.h
硬體相關函式實作解析
nointerrupts ( )
◦ 函式功能:
關閉 global 中斷
◦ 函式實作內容:
對 SREG 的第 7 bit (Global Interrupt Enable) 填 0
Arduino.h
interrupt.h
硬體相關函式實作解析
attachInterrupt(intnum, userFunc, mode)
◦ 函式功能:
掛載使用者的 callback function, 並依據輸入
mode 決定觸發外部中斷的條件
◦ 函式實作內容:
1. 掛載 user function
2. 設定觸發 mode
3. 依據指定的 intnum, 啟用可作為外部觸發功能的腳位
WInterrupts.c
硬體相關函式實作解析
detachInterrupt(intnum)
◦ 函式功能:
卸載指定 intnum 的外部中斷功能
◦ 函式實作內容:
依據使用的 Arduino 版本, 關閉指定的外部中斷
WInterrupts.c
硬體相關函式實作解析
Serial.begin (baud)
◦ 函式功能:
設定串列傳輸的 buadrate
◦ 函式實作內容:
依據使用的 Arduino 版本, 把指定的 baudrate 設定到 baudrate 暫存器中
啟用 RX、 TX、RX complete 中斷、Data
Register Empty 中斷
HardwareSerial.cpp
硬體相關函式實作解析
Serial.available ( )
◦ 函式功能:
判斷 COM port 是否收到數據
◦ 函式實作內容:
回傳 rx buffer 中的 data 個數
HardwareSerial.cpp
硬體相關函式實作解析
Serial.read ( )
◦ 函式功能:
讀取 COM port 讀到的數據
◦ 函式實作內容:
判斷 rx buffer 是否為空, 如果是則回傳 -1, 如果不是則讀取一個 data, 回傳給使用者
HardwareSerial.cpp
硬體相關函式實作解析
Serial.write ( )
◦ 函式功能:
寫入 data 到 COM port
◦ 函式實作內容:
判斷 tx buffer 是否為已滿, 如果是, 則等到有空間時把一個 data 丟進 tx buffer, 如果不是則立刻把一個 data 丟進去
HardwareSerial.cpp
下一個推倒對象: 86Duino
86Duino硬體配置
多功能外部中斷 I/O
USB 2.0
Arduino Leonardo 相容 I/O
Arduino Leonardo 相容 I/O
Arduino Leonardo 相容 I/O
86Duino硬體配置
MicroSD
LAN
PCI-e target
86Duino 軟體設計概觀
IDE 設計原則
◦ 不改變 Arduino IDE 原有功能的前提下, 加入對 86Duino 的編譯及燒錄支援
移植 coreboot + SeaBIOS 做為 86Duino
的開源 BIOS
韌體使用 FreeDOS 做為 OS
◦ 快速開機: 通電 2 秒內 run 起使用者程式
◦ 中斷掛載容易實現
◦ 架構上最接近 Arduino 韌體架構
86Duino 軟體設計概觀
採用 DJGPP 做為 86Duino 的編譯系統
◦ DJGPP: 第一款出現在 x86 上的 GUN gcc
◦ 相容大部分 avr-gcc 的語法
◦ 執行於 x86 保護模式下, 無記憶體使用限制
使用 DJGPP 的問題
◦ DJGPP 為 DOS 程式, 無法直接在 Linux, Mac,
64-bit Win7/Win8 下執行
◦ 目前解決方法: 86Duino IDE 調用 DOSBOX
執行 DJGPP
軟體開發背後堅持的原則
在軟體系統每個環節, 只使用歐噴壽司工具
◦ BIOS: coreboot + SeaBIOS (open source)
◦ OS: FreeDOS (open source)
◦ 編譯系統: DJGPP & DOSBOX (open source)
◦ 程式庫: DJGPP & Arduino上各種第三方開源程式庫 (ex: Allegro)
◦ IDE: Processing/Arduino IDE (open source)
◦ 燒錄軟體: 自己寫 (open source)
Arduino Standard API 在 86Duino
上的移植 直接來自 C/C++ standard library 的 API
◦ DJGPP 與 avr-gcc 相容, 無需移植
直接使用 C 語言巨集定義的 API
◦ 直接沿用 Arduino 原始碼
平台獨立 API
◦ 直接沿用 Arduino 原始碼
硬體相關 API
◦ 重新改寫至 x86 平台
硬體相關 API 在 86Duino 上的實作
pinMode( )
◦ 函式功能:
用以配置腳位為輸出或輸入模式
◦ 程式設計流程:
與 Arduino 流程相同
改填屬於 86Duino 自己的暫存器
wiring_digital.cpp
硬體相關 API 在 86Duino 上的實作
digitalWrite (pin, value)
◦ 函式功能:
設定腳位輸出電位為 HIGH/LOW
◦ 函式實作內容:
1. 檢查指定 pin 上的硬體 PWM 是否正在被使用,
如果是則停止它
2. 由 pin 編號找到對應的暫存器
3. 依照輸入的 value, 修改暫存器設定
wiring_digital.cpp
digitalRead (pin)
◦ 函式功能:
讀取指定腳位的輸入電位, 回傳 HIGH/LOW
◦ 函式實作內容:
1. 檢查指定 pin 的硬體 PWM 是否正在被使用, 如果是則停止它
2. 由 pin 編號找到對應的暫存器, 讀取並判斷暫存器值後, 回傳 HIGH/LOW
硬體相關 API 在 86Duino 上的實作
wiring_digital.cpp
硬體相關 API 在 86Duino 上的實作
analogRead (pin)
◦ 函式功能:
讀取 A/D 的電壓數值
◦ 函式實作內容:
1. 初始化 A/D
2. 設定 PIN
3. 開始做 A/D 轉換
4. 回傳轉換後的值
wiring_analog.cpp
硬體相關 API 在 86Duino 上的實作
analogWrite (pin, val)
◦ 函式功能:
使用 86duino 中的 MCM 之 PWM 硬體送出指定
duty 的 PWM
◦ 函式實作內容:
1. 從指定 pin 編號, 找到相對應的 MCM
2. 將指定 pin 切成 PWM 輸出
3. 把 val 設定至 PWM 相關暫存器
4. Enable PWM
wiring_analog.cpp
wiring_analog.cpp
硬體相關 API 在 86Duino 上的實作
tone (pin, frequency, duration)
◦ 函式功能:
從指定的 pin 送出 frequency 頻率的 pulse (duty
為 50%) 並持續一段 duration 時間
◦ 函式實作內容:
1. 計算輸入的 frequency 並將結果填入設定 PWM
相關暫存器
2. 將 duration 與 frequency 換算成 PWM period
的總個數
3. 開啟 MCM 中斷, 在 ISR 中進行/停止 pulse 輸出
Tone ( ) 的 PWM 主要作為 timer 用, 指定腳位上不會輸出 PWM pulse
Tone.cpp
Tone.cpp
硬體相關 API 在 86Duino 上的實作
noTone (pin)
◦ 函式功能:
停止指定 pin 的 pulse 輸出
◦ 函式實作內容:
1. 關閉 MCM PWM
2. 將輸出 pin 的電位設定為 0
Tone.cpp
硬體相關 API 在 86Duino 上的實作
shiftOut (datapin, clockpin, bitorder, val)
◦ 函式功能:
選用兩個 pin 作為 datapin 與 clockpin, 將 8-bit val
用指定的 bitorder 送出
◦ 函式實作內容:
內容與 Arduino 相同, 未做任何修改
硬體相關 API 在 86Duino 上的實作
shiftIn (datapin, clockpin, bitorder)
◦ 函式功能:
選用兩個 pin 作為 datapin 與 clockpin, 用指定的
bitorder 讀取 8-bit 的 value
◦ 函式實作內容:
內容與 Arduino 相同, 未做任何修改
硬體相關 API 在 86Duino 上的實作
PulseIn (pin, state, timeout)
◦ 函式功能:
對指定的 pin 去計算輸入 state (high/low) 的持續時間
◦ 函式實作內容:
1. 設定硬體 PWM 參數
2. 等待 pin 的電位變化到非指定的 state
3. 等待 pin 的電位變化到指定的 state 並開始計時
4. 等待 pin 的電位變化到非指定的 state 並停止計時
5. 回傳硬體 PWM 的 sample cycle
6. 計算時間並回傳數值
wiring_pulse.cpp
硬體相關 API 在 86Duino 上的實作
millis ( )
◦ 函式功能:
回傳 86duino 運行後所經過的時間, 單位是
millisencond, resolution 是 1ms
◦ 函式實作內容:
回傳 timer_nowtime ( ) 的值
timer_nowtime() 函式實作內容: 在 DOS DJGPP 環境底下調用 uclock(), 取得的時間換算成 millisecond 後回傳
wiring.cpp
common.cpp
硬體相關 API 在 86Duino 上的實作
micros ( )
◦ 函式功能:
回傳 86duino 運行後所經過的時間, 單位是
microsencond, resolution 是 1us
◦ 函式實作內容:
1. 取得 CPU clock count
2. 將 count 以 CPU 時脈換算後回傳
wiring.cpp
common.cpp
硬體相關 API 在 86Duino 上的實作
delay (ms)
◦ 函式功能:
延長一段指定的時間, 單位是 ms, resolution 是
1ms
◦ 函式實作內容:
1. 將 timer_nowtime ( ) 得到的數值加上輸入的
ms 計算出目標時間
2. 無限等待, 直到超過/到達目標時間
wiring.cpp
common.cpp
硬體相關 API 在 86Duino 上的實作
delayMicroseconds (us)
◦ 函式功能:
延長一段指定的時間, 單位是 us, resolution 是
1us
◦ 函式實作內容:
將目前時間減去進入此 function 的時間, 將結果轉換成 microsecond 單位後與輸入的 us 值比較,
直到值大於 us 為止
wiring.cpp
common.cpp
硬體相關 API 在 86Duino 上的實作
interrupts ( )
◦ 函式功能:
打開 global 中斷
◦ 函式實作內容:
對 EFLAGS 的 IF bit 填 1
Arduino.h
io.c
硬體相關 API 在 86Duino 上的實作
nointerrupts ( )
◦ 函式功能:
關閉 global 中斷
◦ 函式實作內容:
對 EFLAGS 的 IF bit 填 0
Arduino.h
io.c
硬體相關 API 在 86Duino 上的實作
attachInterrupt(intnum, userFunc, mode)
◦ 函式功能:
掛載使用者的 callback function, 並依據輸入
mode 決定觸發外部中斷的條件
◦ 函式實作內容:
1. 掛載 user function
2. 設定觸發 mode
3. 依據指定的 intnum, 啟用可作為外部觸發功能的腳位
WInterrupts.cpp
硬體相關 API 在 86Duino 上的實作
detachInterrupt(intnum)
◦ 函式功能:
卸載指定 intnum 的外部中斷功能
◦ 函式實作內容:
關閉指定的 intnum 中斷
WInterrupts.cpp
硬體相關 API 在 86Duino 上的實作
Serial.begin (baud)
◦ 函式功能:
設定串列傳輸的 buadrate
◦ 函式實作內容:
1. 設定鮑率。
2. 設定傳輸資料長度、同位元檢查、停止位元。
3. 清空 TX、RXQueue。
4. 設定 timeout。
HardwareSerial.cpp
硬體相關 API 在 86Duino 上的實作
Serial.available ( )
◦ 函式功能:
判斷 COM port 是否收到數據
◦ 函式實作內容:
回傳 rx queue中的 data 個數
HardwareSerial.cpp
硬體相關 API 在 86Duino 上的實作
Serial.read ( )
◦ 函式功能:
讀取 COM port 讀到的數據
◦ 函式實作內容:
調用 com lib 中的 com_Read ( )
讀取一個 rx buffer 中的值並回傳
HardwareSerial.cpp
硬體相關 API 在 86Duino 上的實作
Serial.write ( )
◦ 函式功能:
寫入 data 到 COM port
◦ 函式實作內容:
調用 com lib 中的 com_Write ( ), 傳送一個 8-bit
值
HardwareSerial.cpp
Arduino 原始碼讀書會
(II) : Bootloader 解析
找出 bootloader 原始碼
Arduino 原始碼根目錄
改過的 Processing IDE 原始碼 (Arduino 相關)
Processing IDE 原始碼
存放編譯結果的目錄
Arduino Bootloader, Standard API 原始碼
Arduino Library 原始碼
hardware → arduino
各種版本 bootloader 原始碼
Standard API 原始碼(共用部分)
各種周邊配套的處理器韌體
Standard API 不共用部分
定義 Board 選單及編譯參數
定義 Programmers 選單及燒錄參數
hardware → arduino → bootloaders
Duemilanove , Diecimila , Nano , Fio .......
Arduino NG or older w/ ATmega8
BT ATmega328 , BT ATmega168
Arduino Robot
LilyPad Arduino USB
Leonardo , Micro , Esplora
LilyPad Arduino ATmega168
Uno , Mini ATmega328 , Ethernet
Mega 2560 , Mega ADK
本次原始碼解析目標 (其它bootloaders可以此類推)
UNO Bootloader
optiboot source
code
已編譯好的各種版本 16 進位檔
Leonardo Bootloader
caterina source
code
已編譯好的各種版本 16 進位檔
各種版本
bootloader 說明
Serial Bootloader 解析
--- 以 UNO 為例
Arduino UNO Bootloader
Arduino UNO 使用了 optiboot,優點:
◦ 佔用空間只有1.5kB
◦ 鮑率115200,上傳程序速度較舊版 ATmega
bootloader 快
◦ 程式碼進行了優化,運行效率較舊版提高,並且無看門狗問題
◦ 支持較多的 ATmega 晶片
與 Bootloader 有關的電路
USB to Serial bridge Arduino 主晶片
USB 接頭
PC USB Serial
與 Bootloader 有關的電路
Serial TX/RX 資料傳輸線
Serial DTR (用於 reset
Arduino)
Arduino
主晶片
USB to Serial
bridge
UNO Bootloader 程式流程
UART init
Watchdog init
是否由 RESET
Pin 引起
執行 Arduino F/W
否
是
依命令把 Arduino
F/W寫入 Flash
把 watchdog 設定成 16ms,
並等待系統自行 reset
reset
每接收一個字元都會重設 watchdog
Watchdog 預設 1s
(timeout 自行 reset)
TX, RX 燈號是由
Atmega16U2(USB to
Serial bridge) 控制
由 serial port 接收
Host 命令
是
否 收到
exit bootloader
命令
Bootloader 原始碼重要細節
判斷 reset 來源, 如果不是
reset button 或 serial DTR
reset, 就呼叫 appStart() 直接執行 Arduino F/W
初始化 watchdog timer = 1s, (如果一秒內 bootloader
沒有從 serial 收到任何資料, 將會自動跳出並執行
Arduino F/W)
Bootloader 原始碼重要細節
從 serial port 讀取字元
Bootloader
main loop
進行 STK500 通訊協定的命令處理
(STK500協定規範請自行參考 Atmel 文件:
http://www.atmel.com/Images/doc2591.pdf)
如果收到 exit 命令, 則設定
watchdog timer = 16ms, 並呼叫 verifySpace() 等待系統自行 reset
Bootloader 重要函式註解
getch ( )
◦ 從 serial port 讀取一字元
putch ( )
◦ 由 serial port 送出一字元
verifySpace ( )
◦ 接受並回應 STK500 命令結尾 token
Bootloader 重要函式註解
watchdogReset ( )
◦ Reset watchdog timer
watchdogConfig ( )
◦ 設定 watchdog timer
appStart ( )
◦ 執行使用者燒錄的 Arduino 韌體程式
Arduino IDE 對 Bootloader 的操作
IDE 將編譯程式, 並透過
bootloader 將編譯結果
上傳到
Arduino
板子上
Arduino IDE 對 Bootloader 的操作
IDE 上傳程式的流程
取得要燒錄的檔案所在路徑和檔案名稱
由板子版本決定燒錄參數
取得燒錄程式路徑與檔名
執行燒錄程式 avrdude.exe
是否燒錄成功?
回傳失敗
是 (由 avrdude.exe 的回傳值決定)
否
(IDE 這部分的原始碼等之後的讀書會再進行詳細解析)
回傳成功
Avrdude 會自行透過 DTR
重啟 Arduino, 以進入
bootlaoder
USB Bootloader 解析
--- 以 Leonardo 為例
Arduino Leonardo Bootloader
Arduino Leonardo 使用 caterina
bootloader
◦ 透過 USB 直接與 PC 通訊, 省掉 USB to
Serial bridge, 降低成本
◦ 使用 LUFA library 來進行 USB 通訊
LUFA 是一套 AVR 系列微處理機專用的通訊程式庫, 支援各種 USB Class
caterina 只用到 CDC Class 的功能
與 Bootloader 有關的電路
USB 通訊線
Arduino 主晶片
與 Bootloader 有關的電路
USB 通訊線
USB 電源輸入
USB 接頭
Leonardo Bootloader 程式流程
HW init
Timer
LUFA init
是否有
POWER-ON
reset
Detach USB 並執行
Arduino F/W
否
是
Timer 中斷設定成每 1ms 觸發一次, 裡面處理 TX/RX LED 與
bootloader timeout
依命令把 Arduino F/W 寫入
flash, 並重置 timeout count
(timeout count
> 8000)
把 timeout count 設定為 7500 等待 timeout
timeout count
不累加
Arduino
F/W 是否存在?
timeout count ++
是 否
點滅 TX/RX LED 是否
timerout
是
否
Timer 中斷副程式
Bootloader 主程式
由 USB 接收 Host 命令,
點亮 TX/RX LED
收到 exit
bootloader
命令
是
否
Bootloader 原始碼重要細節
如果是由 reset button 引起的 reset, 則進入
bootloader
如果不是軟體 reset, 則直接執行 Arduino F/W
如果是 POWER-ON reset, 則直接執行 Arduino F/W
Bootloader 原始碼重要細節
Bootloader
main loop
執行 AVR910 通訊協定命令
LUFA library 的 USB 通訊處理
超過 8 秒沒從 host 收到資
料, 則跳出 bootloader 執行
Arduino F/W
執行 Arduino F/W
切斷 USB 連結 (Arduino F/W 會自己再一次進行 USB 連接行為)
Bootloader 原始碼重要細節
Bootloader timer 中斷副程式,
每隔 1ms觸發執行一次
Bootloader 使用的 timerout count 變數
Bootloader 原始碼重要細節 AVR910 通訊協定處理函式
指定 USB 資料讀取通道
從 USB 讀取字元
判斷 USB 通道是否有資料存在
如果收到 exit 命令, 則等待 0.5
秒再跳出執行 Arduino F/W
進行 AVR910 通訊協定的命令處理
(AVR910協定請自行參考 Atmel 文件:
http://www.atmel.com/im
ages/doc0943.pdf)
Bootloader 重要函式註解
FetchNextCommandByte ( )
◦ 從 USB port 讀取一字元
WriteNextResponseByte ( )
◦ 由 USB port 送出一字元
SetupHardware ( )
◦ 硬體初始化函式
Bootloader 重要函式註解
StartSketch ( )
◦ 執行使用者的 Arduino 韌體程式
EVENT_USB_Device_ConfigurationChanged ( )
◦ USB 事件處理 callback
EVENT_USB_Device_ControlRequest ( )
◦ USB 事件處理 callback
Arduino IDE 對 Bootloader 的操作
IDE 上傳程式的流程 取得要燒錄的檔案所在路徑
和檔案名稱
由板子版本決定燒錄參數
取得燒錄程式路徑與檔名
執行燒錄程式 avrdude.exe
是否燒錄成功?
對目前的 USB serial port 設定1200 bps baudrate 後再關閉
等待 bootloader USB serial
port 出現
Timeout?(5s)
等待 Arduino sketch USB
serial port 出現 將 Arduino sketch USB
serial port baudrate 改成正常值, 並回傳燒錄
成功 Timeout?(2s) 回傳燒錄成功
回傳失敗
回傳失敗
是 否
是 (由 avrdude.exe 的回傳值決定) 否
是 否
Arduino 自行切斷 USB 再重新連線
(IDE 這部分的原始碼等之後的讀書會再進行詳細解析)
(對 Arduino 做軟體 reset)
Leonardo 軟體 RESET 機制的實作
在 IDE 上傳 Arduino F/W 之前, 會先將 USB serial port 設定成 1200bps baudrate, 然後再關閉 serial port
上述行為會讓 Leonardo 上的 F/W 對一個指定記憶體空間填入 bootkey, 然後再自己 reset
reset 後進入 bootloader, 會去判斷是否為軟體 reset (檢查 bootkey), 如果是, 則進入 bootloader main loop 開始接收資料
輕鬆小品, 休息一下~~
複習 Arduino Bootloader 燒錄
接線路(Arduino to Arduino)
接線路(Arduino to MCU)
開啟 ArduinoISP程式檔
將程式上傳到Arduino板子上
選擇板子種類
開始燒錄bootloader
86Duino 的 Bootloader 實作
86Duino Bootloader 行為
86Duino Bootloader 只是開機第一個執行的 DOS 執行檔
行為大部分與 Arduino Leonardo 相同
只有軟體 reset 可以啟動 Arduino 韌體燒錄機制
◦ Arduino Leonardo 則是軟體 reset 和 reset
button 皆會啟動 Adruino 韌體燒錄機制
86Duino Bootloader 行為
86Duino F/W 先整個被接收到記憶體內,
再一次寫入 flash
◦ 當傳輸過程出錯, 不會破壞原有韌體程式
86Duino F/W 會先燒錄至暫存空間, 成功後再映射至韌體存放空間
◦ 當寫入過程出錯, 不會破壞原有韌體程式
Bootloader 原始碼重要細節
86Duinio F/W 最大 SIZE
Bootloader timeout 時間
可燒錄的程式類型
Bootloader 原始碼重要細節
初始化 I/O port
決定 bootloader 的運作模式
判斷是否有軟體 reset
Bootloader 原始碼重要細節
初始化 USB device port
配置存放 86Duino F/W 的記憶體陣列
Bootloader 原始碼重要細節
Bootloader main
loop 開始
Bootloader 原始碼重要細節
讀取要燒錄的 86Duino
F/W 大小
Bootloader 原始碼重要細節
從 USB 接收 86Duino F/W 檔案
Bootloader 原始碼重要細節
86Duino F/W 燒錄
Bootloader 原始碼重要細節
若是執行 Bootloader 燒錄則 reboot, 反之則執行 86Duino F/W
初版程式燒錄的 protocol
沒有使用 protocol (未來會新增)
初版是直接傳送特定格式的資料
TYPE Data length Data
1 Byte 4 Bytes N Bytes
1: 檔案是 bootloader
2: 檔案是 user program
86Duino 軟體 RESET 機制的實作
由 IDE 把 USB serial port 開啟為
1200bps baudrate 後再關閉 (與 Arduino
Leonardo 相同)
86Duino 收到上述行為後, 會對一個 I/O
空間寫入特定值, 然後自己 reset
reset 後進入 bootloader, 判斷上次是否為軟體 reset, 如果是, 則開始接收新的
F/W
Arduino 原始碼讀書會(III) :
Arduino IDE 解析
Arduino IDE 的編譯
取得 Arduino Source Code
第一種方式:
◦ 使用 git 軟體
下載: http://git-scm.com/downloads
◦ 指令: git clone https://github.com/arduino/Arduino.git
取得 Arduino Source Code
第二種方式:
◦ 直接下載 source code
點選這裡可直接下載
● Arduino IDE Source Code https://github.com/arduino/Arduino
● Cygwin http://cygwin.com/install.html
● Sun Java JDK(Java SE Development Kit)
http://www.oracle.com/technetwork/java/javase/downloads/index.html
● Apache Ant Binary Distributions
http://ant.apache.org/bindownload.cgi
● git
http://windows.github.com/
Windows-下載相關應用程式
Setup steps(1)
安裝 Cygwin
安裝 Java JDK
解壓縮apache-ant-x.x.x-bin.zip至ProgrmFiles
Setup steps(2)
控制台\所有控制台項目\系統\進階系統設定\
進階\環境變數
Setup steps(3)
在使用者變數裡新增
變數名稱 變數值
ANT_HOME C:\Program Files\apache-ant-1.9.2 (apache ant資料夾路徑)
JAVA_HOME C:\Program Files\Java\jdk1.7.0_45 (Java JDK資料夾路徑)
Setup steps(4)
在系統變數中
◦ 將apache-ant中的bin資料夾路徑加入Path變數值中
Setup steps(5)
開啟cmd,移至Arduino底下的build
>ant
>ant run
Setup steps(6)
將…\build\windows中的jre.zip裡面的java
資料夾解壓縮到…\build\windows\work
Linux(Ubuntu)所需的應用程式
● Sun Java JDK $sudo add-apt-repository ppa:webupd8team/java
$sudo apt-get update $sudo apt-get install oracle-java8-installer
● Apache Ant $ sudo apt-get install ant
● avr-gcc, avr-g++, avr-libc $ sudo apt-get install arduino
● Make $ sudo apt-get install make
● (git) $ sudo apt-get install git
Setup steps
● $ git clone git://github.com/arduino/Arduino.git
● $ cd ./Arduino/build/
● $ ant
● $ ant run
Mac OSX所需要的應用程式 ● Java for OSX
http://support.apple.com/kb/dl1572
● (git)
– $ sudo port selfupdate
– $ sudo port install git-core
Setup steps
● $ git clone git://github.com/arduino/Arduino.git
● $ cd ./Arduino/build/
● $ ant
● $ ant run
開胃小菜:
Arduino IDE Hacking Tips
切換到 1.5.x 分支
1.5.x 支援 Arduino Due, Yun, …
Arduino 原始碼根目錄
改過的 Processing IDE 原始碼 (Arduino 相關)
Processing IDE 原始碼
存放編譯結果的目錄
Arduino Bootloader, Standard API 原始碼
Arduino Library 原始碼
App →src →processing →app
Arduino 編譯、燒錄相關程式碼
前置處理的相關程式碼, 例如: 字串轉換…
Arduino IDE 選單、按鈕功能程式碼
處理Sketch.ino 相關程式碼
App →src →processing →app →preproc
處理字串問題程式碼
App→src →processing→app→ debug
Arduino 編譯相關程式碼
語言設定
如何客製化呢?
App →src →processing →app
語言設定檔的位置
中英說明文件 (修改後沒有效果)
英轉中 編碼轉換設定檔
修改中文顯示訊息
Resources_zh_tw.po Resources_zh_tw.properties
英文字串 Open… 替換成 開啟… (unicode 編碼)
修改範例
修改後重新編譯 IDE 即可看到結果
中文: 我愛Fablab (unicode)
在下拉式選單新增選項
新增 Burn Bootloader 子選單
新增 Arduino Bootloader 項目
選項動作 callback
修改 Editor.java 的 buildToolsMenu( )
Example:
新增 86Duino Bootloader 項目
修改結果:
在 \hardware 下新增資料夾。 Ex: 86Duino\x86
資料夾底下需要這幾個資料夾和檔案
以上這些檔案不需重新編譯 Arduino IDE,只需新增檔案並且重新開啟 Arduino IDE 即可看到效果。
添加 Arduino 相容板
bootloaders
libraries
編譯、上傳參數設定
standardAPI
Arduino 相容板子的各種參數
添加 Arduino 相容板
boards.txt platform.txt
設定板子名稱
設定板子相關參數
設定板子選單名稱
添加 Arduino 相容板
尚未添加板子前
boards 選單
添加後的 boards 選單
存放 UI 外觀圖檔及設定檔的路徑:\build\shared\lib\theme
修改 UI 外觀顯示
UI 設定檔
theme.txt 文件內容
更換 IDE 的啟動 logo
直接修改或更換此檔案,並且重新編譯Arduino IDE,編譯完成即可看到更改後圖案。
修改 IDE 顯示版本號
修改此字串並重新編譯, 即可更換 IDE 顯示的版本號
build.xml 內容
修改 IDE 顯示版本號
修改的版本號:~1.5.4^^
修改 IDE 視窗左上角小圖示
打開 Papplet.java 檔
修改 IDE 視窗左上角小圖示
把 ICON_IMAGE 陣列內容換成想換的小圖示, 格式必須為 GIF
修改 IDE 視窗左上角小圖示
IDE 視窗 Serial monitor 視窗
修改結果:
修改後重新編譯 IDE 即可看到效果
第一道主菜:
Sketch 編譯機制
Arduino 編譯流程
IDE 視窗 按下編譯按鈕主要做了三個動作:
1. 產生暫存資料夾
(Sketch.java)
2. 前處理 Sketck.ino 檔
(PdePreprocessor.java)
3. 編譯 Sketck.ino 檔
(Compiler.java)
App →src →processing →app
Arduino 編譯、燒錄相關程式碼
處理字串問題相關程式碼
Arduino IDE 選單、按鈕功能程式碼
處理Sketch.ino 相關程式碼
產生存放編譯結果的暫存資料夾
處理 Sketch.ino檔 並開始編譯
Editor.java – DefaultPresentHandler( )
App →src →processing →app
Arduino 編譯、燒錄相關程式碼
處理字串問題相關程式碼
Arduino IDE 選單、按鈕功能程式碼
處理Sketch.ino 相關程式碼
前處理 Sketch.ino檔案
找出 .ino 和 .pde 檔
在 .ino/.pde 檔中加入檔頭修正
(請參考第 1 次讀書會內容)
Sketch.java - preprocess( )
修改Sketch.ino 檔案
App →src →processing →app →preproc
Sketch 前處理的程式碼
前處理 Sketch.ino檔案 PdePreprocessor.java – writeProgram( )
在 .ino/.pde 檔中加入
#include “Arduino.h ”
加入函式原型宣告
加入行號修正
原始 skecth
被 IDE 改寫的 skecth
Sketch 處理前後之差異
找出 sketch 內呼叫的 libraries
找出 include 的 library
PdePreprocessor.java – writePrefix( )
App→src →processing→app→ debug
Arduino 編譯相關程式碼
Sketch 編譯流程重點講解
取得暫存資料夾路徑
取得libraries路徑
Compiler.java - compile( )
Sketch 編譯流程重點講解
編譯Sketch.ino
編譯libraries
編譯standardAPI
產生.elf檔
產生.eep檔
產生.hex 執行檔 並結束編譯
Sketch 編譯流程重點講解
Compiler.java - compileSketch( )
呼叫 CompileFiles 執行 sketch 編譯
Sketch 編譯流程重點講解
Compiler.java - compileLibraries( )
讀取所有被 include 的 libraries
在 tmp 建立 library 資料夾
在 library 資料夾 底下建立 utility 資料夾
編譯 library
編譯 library 底下 utility
Sketch 編譯流程重點講解 Compiler.java - compileCore( )
讀取 standaAPI 路徑
讀取 variant 資料夾路徑
編譯 standaAPI
編譯 variant 資料夾下的檔案
取得 .a 檔編譯 pattern
編譯 .a 檔
加入編譯 .a 檔參數
產生編譯.s檔案的 gcc 命令 執行avrgcc
Compiler.java - compileFiles( )
Sketch 編譯流程重點講解
Sketch 編譯流程重點講解
產生編譯 .c檔案的 gcc 命令
產生編譯 .cpp檔案的 gcc 命令
執行 avrgcc
執行 avrgcc
Sketch 編譯流程重點講解
將編譯結果顯示到 IDE 訊息框內
開新的 process 執行avrgcc 命令
Compiler.java - execAsynchronously( )
配菜:
加入 86Duino 程式編譯
對 IDE 所做的修改
86Duino 編譯系統
DOSBox + DJGPP
DOSBox 是一個跨平台的 DOS 模擬軟體
◦ 在 IDE 的路徑: \build\windows\work\DOSBox-0.74
DJGPP 是一個可在 DOS 下編譯程式的 GNU
gcc
◦ 在 IDE 的路徑: \build\windows\work\DJGPP
86Duino 編譯流程
86Duino原始碼重要細節
如果使用者選的板子是 86Duino, 則跳到
duinocompiler.java 編譯
Compiler.java - compile( )
86Duino原始碼重要細節
設定所需程式的路徑參數
DuinoCompiler.java - compile( )
86Duino原始碼重要細節
Makefile 檔案路徑、編譯出檔案路徑設定
DuinoCompiler.java - compile( )
86Duino原始碼重要細節
Dosbox config設定
Makefile 設定
設定 DOSBox 執行命令
將編譯訊息寫到MESSAGE.TXT
DuinoCompiler.java - compile( )
86Duino原始碼重要細節
Dosbox參數設定
Dosbox執行編譯參數設定
DuinoCompiler.java - writeDosboxconf( )
86Duino原始碼重要細節
設定編譯命令參數
將之前Makefile設定讀取進來並編譯
DuinoCompiler.java - writeMakefile( )
第二道主菜:
Arduino 程式的燒錄機制
實作燒錄的 IDE 原始碼:
app→src → processing → app → debug
本次解析目標
處理 Arduino 程式燒錄動作
處理 Arduino 程式燒錄動作
Arduino 程式燒錄設定檔
hardware→arduino→avr
分別簡要介紹
定義 boards 選單、編譯及燒錄參數
定義編譯及燒錄參數
本次解析目標
燒錄設定檔的用途
Arduino IDE 在燒錄程式之前, 會從燒錄設定檔讀取與板子相關的燒錄參數
這些設定檔中, 使用一種特定的格式, 記錄了每塊 Arduino 板子的差異, 例如:
◦ CPU時脈、燒錄的 protocol、燒錄檔案的最大 size
等等
只要推出一片新的板子, 依照指定的格式加入新參數, 就可以直接套用到目前的 IDE, 不需要重新編譯程式
boards.txt 格式: 以 Leonardo 為例
要在 Boards 選單上顯示的名稱
燒錄工具程式檔名
燒錄用的 protocol 允許燒錄的 binary 最大 size
燒錄用的 baudrate
板子名稱 燒錄 sketch 的參數設定
燒錄 bootloader 的參數設定
設定燒錄時 IDE 清空 serial data 值
設定燒錄時用 1200 baudrate 來 reset Arduino
設定 reset Arduino 後要等待 upload port 出現才可進行燒錄
這次讀書會不講燒錄
bootloader 部分
boards.txt 格式: 以 Leonardo 為例
板子名稱
編譯 sketch 的相關參數 Leonardo 的 CPU 型號(
燒錄會用到的參數)
platform.txt 的格式: 以 Leonardo 為例
在 linux 下, 燒錄工具程式的位置以及燒錄 config 檔的位置
在 windows 和 Mac 下, 燒錄工具程式的位置以及燒錄 config 檔的位置
platform.txt 格式: 以 Leonardo 為例
IDE 燒錄程式時下 的命令列參數
燒錄過程中會被替換成正確參數
實際燒錄一次, 看看輸出訊息
燒錄程式 Avrdude config 檔
Leonardo CPU COM port 燒錄 baudrate
要燒錄的檔案
protocol
IDE 執行燒錄的機制
執行 avrdude 燒錄命令
取得 sketch binary 檔路徑
Reset Arduino
使用 serial bootloader 的板子, 例如: UNO …
使用 usb bootloader 的板子, 例如: Leonardo
詳細流程請參考第 2 次 Arduino 原始碼讀書會內容
設定正確的板子燒錄參數
BasicUploader.java 是否需要 reset
Arduino board
Uploader.java
是
否
等待 upload
port 出現
燒錄工具程式 Avrdude 簡介
普遍用來燒錄 Atmel AVR 的工具程式
跨多種平台, Windows, FreeBSD, linux, UNIX …
使用命令列來完成燒錄動作
參考資料
◦ 馬大的 Avrdude GUI 教學
http://www.coopermaa2nd.blogspot.tw/2011_06_01_archive.h
tml
◦ 詳細的 avrdude 命令, 可見 AVR Tutorial:
http://www.ladyada.net/learn/avr/avrdude.html
◦ Avrdude 原始碼
https://github.com/arduino/avrdude
Avrdude 程式放在哪?
Windows/Mac : hardware\tools\avr\bin
Avrdude 程式放在哪?
Linux : hardware\tools
Arduino 燒錄機制原始碼重點講解
BasicUploader.java → Class BasicUploader
→ uploadUsingPreferences()
取得目前 Serial USB port
對 upload port 設定 1200 baud 再關閉 (soft-reset Arduino)
如果設定要等待 upload port 出現
如果設定 1200 baudrate reset → 執行 USB bootloader 燒錄流程
取得 user 設定的 upload port
等待 Arduino reset 完畢, 重新 取得 upload port (見下頁)
BasicUploader.java → Class BasicUploader
→ waitForUploadPort()
Timeout 時間: 20 秒
找出 Arduino reset 後, 重新連線的 upload port
如果找到 upload port
如果沒有找到 upload port, delay 250ms 再重新尋找
如果超過時限未找到新 upload port (win: 10 秒, 其他: 500ms), 且 user 選擇的 upload port 並未消失, 則回傳 user 選擇的 upload port
將找到的 upload port 回傳
回到 BasicUploader.java → Class
BasicUploader → uploadUsingPreferences()
取得燒錄程式用的命令列pattern
設定正確的命令列參數
執行 avrdude 燒錄工具程式(見下頁)
Uploader.java → Class Uploader→
executeUploadCommand ()
開新的 process 執行燒錄命令
將編譯結果顯示到 IDE 訊息框內
等待燒錄 process 執行完畢
檢查燒錄是否燒錄成功
回到 BasicUploader.java → Class
BasicUploader → uploadUsingPreferences()
如果設定要等待 upload port 出現
檢查 upload
port 是否出現,
timeout = 2s
如果找到 upload port
將 upload port 設定回 9600 baudrate
點心:
加入 86Duino 程式燒錄
對 IDE 所做的修改
加入 86Duino 板子的資料夾結構
建立 boards.txt
建立 platform.txt
修改 BasicUploader.java
加入板子判斷
根據 86Duino 修改燒錄參數
將燒錄工具程式放到 IDE 指定資料夾下
燒錄工具程式必須依不同的平台而放在不同的目錄(下頁說明)
在 build IDE source code 的過程, 燒錄工具程式會自動被移到 IDE 規定的執行時期位置 (
詳見 build.xml 中的設定)
Case 1: Windows XP/7/8
將 window 版的 86Duino 燒錄工具壓縮至 avr_tool.zip 裡面
Case 2: Mac OS X
將 MAC 版的 86Duino 燒錄工具壓縮至 avr_tool.zip 裡面
Case 3: Linux
將 Linux 版的 86Duino 燒錄工具複製到此資料夾下
燒錄工具程式的一點開發經驗
在 ubuntu 遇到的問題
◦ 在 ubuntu 11.04 版本及之後的版本, 內建的 modem
manager 會干擾 USB CDC 裝置的傳輸
◦ https://bugs.launchpad.net/ubuntu/+source/modemmanager/+bug
/1153632/+activity
◦ 這會造成燒錄程序被干擾而失敗
◦ 解決方式: 將 USB CDC 裝置的 PID 和 VID 加入
modem manager 的忽略清單
燒錄工具程式的一點開發經驗
在 Mac OS X 遇到的問題 ◦ USB CDC 裝置的 Call Management Functional Descriptor 中的
最後一個 data 必須為 0x01, 否則 Mac 會認不到 USB CDC 裝置
◦ http://stackoverflow.com/questions/5009593/acessing-
a-serial-to-usb-device-with-i-o-kit
燒錄工具程式的一點開發經驗
在 Mac OS X 遇到的問題 (cont.)
◦ USB CDC 裝置的 Configuration Descriptor 中不可宣告 remote wakeup 功能, 否則會大大延長 Mac 辨識此 USB CDC 裝置的時間
Arduino 原始碼讀書會(IV) :
Arduino Standard Libraries
重點解析 (上)
Wire 函式庫
TWI 介面
TWI 全名: Two Wire Interface
Atmel 當初是為了避免侵犯 I2C 的註冊商標, 特將此介面命名為 TWI
在傳輸資料上仍為 I2C 協定
認識 I2C 介面
它是由 Philips 公司在 1980 年時期,為了方便同一個電路板上的各個組件互相通信,而開發出來的一種介面
全名: Inter-Integrated Circuit
I2C 介面只用兩條訊號線來連接其他元件:
◦ SDA (資料線)
◦ SCL (時脈線)
I2C 元件的連接方式
圖片出處:http://en.wikipedia.org/wiki/I%C2%B2C
Master 端:通常是微處理器,負責發送時脈和slave 位址
Slave 端:通常是感測器元件 (或其他微處理器),每個元件都有自己的位址
Pull-up 電阻:由於 I2C 採 open-drain 設計,master 端若需要送出 HIGH 信號,必須靠上拉電阻接 Vdd,讓信號線電位呈現
HIGH 狀態
詳細的 I2C 通訊協定
I2C 的通訊協定較為複雜, 此次讀書會不詳細說明, 有興趣的朋友可以參考下面的連結內容:
◦ Wiki: http://en.wikipedia.org/wiki/I%C2%B2C
Arduino Leonardo 的 TWI 輸出腳位
SCL 和 SDA 與第 2,
3 腳位共用
Arduino Uno 的 TWI 輸出腳位
R3 版本後, 額外拉出
SDA 和 SCL 腳位
(與 A/D第 A4, A5 腳位共用)
I2C 連接範例: 使兩塊 Arduino Uno 可以互相傳輸資料
圖片出處:Arduino 互動設計入門 旗標出版
Wire 函式功能與使用範例
Wire.begin()
函式功能:
◦ 初始化 I2C 硬體
◦ 把 Arduino 當 I2C master
Wire.begin(address)
函式功能:
◦ 初始化 I2C 硬體
◦ 除了把 Arduino 當 master 之外, 也啟動 I2C
slave 功能
Slave 位址使用傳入的 address 參數 (注意必需為
7-bit 位址)
當 Arduino 在 I2C Bus 上收到 address 時, 會進入
slave 模式
Wire.beginTransmission(address)
函式功能:
◦ 用來啟始 I2C master 的資料傳輸
◦ address 指定外部 I2C slave 裝置的位址 (7-
bit)
注意:
Wire.beginTransmission() 的參數必須輸入 7-bit 形式的
slave address, 但市面上有些產品開發商只提供 8-bit
slave address, 使用者必須自行轉換成正確的形式
例如:
廠商提供的 8-bit address 是 0x6A, 轉換成 7-bit 後, 值是
0x35, 然後將 0x35 填入 Wire.beginTransmission() 中
Wire.write(value)
Wire.write(data, length)
Wire.write(string)
函式功能:
◦ 當 Arduino 是 I2C master 時: 傳送資料給
slave 端
資料會先寫入內部 master queue, 再由中斷副程式傳送出去
◦ 當 Arduino 是 I2C slave 時: 回應資料給
master 端
資料會先寫入內部 slave queue, 再由中斷副程式傳送出去
Wire.endTransmission()
Wire.endTransmission(stop)
函式功能:
◦ 結束資料傳輸動作
啟動中斷副程式, 把 master queue 中的資料傳送出去
◦ 若沒有 stop 參數, 則資料送完後發 I2C
STOP 信號
◦ 若 stop 參數為 true, 則資料送完後改發 I2C
RESTART 信號
Wire.requestFrom(address, quantity)
Wire.requestFrom(address, quantity, stop)
函式功能:
◦ 用來起始 I2C master 的資料讀取程序
address 是要讀取的 I2C slave 裝置位址
quantity 是要讀取的位元組個數
◦ 將啟動中斷副程式讀取所需要的資料並存放在內部的 rx queue 中
使用者可調用 Wire.read() 取出 rx queue 中的值
◦ stop 參數功能與上頁相同
Wire.read()
函式功能:
◦ 從 rx queue 中取出一個 byte
◦ 假如 rx queue 中沒有值, 會得到 -1
Wire.avairlable()
函式功能:
◦ 回傳 rx queue 中還未讀的 byte 數
Wire.onReceive(myHandler)
函式功能:
◦ 用來掛載 I2C slave 模式下的 callback
function (myHandler)
◦ 當 Arduino 在 I2C slave 模式下收到 master
端的資料後, 會調用此 callback function
使用範例:
透過 I2C 連接兩片 Arduino UNO
連接方式:
Master 端 Slave 端
使用範例:
透過 I2C 連接兩片 Arduino UNO
Slave 端要設定位址
Master 端要送出去的位址
兩者要一樣
把程式個別燒入 UNO 後, 可以在 slave 端的
serial monitor 上看到 master 端傳過來的字串
使用範例:
透過 I2C 連接兩片 Arduino UNO
圖片出處:Arduino 互動設計入門 旗標出版
Wire 函式庫原始碼解析
Wire 函式庫資料夾內容
Wire 資料夾
utility 資料夾
Wire 函式庫的原始碼
硬體相關的程式原始碼
Wire 函式庫實作架構
Wire.cpp:
◦ 實作對外的 API 介面
◦ 透過 twi.c 傳送和接收資料
twi.c:
◦ 操縱 ATmega TWI (I2C)硬體
暫存器, 以中斷服務常式 (ISR)
處理傳送/接收資料的行為
Wire lib
TWI lib
Arduino TWI
hardware
ISR
queue
Wire 函式庫原始碼重要細節
設定 Arduino I2C 裝置的 slave 位址
掛載 callback function
初始化 Arduino I2C 硬體
Wire.cpp:begin()
Wire 函式庫原始碼重要細節 Wire.cpp:requestFrom()
調用 twi.c 中的 function, 將取得的值放到 rxbuffer 陣列中
回傳讀到的總數
Wire 函式庫原始碼重要細節
Wire.cpp:write()
Wire.cpp:read() Master 模式
Slave 模式
把資料寫到 txbuffer 陣列
從 rxbuffer 陣列中讀值
Wire 函式庫原始碼重要細節 Wire.cpp:beginTransmission() 以及 endTransmission()
此時才真正把 txbuffer 中的值寫出去
賦予變數初值
預設為 master 模式
Wire 函式庫原始碼重要細節
Wire.cpp:onReceiveService()
掛載 callback function
把中斷副程式中收到的陣列內容拷貝到 Wire.cpp
中的 buffer
重置 read() 會用到的變數
調用 user 的 callback function
Wire 函式庫原始碼重要細節 Wire.cpp:onRequestService()
掛載 callback function
調用 user 的
callback function
twi.c:twi_readForm()
操作 I2C 暫存器, 開始從 I2C Bus 上讀值
等待讀值過程結束
把讀到的值存到目標陣列
初始化相關變數
I2C 硬體仍忙碌時, 循環等待
設置外部 slave 位址以及讀取命令
twi.c:twi_writeTo()
操作 I2C 暫存器, 開始對 I2C Bus 上寫值
等待寫值過程結束
回傳寫值過程的結果
初始化相關變數
I2C 硬體仍忙碌時, 循環等待
設置外部 slave 位址以及寫命令
twi.c:中斷副程式
不同的 I2C
硬體狀態
(狀態暫存器內的值)
掛載中斷副程式
中斷副程式主要運作方式:
發生某種狀態, 就做對應的事情
繼續送下一個 byte
發生錯誤的處理方式
送 stop condition
送 restart condition
Wire 函式庫的移植:
以 86Duino 為例
Wire 在 86Duino 上的移植
與硬體無關的 code:
◦ 例如: Wire.cpp
◦ 可以直接拿來用, 不用修改
(或者做些把 queue buffer 加大等小改進)
與硬體相關 code:
◦ 例如: twi.c
◦ 根據硬體暫存器之間的規格差異, 移植難度也不同
◦ 86Duino I2C 硬體暫存器設定與 ATmega TWI 頗為相似, 因此移植時需要修改的地方並不多
86Duino twi.c:中斷副程式
Master 完成傳送位址、資料的狀態
取得 I2C 狀態暫存器的值
假如要從 I2C Bus 上讀值
讀值的
操作
過程
使 I2C 硬體開始讀值
送 stop condition
等待讀值過程結束
讀最後一筆
86Duino twi.c:中斷副程式 (續)
把 1 byte 寫到 I2C BUS 上
寫值的
操作
過程
其它部分的移植方法都是相同概念:
把硬體暫存器的存取改成 Vortex86EX 的版本
送 stop condition
送 restart condition
假如是最後一筆
Servo 函式庫
RC Servo 簡介
Radio Control Servo
Servo 是一個整合馬達、減速機、控制驅動的系統, 可根據外部命令, 控制馬達轉到目標角度或速度
RC Servo 的應用領域
在遙控模型(遙控車、遙控飛機)上, 用來控制遙控模型的姿態和移動方向
RC Servo
目前也廣泛應用在機器人領域, 做為機器人的關節致動器
RC Servo 的組成
圖片出處:Arduino 開發實戰指南 機械工業出版社
基本功能: 接收主控器傳過來的目標位置信號, 與目前馬達位置比較後, 以 PID 方式, 控制使馬達轉到目標位置
用來測量馬達的轉動角度
用來放大直流馬達的扭力
常見的 RC Servo 控制信號
主流為 PWM, 部分機器人專用伺服機則採用 RS232 或 RS485
Arduino 的 Servo 函式庫只以 PWM 命令的 RC Servo 為控制對象
生產公司 RC Serco 的通訊方式
Robotics(韓國) RS232、RS485
KONDO (日本) PWM、RS232
祥儀 (台灣) PWM
廣營 (台灣) PWM
TOWERPRO (大陸) PWM
PWM 信號(Pulse Width Modulation)
全名為脈波寬度調變
如下圖所示:
Duty Cycle:
在一個週期中,
HIGH 電位的時間所占的比例
Period:
即一個 PWM
pulse 的週期
PWM 信號與 RC Servo 位置關係圖
使用 PWM duty 的寬度來控制 servo 的轉動角度:
duty 範圍通常介於
700us ~ 2300us 之間
PWM 週期通常為 20ms
圖片出處:Arduino 開發實戰指南 機械工業出版社
用 Arduino 連接 RC Servo
電源線
接地
PWM 信號線
PWM servo 連接線:
Servo.attach(pin)
函式功能:
◦ 指定要做為輸出 PWM 信號的 pin 編號
Servo.write(angle)
函式功能
◦ 輸出指定 duty 寬度的 PWM 信號
◦ 參數 angle
可以輸入 0 ~180, 代表轉動角度
也可以輸入實際的 duty 時間值, 例如: 800, 1500
等數值, 單位是 us
Servo 函式庫原始碼解析
Servo 函式庫實作
實作概要:
◦ 使用軟體設定 I/O pin
HIGH/LOW 的方式模擬
PWM 信號
◦ HIGH/LOW 時間長短由
Timer 中斷所控制
◦ Arduino 1 個 Timer 最多控制
12 個 PWM channels
◦ 超過 12 channels, 則需要用到更多 Timer (某些
Arduino 版子只能支援 12 channels)
Servo 函式庫會與 analogWrite() 衝突, 通常不能同時使用
Servo Lib
每個 channel 結構
Timer 中斷副程式
GPIO 輸出
多個 channels 的 PWM 輸出
你可能以為是這樣:
‧ ‧ ‧
Channel 1
Channel 2
Channel n
20ms (PWM period)
實際上, Arduino 是這樣輸出的:
多個 channels 的 PWM 輸出
‧ ‧ ‧
20ms
Channel 1
Channel 2
Channel 3
假設平均 duty 是 1500us (Servo 中點),
則有 20ms / (1500us+ISR overhead) 12
如果所有 12 channels 都輸出最大 duty
2400us, 則 PWM period 會超過
12 x 2400us = 28.8ms
◦ 若你使用的 RC Servo 不允許超過 20ms 的
PWM 週期, 則需注意 Servo 函式庫此特性
Servo Lib 的運作架構
建構 Servo 物件
設定 channel
Timer 中斷副程式
初始化 Timer
Servo.attach()
更新 channel 的
duty 值 Servo.write()
有下一個
channel?
目前時間是否超過 20ms?
將此 channel 設 HIGH
並且設定下一次進入中斷的時間 = duty
設定下次進入中斷的時間為 2us 後
設定下一次進入中斷的時間 = 20ms – 目前時間
新的 20ms
周期開始?
將原 channel
設 LOW
否 是
否
否
是
是
宣告 Servo
Servo Lib 原始碼重要細節 Servo.cpp 中的 attach() 函式
若是第一次使用此 Timer 初始化Timer 中斷
取得 servo pin 對應的 timer 編號
Servo Lib 原始碼重要細節
Servo.cpp 中的
write() 函式
若輸入的值小於 544
把值 map 到 544 ~ 2400
調用 writeMicroseconds
把值傳給中斷副程式中會用到的變數
把 us 轉成 Timer 單位
Servo Lib 原始碼重要細節
將原 channel 輸出 LOW
若是新的周期開始, 就重置 Time counter
指向下一個 channel
Servo.cpp 中的 Timer 中斷程式碼
將此 channel 設 HIGH
假如沒有可用的 channel
設定 Timer = duty
目前時間超過 20ms, 將下次中斷時間設成 2us 後
若目前時間還小於 20ms, 將下次中斷時間設成 20ms
因為用軟體模擬 PWM, 所以實際輸出的
PWM duty 時間會有抖動現象 (jitter)
當使用的 PWM channel 超過 12 組,
Arduino 會啟用更多組 Timer,
多個 Timer 中斷彼此干擾, 有時會惡化
PWM jitter 現象
Servo 函式庫的移植:
以 86Duino 為例
Servo 函式庫在 86Duino 上的
實作改良
用新的算法在 20ms 內模擬更多 PWM
channels
支援硬體 PWM 功能的 I/O pin, 直接以硬體功能輸出 PWM 信號
以一個 Timer 中斷支持最多 45 個 PWM
channels
接上 32 顆 RC Servo 的 Demo
+ = 32 channels
RC Servo
Demo 影片:
86Duino ONE Arduino sensor shield
18 channels
14 channels
https://www.youtube.com/watch?v=1H72d62AB08
86Duino Servo 函式庫的 PWM 輸出
‧ ‧ ‧
20ms
Channel 1
Channel 2
Channel 3
Channel n
PWM 模擬算法概要
排序所有 channels 的 duty 值
根據 duty 大小的排序, 依序在對應的 I/O pin
上輸出 PWM
同時在兩個 I/O pin 上輸出 PWM 波型
使用 Vortex86EX event trigger 功能縮小由於
interrupt latency 造成的 PWM jitter
Servo.cpp 中的 attach() 函式
初始化硬體 PWM pin
初始化以軟體模擬的 PWM pin
Servo.cpp 中的 writeMicroseconds() 函式
更新排序內容, 然後把結果複製到 A 排序陣列中
若是第一次更新, 初始化 B 排序陣列
啟動 Timer
Servo.cpp 中的中斷副程式
外部非正在更新排序
排序方式有被更新過
交換排序陣列 A, B 的
指標, 完成資料更新
週期結束
啟動下一個週期
Servo.cpp 中的中斷副程式(續)
若是最後的 pin, 將它設定為 LOW, 結束 PWM 週期
若是倒數第二 pin, 將自己設定為 LOW, 不啟動下一個 pin
若都以上條件都不滿足, 就把目前 pin 設定為
LOW, 把下一個 pin 設定為 HIGH
PWM Duty 抖動現象
由於在 Servo 函式庫裡 PWM 是用軟體模擬的方式來實現, 所以實際輸出的 duty 會有抖動現象
因抖動造成的誤差範圍與 CPU 特性及軟體模擬算法有關
抖動現象
PWM 抖動現象的影響
在命令解析度比較高的 RC Servo 上, 會造成
servo 輸出軸實際的抖動
一般模型用的低價 RC Servo 解析度較低, 受
PWM 抖動現象的影響不大
Arduino UNO
+
KONDO KRS4014 servo
Servo抖動 實況影片
在 Arduino 和 86Duino 上只使用 1 個 servo
pin, 並量測輸出的 PWM duty 與目標值的誤差, 所測得的數據如下表所示:
各板子的 PWM Duty 抖動實測
板子 目標 duty 實際量測值 duty 誤差範圍
最小 最大
Arduino UNO 1000 us 1000.04 us 1006.42 us 約 6 ~ 7 us
Arduino Leonardo 1000 us 1000.04 us 1007.92 us 約 7 ~ 8 us
Arduino DUE 1000 us 998.200 us 998.280 us 約 1 ~ 2 us
Arduino Mega2560 1000 us 1001.12 us 1008.87 us 約 8 ~ 9 us
86Duino 1000 us 998.64 us 1001.1 us 約 1 ~ 2 us **
** 在 86Duino 有標註硬體 PWM 功能的 I/O pin 上, 誤差則是 0
在 Arduino DUE / Mega2560 和 86Duino 上啟用 45 組 servo pins, 並量測其中一個 pin 輸出的 PWM duty 與目標值的誤差, 所測得的數據如下表所示:
各板子的 PWM Duty 抖動實測
板子 目標 duty 實際量測值 duty 誤差範圍
最小 最大
Arduino DUE 1000 us 998.05 us 1004.68 us 約 2 ~ 5 us
Arduino Mega2560 1000 us 1001.09 us 1076.96 us 約 1 ~ 77 us
86Duino 1000 us 998.70 us 1001.31 us 約 1 ~ 2 us
Arduino 的 Servo 函式庫在超過 12 組 channels 時, 會啟用 2 組以上 Timer
中斷, 以上表格可以看出多組 Timer 中斷互相影響所造成的 jitter 惡化情形
FIRMATA
Command Message
Firmata
Examples
◦ Simple Analog Firmata
◦ Servo Firmata
Firmata.h
Firmata.cpp
Boards.h
Firmata
• Examples
–Simple Analog Firmata
–Servo Firmata
• Firmata.h
• Firmata.cpp
• Boards.h
Firmata.attach(); //掛載要執行的函式
Firmata.processInput(); //接收並執行來自HOST的指令
Firmata
• Examples
–Simple Analog Firmata
–Servo Firmata
• Firmata.h
• Firmata.cpp
• Boards.h
Firmata
• Examples
–Simple Analog Firmata
–Servo Firmata
• Firmata.h
• Firmata.cpp
• Boards.h
#include “Boards.h” //依照不同晶片,套用不同的腳位定義
Firmata
• Examples
–Simple Analog Firmata
–Servo Firmata
• Firmata.h
• Firmata.cpp
• Boards.h
Firmata.begin(); //即為Serial.begin()
Firmata.available(); // 即為Serial.available()
將一包訊息接收完成後,依照不同的command做不同的處理
利用function pointer來執行先前attach的function
switch依照不同的command來做不同的設定
回傳至HOST的訊息也用相同的格式
Firmata
• Examples
–Simple Analog Firmata
–Servo Firmata
• Firmata.h
• Firmata.cpp
• Boards.h
不同的晶片有不同的腳位定義,利用Boards.h來設定
readPort();
writePort();
Firmata @ 86Duino
Firmata @ 86Duino
• Examples
–Simple Analog Firmata
–Servo Firmata
• Firmata.h
• Firmata.cpp
• Boards.h
SD CARD
SD卡的傳輸模式
SPI ◦ Chip Select, Clock, MISO, MOSI
1-bit SD data transfer mode ◦ Command, Clock, DATA0
4-bit SD data transfer mode ◦ Command, Clock, DATA0, DATA1, DATA2, DATA3
FAT檔案系統
SD
./Examples
◦ Files.ino
◦ Datalogger.ino
./utility →檔案系統
SD.h
SD.cpp →開關存取SD卡
File.cpp →開關存取檔案
SD
• ./Examples
–Files.ino
–Datalogger.ino
• ./utility
• SD.h
• SD.cpp
• File.cpp
#include <SPI.h> // Arduino透過SPI介面連接SD卡
File myFile; //建立一個自己的File物件
SD.begin(); //開啟SD卡
SD.open(“example.txt”, FILE_READ); //從頭開始讀
SD.open(“example.txt”, FILE_WRITE); //從尾繼續寫
SD
• ./Examples
–Files.ino
–Datalogger.ino
• ./utility
• SD.h
• SD.cpp
• File.cpp
dataFile.println(dataString); //將字串寫入檔案
dataFile.close(); //儲存並關閉檔案
SD
• ./Examples
–Files.ino
–Datalogger.ino
• ./utility
• SD.h
• SD.cpp
• File.cpp
SdFile.cpp
SdVolume.cpp
FAT檔案系統
Sd2Card.cpp SD卡讀/寫指令、寫入資料
SPI.cpp 透過SPI模式對SD卡存取
SD
• ./Examples
–Files.ino
–Datalogger.ino
• ./utility
• SD.h
• SD.cpp
• File.cpp
SD @ 86Duino
SD @ 86Duino
86Duino透過作業系統運作,SD卡為系統磁碟
86Duino的作業系統內就有FAT檔案系統
86Duino利用stdio.h內的FILE structure來存取檔案
86Duino使用4-bit模式與SD卡連接
SD @ 86Duino
• File.cpp
• SD.h
• SD.cpp
fwrite 寫入檔案
fgetpos 取得目前在檔案內的位置
fread 從檔案讀出
fflush 寫入檔案
fseek 移至檔案的某個特定位置
ftell 取得目前在檔案內的位置
fclose 關閉檔案
fopen 開啟檔案 (r,w,a為開啟模式)
EEPROM Library
EEPROM 簡介
EEPROM, 或寫作E2PROM, 全稱電子抹除式可複寫唯讀記憶體, 是一種可以通過電子方式多次複寫的半導體存儲設備
Arduino EEPROM 大小:
◦ ATmega328 : 1024 Byte
◦ ATmega168, ATmega8 : 512 Byte
◦ ATmega1280, ATmega2560 : 4K Byte
EEPROM 函式
EEPROM.read(address)
◦ 從 address (位址)讀取值出來
EEPROM.write(addr, val)
◦ 將 val (數值)寫到 addr (位址)
EEPROM 函式庫範例
從EEPROM讀值
寫值到EEPROM
EEPROM 函式庫原始碼解析
EEPROM 函式庫原始碼重要細節
呼叫 AVR EEPROM 函式庫從 EEPROM 讀值
引用 Atmel 官方提供的 AVR
EEPROM 函式庫
呼叫 AVR EEPROM 函式庫寫值到EEPROM
EEPROM 是 Arduino 標準函式庫裡實作最簡單的一個
但不代表容易移植
EEPROM 函式庫的移植:
以 86Duino 為例
叫我移植這麼簡單的EEPROM Lib ???
我覺得我被輕視了…
苦主 RD
什麼! 86Duino 沒有
EEPROM!
這麼爛的板子是誰做的啊!
苦主 RD
開個檔案模擬
EEPROM 讀寫,
十分鐘搞定, 嘿嘿...
苦主 RD
這樣是嚇不倒我滴!
結果在測試了 EEPROM 的官方範例之後…86Duino
就葛屁了(DOS 檔案系統損毀) Orz
以檔案模擬 EEPROM 的問題
若 86Duino 在寫入檔案時, 被斷電或重置…
◦ 輕則檔案資料遺失
◦ 重則整個檔案系統損毀 需要設計一個不怕斷電與重置的
演算法…
苦主 RD
嗚嗚, 只好
重寫了~~
86Duino 兩種實現 EEPROM 等價功能的方法
方法一: 利用板上 CMOS 記憶體
◦ 優點: 速度快, 程式簡單, 讀寫時不怕 CPU 斷電
◦ 缺點: 容量只有 200 bytes, 移除 86Duino 板上 RTC 電池會使資料遺失
方法二: 利用 BIOS flash 的剩餘空間
◦ 優點: 容量可達 16 KB, 不需電池仍可保存資料
◦ 缺點: 速度較慢, 需設計容錯算法避免讀寫時斷電的資料損毀 實作較複雜
CMOS bank 方法的實作
CMOS 方法原始碼重要細節
切換 CMOS 分頁(分為兩頁各128 bytes)
設定要讀取的 CMOS 位址
從 CMOS 讀值
CMOS 方法原始碼重要細節
切換 CMOS 分頁(分為兩頁各128 bytes)
設定要寫入的 CMOS 位址
寫值到CMOS
Flash bank 方法的實作
算法原理
算法原理
EEPROM
初始化
初始化物件
SPI Flash /
DRAM
SPI
Flash (c) 清除 SPI Flash (c)
選擇
SPI Flash (a/b)
將 Flash 資料讀取到 DRAM
結束
滿
未滿
EEPROM
write
SPI
Flash(a /b)
清除 SPI Flash (a/b)
將 DRAM 內容寫回
SPI Flash (a/b) (1k)
SPI Flash
(c) 清除 SPI Flash (c)
填 1-bit 0 到
SPI Flash (c)
更改 SPI Flash (a/b)
開始寫資料的位置
寫值到 DRAM
寫值到 SPI Flash(a/b)
結束
滿
滿
未滿
未滿
Flash 方法原始碼重要細節
宣告 SPI Flash ( a )
宣告 SPI Flash ( b )
宣告 SPI Flash ( c )
宣告 DRAM 4k
判斷 SPI Flash ( c ) 是否滿了
判斷目前使用
SPI Flash (a/b)
選擇目前使用
SPI Flash (a/b)
將SPI Flash
填到DRAM 計算DRAM 正確的值
Flash 方法原始碼重要細節
判斷SPI Flash ( a / b ) 是否滿了
Reset SPI Flash ( a / b )
將數值寫回SPI Flash ( a / b )
前面 1k 部分
寫 0 到 SPI Flash ( c )
寫入位置回到初始狀態
Flash 方法原始碼重要細節
填值到 DRAM
決定填值到哪一組 SPI Flash ( a / b )
填值到SPI Flash ( a / b )
Flash 方法原始碼重要細節
從DRAM 讀值
EEPROM 讀取性能測試
板子 平均讀取時間
Arduino Leonardo 實體 EEPROM 1 us
86Duino (CMOS bank) 2 us
86Duino (Flash bank) < 0.1us
測試程式:
連續讀取200 次,
計算平均讀取時間
EEPROM 寫入性能測試
板子 平均寫入時間
Arduino Leonardo 實體 EEPROM 3395 us
86Duino (CMOS bank) 3 us
86Duino (Flash bank) 125 us (沒有跨 1K 邊界時的情況)
510 us (跨 1K 邊界時的情況)
測試程式:
連續寫入200 次,
計算平均寫入時間
Arduino 原始碼讀書會(V) :
Arduino Standard Libraries
重點解析 (下)
DMP Electronics Inc. (瞻營全電子)
SPI 函式庫
SPI
全名: Serial Peripheral Interface
許多電子裝置都有用到它,例如:
◦ SD 記憶卡
◦ 數位/類比轉換 IC (例如 AD7928)
◦ LED 控制晶片 (例如 MAX7219)
◦ 還有很多
這裡無法一一列出…
SPI 介面
SPI 採用四條線連接主機和周邊設備, 這四條線的名稱和用途如下:
◦ SS:周邊選擇線(Slave Select),指定要連接哪一個周邊設備。這條線也稱為 CS (Chip Select 晶片選擇線)
圖片出處:超圖解 Arduino 互動設計入門第一版 旗標出版
SPI 介面 (續)
◦ MOSI:從主機(master)送往周邊(slave)的資料線,(Master Output Slave Input)
◦ MISO:從周邊(slave)送往主機(master)的資料線,
(Master Input Slave Output)
◦ SCK:序列時脈線
圖片出處:超圖解 Arduino 互動設計入門第一版 旗標出版
Arduno UNO 上的 SPI 腳位 有用 UNO 燒錄過 Arduino bootloader 的人,對 SPI 腳位應該不陌生。如下圖所示:
圖片出處:超圖解 Arduino 互動設計入門第一版 旗標出版
UNO 上的 SPI 腳位 (電路圖)
圖片出處:Arduino UNO 官方電路圖
Reset
有 4 支腳
有 3 支腳
Arduno Leonardo 上的 SPI 腳位
圖片出處:超圖解 Arduino 互動設計入門第一版 旗標出版
SS pin 為什麼在這裡!?
(看下頁)
Leonardo 上的 SPI 腳位 (電路圖)
圖片出處:Arduino Leonardo 官方電路圖
SS 與
RXLED 是共用腳位
電路圖中搜尋 RXLED 關鍵字
心中 OS:或許選一支 GPIO 來當 SS 會比較方便
直接與 LED
相連
一路上都是 SS
SPI 的通訊格式
SPI 是一種同步全雙工的序列埠,主機和周邊之間的資料傳遞,都要跟著時脈的 High、Low 一同進行
SPI 介面沒有強制規範時脈訊號的標準,大部分是由
SPI 介面晶片來決定使用哪一種時脈訊號格式
一般來說,SPI 可以由 CPOL 和 CPHA 來組成四種不同的格式:
◦ CPOL:時脈極性,時脈信號的電位基準
圖片出處:超圖解 Arduino 互動設計入門第一版 旗標出版
SPI 的通訊格式 (續)
◦ CPHA:時脈相位,資料在時脈的上升階段或者下降階段被讀取/送出
圖片出處:http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
兩個模式的共同之處:
資料在時脈上升或下降階段,同時收或送資料
SS 要先設為 Low
來啟用裝置
接收與發送 1 個 byte 的過程
SPI 的通訊格式 (續)
CPOL 和 CPHA 配合後,四種組合如下:
與裝置通訊前的確認事項
◦ 資料傳遞的格式
◦ 訊號傳遞位元順序 (bit order):分成高位元先傳
(MSBFRIST) 和低位元先傳 (LSBFIRST) 兩種
◦ 裝置所能接收的最高時脈頻率
圖片出處:超圖解 Arduino 互動設計入門第一版 旗標出版
Arduino 函式庫中的命名
SPI 函式功能與使用範例
SPI.begin()
函式功能:
◦ 初始化 SPI 硬體
設定為 Master 端
預設資料格式是 CPOL = 0,CPHA = 0
預設傳輸速度是 4MBps
預設傳遞位元順序是高位元先傳 (MSBFIRST)
SPI.end()
函式功能:
◦ 關閉 SPI 硬體功能
SPI. setBitOrder()
函式功能:
◦ 設定傳遞位元順序,可輸入兩種參數:
MSBFIRST
LSBFIRST
SPI. setDataMode()
函式功能:
◦ 設定資料格式,可輸入四種參數:
SPI_MODE0
SPI_MODE1
SPI_MODE2
SPI_MODE3
SPI. setClockDivider()
函式功能:
◦ 設定傳輸速度,可輸入七種參數:
SPI_CLOCK_DIV2 :8MBps
SPI_CLOCK_DIV4 :4MBps
SPI_CLOCK_DIV8 :2MBps
SPI_CLOCK_DIV16 :1MBps
SPI_CLOCK_DIV32 :500KBps
SPI_CLOCK_DIV64 :250KBps
SPI_CLOCK_DIV128 :125KBps
SPI. transfer()
函式功能:
◦ 傳送並同時接收資料
SPI 使用範例
傳送/接收資料
Int data;
void setup() {
SPI.begin();
}
void loop() {
digitalWrite(SS, LOW); // 把 SS 設定為 LOW,開始傳送資料
SPI.transfer(0x01); // 送 0x01 給 slave
data = SPI.transfer(0x00); // 讀取 slave 回傳的值
digitalWrite(SS, HIGH); // 把 SS 設定為 HIGH,結束資料傳送
Serial.println(data);
delay(10);
}
這裡傳送的資料是隨便給的實際要看 slave 晶片而定
SPI Library 常常被引用
它們都是 SPI 介面:
◦ Ethernet Library
◦ TFT Library
◦ SD Library
◦ Wifi Library
◦ SpiderL3S Library
Arduino Ethernet shield Arduino TFT
SD shield
Arduino Wifi shield SpiderL3S
(CC3000 Wifi module)
SPI 函式庫原始碼解析
SPI 函式庫資料夾內容
https://github.com/arduino/Arduino/tree/master/libraries/SPI
SPI 函式庫原始碼重要細節 SPI.cpp:begin()
將 SS pin 設定為 output HIGH
確保等一下 enable SPI 後會是
Master 狀態
將 SPI 設定為 Master 然後 enable
SPI enable 後,SCK 和 MOSI pin
的方向需自行定義
SPI 函式庫原始碼重要細節
SPI.cpp:end()
SPI.cpp:setBitOrder()
LSBFIRST
MSBFIRST
SPI 函式庫原始碼重要細節
SPI.cpp: setDataMode()
SPI.cpp: setClockDivider()
設定 divider 暫存器
SPI 函式庫原始碼重要細節
SPI.h: transfer()
資料送完的時候,SPIF 會設 1
(設 1 後第一次對它讀取會清 0)
將要送的資料填入 SPDR 暫存器
回傳收到的值
SPI 函式庫的移植:
以 86Duino 為例
SPI 在 86Duino 上的移植
移植重點:
◦ 將填 ATmega CPU 內的 SPI 暫存器的行為,改成填
86Duino CPU 內的 SPI 暫存器
◦ 其它與硬體無關的程式碼幾乎無需改動,可直接延用
86Duino spi.cpp:begin()
在 86Duino 中,SPI 是一個 PCI 裝置,所以需要先取得 I/O address
設定 SPI 為全雙工
預設速度為 4MHz
預設傳輸格式為
SPI_MODE0
開啟 FIFO 功能
將 SS (實際上是 SPICS) 設定為 HIGH
86Duino spi.cpp:end()
86Duino spi.cpp:setBitOrder()
設定為 LSBFIRST
設定為 MSBFIRST
86Duino spi.cpp:setDataMode()
86Duino spi.cpp:setClockDivider()
限制值的範圍在 1 ~ 4095,因為 divider register 只有 12 bit
設定 SPI 傳輸模式
86Duino spi.cpp:transfer()
檢查 Output FIFO 是否為空
檢查 Input FIFO 是否為空
回傳收到的值
否
是
是
否
送出值
不同 Arduino 板子的 SPI 速度差異
在 Arduino UNO 上,SPI 速度只有固定那七種
在 Arduino Due 及 86Duino 上,可允許更快的 SPI 速度
◦ 在新版的 Arduino IDE 1.5.x 新增了beginTransaction()
來使用更快的 SPI 速度
但目前 Arduino 網站尚未提供關於此函式的使用文件~冏rz
◦ 另一種使用更快 SPI 速度的方式是直接對
setClockDivider() 輸入對應的 divider 數值 (而不是像 SPI_CLOCK_DIV2 這樣的常數)
但使用此法需要先知道不同 Arduino 板子上 SPI 速度的計算方式
86Duino SPI clock 的計算方式
在 86Duino 上,計算 SPI 速度的公式如下:
當 div 設成 1 時,86Duino 最快可輸出的 SPI
clock 速度:50MHz
100MHz / (2 div) div 是 divider 值,範圍:1 ~ 4095
Ethernet 函式庫
Arduino Ethernet shield 簡介
操作電壓:5V
控制晶片:W5100
速度:10/100 Mbps
通訊介面:SPI
WIZnet W5100 datasheet:
http://www.wiznet.co.kr/UpLoad_Files/ReferenceFiles/W5100_Datasheet_v1.2.2.pdf
Ethernet shield 外觀
WIZnet W5100
Arduino Ethernet shield 電路
對 Arduino 的通訊介面:SPI
輸出:LAN 的差動信號
圖片出處:http://arduino.cc/en/uploads/Main/arduino-ethernet-shield-06-schematic.pdf
Arduino Ethernet shield 工作原理
OSI 模型: http://linux.vbird.org/linux_server/0110network_basic.php#route_route
將每個 frame
轉換為 0、1
信號
網路接頭
使用 IEEE
802.3 封包協議來傳送 MAC
支援的通訊協定
軟體處理部分
硬體處理部分
Ethernet 使用範例:ChatServer
把 Arduino 當作 WebServer,等待 client 連線
設定網路卡實體位址、IP位址、子網路遮罩和閘道器位址
設定伺服器的 port 為 23,預設使用 Telnet 服務
啟動乙太網路連線
啟動伺服器
ChatServer:loop()
聆聽用戶的連線請求
如果收到用戶的連線請求
第一個用戶連線,送出
Hello 字串
檢查用戶是否有送字元過來,如果有,則返回相同的字元給所有已連結上的用戶
ChatServer 執行結果
PC:putty.exe 86Duino :Serial Monitor
Server 端 Client 端
Ethernet Library 原始碼概觀
由於 Ethernet Library 相當龐大,這裡不深入討論上面每個 .cpp 裡的函式細節
只討論和移植有關的大架構
Ethernet 資料夾
從 DHCP 伺服器取得動態 IP
從 DNS 伺服器將網址轉換成實際位址
Ethernet 初始化函式
建立 client 端,以 TCP/IP 方式連線
建立 server 端,以 TCP/IP 方式連線
以 UDP 方式連線
https://github.com/arduino/Arduino/tree/master/libraries/Ethernet
Ethernet / utility 資料夾
https://github.com/arduino/Arduino/tree/master/libraries/Ethernet/utility
透過 SPI 介面將命令送給 W5100 晶片
Arduino 提供的底層 API
(部分遵循 BSD Socket 標準,但沒有完全相容)
Arduino Ethernet Library 的架構
Socket
W5100 命令
Arduino Ethernet Shield
Server Client UDP Ethernet
SPI Bus
初始化函式
呼叫
取得/設定 IP、子網路遮罩、閘道器位址等等
Ethernet Library
在 86Duino 上的移植
86Duino 內建的 Ethernet 電路
網路卡內建在 CPU 中 (含 PHY)
Vortex86EX
CPU LAN
差動信號輸出
Transformer
86Duino
底板
(PCI 裝置)
網路接頭
DOS 下的網路驅動程式
內建在 86Duino 韌體裡的 NDIS driver
NDIS: Network Driver Interface Specification,參考資料:
◦ http://en.wikipedia.org/wiki/Network_Driver_Interfac
e_Specification
DOS 下的 Socket Library
BSD Socket 規範: http://web.mit.edu/macdev/Development/MITSupportLi
b/SocketsLib/Documentation/sockets.html
DOS 下常用的 Socket Library:
◦ Watt32: http://www.watt-32.net/
◦ SwsSock (86Duino 使用的 Socket Library):
http://www.softsystem.co.uk/products/swssock.htm
◦ …
Ethernet Library 移植方式
Socket
W5100 命令
Ethernet Shield
Server
Client UDP
Ethernet 沿用大部分 Arduino
的 API 名稱,但內容全部用標準 Socket
API (SwsSock library
提供) 來實作
虛擬層 (NDIS to Package)
NDIS driver
86Duino LAN
Arduino Ethernet Library 86Duino Ethernet Library
移植
替換
移植後的差異:
以 Ethernet Server:begin() 為例
86Duino
Arduino 呼叫 Socket API
WiFi 函式庫
WiFi shield 簡介
工作電壓:3.3V
控制晶片:AT32UC3A1256
無線模組:HDG104
無線網路通訊標準:802.11/g
資料加密方式:WEP 或 WPA2
與 Arduino 的通訊介面:SPI
WiFi shield 外觀
AT32UC3A1256 datasheet:
http://www.gaw.ru/pdf/Atmel/AVR_32/AT32UC3A0512_0256_0128_1512_1256_1128s.pdf HDG104 datasheet:
http://datasheet.octopart.com/HDG104-DN-3-H%26D-Wireless-datasheet-11793609.pdf
AT32UC3A1256 HDG104
Arduino WiFi shield 電路
圖片出處:http://arduino.cc/en/uploads/Main/arduino-wifi-shield-schematic.pdf
AT32UC3
A1256
電壓準位從
5V 轉成 3.3V
對 Arduino 的通訊介面:SPI
透過內部第二組
SPI Bus
和無線模組通訊
無線模組
HDG104
WiFi 使用範例:WifiCharSever
輸入你的 SSID
輸入 WEP/WAP2 形式的密碼
檢查 WiFi shield
是否存在
WifiCharSever:setup()
begin() 會不斷嘗試連線
假如尚未連線成功
連線成功後,啟動伺服器
印出目前的 SSID,取得的 IP,以及訊號強度
WifiCharSever:loop()
聆聽用戶的連線請求
如果收到用戶的連線請求
第一個用戶連線,送出
Hello 字串
檢查用戶是否有送字元過來,如果有,則返回相同的字元給所有已連結上的用戶
WifiChatServer 執行結果
PC:putty.exe 86Duino :Serial Monitor
Server 端
Client 端
WiFi 原始碼概觀
功能皆類似
Ethernet Library
https://github.com/arduino/Ardu
ino/tree/master/libraries/WiFi
Ethernet
WiFi 原始碼概觀
https://github.com/arduino/Arduin
o/tree/master/libraries/WiFi/utility
Ethernet/utility
底層的 SPI
driver
底層的 SPI
driver
Socket
Interface
Socket
Interface
WiFi Library 在 86Duino 上的移植
移植重點:WiFi Library 與硬體相關的只有 SPI 的部分,其它部分程式碼與硬體無關,可以在 86Duino 上直接延用
◦ 將 spi_drv.cpp 存取方式換成 86Duino SPI 的存取方式
◦ 上層的 wifi_drv、server_drv、WIFIClient
等等都不用動
移植後的 spi_drv.cpp 差異 86Duino Arduino
替換
替換
GSM 函式庫
GSM 簡介
GSM 全名 Global System for Mobile
Communications (全球行動通訊系統)
在台灣,GSM 的頻段是 900、1800MHz,由於在同一頻段內要讓多人使用又要抗干擾,所以實際上傳送數據的速度只達到 9.6Kbps(相當於看一個 200KB 的網頁要需要等 20 幾秒)
GPRS 簡介
GPRS 全名 General Packet Radio Service,在現有 GSM 技術上,加上數據交換節點(具有處理封包的能力),配合動態分配頻段來增加使用率,在連線人數不多的情況下,速度可達 56Kbps ~ 100多Kbps,用來看網頁和圖片,已經綽綽有餘。
Arduino GSM shield 簡介
使用 M10 晶片:
有 GSM + GPRS 功能
支援 4 種頻段:
GSM850MHz
GSM900MHz
DCS1800MHz
DCS1900MHz
支援 TCP/UDP 和
HTTP 網路通訊協定
上傳和下載的速度
最高可達 85.6Kbps
Arduino 透過 AT command
控制 GSM shield
GSM shield 外觀
圖片出處: http://arduino.cc/en/Main/ArduinoGSMShield
Arduino GSM shield 電路
使用 PIN2 和
PIN3 做為
Serial 腳位
增加訊號的驅動能力
M10
圖片出處:http://arduino.cc/en/uploads/Main/arduino-gsm-shield-schematic.pdf
使用 GSM shield 前應注意的事項
GSM Library 中預設使用 software serial 來傳送 AT
command:
◦ M10 晶片支援的 AT command:http://arduino.cc/en/uploads/Main/Quectel_M10_AT_commands.
◦ GSM shield 使用 PIN2 和 PIN3 做為預設的 software serial 輸出腳位,不使用 hardware serial (PIN0 和 PIN 1) 來傳輸資料,因為這會與 Arduino 燒錄程式的腳位衝突。
GSM shield 在使用 modem 傳送資料的時候用電量稍大,建議接上外部電源 (700mA ~ 1A),而不要只使用 USB 供電。
需要一張 SIM 卡才能撥打電話、發簡訊以及上網(必須先向電信業者開通上網功能)
使用 GSM shield 前應注意的事項(續)
在 Atmega 2560 上,GSM shield 需要額外的跳線:
第一步:把 PIN2
的腳往外扳
第二步:將 PIN2
與 PIN10 相連
第三步:插上 GSM
shield
這是因為具有 toggle trigger 的中斷腳位,在 UNO 上是 PIN2 ,在 2560
上是 PIN10 的緣故。(在 Leonardo 上則要換成 PIN8)
圖片出處: http://arduino.cc/en/Main/ArduinoGSMShield
用 Arduino + GSM shield
打電話和接電話
準備
首先,拿一張可用的 SIM 卡,插入 GSM shield
圖片出處: http://arduino.cc/en/Guide/ArduinoGSMShield#toc4
用的是 mini
SIM 卡,手機用的是 micro
SIM 卡,需要使用轉卡才不會掉出來
MakeVoiceCall 範例程式
使用者可透過 Serial monitor 輸入電話號碼來撥打給對方
#include <GSM.h>
#define PINNUMBER “"
GSM gsmAccess;
GSMVoiceCall vcs;
String remoteNumber = “";
char charbuffer[20];
void setup()
{
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
如果已經在手機上已取消 PIN 碼,這裡就不用輸入 PIN 碼
初始化 GSMAccessProvider class
初始化 GSMVoiceProvider class
MakeVoiceCall:setup() …
// Start GSM shield
// If your SIM has PIN, pass it as a parameter of begin() in quotes
while(notConnected)
{
if(gsmAccess.begin(PINNUMBER)==GSM_READY)
notConnected = false;
else
{
Serial.println("Not connected");
delay(1000);
}
}
Serial.println("GSM initialized.");
Serial.println("Enter phone number to call.");
…
begin() 完成基本的初始化
回傳結果都沒問題,就可以開始打電話了
上傳 MakeVoiceCall 之後…
3. 撥出電話
4. 對方接通,開始通話
5. 對方掛斷電話,結束通話
1. 打開 serial monitor
2. 輸入要撥打的電話 (前面需要加上台灣區碼:+886),輸入完後按 Send
用 GSM shield 接電話
ReceiveVoiceCall 範例程式 (內容與 MakeVoiceCall 類似,所以略過)
上傳後打開 serial monitor:
接到一通電話,並顯示電話號碼
雙方通話中
初始化完成,等待別人撥電話進來
Send ‘\n’ 後結束通話
GSM Library 原始碼概觀
GSM Library
到 Arduino 的 GitHub 網站,看看 GSM 資料夾內的檔案: ◦ https://github.com/arduino/Arduino/blob/master/libraries/GSM
GSM Library 檔案
GSM Library 裡面的檔案很多,我們可以大致分類如下:
負責管理 SoftSerial
使用的 buffer
負責送出 PIN 碼、偵測 GPRS 網路等等
負責撥號和建立語音連線
GSM Library 檔案 (續)
負責收發簡訊
在 GPRS 網路上,建立 server/client 端
以 Software Serial 的方式收送 AT command
GSM Library 檔案 (續)
PIN 碼管理工具
Modem 測試工具
GPRS 網路掃描工具
GSM Library 架構
GSM class (負責送出初始化命令)
GSM Soft Serial class
GSM Shield
GSM_SMS
class
GSMClient
Class
GPRS class (負責送出初始化命令)
GSMClient
class
GSMServer
class
1. 每個 class 包含自己專用的 AT
command
2. 當使用者呼叫
GSM API 時,該
API 會把自己的處理程序註冊到一個特殊列表中,讓
SoftSerial 中斷副程式以 callback 的方式來呼叫
GSM Library 運作方式:
以 GSM.begin() 為例
Begin()
ModemConfiguration()
ModemConfigurationContinue()
manageResponse()
GSM3AccessProvider.cpp
GSM3SoftSerial.cpp
Recv() 呼叫 callback 函式
openCommand()
GSM3ShieldV1ModemCore.cpp
manageMsgNow()
中斷副程式
API
其他功能的運作方式皆大同小異
ModemConfigurationContinue()
在其他功能的 .cpp 中,看到以
Continue 結尾的函式,函式內容都差不多,只差在 AT command
不同
第一個 AT
指令
檢查回傳結果 第二個 AT 指令
檢查回傳結果 第三個指令
檢查回傳結果 第四個指令
此函式內容可以說是 AT
command 的腳本。
它最後會被 SoftSerial 的中斷副程式呼叫。
GSM Soft Serial 運作方式:recv() GSM 以既有的 lib 為基礎,再寫一個新的 SoftSerial
用 digitalRead() +
delay() 讀取 8bit 值
假如 buffer 滿了
就送 XOFF 回去
請對方暫緩傳送
檢查是不是特殊字元 ‘w’
假如不是,就放入 buffer 中
這部分是
新增的
GSM Soft Serial 運作方式 (續)
buffer 滿了,則呼叫
callback 處理函式
收到 LF 符號,則呼叫
callback 處理函式
收到 space 符號,則呼叫
callback 處理函式
這部分是
新增的
GSM Library
在 86Duino 上的移植
GSM Library 在 86Duino 上的移植
移植重點:
GSM Library 與硬體相關的只有
SoftSerial 的部分,其它部分程式碼與硬體無關,可以在 86Duino 上直接延用
GSM Library 在 86Duino 上的移植
GSM3SoftSerial.cpp
◦ 直接套用 86Duino softserial Library 的實作方式,然後再加入 GSM SoftSerial 新增的那些 code
◦ 預設的腳位更動,RX 是用 PIN42 (有
attachInterrupt 功能的),TX 是用 PIN3
小改進:
◦ 新增 Hardware Serial 的通訊方式,可以選用
COM1 ~ COM3 其中一組來通訊。
◦ 使用 Hardware Serial 可以保證 GSM Library 工作更穩定
加入 Hardware Serial 功能
不使用中斷
◦ 因為 SoftSerial 已經與各項功能的 callback function
和軟體 flow control 寫在一起,為了實現相同的功能,可能要大改既有的 Hardware Serial library。
改用 polling
◦ 經過觀察,主要 class 在運行過程中,都會持續呼叫名為 ready 的函式,用來得到送出 AT command
的回傳結果
◦ 因此就把 hardware serial 函式加入 ready(),實現原來 SoftSerial 會做的事
GSM3ShieldV1ModemCore.cpp
: manageReceivedData() GSM3SoftSerial.cpp:recv()
添加的
Hardware
Serial code
呼叫
行為相同
原來的 Software
Serial code
GSM3ShieldV1BaseProvider.cpp:ready()
小插曲-遇到 Arduino GSM
Library 的 Bug GSM3VoiceCallService.cpp
GSM3ShieldV1VoiceProvider.cpp
呼叫建構子
小插曲-遇到 Arduino GSM
Library 的 Bug
Bug 來由:
◦ 靜態物件 GSM3VoiceCallService 的實作方式要求另一個靜態物件 GSM3MobileVoiceProvider 先被初始化
◦ 但這兩個物件的初始化順序與編譯器有關,不同平台的編譯器會得到不一樣的結果
◦ 錯誤的初始化順序會造成 GSM3VoiceCallService 使用到尚未給定初值的 theGSM3MobileVoiceProvider
變數而當機
SoftwareSerial 函式庫
SoftwareSerial 簡介
簡單的說,就是用 GPIO 來模擬硬體 UART
的 TX、RX 行為
在 Arduino UNO 上,預設使用 PIN2 和 PIN3
做為 RX 和 TX。
SoftwareSerial 函式庫不允許隨意指定腳位,因為 RX 的行為需要由具有 toggle 中斷的
PIN 腳來模擬。
具有 toggle 中斷的 PIN 腳
Arduino UNO
◦ PIN2
Arduino Mega2560:
◦ PIN10 ~ 15,PIN50 ~ 53,A8 ~ A15
Leonardo:
◦ PIN8 ~ 11,PIN14 ~16
Start bit Stop bit 8 bits data
Arduino 上,RX 行為的模擬方法:
中斷觸發,進入中斷副程式
delay
digitalRead()
delay delay delay delay delay
……
delay delay
digitalRead() digitalRead()
delay
SoftwareSerial 函式庫原始碼解析
SoftwareSerial 函式庫資料夾內容
https://github.com/arduino/Arduino/tree/master/libraries/SoftwareSerial
鮑率延遲時間表
這些數值在不同的 CPU 都不一樣,需要實際 tune 過才能確定
SoftwareSerial.cpp: SoftwareSerial()
Start bit 的 delay 資料和 Stop bit 的 delay TX 的 delay
SoftwareSerial 函式庫原始碼重要細節
SoftwareSerial.cpp: SoftwareSerial()
初始化數值
設定 Tx, Rx 腳位
SoftwareSerial 函式庫原始碼重要細節
設定鮑率
設定傳輸, 接收 延遲時間
等待中斷發生 開始接收資料
SoftwareSerial.cpp: begin()
SoftwareSerial 函式庫原始碼重要細節
等待接受訊號觸發中斷
觸發中斷執行 recv()
SoftwareSerial.cpp: ISR()
SoftwareSerial.cpp: handle_interrupt()
SoftwareSerial 函式庫原始碼重要細節 SoftwareSerial.cpp: available()
SoftwareSerial.cpp: read()
回傳 buffer 資料數量
接收資料
SoftwareSerial 函式庫原始碼重要細節
傳送資料
傳送延遲
傳送延遲
傳送延遲
SoftwareSerial.cpp: write()
SoftwareSerial 函式庫原始碼重要細節
讀取 8bit 資料
Start bit 延遲
Stop bit 延遲
SoftwareSerial.cpp: recv ()
SoftwareSerial 函式庫原始碼重要細節
判斷 buffer 是否滿了
將資料丟進 buffer
SoftwareSerial.cpp: recv ()
SoftwareSerial 函式庫的移植:
以 86Duino 為例
SoftwareSerial在 86Duino 上的移植
移植重點:
◦ 在 86Duino 上,要使用具有 attachInterrupt() 功能的 PIN 腳來模擬 RX(TX 與原來一樣),掛中斷的方式與 Arduino 不同
◦ 鮑率延遲的時間表需要實際 tune 過一次
◦ 其餘部分不用動到
86Duino 的鮑率延遲時間表
SoftwareSerial函式庫原始碼重要細節
SoftwareSerial.cpp: listen()
中斷觸發, 執行 handle_interrupt
判斷中斷觸發腳位
SoftwareSerial函式庫原始碼重要細節 SoftwareSerial.cpp: write()
傳送資料
傳送延遲
傳送延遲
傳送延遲
SoftwareSerial函式庫原始碼重要細節 SoftwareSerial.cpp: recv ()
將腳位切換成Encoder
判斷 buffer 是否滿了
將資料丟進 buffer
TFT 函式庫
TFT shield 簡介
工作電壓:5V
螢幕解析度:160 x 128 pixels
色彩深度:16bit (R-5bit, G-5bit, B-6bit)
通訊介面:SPI
附帶 microSD 插槽
TFT 外觀
TFT 函式庫原始碼概觀
https://github.com/arduino/Arduino/tree/master/libraries/TFT
畫圖函式 (點、線、圓等等)
初始化函式
TFT函式庫原始碼重要細節
TFT.h
初始化自定義腳位
初始化 TFT 螢幕
高階的畫圖函式
低階的繪圖函式
(含初始化命令)
TFT 函式庫原始碼重要細節
設定螢幕寬
設定螢幕高
初始化 TFT (送出連續的初始化命令)
設定畫面旋轉方向
TFT.cpp
初始化自定義腳位
TFT 函式庫原始碼重要細節
以 drawFastVLine() 為例:
透過 SPI 函式送命令,其它函式也是一樣
CS 設為 LOW,RS 設為 HIGH
CS 設為 HIGH
utility / Adafruit_ST7735.cpp
TFT Library 在 86Duino 上的移植
移植重點:
◦ 將直接存取 ATmega 暫存器的程式,用
digitalWrite()、 digitalRead() 函式替換
◦ 延用 86Duino 既有的 SPI 函式庫,相關的
函式如 SPI.write() 不需要改動
TFT Library 在 86Duino 上的移植
86Duino: Ada_ST77.cpp Arduino: Adafruit_ST7735.cpp
移植
TFT demo 影片
https://www.youtube.com/watch?v=eZefqL6FtOE