RoBoard魔人的機器人日誌

2016/11/14

[86Duino] IOT盆栽澆水寶 (栽寶ZaiBo)

什麼是ZaiBo

ZaiBo 是一個在 86Duino Zeo上使用 L86duntu作業系統的工具程式,使用 USB WiFi 網卡做一個IOT系統。
先讓我們來看看ZaiBo的介紹影片:



ZaiBo分為兩種模式:
第一種為完整版,使用USB Display提供觸控功能、自動澆水並且有遠端監控等功能。

第二種為精簡版,精簡版雖然沒有自動澆水功能,但它可以提醒使用者澆水,並且採用TFT LCD做為簡易的螢幕,而網站功能皆相同。

ZaiBo除了把原始碼公開以外,也將建構教學也一併公開,任何人都可以自由使用、修改、散佈。

其主要功能為:
Ø   自動澆水
使用者可以依據不同品種的盆栽來設定土壤濕度低於多少才澆水。
Ø   即時檢視
使用者可以在ZaiBo上的觸控螢幕進行互動。
Ø   遠端監控
使用者可以開啟ZaiBo網站進行即時監控,支援即時影像與資料顯示,也可以使用網站功能進行遠端控制。
Ø   提醒通知
使用者可以設定土壤濕度過低時寄信提醒使用者澆水,也可以開啟自動澆水模式,並設定水桶水量不足時可以寄信提醒使用者補水。
Ø   記錄圖表
ZaiBo也有記錄功能,使用者可以在網站上看到過往的圖表資料。

事前準備材料:

  1. 86Duino Zero
  2.  MicroSD 卡
  3. USB Wifi
  4. USB Display / TFT LCD
  5. USB Camera
  6. USB HUB
  7. 超音波模組
  8. 沉水馬達與開關控制電路
  9. 土壤濕度分壓電路

如何架設環境
首先,可以依照官方教學在SD卡上安裝L86duntu作業系統:



安裝完後就可以把安裝有 L86duntu 的 SD 卡插入 Zero 的 SD 卡插槽後通電開機,由於是使用Zero,所以我們必須要遠端網路連線的方式來登入L86duntu,詳情一樣可以參照官方教學:


登入後首先解決開機的問題,L86duntu在不正常關機後重開機會在下面看到的選單上等待:


如果不是使用One接上顯卡,我們很難知道L86duntu是不是卡在這個選單上了,所以我們先將這個功能設定為自動關閉。
首先將 /etc/grub.d/00_header 檔案裡的
 -> make_timeout() {... set timeout = -1 ...}
修改為
 -> make_timeout() {... set timeout = 10 ...}
﹝我們可以直接使用vim的搜尋功能 /make_timeout直接找到該列﹞



再將 /etc/default/grub 檔案中新增一列
 -> GRUB_RECORDFAIL_TIMEOUT=$GRUB_TIMEOUT


最後輸入指令
$ update-grub

修改完後就不會出現選單的問題囉!

接下來我們要安裝SMTP Server,因為ZaiBo的提醒功能是利用寄信來提醒通知的,所以我們需要有一個可以寄送郵件的伺服器。
先接上網路線後執行下面這一行指令來安裝相關套件:

$ sudo apt-get install mailutils

在安裝途中會出現設定視窗,按Tab切換到OK後進行下一步設定:

接下來將選項選擇到「Internet Site」,然後按 Tab 鍵到 OK 後按 Enter 鍵,接著就會完成 Postfix 的設定了:


接下來可以依照你的喜好設定信箱名稱,然後一樣按Tab選到OK按Enter鍵:
之後會看到完成畫面:



接下來我們要安裝SDL的套件,因為在USB Display上就是使用SDL來繪製圖形介面的,但如果你要做的是精簡版的ZaiBo的話,可以不安裝SDL,我們先來看看如何安裝SDL:
我們需要用到的其他函式庫有SDL_ttf、SDL_image、SDL_gfx,所以我們到SDL官網上下載原始碼,用FileZilla將檔案傳到L86duntu上。

解壓縮後,之後到各個目錄輸入指令開始安裝:

$ tar zxvf filename.tar.gz
$ cd filename
$ ./configure && make && make install

在此以SDL-1.2.15為例:

