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

沒有留言:

張貼留言

Google Code Prettify