` 本帖最后由 MOPPLAYER 于 2016-3-15 23:01 编辑
前言:
項目已經完成差不多了,開始補文,本系列開始即是各功能的實作,起因於家人忙碌工作卻無暇顧及小魚缸的孔雀魚,因此我就幫忙設計一套簡易的水族箱自動化功能,涵蓋自動餵食,溫控和網路觀賞三大部分
準備:
1. Guitar開發板
2. 5V~12V Adapter
3. 不鏽鋼鍋墊
4. 寶特瓶一大一小
5. 魚飼料
6. 符合不鏽鋼鍋墊的小魚缸
7. 28BYJ-48步進馬達
8. ULN2003驅動模塊
9. 銲烙鐵
10. 剪刀和壓力鉗
11. M4螺絲釘和螺帽
12. 雙面泡棉膠
13. 麥克筆和尺
實作:
1. 28BYJ-48步進馬達和ULN2003驅動模塊一般在網路上很好取得資料,這裡將不再贅述控制訊號部分,步進馬達使用8相,ULN2003則是Darlington放大器
2. 寶特瓶是本餵食器的主體,寶特瓶隨處可取得,只要大小底座的部分,所以您需要使用大剪刀來裁切底座,一大一小的目的是因為可以互相套牢,這是避免魚飼料灑出,裁切時先用直尺畫好圓周再進行裁切,我製作的大小為小的高4cm,直徑6cm,大的高4.5cm,直徑7cm
3. 將銲烙鐵插上電源,我們利用焊q1an9來溶解出需要的插孔給步進馬達,燒完清潔銲烙鐵即可,將小的寶特瓶底座中心先用銲烙鐵燒出一小孔,再用銲q1an9對步進馬達軸心加熱,讓步進馬達的軸心剛好可以穿過底座中心,此燒法是確保可以完全緊實的套牢在軸心上
4. 接下來是對大的寶特瓶底座,上面剪開,也可不剪,我是方便穿過接線頭,在底座中心和馬達的軸心要對應,將馬達上兩個螺絲孔穿過M4螺絲釘,並用銲烙鐵對螺絲釘加熱,以穿過底座,之後分別用螺帽固定,一隻M4螺絲釘各兩個螺帽,因為底座會有凸起的地方,所以會使得步進馬達稍微傾斜向上,這樣會使得飼料往大底座掉落,所以要避免大量掉落方式就是向下幅度傾斜,大約5度即可,用雙面泡棉膠墊住步進馬達即可
5. 對大的寶特瓶底座進行燒熔出口,一個是接線頭部分,一個是飼料出口,如下圖
Fig.1 左側為飼料大出口約4.5cm x 2cm,步進馬達軸心和底座中心對齊,並用M4螺絲釘固定
6. 對小的寶特瓶底座進行燒熔小出口,這個大小將決定您一次的飼料量,建議燒小孔即可,我的直徑大約3mm
Fig. 2 小寶特瓶底座部分,小飼料出口孔直徑為3mm
7. 接下來要來實作步進馬達程式部分,如何讓它能夠轉動來掉落飼料,思維很簡單,轉動內部的小寶特瓶底座,會翻動飼料,利用重力所產生的加速度,當轉動到小出口時,魚飼料會對小出口做平行拋射運動,因此可達到目的
8. 本程式部分使用mmap 函式映射Memory的方式來操作GPIO,達到高速存取的目的,比起傳統的sysfs更為有效率,使得後面其他功能更為Real time,首先創建 gpio.h,宣告函式的原型
- #include
- #include
- #include
- #include
- #include
- #include
- #define MMIO_SUCCESS 0
- #define MMIO_ERROR_DEVMEM -1
- #define MMIO_ERROR_MMAP -2
- #define BASE 0xB01B0000
- #define GPIO_INC 3
- #define GPIO_OUTEN 0
- #define GPIO_INEN 1
- #define GPIO_DAT 2
- #define GPIO_LENGTH 596
- int s500_mmio_init(void);
- void s500_mmio_set_input(int gpio_number);
- void s500_mmio_set_output(int gpio_number);
- void s500_mmio_set_high(int gpio_number);
- void s500_mmio_set_low(int gpio_number);
- uint32_t s500_mmio_read_input(int gpio_number);
- void set_max_priority(void);
- void set_default_priority(void);
复制代码
其中大部分都是GPIO常用的功能,和程式的Priority,其中BASE,GPIO_INC,GPIO_OUTEN,GPIO_INEN,GPIO_DAT和GPIO_LENGTH由Action S500的Datasheet來決定
Fig.3 決定GPIO的Register Address
Fig.4 另外,跟一般SOC不一樣的是,GPIO有著最高優先權的處理方式,因此各個GPIO對應的MUX Register就不需要特別設置功能為GPIO
9. 接下來實作各函式主體,產生高速的GPIO函式庫,創建 gpio.c
- #include "gpio.h"
- volatile uint32_t* s500_mmio_gpio = NULL;
- int s500_mmio_init(void) {
- if (s500_mmio_gpio == NULL) {
- int fd = open("/dev/mem", O_RDWR | O_SYNC);
- if (fd == -1) {
- // Error opening /dev/mem. Probably not running as root.
- return MMIO_ERROR_DEVMEM;
- }
- // Map GPIO memory to location in process space.
- s500_mmio_gpio = (uint32_t*)mmap(NULL, GPIO_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BASE);
- close(fd);
- if (s500_mmio_gpio == MAP_FAILED) {
- // Don't save the result if the memory mapping failed.
- s500_mmio_gpio = NULL;
- return MMIO_ERROR_MMAP;
- }
- }
- return MMIO_SUCCESS;
- }
- void s500_mmio_set_input(int gpio_number) {
- *(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_INEN) |= (1<<((gpio_number)%32));
- *(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_OUTEN) &= ~(1<<((gpio_number)%32));
- }
- void s500_mmio_set_output(int gpio_number) {
- *(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_INEN) &= ~(1<<((gpio_number)%32));
- *(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_OUTEN) |= (1<<((gpio_number)%32));
- }
- void s500_mmio_set_high(int gpio_number) {
- *(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_DAT) |= (1<<((gpio_number)%32));
- }
- void s500_mmio_set_low(int gpio_number) {
- *(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_DAT) &= ~(1<<((gpio_number)%32));
- }
- uint32_t s500_mmio_read_input(int gpio_number) {
- return (*(s500_mmio_gpio + ((gpio_number)/32)*GPIO_INC + GPIO_DAT) & (1<<((gpio_number)%32)) ? 1 : 0);
- }
- void set_max_priority(void) {
- struct sched_param sched;
- memset(&sched, 0, sizeof(sched));
- // Use FIFO scheduler with highest priority for the lowest chance of the kernel context switching.
- sched.sched_priority = sched_get_priority_max(SCHED_FIFO);
- sched_setscheduler(0, SCHED_FIFO, &sched);
- }
- void set_default_priority(void) {
- struct sched_param sched;
- memset(&sched, 0, sizeof(sched));
- // Go back to default scheduler with default 0 priority.
- sched.sched_priority = 0;
- sched_setscheduler(0, SCHED_OTHER, &sched);
- }
复制代码
以上所參考的Datasheet如下
Fig.5 分別設置OUT或者IN的Register為Enable即可,雖然官方文件提到不用關閉相反功能的Register,但本範例為互斥,而DAT為資料Register可讀取也可寫入,因為每3個Word(32 Bit)為一個GPIO Bank,因此設置累加為3,即GPIO_INC,而每個Bank中OUT的offset為0,IN為1,DAT為2,即可完成對於任何GPIO number找到對應的Register Address來進行操作
10. 函式庫完成以後,我們要實作步進馬達部分的處理函式,讓它可以來回轉動180度,由之前的學習知道步進馬達的齒輪比為1:64,因此64為一相,八相512轉完為一圈360度,因此180度為256,即
- 64 <=> 1相 <=> 45度
- 512 <=> 8相 <=> 360度
- 256 <=> 4相 <=> 180度
复制代码
知道以上關係以後,創建 main.c檔案
11. 在 main.c檔案中輸入以下程式碼
- #include
- #include "gpio.h"
- #include
- #include
- #define IN1 131
- #define IN2 130
- #define IN3 50
- #define IN4 64
- #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,j;
- 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]);
- 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 : %d
- ",now->tm_hour,now->tm_min,now->tm_sec);
- printf("Setting time on %d
- ", ss);
-
- while(1)
- {
- 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 : %d
- ",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 : %d
- ",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);
- }
- set_default_priority();
- return 0;
- }
复制代码
至此就完成餵食器部分的測試程式,之後文章會跟其他功能結合,步進馬達照著八相順序寫入GPIO即可正轉和反轉,透過For Loop的遞迴次數即可決定角度,即256 = 180度,達到正反轉180度回到初始位置的設置,時間設置的方式取用struct tm來定義時間結構,使用time函式先獲取當前CPU時間,但是是一組數據不易解讀,因此再使用localtime函式轉成現在時區的格式,struct tm結構定義如下:
- struct tm {
- //the number of seconds after the minute, normally in the range
- 0 to 59, but can be up to 60 to allow for leap seconds
- int tm_sec;
- // the number of minutes after the hour, in the range 0 to 59
- int tm_min;
- // the number of hours past midnight, in the range 0 to 23
- int tm_hour;
- // the day of the month, in the range 1 to 31
- int tm_mday;
- // the number of months since January, in the range 0 to 11
- int tm_mon;
- // the number of years since 1900
- long tm_year;
- // the number of days since Sunday, in the range 0 to 6
- int tm_wday;
- // the number of days since January 1, in the range 0 to 365
- int tm_yday;
- };
复制代码
因此轉換出來的時間格式資料就可輕易的讀取,注意各個參數的範圍即可,另外flag為main程式的第四個參數,用來決定是否為測試模式,1的話為會設定每分鐘餵食一次的測試模式,其他設置忽略,0的話為定時,小時,分,秒皆為有效,以下是main參數定義
- 參數1 <=> 定時的小時 hh
- 參數2 <=> 定時的分 mm
- 參數3 <=> 定時的秒 ss
- 參數4 <=> 測試模式 (1為開啟,0為關閉)
复制代码
12. 編寫 makefile,幫助我們取用函式庫的資源,也方便編譯的工作
- COPS = -Wall -I . -L .
- gcc:libgpio.so motor
- libgpio.so:gpio.c gpio.h
- gcc $(COPS) [ DISCUZ_CODE_428 ]lt; -c -Werror -fPIC
- gcc $(COPS) gpio.o -shared -o libgpio.so
- motor:main.c gpio.c gpio.h
- gcc $(COPS) [ DISCUZ_CODE_428 ]lt; -o blink -lgpio
- clean:
- rm -f *.o
- rm -f *.so
- rm -f motor
复制代码
完成以上編寫後,可以在開發板上編譯了
13. 要存取Memory裝置的話需要切換到root,輸入以下指令來編譯,並且設定GPIO函式庫的路徑
- sudo -i
- cd
- make
- export LD_LIBRARY=$LD_LIBRARY:
复制代码
接下來可以開始測試了
14. 將餵食器下方的雙面泡棉膠黏貼在不鏽鋼鍋墊上,來實際測試轉動餵食的情況如何,ULN2003驅動模組連接步進馬達和開發板
Fig.6 將餵食器黏貼,出口剛好在不鏽鋼鍋墊兩鐵條間隔上
開發板,ULN2003驅動模組和步進馬達連接,即接線如下:
- GPIOE3 <=> IN1
- GPIOE2 <=> IN2
- GPIOB18 <=> IN3
- GPIOC0 <=> IN4
- 5V <=> V+
- GND <=> V-
复制代码
Fig.7 接線好的實品圖
15. 輸入以下指令來測試
其中因為開啟了測試模式,因此前兩參數忽略,而每分鐘0秒時候,餵食一次,注意要確實傳入共四個參數,否則會有錯誤
16. 觀察來回轉動180度以後,掉落在紙張上的飼料量,完成這部分設計
Fig.8 掉落適量的飼料,即完成自動餵食的設計
`
0
|
|
|
|