$ tar zxvf SDL-1.2.15.tar.gz
$ cd SDL-1.2.15
$ ./configure && make && make install





全數安裝完後我們就可以開啟我們的程式來做測試了。

擴充1G的swap                                                                                            

由於我們安裝了L86duntu又使用了很多的周邊裝置,所以128M的記憶體或許稍顯不足,所以我們可以將swap擴充,讓硬碟的一些空間拿來當作虛擬記憶體,指令如下:
$ cd /var
$ fallocate -l 1G swapfile
$ chmod 600 swapfile
$ mkswap /var/swapfile
$ swapon /var/swapfile



然後我們要在每一次開機時讓它自動啟動,所以我們需要增加 /etc/fstab 的內容:
$ /var/swapfile    none    swap    sw    0    0



修改完後我們可以重開機,輸入指令 free –m 來確認是否有變成2G的swap。

如何使用USB Wifi


在86Duino上使用USB Wifi很簡單,如果各位也是使用同一個USB Wifi晶片的話就可以照著官網的教學建立Wifi。
我在此有將指令稍做修改,因為我們的ZaiBo必須要自動連線,所以要自己辨識自己的網路編號:

$ cd /home/dmp
$ modprobe rt2800usb
$ echo 148f 5370 | tee /sys/bus/usb/drivers/rt2800usb/new_id
$ sleep 1
$ ID=$(exec iwconfig | grep -o -P "wlan[0-9]*")
$ echo $ID > /tmp/usbwifiID
$ ifconfig $ID up
$ killall wpa_supplicant
$ killall wpa_supplicant
$ killall wpa_supplicant
$ killall wpa_supplicant
$ killall wpa_supplicant
$ killall wpa_supplicant
$ killall wpa_supplicant
$ wpa_supplicant -i $ID -D nl80211 -c /etc/wpa_supplicant.conf -B
$ sleep 1
$ udhcpc -i $ID

在這段指令中,會將網卡的名稱存到 /tmp/usbwifiID 中,而在UI顯示的程式中也有來存取這個名稱,所以此一修改是必要的。

如何使用USB Display

和USB Wifi一樣,USB Display在86duino的官網一樣有詳盡的教學,但我們不需要進入圖形介面,所以可以自行修改run_usbdisp.sh內的內容:
$ modprobe rp_usbdisplay
$ sleep 1
$ modprobe -r rp_usbdisplay
$ modprobe rp_usbdisplay
$ sleep 1
$ modprobe -r rp_usbdisplay
$ modprobe rp_usbdisplay
$ cp /home/dmp/rpusbdisp-master/drivers/linux-driver/xserver_conf/10-disp.conf /usr/share/X11/xorg.conf.d/

如何開機時自動啟動所有的裝置

首先我們在 /etc/init.d/ 下建立一個檔案,在此我以plant為例。

建立完後在裡面輸入內容:﹝可能因裝置不同需要做更動﹞
#! /bin/sh
# /etc/init.d/plant
#

### BEGIN INIT INFO
# Provides:          plant
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     1 2 3 4 5 6
# Default-Stop:      0
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO

# setting usbwifi


# Some things that run always
touch /var/lock/plant

# Carry out specific functions when asked to by the system
case "$1" in
  start)
    export PATH=$PATH:/usr/local/bin
       
    # Starting usb wifi
    cd /home/dmp
    modprobe rt2800usb
    echo 148f 5370 | tee /sys/bus/usb/drivers/rt2800usb/new_id
    sleep 1
    ID=$(exec iwconfig | grep -o -P "wlan[0-9]*")
    echo $ID > /tmp/usbwifiID
    ifconfig $ID up
    killall wpa_supplicant
    killall wpa_supplicant
    killall wpa_supplicant
    killall wpa_supplicant
    killall wpa_supplicant
    #wpa_supplicant -i $ID -D nl80211 -c /etc/wpa_supplicant.conf -B
    iwconfig $ID key 0C04E5B3CD3C9A00DC825FB64B
    iwconfig $ID essid 3Com
    sleep 1
    udhcpc -i $ID

    # Starting plant website
    sleep 1
    chmod 755 /home/dmp/plant/start.sh
    nohup /home/dmp/plant/start.sh

    if [$(exec lsusb | grep "fccf:a001") == ""]; then
        # Starting TFT display
        sleep 1
        chmod 755 /home/dmp/TFT_display/TFT_display
        cd /home/dmp/TFT_display
        ./TFT_display
    else
        # Starting usbdisp
        modprobe rp_usbdisplay
        sleep 1
        modprobe -r rp_usbdisplay
        modprobe rp_usbdisplay
        sleep 1
        modprobe -r rp_usbdisplay
        modprobe rp_usbdisplay
        cp /home/dmp/rpusbdisp-master/drivers/linux-driver/xserver_conf/10-disp.conf /usr/share/X11/xorg.conf.d/

        sleep 3
        chmod 755 /home/dmp/UI_display/UI_display
        cd /home/dmp/UI_display/
        xinit ./UI_display
    fi
    ;;
  stop)
    echo "Stopping 86Duino Planter"
    echo "Stopping usbdisp"
    ;;
  *)
    echo "Usage: /etc/init.d/plant {start|stop}"

    exit 1
    ;;
