2016年3月29日 星期二

Deep C 心得 -- Part 1 (C)

為了增進對C語言的認識,我這幾天看了些成大資工"2016年系統軟體課程"分享的資料
以下是我看Olve Maudal 的投影片:Deep C 的心得
(為了了解內容我有參考wiki跟jserv大的C語言講座)
本文只有提到C的部份,C++的部份會另外寫。
投影片連結:
http://slideshare.net/olvemaudal/deep-c

1. 指出以下程式碼的結果與缺失

  • 需 #include <stdio.h> 以明確宣告printf函式
    • 若使用C++編譯器編譯會失敗,因為C++編譯器需要明確(explicit)宣告使用函式。
    • 一般的C編譯器在函式未宣告的情況下會自動新增implicit宣告函式。
      • 當連結(link)到標準函式庫,編譯器會找到prinf這個函式
  • main為int型態卻沒有回傳值(return 0)
    • C99或更新的版本,main的回傳值為執行的成功(0)與否
    • 但是ANSI C或K&R C,在這種情況下回傳值為未定義(undefined)
      • 有可能是3(因為printf的回傳值為輸出的char數)
  • 標準C應該要用main(void) (void表示不需要額外參數)
  • 標準C表示程式碼必須以一個空行做結。
 2. C語言的變數宣告
  • static變數和global(全域)變數(被宣告在main外面)被宣告時自動被預設為0,非static變數(在{}之內時被自動設為auto)宣告時預設值為undefined,這是因為
    • "預設變數為0"這個動作是要額外消耗CPU的,而C語言是非常在乎程式效能的,所以在不需要的情況下不會刻意去做這種消耗CPU的事。
    • static跟global變數是被存在Data(有初始值)或BSS(無初始值)內,這兩個區塊中的變數都是在編譯期就會固定住變數的位址,所以被預設為0不會影響到執行的效率。此兩種變數的生命期跟程式的執行期一樣長
    • 而auto變數是被儲存在stack裏面,其位址並沒有被固定住。此種變數的生命期只限於宣告位置前後的{}。
  • static變數的影響力只限宣告該變數的檔案,global變數的影響力括及其他的obj檔案。
  • 但是在C++的情況下,static變數是被預設成"某個預設值"(naive的情況就是0)
  • 使用malloc/calloc/realloc宣告的某pointer空間,其生命期截止於free該pointer的時候
3. 預測以下程式碼的結果

  • a預設值為undefined
  • 但在除錯模式的情況下,執行期有可能代為執行memset使得預設值為0
  • 依本程式來看,a宣告的時候有可能都出現在stack內同樣的address中,所以本程式的執行結果可能會顯示出3個連續的數列(ex: 1,2,3 or 0,1,2 or 22,23,24),不過這視編譯最佳化的結果而定。
4.  最佳化參數的影響
預測以下程式碼的結果並解釋原因

  • 沒最佳化的情況會是42
  • 有最佳化的可能情況:
    • 省去bar(),反正沒影響
    • foo會變成main內的inline函式(反正沒其他函式會呼叫,這樣節省函式調用時間)
    • 所以結果可能會變成某個奇怪的數字(undefined)
  • 平常儘可能用最佳化參數,以發現潛在的問題。
5. 關於sequence point(順序點):
順序點是副作用發生與否的分界點,在順序點左邊的表示已經發生,右邊的還未發生。在兩個順序點之間變量只能改變一次,不然結果會是undefined。ex: (i=i++; 在分號前i的值改變兩次,結果會是undefined)。C/C++裡的順序點其實不多,大致如下:
  • &&, ||, 或?(三元運算符)
    ex: (*a++ != 0 && *b++ != 0) -> 會先算出*a++ != 0,再算出*b++ != 0,然後比較&&
  • 完整表達式(用;當然這有點廢話)
  • 以逗號分開的變數初始化
    ex: int a[3] = {b++,c--,d(101)};  int x=a++, y=a++; 一定是從左到右(好像是從C++11開始?)
  • 函式的進入點
    ex: f(i++) + g(j++) + h(k++), 進入函式f/g/h的是增加過後的i/j/k,不過f/g/h哪個函式先開始呼叫並沒有指定(unspecified behavior)。
  • 涉及到I/O
    ex: printf("Bug %n %d", &a, 11);  在printf %d前會先執行%n(類似fprintf出多少char)的動作(這裡有爭議)
