` 本帖最后由 MOPPLAYER 于 2016-3-16 00:06 编辑
前言:
繼上一篇,已經實作了驅動步進馬達來達到餵食魚兒的目的,本篇將繼續第二個功能,溫控,溫度控制一直是水族魚兒們重要的環節,因為寒流或者冷氣團會使得水溫驟降,使得魚兒暴斃,因此控制水溫將結合本開發板,另外要注意的是主動溫控只能升溫不能降溫,但多半低溫才會造成魚兒死亡,因此即便是一般市售產品也不具有降溫功能,只能依照環境溫度來被動降溫
準備:
1. Guitar開發板
2. 5V~12V 2A Adapter
3. DS18B20防水型溫度感測器
4. 杜邦線數根
5. 石英管加熱器 50W 14cm長
6. 繼電器模塊
7. 剪刀和壓力鉗
8. 絕緣膠帶
9. 螺絲起子
10. 虛擬機搭載Ubuntu 14.04 64Bit LTS
實作:
1. 本篇採用DS18B20溫度感測器,此感測器被廣泛使用作為室溫感測,但也有被做成水族箱可以使用的防水型,因此需使用這種設計來實作溫控的部分,注意,一般感測器都無防水功能,切勿隨便拿其他溫度感測器泡入水中
Fig.1 防水型DS18B20
2. 查閱DS18B20的Datasheet,這裡將主要驅動設計的環境做個簡易的介紹,因為本篇不另外設計驅動而使用Kernel所附帶的驅動,因此不會太詳細描述各個功能Register,DS18B20採用One wire方式來收發資料,和DHT11/DHT22類似
3. 首先DS18B20執行任何的指令都須先初始化,因此主控的MCU必須對DS18B20傳送初始訊號,DS18B20收到後會回傳響應訊號,等到這步驟結束後,即可對DS18B20下達指令,訊號時序圖如下
Fig.2 各個有效脈衝的時序圖
4. 初始化後,就可以對ROM來進行讀取,ROM是存放該DS18B20的序列產品號碼,主要目的是用來區分在One wire Bus上各個DS18B20,因此這個設計可以使用多個DS18B20同時接在同一Bus上,ROM的內容如下:
Fig.3 ROM內容定義
Fig.4 一般自己設計DS18B20的話會使用Skip ROM,即假設只有一個裝置在Bus上,但Kernel驅動會使用Read ROM/Search ROM來獲取序列號碼
5. ROM指令結束後,就可以對DS18B20的Memory進行存取,這裡將存放溫度資訊,如下圖
Fig.5 通常我們會讀取的為Address 0,1的溫度數據資料,而CRC為校驗用,因此也會進行讀取,EE RAM為寄生供電的方式會使用到,但我們一般都會接上電源,所以EE RAM將不使用到,溫度解析度可設定,出廠預設為12 Bit
Fig.6 設置解析度的Register
Fig.7 解析度設置方法,預設是R1=1,R0=1
Fig.8 溫度資料Register的內容,當我們對DS18B20發送讀取指令時,回傳的資料格式
Fig.9 對於Memory的指令,我們最常用的為Convert T,將溫度讀值做轉換並存放在Memory Address 0 和1,然後再使用Read Scratchpad來讀取溫度資料的Register,最後回傳給MCU做判讀
Fig.10 當然,每次進行存取指令前都要校驗一下,CRC計算採用LFSR的方式,將ROM的前56個Bit進行移位和XOR運算,MCU確認無誤後方可繼續執行Memory或者EE RAM的存取指令
6. 最後我們要知道如何判定資料是0或者1,即高電位資料和低電位資料,時序圖如下
Fig.11 特別注意判讀0或者1的採樣時間長度,MCU和DS18B20稍有不同,一周期約為60us,依照以上資料的分析,想必要自己寫一份驅動都不是很困難的事情,但Kernel原生就已經有包含DS18B20的驅動,因此可以不必額外在寫一份了
7. 下載Guitar的BSP,可在官方網站找到,輸入以下指令來獲取和快速編譯
- git clone https://github.com/LeMaker/linux-actions-bsp.git
- cd linux-actions-bsp/
- ./configure
- (輸入1 按下Enter按鍵)
- make -j8
复制代码
整個BSP編譯完畢後會下載好所有的原始碼,所以此為必要動作
8. 再次輸入以下指令來編譯我們要的One wire驅動
Fig.12 在--->Device driver中找到Dallas's 1-wire support --->
Fig.13 在--->1-wire Bus Masters中選擇GPIO 1-wire busmaster,使得CPU可以使用GPIO的方式來操作One wire Bus
Fig.14 在--->1-wire Slaves中選擇Thermal family implementation,因DS18B20當作Slave
9. 設置好以後按下方向右鍵選擇 ,選擇,退出設置,但光是編譯好驅動還是不夠的,還需要板級的設置,才能讓Kernel正確的使用外設,而Actions S500 BSP採用DT(Device Tree)的方式來設置板級資訊,因此打開檔案/linux-actions-bsp/linux-actions/arch/ARM/boot/dts/lemaker_guitar_bbb.dts
- leds {
- compatible = "gpio-leds";
- led@1 {
- label = "green:GPIOB12";
- gpios = <&gpio 44 1>; //Active High
- linux,default-trigger = "heartbeat";
- default-state = "ON";
- };
- led@2 {
- label = "blue:GPIOB31";
- gpios = <&gpio 63 0>; //Active High
- linux,default-trigger = "";
- default-state = "OFF";
- };
- };
- w1_device: onewire@0 {
- compatible = "w1-gpio";
- label = "w1:GPIOC27";
- gpios = <&gpio 91 0>;
- };
复制代码
在led板級資訊下方增加w1_device資訊,其中onewire名稱不可變,而compatible設置為w1-gpio,而gpios即GPIO Pin的設置則隨人喜好,我是設置GPIOC27來開啟One wire bus,即No. 32x2+27=91,gpio 91,Guitar的GPIO Pin可參考如下圖
Fig.15 Guitar的40個GPIO Pin
10. 以上修改都完畢以後,可以開始進行編譯,輸入以下指令來單獨編譯Kernel
Fig.16 以上就完成編譯工作
11. 編譯好了我們來進行簡易的替換,不需要重新燒寫,將開發板接上電源以後,即可在檔案系統中看到 misc分區
Fig.17 這些都是開機所需要的鏡像檔案,其中對應如下,將檔案重新命名即可
- kernel.dtb <=> lemaker_guitar_bbb.dtb
- uImage <=> uImage
复制代码
將編譯好的檔案利用SFTP或者USB Stroage的方式傳送到開發板上,並替換 misc分區的兩個檔案即可
12. 將開發板重新啟動,您應該可以順利的再次進入桌面系統,至此就完成Kernel端的驅動修改
13. 石英管加熱器是一般水族箱的加熱器,可使水溫升高,使用上注意勿離開水中直接通電加熱,會造成溫度過高而燒熔保險絲
Fig.18 石英管加熱器
14. 將石英管加熱器的火線使用剪刀和壓力鉗裁剪成兩端,一端插入NO,一端插入COM,當高電位觸發時,就會形成通電狀態,接線如下
- 石英管加熱器火線剪開一端 <=> NO
- 石英管加熱器火線剪開另一端 <=> COM
复制代码
至此就完成石英管加熱器的改造
15. 我們接下來要實作使用者端的應用程式部分,以上一篇的程式碼為基礎,加入溫控的部分,因為驅動將裝置模擬成File,因此我們仍舊可以使用Sysfs的方式來對DS18B20進行讀取
- #include
- #include "gpio.h"
- #include
- #include
- #define IN1 131
- #define IN2 130
- #define IN3 50
- #define IN4 64
- #define DS18B20_PATH "/sys/bus/w1/devices/28-021564c894ff/w1_slave"
- #define RELAY 90
- #define TEMP_HIGH 30
- #define TEMP_LOW 24
- #define SPEED 1000
- void turnCounter()
- {
- s500_mmio_set_high(IN1);
- s500_mmio_set_low(IN2);
- s500_mmio_set_low(IN3);
- s500_mmio_set_low(IN4);
- usleep(SPEED);
- s500_mmio_set_high(IN1);
- s500_mmio_set_high(IN2);
- s500_mmio_set_low(IN3);
- s500_mmio_set_low(IN4);
- usleep(SPEED);
- s500_mmio_set_low(IN1);
- s500_mmio_set_high(IN2);
- s500_mmio_set_low(IN3);
- s500_mmio_set_low(IN4);
- usleep(SPEED);
- s500_mmio_set_low(IN1);
- s500_mmio_set_high(IN2);
- s500_mmio_set_high(IN3);
- s500_mmio_set_low(IN4);
- usleep(SPEED);
- s500_mmio_set_low(IN1);
- s500_mmio_set_low(IN2);
- s500_mmio_set_high(IN3);
- s500_mmio_set_low(IN4);
- usleep(SPEED);
- s500_mmio_set_low(IN1);
- s500_mmio_set_low(IN2);
- s500_mmio_set_high(IN3);
- s500_mmio_set_high(IN4);
- usleep(SPEED);
- s500_mmio_set_low(IN1);
- s500_mmio_set_low(IN2);
- s500_mmio_set_low(IN3);
- s500_mmio_set_high(IN4);
- usleep(SPEED);
- s500_mmio_set_high(IN1);
- s500_mmio_set_low(IN2);
- s500_mmio_set_low(IN3);
- s500_mmio_set_high(IN4);
- usleep(SPEED);
- }
- void turn()
- {
- s500_mmio_set_high(IN4);
- s500_mmio_set_low(IN3);
- s500_mmio_set_low(IN2);
- s500_mmio_set_low(IN1);
- usleep(SPEED);
- s500_mmio_set_high(IN4);
- s500_mmio_set_high(IN3);
- s500_mmio_set_low(IN2);
- s500_mmio_set_low(IN1);
- usleep(SPEED);
- s500_mmio_set_low(IN4);
- s500_mmio_set_high(IN3);
- s500_mmio_set_low(IN2);
- s500_mmio_set_low(IN1);
- usleep(SPEED);
- s500_mmio_set_low(IN4);
- s500_mmio_set_high(IN3);
- s500_mmio_set_high(IN2);
- s500_mmio_set_low(IN1);
- usleep(SPEED);
- s500_mmio_set_low(IN4);
- s500_mmio_set_low(IN3);
- s500_mmio_set_high(IN2);
- s500_mmio_set_low(IN1);
- usleep(SPEED);
- s500_mmio_set_low(IN4);
- s500_mmio_set_low(IN3);
- s500_mmio_set_high(IN2);
- s500_mmio_set_high(IN1);
- usleep(SPEED);
- s500_mmio_set_low(IN4);
- s500_mmio_set_low(IN3);
- s500_mmio_set_low(IN2);
- s500_mmio_set_high(IN1);
- usleep(SPEED);
- s500_mmio_set_high(IN4);
- s500_mmio_set_low(IN3);
- s500_mmio_set_low(IN2);
- s500_mmio_set_high(IN1);
- usleep(SPEED);
- }
- int main(int argc, char *argv[])
- {
- int i;
- time_t t;
- struct tm* now;
- int ss=atoi(argv[3]);
- int mm=atoi(argv[2]);
- int hh=atoi(argv[1]);
- int test=atoi(argv[4]);
- int fd =0;
- char crc_buf[4]={};
- char temp_buf[10]={};
- float temp;
- if (s500_mmio_init() < 0) {
- printf("error open mem!n");
- }
- set_max_priority();
- s500_mmio_set_output(IN1);
- s500_mmio_set_output(IN2);
- s500_mmio_set_output(IN3);
- s500_mmio_set_output(IN4);
- s500_mmio_set_output(RELAY);
- time(&t);
- now = localtime(&t);
- printf("System starting on %d : %d : %dn",now->tm_hour,now->tm_min,now->tm_sec);
- printf("Setting time on %dn", ss);
- while(1)
- {
- fd= open(DS18B20_PATH, O_RDONLY);
- if(fd < 0)
- {
- perror("w1_device:");
- }
- else
- {
- pread(fd,crc_buf,sizeof(crc_buf),36);
- if(strncmp(crc_buf,"YES",3)!=0)
- {
- printf("CRC error, so drop it!n");
- }
- else
- {
- pread(fd,temp_buf,sizeof(temp_buf),69);
- temp=(float)atoi(temp_buf)/1000;
- printf("Temp= %fn",temp);
- if(temp > TEMP_HIGH)
- s500_mmio_set_low(RELAY);
- else if (temp < TEMP_LOW)
- s500_mmio_set_high(RELAY);
- }
- }
- time(&t);
- now = localtime(&t);
- if(test==0 && now->tm_sec==ss && now->tm_min==mm && now->tm_hour==hh)
- {
- printf("Feeding food on %d : %d : %dn",now->tm_hour,now->tm_min,now->tm_sec);
- for(i=0;i<256;i++)
- {
- turnCounter();
- }
- for(i=0;i<256;i++)
- {
- turn();
- }
- }
- else if(test==1 && now->tm_sec==ss)
- {
- printf("Feeding food on %d : %d : %dn",now->tm_hour,now->tm_min,now->tm_sec);
- for(i=0;i<256;i++)
- {
- turnCounter();
- }
- for(i=0;i<256;i++)
- {
- turn();
- }
- }
- sleep(0.5);
- close(fd);
- }
- set_default_priority();
- return 0;
复制代码
其中DS18B20的裝置位置在 /sys/bus/w1/devices/(28-021564c894ff),括號中的序列號碼會不一樣,接線上DS18B20時,即可被驅動讀取出來,w1_slave即是從Memory獲取到的溫度資料,如果您直接對該檔案使用cat指令如下:
Fig.19 兩行的16進制碼為相同,即Memory的9個Address中的資料, d4 01為溫度資料,計算可得29250,CRC為第9個Address資料,即 6f,若校驗無錯誤的話,第一行會輸出 YES,第二行計算出溫度 t=xxyyy, xx是實數, yyy為三位小數,因此在程式碼中使用pread對資料進行位移來讀取,第一行的Y,offset為36,第二行的=(等號),offset為69,因此可利用此規則來擷取出需要的資料,當CRC校驗成功才會繼續執行溫度的取值,RELAY即繼電器腳位,TEMP_HIGH和TEMP_LOW為溫度區間,當高於TEMP_HIGH時繼電器開路,低於TEMP_LOW時繼電器短路,因此可達到溫控的效果,並定時輸出溫度資訊來測試,自動化以後將不需要顯示出來,設計上溫度優先權高於餵食,更好的寫法可用Thread來處理兩個工作
17. 編寫好以後輸入以下指令來編譯
之後產生新的二進制檔案,我們繼續接線的工作
18. 將DS18B20做接線,特別注意的是DATA線要和您在DT中所設置的Pin腳相同
- VCC (紅線) <=> 3.3V
- GND (黑/灰線) <=> GND
- DATA (綠/藍/黃線) <=>GPIOC27
复制代码
Fig.20 DS18B20使用杜邦線接線
19. 將繼電器模塊作接線
- DC+ <=> 5V
- DC- <=> GND
- IN <=> GPIOC26
复制代码
Fig.21 繼電器模組使用杜邦線接線
20. 將石英管加熱器電源插頭插上,並將石英管加熱器的主體沉入水中,注意須將整個主體沉入,之後回到開發板上,輸入以下指令
來觀看溫度的讀數是否正確,並觀察是否依照您的溫度範圍設置,啟動和關閉加熱
Fig.22 至此就完成溫控部份的設計
Fig.23 實品圖
`
|
|
|
|