esac

exit 0

儲存完成後,我們要先將此檔案的權限修改為可執行:
$ chmod 755 /etc/init.d/plant
然後使用update-rc.d更新開機script
$ update-rc.d plant start 99 1 2 3 4 5 6 . stop 80 0 .

設定完後每次重新開機就會啟動這隻script,而我在這隻script中也有做判斷是否使用USB display的裝置,若是沒有接上USB display的話就會啟用TFT LCD來做顯示。

如何建設硬體設備

首先我們需要兩根可以導電的鐵棒﹝我使用露營用的鐵棒﹞:








然後將線焊上,再焊上一顆200Ω的可變電阻﹝方便依據土壤品質調整分壓阻值﹞,再分別接到3.3V、A4、Gnd腳位上:

如此一來就可以使用兩根鐵棒簡單的量測土壤目前的濕度。

因為一般直流抽水馬達會有很多的雜訊,如果要做過濾雜訊的電路會麻煩許多,所以澆水功能的沉水馬達建議使用電腦水冷系統用的馬達。

而開關電路甚至可以很簡單的使用電晶體解決。﹝當然不是好辦法,各位也可以使用繼電器來作開關﹞





而超音波模組是用來量測水桶的水量的,我們可以利用水面高度與桶子深度來簡易的計算剩餘的水量。



而溫度濕度及大氣壓力則是使用RM-G185晶片,來做讀取。
網站功能

在ZaiBo開機後會自動開啟網站功能,各位可以看到簡單易懂的介面:

Current State也就是當前所有的狀態,分別是水箱水量、土壤濕度、空氣濕度、溫度以及大氣壓力。
而下方可以看到歷史記錄的圖表,讓使用者快速掌握近期的狀況。

Live Stream也就是即時串流,使用者可以在直接連上網站,就可以透過Camera知道目前的狀態。

在上方的橫欄也有Settings可以做設定:

Settings中提到的兩種模式,Warning mode也就是精簡版,只會在該澆水時寄信提醒你,而Watering mode則是完整功能版,也就是將抽水馬達澆水的功能也加進去了,會在水箱沒水時提醒補水。



由於是網頁,所以使用智慧型手機連線也是可以的!






寄信功能

ZaiBo是利用寄信來通知使用者該補水或澆水,以下可以看到ZaiBo寄信通知的功能:



Share:

2016/2/11

[86Duino] 機器人動態同步的物聯網應用

今天本魔已將先前提到的機器人動態同步上傳至Github,
當環境都架設好之後便可以使用,
而機器人動態同步的主系統為ROS,
所以在Linux上架設好ROS環境後,
開啟ROS Serial Server,
讓所有機器人連線到同一區域網路下,
確認所有機器人皆連到主機後,
就可以開始同步動作啦~。



而這機器人分別是史加納以及86小六足,
在此奉上它們的開源Github,
要做幾隻就做幾隻,
只要WIFI AP夠強,
要做一整個小巨蛋的小六足跳舞也不是問題呢XD
Github中已有編好的妖怪舞給各位做為範例!
86Hexapod: https://github.com/roboard/86Hexapod
86小六足
Skarner: https://github.com/RoBoardGod/Robot-Skarner
史加納(偽娘型態)