6. 關於sizeof
  • 此為size_t變數,在32bit的機器上通常為unsigned int,而在64bit的機器上通常為unsigned long
  • 所以printf的時候有時會出問題,不能都用%u或%lu。C99以後可以用%zu代表size_t的變數
7. 關於word alignment(對齊):變數大小對齊有助於改善效能
預測以下程式碼的結果並說明原因(假設為64bit機器):

  • 若有類似gcc中-fpack-struct的參數去收緊struct大小,結果會是4,1,9(不做alignment)
  • 有word alignment的情況下會是4,1,12
  • 若是在struct X中多加一個char d;
    • 如果char d;在int c;後面,可能會變成4,1,16
    • 如果char d;在char b;後面,可能會維持4,1,12
    • 結論是看最佳化的演算法而定
  •  若是在struct X中多加一個char* d;
    • 以alignment的結果而定,有可能會是4,1,20或4,1,24
  • C/C++編譯器並不會以紀錄struct內變數成員的方法去做最佳化
 8. C語言的精神:
  • 相信coder的能力
  • 語言本身儘可能設計得簡單扼要
  • 只允許單一方法完成一個動作
  • 執行起來愈快愈好,就算要讓程式本身大一點
  • 維護起來簡單
  • 不會阻止coder去做必須要做的事(因為相信coder)


2015年10月24日 星期六

PiQuadcopter(掰咖考特) -- 用Raspberry pi 做四軸飛行器(三) 四軸運動原理與控制

不是所有東西裝上馬達配螺旋槳就能飛起來的,就算飛得起來也不表示一定飛得穩!

一切都是從角動量守衡開始

想像一下,一個物體裝上有足夠馬力能把物體拉起來的螺旋槳,像這樣:

結果不會像漫畫一樣美好,因為角動量守衡的關係,如果這螺旋槳順時針方向轉,那這螺旋槳(跟接著螺旋槳的物體)會一起逆時針方向轉。所以哆啦A夢插上竹蜻蜓後應該會不停轉轉轉。

至於直昇機怎麼飛?它除了頭上那頂大螺旋槳以外,尾巴上還有個尾槳可以抵消上段中角動量守衡造成的力矩。

那四個螺旋槳要怎麼辦?如果全部逆時針轉,那根據角動量守衡,整個機體就得順時針轉,像這樣。
想要它機體不轉的話也簡單,對角線那兩對,一對轉順時針,一對轉逆時針,總角動量就是0了(假設四個軸的重量均勻分佈)。
那我要怎麼讓它移動?
假設要像下圖那樣走,就把那方向的相鄰兩個(或一個)馬達轉速降低,或提高反方向馬達轉速,這樣低速馬達相對位置降低,高速馬達相對位置升高,四個螺旋槳不垂直於地面,就產生一個水平分量,朝著低速馬達的方向前進。


如果要水平轉動,那就調整某對角線那組的馬達,讓角動量守衡造成的力矩轉動機身。



四軸本身需要的材料

首先你需要四組馬達+螺旋槳(廢話!),而且這四組馬達+螺旋槳不但需要提供足夠的升力去抵抗重力跟阻力(有風的情況下),還要有餘力去移動!一般的建議是馬達+螺旋槳能提供的最大升力必須大於四軸總重的一半:假設四軸總重為1.5kg,搭配的馬達+螺旋槳必須能提供3kg的最大升力,也就是說每一組馬達+螺旋槳的最大升力必須大於~770g。

根據前人的經驗,對於F450(對角線長度450mm,要用Raspberry Pi的話大概這是你可以用的"最小"機架了)的四軸來說,搭配2212 1000KV無刷馬達 + 1045螺旋槳 + 11.1V 電池的螺旋槳通常可以達到總升力為3kg。這裡2212是馬達的大小,表示馬達直徑22mm跟高12mm(馬達愈大通常出力愈大),1000KV表示電源每多增加1V則馬達多轉1000次/秒(馬達沒加螺旋槳的情況),1045則是螺旋槳的長度跟曲度。

鋰聚(LiPo)電池由於相對於別種電池來說同電量比較輕的緣故常被四軸選用。單節鋰聚電池的規格是3.7 V(最高到4.2 V),我用的是3S1P(就是三個單節並聯成一個)標準就是11.1V,它的放電速率跟電量各為35C跟5500mAh,意思就是說這電池可以在電流為5.5A的情況下維持1小時,然後放電速率為35*5500mA = 192.5A。

