單晶片lab2結報

tags: arduino
實驗日期 : 2021/09/30

上課教材

lab1

SPEC

密碼鎖 - 自訂一個密碼(ex:1234),使用keypad來輸入值,輸入完成後按A送出。若輸入值與密碼相同則印出Unlock!,錯誤則印出Wrong passwords!

實現方法

if (enter != NO_KEY) {
    if (enter == 'A') {
        break;
    } else {
        value[count++] = enter;
        Serial.print(enter);
    }
}

將輸入的字串存於字符陣列,但'A'不儲存。

if (strcmp(value, password) == 0) {
        Serial.println("Unlock!");
    }

使用C-string字串處理的函式,與正確密碼的字符陣列去做比較。

lab2-1

SPEC

承上題,增加2項功能 :

  1. 修改密碼
  2. 添加驗證碼(透過七段顯示器顯示經過random()得出的3位驗證碼,並在輸入錯誤時再隨機變換一組驗證碼直到輸入正確)。

實現方法

延續lab1程式

if (enter != NO_KEY) {
        if (enter == 'B') {/*revise password*/} 
        else if (enter == 'A') {/*enter password*/}

分2個個模式 - 按B為修改密碼,按A為輸入密碼。

void input(char terminal_char, char value[]);
void input_3(char terminal_char, char arr[]);

2個subfunction中相同點為傳入之參數 :

  • 第1個參數判定keypad輸入終止字串的字符。
  • 第2個參數傳入儲存之字串陣列。

不同點1為使用時機與功能 :

  • 前者是輸入並儲存4-digit的數值。
  • 後者是輸入並儲存3-digit的數值(驗證碼),並且要在等待使用者輸入的同時,讓七段顯示亮燈,關於亮燈我是寫進一個class,要用到的時候就可直接呼叫成員函數show_seg.show(verification);,詳見lab4實現方法第2點

不同點2為是否有把最後終止字符儲存至字串陣列 :

  • 前者是字串比較,承如lab1實現方法,只會以char型態儲存輸入之數值,最後不會儲存終止字符。
if (strcmp(input_value, password) == 0) {
    Serial.println("Unlock!");
}
  • 後者為整數比較,最後會儲存字符,才能進行atoi()的正確轉換。
    使用整數的目的為
    1. 配合long random(min, max)回傳值的資料型態
    2. 方便調用我寫的7-seg display這個class成員函數的接口void SHOW_SEG::show(int num);
if (verification_code == atoi(input_value)) {
    Serial.println("Correct!");
}

lab2-2

SPEC

使用keypad輸入一個三位數字,實現終極密碼 \(\text{answer} \in [0, 999]\)

實現方法

while (true) {  //4-digit number
    enter = keypad.getKey();
    if (enter != NO_KEY) {  //input
        guess_ch[count++] = enter;
        if (isDigit(enter)) {
            Serial.print(enter);
        } else if (enter == 'A') {
            break;
        }
    }
}
  • 第一層if判斷檢查是否輸入數值,內建NO_KEY為按鍵沒有被按下的值。
  • 第二層if判斷輸入的合法性(validity),限定為輸入的資料型態為數字。
guess = atoi(guess_ch);

由於在前一程式最後按下A輸出時,會把字符'A'存進輸入數值的最後,在這邊起到辨識數值長度的功能,因為atoi()遇到非數字字符串結束('/0') 時會結束轉換,並將結果返回。

lab3

SPEC

利用MPU-6050做出計步器功能。(只使用三軸加速度(\(a_x, a_y, a_z\))之值來進行運算,代表只有判斷旋轉角度)

觀念

MPU6050是一種非常流行的空間運動傳感器芯片,可以獲取器件當前的3個加速度分量3個旋轉角速度。由於其體積小巧,功能強大,精度較高,不僅被廣泛應用於工業,同時也是航模愛好者的神器,被安裝在各類飛行器上馳騁藍天。

自帶了一個數據處理子模塊DMP,已經內置了濾波算法。數據接口用的是\(\rm I^2C\)(讀音 : I-squared-C)總線協議(原理)。

數據寫入和讀出均通過其IC內部的寄存器實現,這些寄存器的地址都是1個byte,也就是8位的尋址空間。

實現方法

  1. 正規化
int sensitivity = 32767 / 250;

\(G_X\)取正最大值\(32768\)時,當前角速度為順時針\(250\;\omega/s\),故可得靈敏度,即為角速度1秒內1弧度所代表的數值為

\[ \frac{32767}{250\;\omega/s} \tag{1.1} \]

  1. 將加速度數據代入回推變化角度

\[\tan^{-1}\Bigg[\; \frac{A_x} { \sqrt{(A_y)^2 + (A_z)^2} }\;\Bigg] \times \frac{180}{\pi} \tag{2.1}\]

\[\tan^{-1}\Bigg[\;\frac{A_y} { \sqrt{(A_x)^2 + (A_z)^2} }\;\Bigg] \times \frac{180}{\pi} \tag{2.2}\]

\[\tan^{-1}\Bigg[\;\frac{ \sqrt{(A_x)^2 + (A_y)^2} }{A_z} \;\Bigg] \times \frac{180}{\pi} \tag{2.3}\]

  1. 50筆資料內,每測量一次更新最小與最大值,並相減。
  2. 最後檢查是否超出臨界值,條件為 : \[(θ_x)^2 + (θ_y)^2 + (θ_z)^2 < (\text{threshold})^2 \tag{4.1}\]

lab4

SPEC

承上題,加上七段顯示器顯示

實現方法

延續lab3程式

  1. 觀察題目要求增加步數之數列為: \[0 + 1 + 8 + 27 + ...... +\; n^3 = \sum_{k=0}^{n} k^{3} \tag{1.1}\]

  2. 為簡化程式碼,使用OOP觀念,將4-digit 7-segment display驅動封裝成一個class,因此在主函數只需要增加2行 :

SHOW_SEG::SHOW_SEG(int first_pin, int last_pin)

創建類對象,並調用有參構造函數,第一至第二參數為七段顯示器連續接口,即 \(\text{pin} \in [2, 13]\)

void SHOW_SEG::show(int num);

調用這個類的成員函數show(),外部使用者只需要輸入一個整數,即可使七段顯示器亮燈,不需知道內部是如何實現,實現函數封裝。

課後習題

Question 1

請說明非同步傳輸中的RS-232, RS-422以及RS-485,並討論這三者的差別為何?

Answer 1

Arduino藉由UART作為資料傳輸介面,其線路僅有傳輸(TX)、接收(RX)兩個,以邏輯電路的形式傳輸資料,高電位時為1、零電位時為0。在Arduino要與PC溝通時,由於PC沒有UART的傳輸介面,則需要以下傳輸介面作為轉換,其差異如下:

傳輸介面 說明
RS-232 點對點通訊,傳送和接收端共用一個接地電位,並用一根訊號線上的電壓來判斷傳送的狀態爲0或1,容易引起干擾。
RS-422 差動的方式傳輸資料,兩根接收、兩根傳輸,因此可以同時發送與接收信號,即:全雙工通信(full-duplex)。藉由差動的方式可以減少雜訊干擾,可用於遠距離傳輸。
RS-485 RS-422差動傳輸原理相同,差別在於只有兩根線同時作為傳輸、接收使用,因此無法同時發送與接收信號,故只能使用半雙工通信(half-duplex)。因為僅需兩條線就可傳輸,適用於佈線成本高但是資料傳輸量需求不高的應用中。

|

  • 各傳輸介面之驅動電壓
傳輸介面 邏輯0 邏輯1
UART 0 V 3.3 or 5 V
RS-232 3~15 V -3~-15 V
RS-422 -2~-6 V 2~6 V
RS-485 -1.5~-6 V 1.5~6 V
  • UARTRS-232C之間要如何轉換?
    透過一顆RS-232C的傳送/接收器叫做MAX232,內部設計2個非常巧妙的電路 - charge pump升壓反向電路,儘管只有這顆IC只有5V供電,還能輸入RS-232的正負電壓與高於5V之電壓。以下2個電路在MAX232內部是由4個電容器和一系列的開關切換電路自動完成 :

    1. Charge pump
    原始 後來
    兩個電容器C1C2,分別用5V電源將它們充飽 C1C2脫離電源,並串聯起來
    1. 反向電路
    原始 後來
    一個電容器充電 將它的極性對調,讓原來電位比較高的那支腳與系統的0 V電位相連,由於電容器裡面的電位差不會改變,所以原來低電位那隻腳,現在相對於0V來說就是負電壓
  • ref : 【Maker電子學】認識UART界面#1—訊號格式

Question 2

MPU-6050的接腳XDAXCLAD0功能為何?

Answer 2

接腳 介紹
VCC 提供MPU-6050系統電壓,3~5V皆可。
GND MPU-6050系統接地。
SCL \(\rm I^2C\)通訊協定所需的時脈週期
SDA 藉由\(\rm I^2C\)通訊協定傳輸資料
XDA MPU-6050為master、額外模組為slave,傳輸資料給額外模組
XCL MPU-6050為master、額外模組為slave,提供時脈週期給額外模組
AD0 當有兩個MPU-6050連接在同一個master(arduino board)上時,須分別給兩個slave(MPU-6050)不同通訊位址LOW->0x68HIGH->0x69,可自行選擇其通訊位置。
INT 中斷MPU-6050計算程序,使arduino可讀取其資料,以避免MPU-6050資料溢位。

Question 3

請問取random數字時不使用randomSeed()的差別在哪?

Answer 3

  • random(max), random(min, max)
    生成隨機亂數。
  • randomSeed(unsigned long seed)
    初始化偽隨機數生成器,使其從隨機序列中的任意點開始。生成序列取決於種子碼的值,因此都是一樣的,若要不同,則需指定一個亂數種子。
  • 舉例說明
    以lab2-1、lab2-2為例,使用randomseed()時,其seed的來源為未連接的pin腳analogRead(0)所讀取到的值,由於該腳位為連線,因此會受其他因素(其他模擬輸入的值、手與電路板的距離等)影響。

此觀念與C/C++相同。

Question 4

下方為不呼叫MPU-6050 library來讀出MPU-6050數值之程式, 請為其標上完整的註解。

Answer 4

#include<Wire.h>
  • 函式庫引用
    MPU6050使用\(\rm I^2C\)(讀音 : I-squared-C)總線協議(原理),因此我們需要頭文件<Wire.h>來實現Arduino與MPU6050之間的傳輸。
const int MPU_addr=0x68; 

//在程式執行開始時會執行一次,用於初始化設定
void setup(){
    Wire.begin(); 
    //初始化Wire.h並作為master or slave加入I2C bus
    //如未指定7-bit slave地址(函數重載),則為加入者為master
    Wire.beginTransmission(MPU_addr); //開啟MPU6050的傳輸
    Wire.write(0x6B); //指定寄存器地址
    Wire.write(0); //寫入一個字節的數據
    Wire.endTransmission(true); //結束傳輸,true表示釋放總線
  • 將數據寫入MPU-6050
    在每次向器件寫入數據前要先打開Wire的傳輸模式,並指定器件的總線地址,MPU6050的總線地址是0x68(AD0引腳為高電平時地址為0x69),地址可於IC的datasheet中查閱或自己寫i2c_scanner檢測。然後寫入一個字節的寄存器起始地址,再寫入任意長度的數據

    這些數據將被連續地寫入到指定的起始地址中,超過當前寄存器長度的將寫入到後面地址的寄存器中。寫入完成後關閉Wire的傳輸模式。示例是向MPU60500x6B寄存器寫入一個byte0
    Serial.begin(38400); //設定傳輸時的baud(每秒有幾個bit)
}

//重複執行在loop()函式中的程式碼,直到Arduino硬體關閉
void loop(){
    Wire.beginTransmission(MPU_addr); //開啟MPU6050的傳輸
    Wire.write(0x3B); //指定寄存器地址
    Wire.endTransmission(false); //開啟傳輸模式
    //If false, endTransmission() sends a 
    //restart message after transmission.
    //The bus will not be released, 
    //which prevents another master device 
    //from transmitting between messages.
    Wire.requestFrom(MPU_addr,14,true); //從MPU要求14byte資料
    AcX=Wire.read()<<8|Wire.read(); //兩個字節組成一個16位整數 
    AcY=Wire.read()<<8|Wire.read();  
    AcZ=Wire.read()<<8|Wire.read();
    //Tmp=Wire.read()<<8|Wire.read(); //題目少一行,與溫度有關之數值
    GyX=Wire.read()<<8|Wire.read();  
    GyY=Wire.read()<<8|Wire.read(); 
    GyZ=Wire.read()<<8|Wire.read(); 
  • 從MPU-6050讀出數據
    讀出和寫入一樣,要先打開Wire的傳輸模式,然後寫一個字節的寄存器起始地址。接下來將指定地址的數據讀到Wire.h的緩存中,並關閉傳輸模式,最後從緩存中讀取數據。

    示例是從MPU60500x3B寄存器開始讀取14個byte的數據。由於Wire.read()回傳值下一個接收到byte,總共14Wire.read(),因此master才會要跟slave要14個byte。依序兩兩一組,先讀到的A向左位移8bit,後讀到的B與A做Bitwise OR因此等同於2個8個bit整數concatenate成一個16個bit整數。

    舉例來說 \[ \begin{array}{clcr} \text{字串A} & 1 & 0 & 0 & 1 & 1 & 1 & 1 & 1\\ \text{字串A left shift 8 bits} & 1 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ \text{字串B} & & & & & & & & & 1 & 0 & 1 & 0 & 0 & 1 & 0 & 1\\ \text{與左移之字串A做Bitwise OR} & 1 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 1 & 0 & 1 & 0 & 0 & 1 & 0 & 1\\ \end{array} \]
    Serial.print("AcX = "); Serial.print(AcX); //傳輸並顯示在serial monitor
    Serial.print(" | AcY = "); Serial.print(AcY);
    Serial.print(" | AcZ = "); Serial.print(AcZ);  
    Serial.print(" | GyX = "); Serial.print(GyX);
    Serial.print(" | GyY = "); Serial.print(GyY);
    Serial.print(" | GyZ = "); Serial.println(GyZ);
    delay(333); //延遲333ms
}
  • 印出數據
    將結果透過TX腳位並藉由之前所設定之baud的傳輸速率,傳到電腦並顯示在serial monitor上。

  • 參考資料

  1. Arduino and MPU6050 Accelerometer and Gyroscope Tutorial
  2. MPU6050的數據獲取、分析與處理
  3. EduCake 的 I2C 通訊
  4. 認識UART界面
  5. How I2C Communication Works and How To Use It with Arduino

心得

劉永勝

這次實驗還是沒能在時間內完成,在lab2-1的地方,原先我們使用switch語法,增加兩個新功能,但在燒入Arduino時,始終無法操作keypad,搜尋了許多資料,最後只好改成if-else語法才終於通過,其問題原因不明。

而在取MPU-6050之數值時,我認為取角度來作計步器的條件不太好,計步器應該是要接受到震動再作用,所以若用加速度可能比較符合實際計步器

李宇洋

此次實驗已經較為熟悉Arduino的操作方式以及接線方法,但是因為程式端的問題,所以我們組別還是超過上課時間後才把demo做完。這次主要卡在lab2-1新密碼字串處理的問題以及switch裡的default不知為何無法被Arduino IDE正確讀取到,因為被switch語法折騰了很久,所以我們後來乾脆改用if-else語法,才終於解決這部分的問題。

而處理字串的問題礙於時間關係因此沒有完善的處理好,本來的想法是利用string.length()將每次輸入的新密碼長度記住後再將buffer清空,並重複每一次新密碼的輸入,但是不知為何會一直讀取到空字元,希望下次再次遇到類似問題時能順利解決。

陳旭祺

lab1、lab2-1、lab2-2我傾向於不使用stringpassword這兩個class的調用,而使用傳統的C-style string進行字串處理,以方便操作底層。

而lab3、lab4使用MPU-6050做出計步器功能,這部分重點在於取50點數據中最小與最大值,並相減與臨界值進行比對。我認為題目限定判斷條件只用旋轉角度,而不使用加速度,不是一個好的設計,因為平常在跑步時,除非計步器沒有固定好在腰上,不然計步器是隨人的身體上下移動,而比較少會有大幅度旋轉的運動軌跡

另外,以下記錄我遇到的問題與解決方法 :

項目 說明
問題 Arduino第一次Serial.prtint常常出現亂碼、錯誤縮排。
推測原因(有待商榷) stdin的觀念思考,緩衝區一開始未清空字符,印出不預期之空格。
解決方法 在設定鮑率Serial.begin()前面加上Serial.println();
先自行flush掉緩衝區內的字元。
Select a repo