而史加納與86小六足接線圖也請參考下圖:
86Hexapod for 86Duino Zero
86Hexapod for 86Duino One

Skarner for 86Duino One





Share:

2016/2/2

[86Duino] 在SPI上使用ESP8266

ESP8266,是一個極為便宜的Wifi晶片,
因為網路上已經有許多的介紹文,所以本魔在此就不多贅述。

首先先講講一般ESP8266的接法及使用,
標準接線圖
實際範例
將ESP8266接上86duino後,
就可以將下列程式碼燒入86duino,
這樣就可以透過來做AT Command的控制。

void setup()
{

 Serial.begin(9600);
 Serial1.begin(115200);
}
void loop()
{
 while (Serial1.available()) {
  Serial.write(Serial1.read());
 }
 while (Serial.available()) {
  Serial1.write(Serial.read());
 }
}


燒入後可以用
這樣就可以做一些基本連線及設定的控制,
至於下AT Command的方法請參考文件。



然而為什麼要改裝到SPI上呢?
請看下圖!

改裝後
沒錯,這樣就可以牢牢的插在86Duino上,
因為在本魔實驗時常常遇到因機器人晃動造成ESP8266電源接觸不良導致斷線,
而且這樣也不用考慮整線的問題。

那麼就開始進入我們的教學,
首先要做出我們的轉接座。
轉接座及ESP8266
材料也很簡單,
兩顆母座2x5 ROHS及一條cable
母座2x5 ROHS

或許你會問,
為什麼需要多一條線?
SPI不就有電源輸出了嗎?
為什麼還要再跳線?
這是因為SPI是只有5v輸出,
而ESP8266的電源必須3.0V至3.6V間,
所以多拉一條跳線接3.3v。
本魔測試過ESP8266接5v電源也是可以工作的
,只是有過熱燒壞的風險,或其他狀況,所以也不是很推薦這樣使用。


SPI腳位圖
本魔再對照過後認為將SPIDI改TX、SPICS改RX會比較好焊。

轉接圖
也就是轉接頭只焊3PIN,其餘兩PIN3.3v直接跳線。
本魔的方法是,將不需要的PIN腳拔掉,再將PIN腳折至相連。
焊接前
焊接後
當然,只有三根腳支撐是相當不牢固的!
所以本魔在中間灌入熱熔膠,固定並防止短路。
切記接上ESP8266時不要接反了,
為防止接錯本魔還貼了辨識用的紅紙。
熱熔膠及辨識用的紅紙



結束後就可以撰寫轉換的程式囉!

在86Duino libraries中的io.h有一個io_outpb,
io_outpb它是可以直接存取硬體IO的function,
這function是非常低階的控制,
基本上是要到Hacker等級的才會玩的!
所以如果你不是Hacker的話就不要亂玩唷!
因為很重要所以要標紅字。

直接存取硬體IO的話就可以將你要的腳位切換成你要的功能,
所以首先要先去了解86Duino的IO Port,
可以在官方找到86Duino CPU的Datasheet(Vortex86EX Datasheet V1.4),
我們可以看到P306.文中的CrossBar Config Registers內有寫各功能的編號,
找出我們所需要的就是將COM1的TX、RX更換為SPI的CS、DI,
000001b COM1-TXD1
000010b COM1-RXD1
001001b SPI-CS1
001100b SPI-DI

而COM1的TX、RX及SPI的CS、DI的address分別為:
0x0A12 COM1 TX
0x0A13 COM1 RX
0x0A20 SPICS
0x0A22 SPIDI
如此一來我們就可以寫出接上轉接座的控制程式碼:

void setup()
{
 io_outpb(0x0A12,0x00); //enable COM1 TX pin
 io_outpb(0x0A13,0x00); //enable COM1 RX pin
 io_outpb(0x0A20,0x02); //SPICS to RX
 io_outpb(0x0A22,0x01); //SPIDI to DX
 Serial.begin(9600);
 Serial1.begin(115200);
}
void loop()
{
 while (Serial1.available()) {
  Serial.write(Serial1.read());
 }
 while (Serial.available()) {
  Serial1.write(Serial.read());
 }
}
Share:

魔人歸來



或許有人會問,
在一年多前的COSCUP演講後,
為什麼本魔怎麼就消失了?