Electronic Speed Control (ESC,台灣好像叫電調還是電變)是用來控制馬達出力的,通常是使用脈衝寬度調變(PWM),藉由脈衝的寬度(1-2ms)來控制馬力大小(寬度愈高輸出愈大)。一般來說四軸要能飛最小需要50Hz的更新速率(每個脈衝訊號的頻率),要飛得好飛得穩就要更高的更新速率。要注意的是由於ESC直接輸出三相電壓給無刷馬達,所以要注意ESC本身可承受的電流大小。通常一個2212 100KV馬達接到11.1V電源時,最高可輸出10-12A電流,那ESC最好可以承受到兩倍以上,也就是20-25A(注意是每個馬達,不是四個加起來)。

四軸行進間的航向或姿態需要即時被系統所掌握,不然隨時有翻覆的危險。通常四軸是用慣性測量單元( inertial measurement unit,IMU) 的三軸加速度計(accelerometers)跟三軸角速度計(gyroscopes)得知當前姿態,若是需要知道當前航向則需使用電子羅盤(magnetometers)。想要知道高度的話,氣壓計(barometer)是一個選擇(不過精度要夠小,目前看到比較好的高度誤差可達10cm,做懸停還是很勉強)。GPS也是不錯的選擇,既可以知道位置也可以測量速度,可惜它反應比較慢(5-10 Hz)。

最後,少不了用來溝通遙控器跟四軸的無線通訊模組(RF module),通常我們使用的頻段為2.405GHz-2.485GHz,速度愈快有效距離愈短。

所以要控制四軸,主要的流程就是:
  1. 讀取感測器數據 + 收到控制器訊號
  2. 將感測器數據做數據處理(濾掉雜訊或校正)
  3. 根據處理過的數據換算成目前機體角度(所謂的roll, pitch和yaw是也 https://en.wikipedia.org/wiki/Aircraft_principal_axes)或機體位置
  4. 根據控制器給的命令跟當前角度和位置控制馬達出力(PID是也)
這邊其實對於Raspberry Pi來說,比較麻煩的還是讀取感測器數據的timing。我每次讀取加速度+角速度數據後會設暫停3ms左右,讓量測週期達到4ms,可是不是總是成功,以下是我取了一分鐘數據量測的控制週期,最多可以到8ms (第6秒那邊的空白我也不知道是啥回事) ...
 這個希望以後又Preempt RT kernel或Xenomai改善。

Reference
http://csenichijou.blogspot.fr/2014/03/Quadcopter-2.html


2015年10月6日 星期二

PiQuadcopter(掰咖考特) -- 用Raspberry pi 做四軸飛行器(二) 機體本身的材料

由於RPi本身就很大,沒法裝在小台的四軸上,所以我一開始就選定對角線45cm長的機架。
由於我沒有相關經驗,所以材料選購大部份是參考以下網頁:
http://csenichijou.blogspot.fr/2014/03/Quadcopter-1.html

Amazon真是太強大了,要啥幾乎都找得到。以下是我所選擇的四軸材料。

機架

我選的是 JMT F450 Air multi-Rotor Frame,可裝10吋螺旋槳。

  
規格:
對角線長度:450mm
重量:272g

馬達/螺旋槳

我直接買一整組(馬達*4+螺旋槳*2對)給四軸用
Andoer 4 A2212/13T 1000KV Brushless Motor + 2 paires 1045 10 * 4.5 Helices
規格:
無刷馬達:4 * A2212 13T 1000KV(附子彈型螺帽組)
最大效率: 80%
馬達大小: 高2.5 x 直徑3cm
馬達轉軸直徑:2.5 cm (我用3cm的環箍上去是挺緊的)
螺旋槳:1045 * 4(兩正兩反)
根據經驗,螺旋槳跟螺帽最好多買幾組備用....(摔一次可能就得換)

電池

(老實說我買的有點太大太重,其實3000-4000mAh左右應該就夠了....)
ZOP Power 11.1V 5500mAh 3S1P 35C Lipo Battery For RC Model

 規格:
  • 容量:5500MAH
  • 放電速度: 35C
  • 尺寸: About 28.5*48*155mm
  • 重量:約381g

電調(ESC)

我是嫌焊接麻煩才用4合1的電調,其實四個分開裝在機架上可能好些(重量不會太過集中在中間)。一般來說我的馬達配20-25A就夠了,是沒找到更划算的才選這個。這電調有個好處是有一個5.3V輸出剛好可以給RPi或其他元件用。
HOSdog 30A 4 in 1 Brushless Speed Controleur ESC
 規格:
  • 電流輸出:30A/40A(10秒內)
  • 適用電池種類:2-6s Lipo 或 5-12 cellules NiMH
  • 支援設定更新速率:50Hz - 432Hz.

主控制器

四軸上用Raspberry pi 1 model B+ (用2代好像太大材小用了點,所以不考慮)
還加買了個case...
至於遙控器就用普通的Arduino...

通信模組

我本來買了APC220,可是在linux下他有夠不合作的(怎麼也起不來),後來放棄改用NRF24L01(四軸跟遙控器各一個),它是SPI介面,支援250kbps, 1Mbps, 2Mbps(速度愈快有效距離愈低,我試過2Mbps的情況下有效距離不到1m,這是沒有外加天線的情況下)


加速、氣壓、陀螺儀、磁場感測模組

相當多人用的GY80,I2C介面(支援400kHz傳輸速度)

PWM模組

PCA9685PW,I2C介面(支援400kHz傳輸速度),PWM輸出最高可達1.6kHz,精度為12bit。Adafruit有賣已經焊好的模組,大概兩個50元硬幣大,坊間也有layout差不多的模組可買(便宜多了)。

ADC(量測電池電量用)

MCP3008,10 bit很夠用了,SPI介面。

2015年10月4日 星期日

PiQuadcopter(掰咖考特) -- 用Raspberry pi 做四軸飛行器(一) 前言

想做四軸很久了。會想用RPi不外乎以下理由:
- 磨練C programming
- 了解kernel programming(雖然最後幾乎沒用上)
- 想體驗Real-Time Linux的威力(雖然還沒開始用)

一般來說RPi拿來做四軸有點不太適合,因為作業系統是Linux(雖然據說可以安裝其他的RTOS,不過我暫時不討論這個),而屬於通用型作業系統的Linux為了能在較短時間內完成較多任務,其即時性(從系統收到任務的時間到系統開始處理這任務的時間)比較差(正在處理的任務不能被優先權較高的任務插隊)。我希望用多執行緒程式+搶佔式即時核心(Preempt RT Linux kernel)來改善這個問題。

稍微搜尋了一下前人用Rpi做四軸的經驗:

RPi only
https://github.com/idavidstory/goPiCopter
使用Go程式語言,sensor更新週期50Hz,控制週期10Hz,9 DOF + PWM(PCA9685)
http://blog.pistuffing.co.uk/
看起來似乎沒完成

RPi + Arduino
https://github.com/vjaunet/QUADCOPTER_V2
使用MPU6050,Arduino用來控制SPI介面的硬體和PWM

https://github.com/rpicopter/AvrMiniCopter-wiki/wiki
10 DOF

https://www.raspberrypi.org/forums/viewtopic.php?f=37&t=35746
MPU6050,使用特殊ESC所以不用PWM,sensor更新週期在300-400Hz,以C++編寫程式,不用Real-time OS,Arduino用於RFM12無線模組(與筆電交換訊息控制機體)

RPi + 其他飛控
http://www.instructables.com/id/Autonomous-Cardboard-Rasberry-Pi-Controlled-Quad/
http://www.botched.co.uk/quadrocopter/
使用dsPIC30F(PWM+I2C),10 DOF

看起來完全用RPi不用其他MCU好像也不是不可能。

四軸的飛行控制需要的硬體有:
- 機架、電池、(無刷馬達+螺旋槳)*4 (廢話)
- ESC (electronic speed control) :控制馬達轉速,需要用四個PWM(Pulse-width modulation,脈衝寬度調變)個別控制
- IMU(Inertial measurement unit,慣性測量單元):三軸加速度計(測量重力加速度方向+加速度)+三軸角速度計(測量角速度)+三軸電子羅盤(測知方向)+氣壓計(換算海拔高度),3+3+3+1簡稱10軸 (10 DOF,一般來說6軸勉強夠用)。
- 無線通訊模組(wifi 或RF)
- 如果想知道電池電量是不是快到底了,最好加個ADC(類比轉數位輸出)測量電池電壓。

而我手上的RPi model B+上面有:
- I2C * 1 (可接多個裝置,支援400kHz以上傳輸速度)
- SPI * 1 (可接兩個裝置,支援8MB/s以上傳輸速度)
- PWM * 1 (不夠用...),雖然可以用DMA做軟體PWM,不過更新頻率不能太高(~100Hz差不多極限了)

根據以上需求,結論就是我需要買:
- 10軸IMU:GY80(I2C,400kHz,前6軸資料更新可達1600Hz)
- nRF24L01*2:無線模組(250Kbps-2Mbps,頻寬愈大有效距離愈小)一個給RPi用,一個給Arduino用(操縱四軸),SPI
- ADC:MCP3008:10bit(量電壓夠用了) ,SPI
- PWM:PCA9685(Adafruit有賣已經設計好的電路,坊間也有類似的),I2C,12bit,頻率可達1526Hz

然後就是程式部份,我選擇bcm2835( http://www.airspayce.com/mikem/bcm2835/ ) 而不是比較知名的wiringPi。因為bcm2835比較單純(從mmap找到address->讀寫bit控制),不需要任何驅動程式,而wiringPi的SPI跟I2C控制是建立在驅動程式上,我希望能藉此避免驅動程式的介入把操縱單純化,這樣以後若是用Preempt RT kernel也比較不受影響。

目前我的進度到PID Control(調的好累),希望不久後我的PiQuadcopter能很快飛起來。

參考資料

http://blog.oscarliang.net/build-a-quadcopter-beginners-tutorial-1/

2015年8月4日 星期二

Raspberry pi 外部裝置程式設計

先說基本規則

任何對於硬體的操作都必須經由「讀寫記憶體(memory)中的某個位址(address)」來完成。
舉例來說,想要使連在某gpio的LED亮起來,就要在對應在此gpio的位址上寫入"1"。
所以,想要操縱Raspberry pi,要嘛透過與驅動程式(driver)溝通來完成,像這樣:
使用者 <-> module(driver) <->記憶體
要嘛自己跟硬體溝通:
使用者 <->記憶體

這兩種方法各有其優缺點。用驅動程式當然方便很多,至少不用去看Manual。可是硬體的反應速度可能會因為透過驅動程式間接傳遞而變慢。直接跟記憶體溝通雖然方便,但是每次都要用sudo,而且少了驅動程式確認使用者行為可能會因為不小心而有燒壞硬體的風險。所以自己想一下喔。

如何找位址

Raspberry pi 使用的SoC是BCM2835(屬於BCM2708家族的一員,由於這個家族只有BCM2835可以跑linux,所以要在kernel上找相關模組的話請用bcm2708)。現在來看一下BCM2835的address mapping:

圖片來源:BCM2835 ARM Peripherals Manual

這邊只講Peripheral(外部裝置)的部份:I/O Peripherals的bus address(BCM2835 ARM Peripherals Manual 上面列的都屬於bus address)從0x7E000000~開始,但是經由VC/ARM MMU (Memory management unit) 將bus address 映照到ARM實體位址(physical address)的0x20000000~0x20FFFFFF,然後又被MMU映照到虛擬位址(virtual address) 的0xF2000000。

一般來說,Linux設備驅動程式是不能直接存取實體位址的,只能使用虛擬位址去控制硬體。至於使用者寫的軟體或程式,可以看情況使用實體或虛擬位址控制硬體,不過有兩個例外:當程式需要使用DMA去讀取Peripheral或RAM的時候必須用bus addresses;而當程式需要直接讀取RAM時必須要用實體位址。

如果只有一個使用者在操作peripherial就算了,如果有可能多人或多程序同時操作,必須避免一個程序進行寫入/讀取的同時,另一個程序也進行寫入/讀取的動作(尤其是使用中斷模式的時候),所以寫入/讀取的同時加上mutex lock(互斥鎖)是一定要的。

使用I2C

I2C主要靠兩條雙向訊號線完成溝通:SCL和SDA:SCL是由master端輸出的clock,SDA負責傳送data(雙向)。這兩條訊號線必須連接到"固定電壓(3.3V或5V都可)+上拉電阻"(raspberry pi應該有內建,不用擔心),因為SCL和 SDA2. 都是屬於"open drain"電路:只能調低為0不能調高為1。標準模式的SCL clock頻率為100kHz,快速模式為400kHz,高速模式可到3.4MHz(不過傳輸速度必須master跟slave兩端都支援同樣模式才可行,而Raspberry pi據我所知核心clock的頻率為250MHz,i2c的傳輸速度可以藉由調整CDIV(divider)這個位址來達成(Ex: CDIV = 2500,SCL clock = 250M/2500 = 100kHz))。

圖片來源:http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html

當master端想要跟slave溝通時,必須先給一個開始訊號(start sequence):SCL=1,SDA由1變成0;若是想停止溝通則給結束訊號(stop sequence):SCL=1,SDA由0變成1

圖片來源:http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html

通常一個I2C可以同時接很多個slave(也有多數master的情況,不過這邊不談),可是master在同一時間只能跟其中一個slave溝通。當開始訊號送出後,master必須送出一個slave address(slave address的data sheet通常會提供)以告知所有的slave它將跟哪個slave溝通。通常i2c address可使用7 bit或10 bit(我沒有可用10bit的slave device,所以這邊不談)。以我的RTC DS3231為例,他的slave address是0x68(1101000),而i2C傳輸資料的時候是從高位數開始傳,傳完7bit後加一個bit顯示寫入或讀出,然後slave會回傳一個bit表示接到資料了,所以以我的RTC DS3231來說就會像這樣: 1->1->0->1->0->0->0->1(1表示寫入,0表示讀取)->A,最後的A表示是從slave傳來的確認bit。

然後就是讀取/寫入slave資料的部份。通常一個slave可以讀取/寫入的位址不只一個,所以在做讀取/寫入的時候,必須要先指定一開始讀取/寫入的位址還有想要讀取的長度(byte)。以RTC DS3231為例,若是要讀取RTC DS3231的時間,就要從位址0開始,依序讀取秒、分、時、星期、日、月、年(各1 byte)。所以一個讀取時間的程序為:
  1. 開始訊號(start sequence)
  2. 傳送slave address(0x68) (寫入), slave 回傳ACT
  3. 傳送讀取開始位址(0)(寫入), slave 回傳ACT
  4. 開始讀資料,在每個byte完結後slave 回傳ACT
  5. 讀完傳送結束訊號
至於一個寫入時間的程序只有一點不一樣:
  1. 開始訊號(start sequence)
  2. 傳送slave address(0x68) (寫入), slave 回傳ACT
  3. 傳送寫入開始位址(0)(寫入), slave 回傳ACT
  4. 開始寫資料,在每個byte完結後slave 回傳ACT
  5. 寫完傳送結束訊號

使用SPI

一組SPI傳輸中只能有一個master,slave可以有好幾個。SPI有四條傳輸線,分別是:
  • SCLK : Serial Clock,由master輸出
  • MOSI : master 輸出到slave
  • MISO : slave輸出到master
  • SS : 選擇slave,由master輸出
參考資料:
  • Low Level Programming of the Raspberry Pi in C
    http://www.pieter-jan.com/node/15
  • BCM2835 ARM Peripherals Manual
  • Using the I2C Bus 
    http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html
  • http://wiki.csie.ncku.edu.tw/embedded/SPI

2015年7月23日 星期四

使用 device tree overlay控制industrial i/o

終於把我第1個device tree overlay生出來了!要趕快把心得寫下來。

Device tree的功用

想像若是沒有device tree,為不同的處理器寫kernel modules會變成一件看起來複雜實際上很簡單的事,舉例來說,以raspberry pi model B+跟raspberry pi 2來說,這兩者只有SoC、CPU、記憶體大小不一樣,但是其他該外部設備(I2C、SPI等)都差不多。可是若是沒有device tree,寫kernel module的時候就必須把以下步驟各做一次
  1. 弄一個machine type id
  2. 在kernel裏面建立關於此id的相關文件,設定SoC的相關代碼(包括外部設備如interrupt、timer、memory mapping等等)還有board-specific文件
  3. 設定其他的driver
但是現今的SoC都大同小異,了不起就是pin(gpio、I2C等)的位置不一樣,為了這些小差異,要把上面那三件事重做一次,增加一倍的coding到kerenl,使得kernel最後越來越肥搞到Linus本人都出來罵。Device tree的作法就是把外設資訊(怎麼連接、哪個memory mapping等)以bootloader傳送給kernel,讓kernel把外設需要的module根據Device tree的訊息連接起來。

實際做起來還挺有趣的。我自己寫了兩個可以在raspberry pi model B+連接industrial i/o (iio) driver用的device tree

MCP3008(adc)

如果編譯kernel的時候有勾選industrial i/o driver的時候就可以使用
可以在/lib/modules/{uname -r}/modules.alias找到這個module :
alias spi:mcp3008 mcp320x

根據kernel document 的說明
https://www.kernel.org/doc/Documentation/devicetree/bindings/iio/adc/mcp320x.txt
我寫了mcp320x.dts:

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835", "brcm,bcm2708";
    /* disable spi-dev for spi0.0 */
    fragment@0 {
        target = <&spi0>;
        __overlay__ {
            status = "okay";
            spidev@0{
                status = "disabled";
            };
        };
    };

    fragment@1 {
        target = <&spi0>;
        __overlay__ {
            /* needed to avoid dtc warning */
            #address-cells = <1>;
            #size-cells = <0>;
            mcp3x0x@0 {
                compatible = "mcp3008";
                reg = <0>;
                spi-max-frequency = <1000000>;
            };
        };
    };
};


dts寫好後用dtc編譯:
dtc -@ -I dts -O dtb -o mcp320x.dtb mcp320x.dts
然後把mcp320x.dtb copy到/boot/overlays/
最後在/boot/config.txt加上:dtoverlay=mcp320x  (跟我寫的mcp320x.dtb做連結)
重開機後,只要mcp3008有接對應該就沒問題了。

MPU6050(六軸陀螺儀)

一樣根據 https://www.kernel.org/doc/Documentation/devicetree/bindings/iio/imu/inv_mpu6050.txt 來編輯mpu6050.dts

// Definitions for MPU6050
/dts-v1/;
/plugin/;

/ {
        compatible = "brcm,bcm2708";

        fragment@0 {
                target = <&i2c1>;
                __overlay__ {
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "okay";
                        clock-frequency = <400000>;

                        inv-mpu6050@68 {
                                compatible = "invensense,mpu6050";
                                reg = <0x68>;
                                interrupt-parent = <&intc>;
                                interrupts = <2 22>;
                        };
                };
        };
};


編譯後放到/boot/overlays,/boot/config.txt上加入:dtoverlay=mpu6050

如果想要debug,可以在/boot/config.txt上加入:dtdebug=1
重開機後執行sudo vcdbg log msg 就可看device tree載入訊息:

PS: 記得看看interrupt有沒有衝到(dmesg |tail),如果有衝到interrupts的值+1+2然後重開機應該就沒問題了。
參考資料:
  • Device Tree 背景介紹
    http://www.wowotech.net/linux_kenrel/why-dt.html
  • Device Trees, Overlays and Parameters
    https://www.raspberrypi.org/documentation/configuration/device-tree.md
  • https://patchwork.ozlabs.org/patch/464158/

2015年7月2日 星期四

使用 iio device driver 驅動 DHT11

先說結論:
  • 現在新版的kernel(我用3.18以上)都有附一些硬體的iio device driver,例如DHT11
  • 使用方式請看 /boot/overlays/README
  • 不過就DHT11的部份:它不是很好用(失敗率超高,還沒我自己寫的穩定)
大略說一下使用方法(根據/boot/overlays/README),假設DHT11已經接好,使用gpio pin2讀取資料:
  • 確認kernel已經含有Industrial I/O support (在Device Driver下)
(我是懶得看直接把Industrial I/O support內的細項都選了,因為以後可能用得到,不想花時間的話只選dht11也行)
  • 編輯/boot/config.txt (記得用sudo)
    • 加入:dtoverlay=dht11,gpiopin=2,存檔離開
    • 重新開機
如果一切OK,在/dev下應該會看到有裝置iio:device0,這個裝置就是dht11

然後進到/sys/bus/iio/devices/iio:device0:
可以看到這些variables,他們代表:
dev : device number
in_humidityrelative_input : 溼度(RH%)*1000 (老實說我不知道為何要*1000)
in_temp_input:溫度(C)*1000
name: 名稱(預設為dht11)
uevent:一些設定

Google Code Prettify