其實答案很簡單,
因為國家需要我!本魔去盡國民應盡的義務XD
本魔甚至還去體驗了從1250英呎是跳下來的感覺呢!

回到主題,
近期本魔做了過往的機器人的精華合體,
也會陸陸續續打過程及教學文。
咦?
你問我什麼叫做「過往的機器人的精華合體」?
過往的機器人是指機械史加納還有幾台人型機器人,
那合體該不會是!?

絕對不是你想的這東西!
而是近期本魔用了最近很夯的ESP8266,
製作了機器人動態同步的IOT應用,
沒錯,IOT就是最近很夯的物連網啦!
廢話不多說,請看影片~






( 聽說今年尾牙流行跳妖怪舞 ... XD )最近用 86Duino + ESP8266 為客戶寫了一個 device 動態同步的物聯網應用 sample code, 展示如何動態增刪同步節點, 並在任意時間點同步任意 device. 因為阿宅工程師們覺得這個 sample 也蠻適合拿來做機器人跳舞同步的應用, 於是一群人就玩興大起, 搞出了下面這段新春賀歲特別節目~XDDD
由 RoBoard Lab 貼上了 2016年2月1日



或許有人會問為什麼不一開始就把機器人放著跳呢?
間單來說就是告訴各位我們沒有作弊阿XD
動態同步也就是要隨時開機隨時都能跟上舞步的唷!
如果是編動作,一鍵到底的方式可是沒辦法做出這種效果呢!
Share:

2014/5/16

[EduCake] EduCake平衡車全新再進化

在之前,本魔拿到了這台EduCake平衡車,
本魔認為相當有趣,
因為使用的是實驗用的小馬達及小型行動電源,
所以出現許許多多不同的問題,
但也將這些問題一一排解,現在就來介紹遇到的問題及解決的方法。

首先,最大的問題是平衡,
先前做的版本其實並不是相當穩定,
之後本魔加入了PID去做調整,加入了Kp與Kd雖然穩定許多,
但還是經常出現爆衝無法平衡的問題,
最後加入了Ki,情況才穩定下來。

接著是馬達力道問題,
基本上兩顆馬達的精度就不是很高,
所以有時會出現一輪轉一輪不轉的情況(即一輪未克服摩擦力),
接著就是克服摩擦力的力道有點大,
這樣使得馬達無法相當低速的旋轉,在克服摩擦力後的轉速過快,
平衡時會無法精準的調整到我要的轉速,使得平衡又再難上一個層次。

接著的問題是施力過大時,陀螺儀會當掉,
一開始本魔也是百思不得其解,
電表、示波器都測試過,結果去詢問了電路的達人,
他幫我加了幾顆電容,就解決了,
似乎是電流被拉低,導致陀螺儀重啟。

接著是要在倒地時自己站立起來,
本魔發現力道相當不夠,所以將電源做了更換,
換成7.4V的鋰電池,在加上5V穩壓電路供給控制板。

以上是近期遇到的問題,
也確實的想辦法解決了,
而之後,本魔會再加入移動控制,
請各位敬請關注!



code:

#include <EEPROM.h>

#include "FreeIMU1.h"

#include <Wire.h>

#include <stdio.h>

#include <conio.h>

#include "pitches.h"

int raw_values[9];

float ypr[3]; // yaw pitch roll

unsigned long pretime = 0;

unsigned long pretime1 = 0;

unsigned long pretime2 = 0;

int pin[4] = {10,11,31,32};

float target;

float targetoffset = 0;

float Kp,Ki,Kd;

float pid_u,pid_sum;

float pid_e[2];

char key;

boolean flag = true;

// Set the FreeIMU object

FreeIMU1 my3IMU = FreeIMU1();

void setup() {

 Serial.begin(115200);

 Wire.begin();



 delay(5);

 my3IMU.init(); // the parameter enable or disable fast mode

 delay(5);

 pinMode(10,OUTPUT);

 pinMode(11,OUTPUT);

 Kp = 70;

 Ki = 1.2;

 Kd = 20;

        target = 0;

        pid_u = 0;

        pid_e[0] = 0;

        pid_e[1] = 0;

        pid_sum = 0;

}



void loop() {

 //Get gyro value

        target += targetoffset;

 my3IMU.getYawPitchRoll(ypr);

          //PID calculate

          if(kbhit()){

            key = getch();

            if(key == '1')

              Kp += 5;

            else if(key == '2')

              Kp -= 5;

            else if(key == '3')

              Kd += 5;

            else if(key == '4')

              Kd -= 5;

          }

          if((ypr[2] > 25 || ypr[2] < -25) && flag){

              target = 0;

              targetoffset = 0;

              tone(PCSPEAKER, NOTE_G3,1000/4);

              delay(1000/4 * 1.30);

              noTone(PCSPEAKER);

              flag = false;

         pretime1 = millis();

       analogWrite(pin[0],0);

       analogWrite(pin[1],0);

       analogWrite(pin[2],0);

       analogWrite(pin[3],0);

          }

          if(ypr[2] > 25 && millis() - pretime1> 2000ul){

              target = 0;

              targetoffset = 0;

              tone(PCSPEAKER, NOTE_G4,1000/4);

              delay(1000/4 * 1.30);

              noTone(PCSPEAKER);

              pid_sum = 0;

       analogWrite(pin[0],0);

       analogWrite(pin[1],255);

       analogWrite(pin[2],0);

       analogWrite(pin[3],255);

              delay(100);

       analogWrite(pin[1],0);

       analogWrite(pin[0],255);

       analogWrite(pin[3],0);

       analogWrite(pin[2],255);

              delay(100);

       analogWrite(pin[0],0);

       analogWrite(pin[1],0);

       analogWrite(pin[2],0);

       analogWrite(pin[3],0);

              delay(75);

       pretime2 = millis();

              while(millis() - pretime2 < 2000ul){

                my3IMU.getYawPitchRoll(ypr);

         if(ypr[2] < -5 && ypr[2] > -25){

                  flag = true;

                  break;

                }

              }

          }else if(ypr[2] < -25 && millis() - pretime1> 2000ul){

              target = 0;

              targetoffset = 0;

              tone(PCSPEAKER, NOTE_G4,1000/4);

              pid_sum = 0;

       analogWrite(pin[1],0);

       analogWrite(pin[0],255);

       analogWrite(pin[3],0);

       analogWrite(pin[2],255);

              delay(100);

       analogWrite(pin[0],0);

       analogWrite(pin[1],255);

       analogWrite(pin[2],0);

       analogWrite(pin[3],255);

              delay(100);

       analogWrite(pin[0],0);

       analogWrite(pin[1],0);

       analogWrite(pin[2],0);

       analogWrite(pin[3],0);

              delay(75);

         pretime2 = millis();

              while(millis() - pretime2 < 2000ul){

                my3IMU.getYawPitchRoll(ypr);

         if(ypr[2] < 25 && ypr[2] > 5){

                   flag = true;

                   break;

                }

              }

          }

          if(ypr[2] < 25 && ypr[2] > -25){

                  pid_sum += pid_e[0];

                  pid_e[0] = target - ypr[2];

                  pid_u = Kp * (pid_e[0])

                        + Ki * (pid_sum)

                        + Kd * (pid_e[0] - pid_e[1]);

                  if(pid_u > 0){

             pid_u = pid_u > 255 ? 255: pid_u;

             pid_u = pid_u < 90 ? 0: pid_u;

                  }else{

             pid_u = pid_u < -255 ? -255: pid_u;

             pid_u = pid_u > -90 ? 0: pid_u;

                  }

                  pid_e[1] = pid_e[0];

           analogWrite(pin[1],pid_u > 0 ? pid_u : 0);

           analogWrite(pin[0],pid_u < 0 ? -pid_u : 0);

           analogWrite(pin[3],pid_u > 0 ? pid_u : 0);

           analogWrite(pin[2],pid_u < 0 ? -pid_u : 0);

          }

        if(pid_u > 90 || pid_u < -90){

            targetoffset = pid_u * 0.0003;

        }else{

          targetoffset = 0;

        }

        while(millis() - pretime> 1000ul){

  Serial.print(" Roll: ");

  Serial.print(ypr[2]);

  Serial.println("");

  pretime = millis(); 

 }

}



Share:
技術提供:Blogger.

追蹤者