---
tags: 單晶片助教
---
# [3] Arduino講義:密碼鎖&計步器實作
## Intoduction of UART
* 全文為 **Universal Asynchronous Receiver/Transmitter** ,即**通用非同步收發傳輸器**,是一種硬體介面。
* 通用非同步收發傳輸器是一種異步收發傳輸器
* 全雙工
* 串列
* 非同步

* **單工、半雙工與全雙工**
| 單工 | 半雙工 | 全雙工 |
| -------- | -------- | -------- |
|| ||
|只支持信號在一個方向上傳輸(正向或反向),任何時候不能改變訊號的方向。 EX:計算機和印表機之間的通信、B.B.Call。 | 允許信號在兩個方向上傳輸,但同一時間只允許信號在一個信道上單向傳輸。EX:傳統的對講機。 |允許數據同時在兩個方向上傳輸,即有兩個信道,因此允許同時進行雙向傳輸。 EX:電話、手機、RS232。 |
* 原文網址:https://kknews.cc/zh-tw/other/y2av3rj.html
* **串列 v.s 並列**
* 串列

* 一次傳送一個 bit,傳輸速率較慢、成本較低、可遠距離傳輸。
* EX :滑鼠、鍵盤、USB、HDMI、RS-232 介面。
* 並列

* 一次傳送多個 bit,傳輸速率快、成本高、易受雜訊干擾,僅限短距離使用。
* EX :25Pin D-Type印表機、顯卡PCI介面。
<!-- * 注意:並列傳輸的線路較多,但傳輸速度不一定比較快(因為接收端要同時將傳進來的資料整合及排列,虛耗額外的位元及處理時間)-->
* **非同步 (Asynchronous) v.s 同步 (Synchronous)**
* 非同步

* 雙方有各自的時脈, 在傳送資料時插入額外資訊,表示資料起始、結束。優點是設定時間短、硬體成本低、機器時脈不同也能傳資料,缺點是單次傳輸的資料量較少。
* EX : RS-232 (實現方式簡單低廉)、 RS-422 (較長距離的傳輸)、RS-485 (較多的裝置連接數目)。
* 同步

* 雙方共用時脈(一次只做一件事),額外提供時脈訊號,使兩端機器在溝通時能夠藉此同步收發資料。比起非同步傳輸,同步傳輸不需要 start/stop bit ,因此能夠一次傳較多的資料。
* EX : I2C 、 SPI 。
* 由2個獨立的接收 (RX) 、發送 (TX) 緩衝器構成,可同時發送和接收資料。
* 鮑率即每秒鐘多少位元bits per second (bit/s,EX:1鮑即指每秒傳輸1個符號。
* 若 baud 為 9600 (bits/sec),表示每秒可以傳 960 Bytes 的資料量。
* UART 傳送1 Byte (=8 bits)資料,加上頭尾共為10 bits。
$$\frac{9600(bits)}{startbit(1 bits)+data(8 bits)+stop bit(1 bits)}= 960Bytes$$
[參考資料](https://zh.wikipedia.org/wiki/%E6%B3%A2%E7%89%B9%E7%8E%87)
## UART Protocol

* 每個符號由三種資料共10個位元所組成,共分為:
1. 起始位元 (Start Bit)
2. 資料內容 (Data)
3. 停止位元 (Stop Bit)
* 平時沒用時UART的TX、RX腳都是處於高電位,每個資料在傳送前都會有一個低電位的起始位 (start bit) ;資料傳完之後會接著一組停止位元 (stop bit)通常為1位元,少數為2位元。
* 因此,若假設資料長度為 8bits,加上頭尾,每傳 1byte 至少需要 10bits。
* [UART參考](http://makerpro.cc/2016/04/understand-what-is-uart/)
---
## UART register description
* **Receive buffer** : 只能讀出不能寫入。
* **Transmitter buffer** :只能寫入不能讀出。
* **Interrupt Enable Register** : CPU寫入暫存器告訴UART何時中斷。
### UCSR0A(USART Control and Status Register 0 A)

* 它是一個暫存器,用於指示串行通信的控制和狀態。
* **RXC0** ( USART Receive Complete ) : 當**接收緩衝區**中存在未讀取的數據時,旗標設為1。用來知道何時能收到一個位元組。
* **TXC0**( USART Transmit Complete ) : 當**發送緩衝區**中發送數據時,**TXC0**( USART 發送完成 )變為1。
* **UDRE0** ( USART Data Register Empty ) : 當數據可寫入**傳輸緩衝區**時,**UDRE0**(USART數據暫存器為空)變為1。
### UCSR0B(USART Control and Status Register 0 B)

* 它同樣是一個暫存器,用於指示串行通信的控制和狀態。
* **UCSR0B** 暫存器比較重要的是 **RXEN0** 和 **TXEN0**,這兩個位元用來啟用接收器 (receiver) 和發射器 (transmitter),一般來說,我們會啟用這兩個元,可以用來開啟傳送、接收的位元。
* **UDRIE0** (USART Data Register Empty Interrupt Enable 0) : 決定要不要啟用中斷的位元。如果 **UDRIE0** 設置為 1,則允許UDRIE0標誌中斷。
### UDR0(USART I/O Data Register 0)

* 它是用來存放資料的暫存器。
* **TXB** 和 **RXB** 具有相同的 I/O address (共用UDR0)。
* 當寫入資料到 **UDR0** 時,資料會寫入**TXB register**。
* 當要讀取 **UDR0** 的內容時,實際上是從 **RXB register** 讀取內容。
---
# Arduino講義:密碼鎖實作
* 密碼鎖涵式庫ZIP檔:[Password Library](https://drive.google.com/drive/folders/1brIT2wvlJBPT8Vh8zl58tQ71kJK1dL0V?usp=sharing)
* 函式庫說明: https://playground.arduino.cc/Code/Password/
## Lab1
* 實驗目的:學習使用Keypad及製作密碼鎖。(不一定要使用函式庫)
* 實驗目標:自訂一個密碼(如1234),使用Keypad來輸入值,輸入完成後按A送出,若輸入值與密碼相同則印出Unlock!,錯誤則印出Wrong passwords!。
* 程式碼:

* 結果:
## Lab2
* 實驗目的:學習使用 Keypad 及序列埠視窗。
* 實驗目標:呈上題,增加2項功能。
1.修改密碼
2.添加驗證碼(透過七段顯示器顯示經過random()得出的3位驗證碼,並在輸入錯誤時再隨機變換一組驗證碼直到輸入正確)
<font color="red">##提示:random函式請加上randomSeed()</font>
* 參考pin腳
| 多工七段顯示器 | Arduino | 4x4 Keypad|Arduino|
| :-------: | :-----: |:------:|:------:|
| 0 |Pin10|Row1|A0|
| 1 |Pin11|Row2|A1|
| 2 |Pin12|Row3|A2|
| a |Pin2 |Row4|A3|
| b |Pin3 |Col1|13|
| c |Pin4 |Col2|9|
| d |Pin5 |Col3|A4|
| e |Pin6 |Col4|A5|
| f |Pin7 |
| g |Pin8 |
Arduino Reference:
https://www.arduino.cc/reference/en/language/functions/random-numbers/randomseed/
* 結果:
<iframe width="560" height="315" src="https://www.youtube.com/embed/vTOyNv8Eikg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Bonus 1
* 實驗目的:學習使用 Keypad 實現終極密碼。
* 實驗目標:使用keypad輸入一個三位數字,猜測一個介於0和999之間的數字。
* 結果:
(hint:產生終極密碼請同樣加上randomSeed()函式)

---
# Arduino講義:計步器
## MPU6050模組
* 這次要練習的是 GY-521 的 Sensor,其核心的晶片是 MPU-6050。
* MPU-6050 是一個內包含<font color="red">三軸陀螺儀(gx,gy,gz)</font>以及<font color="red">三軸加速計(ax,ay,az)</font>結合在一起的數位運動處理器(簡稱DMP),以 <font color="red"> I²C </font> 輸出6軸的旋轉矩陣的數位資料。
### GY-521規格
* 核心晶片:MPU-6050 模組(三軸陀螺儀 + 三軸加速度計)
* 供電電源:3-5v(內部低壓差穩壓)
* 通信方式:標準 I²C 通信協議
* 晶片內置 : 16bit AD 轉換器,16位數據輸出
* 陀螺儀範圍:±250 500 1000 2000 °/s
* 加速度範圍:±2 , ±4 , ±8 , ±16g
* MPU6050 晶片座標定義 :
將晶片平放在桌面上,將其表面文字轉至正確角度,此時,以晶片內部中心為原點,水平向右為x軸,水平向上為Y軸,指向天花板為Z軸。

#### 三軸加速度計 :
- 加速度計的三軸分量均為<font color="red">16位有符號整數(正負影響方向)</font>,分別表示物件在各軸上的加速度
- 我們可以把加速度計想象為一個正立方體盒子裏放著一個球,這個球被彈簧固定在立方體的中心。當盒子運動時,根據假想球的位置即可算出當前加速度的值。想象如果在太空中,盒子沒有任何受力時,假想球將處於正中心的位置,三個軸的加速度均為0。
如果我們給盒子施加一個水平向左的力,那麽顯然盒子就會有一個向左的加速度,此時盒內的假想球會因為慣性作用貼向盒內的右側面。
為了保證數據的物理意義,MPU6050的加速度計是以假想球在三軸上座標值的相反數作為三個軸的加速度值。當假想球的位置偏向一個軸的正向時,該軸的加速度讀數為負值,當假想球的位置偏向一個軸的負向時,該軸的加速度讀數為正值。
|示意圖| |
|:-:|:-:|
|||
- 三個加速度分量均以重力加速度 g 的倍數為單位,能夠表示的加速度範圍,即倍率可以統一設定,<font color="red">有4個可選倍率:2g、4g、8g、16g</font>。
- 以 ACC_X 為例,若<font color="red">倍率設定為 2g (默認)</font>,則意味著 ACC_X 取最小值-32768時,當前加速度為沿 X 軸正方向2倍的重力加速度;若設定為4g,取-32768時表示沿 X 軸正方向4倍的重力加速度,以此類推。顯然,<font color="red">倍率越低精度越好,倍率越高表示的範圍越大</font>,這要根據具體的應用來設定。
- 這三個加速度分量是16位的二進制補碼值,且是有符號的。故而其輸出範圍 -32768~32767。 32767/2 = <font color="red">16384 即加速度計的靈敏度</font>。
<!--
- 三軸加速度計 :
- 加速度計的三軸分量 ACC_X、ACC_Y 和 ACC_Z 均為<font color="red">16位有符號整數</font>,分別表示物件在三個軸向上的加速度,取負值時加速度沿座標軸負向,取正值時沿正向。
- 三個加速度分量均以<font color="red">重力加速度 g 的倍數為單位</font>,能夠表示的加速度範圍,即倍率可以統一設定,有4個可選倍率:2g、4g、8g、16g。
- 以 ACC_X 為例,若<font color="red">倍率設定為 2g (默認)</font>,則意味著 ACC_X 取最小值-32768時,當前加速度為沿 X 軸正方向2倍的重力加速度;若設定為4g,取-32768時表示沿 X 軸正方向4倍的重力加速度,以此類推。顯然,<font color="red">倍率越低精度越好,倍率越高表示的範圍越大</font>,這要根據具體的應用來設定。
- 這三個加速度分量是16位的二進制補碼值,且是有符號的。故而其輸出範圍 -32768~32767。 32767/2 = <font color="red">16384 即加速度計的靈敏度</font>。
-->
* <font color="red"> Q&A </font> 那這個靈敏度怎麼使用呢?我們使用 Serial Port 顯示的一組數據來舉個例子:
1. ACC_X=03702
2. 對應的加速度數據是:03702/16384 = 0.23g。
3. 具體的加速度公式 : <font color="red"> 加速度數據 = 加速度軸原始數據/加速度靈敏度</font>。
#### 三軸陀螺儀 :
* 繞 X、Y 和 Z 三個座標軸旋轉的角速度分量均為16位有符號整數。
- 從原點向旋轉軸方向看去,<font color="red">取正值時為順時針旋轉,取負值時為逆時針旋轉</font>。
* 三個角速度分量均以“度/秒”為單位,能夠表示的角速度範圍,即倍率可統一設定,有4個可選倍率:<font color="red">250度/秒、500度/秒、1000度/秒、2000度/秒</font>。
* 以 GYR_X 為例,若倍率設定為250度/秒,則意味著 GYR_X 取正最大值32768時,當前角速度為順時針250度/秒;若設定為500度/秒,取32768時表示當前角速度為順時針500度/秒。顯然,倍率越低精度越好,倍率越高表示的範圍越大。
* 這三個陀螺儀分量是16位的二進制補碼值,且是有符號的。故而其輸出範圍 -32768~32767。32767/2000 = <font color="red">16.4 即陀螺儀的靈敏度</font>。
### 下載 library
* MPU-6050 因為是靠 I2C 溝通的,所以是需要到 I2Cdevlib 去找相關的函式庫 library 。
* 可直接下載此處2個檔案即可 [MPU-6050](https://drive.google.com/file/d/1-CEXrQ3x5exROE9a_0oNvU6Wm2iZQ6tM/view?usp=sharing) 、 [I2Cdev](https://drive.google.com/file/d/1BdefjZYsf_hLud5HF0cMXINTgrV6aif4/view?usp=sharing)
<!--(https://drive.google.com/file/d/1EDCvJBOozDBpI7-4ffF62k8mr7yetxEQ/view?usp=sharing) 。-->
* 法A: 將 MPU-6050 與 I2Cdev 資料夾, 丟進 Arduino 的 libraries 資料夾。
* Windows library預設可用路徑
* 法一:C:\Users\snowwolf415\Documents\Arduino\libraries
* (可以用Library內建範例)
* 法二:C:\Program Files (x86)\Arduino\libraries)
* (有時無法使用Library內建範例)
* 法B: Arduino程式->草稿碼->匯入程式庫->加入.zip程式庫
---
### MPU6050 方向角

---
### 學習如何讀出 MPU-6050 量測之值
* MPU-6050 腳位 :
| Arduino | MPU-6050 | 備註 |
| -------- | -------- | -------- |
| 3.3-**5V** | VCC |<font color="red">注意電源不可接錯</font> |
| GND | GND |<font color="red">注意電源不可接錯</font> |
| A5 | SCL |I2C 時脈線|
| A4 | SDA |I2C 數據線|
| 本次不使用 | XDA | |
| 本次不使用 | XCL | |
| 本次不使用 | AD0 | |
| 2 | INT |中斷腳位(本次不使用)|
* 本次不使用腳位
* 線路接法

* code

---
## 應用方式
### Tilt angle (傾斜角)
* 行動裝置
* MPU6050透過公式求得
* 得出裝置的傾斜狀況

### Euler Angle (歐拉角)
* 穿戴式
* MPU9250透過公式求得
* 固定座標系(磁力計輔助定向)


|模組|角度方式||示意圖|
|:-:|:-:|:-:|:-:|
|MPU6050|MPU6050|加速度計/陀螺儀|Tilt angle (傾斜角)|
|MPU9250|MPU9250|加速度計/陀螺儀/磁力計|Euler Angle (歐拉角)
---
## Lab3
### 計步器實作
* 實驗目標 : 利用 MPU-6050 做出簡易計步器之功能
* 實驗要求 : <font color="red">靜止時或輕輕移動時 </font>,計步器數值不會加一,需<font color="red">轉動加速度超過一定幅度</font>數值才會加一。
* 實驗步驟參考 :
<font color="red">! ! 提醒 : 此實驗步驟為只使用三軸加速度(ax,ay,az)之值來進行運算</font>
* Step 1 : 利用講義上面介紹的加速度公式,得量化之加速度數據
* Step 2 : 透過下方公式將加速度數據代入,回推出變化之角度

* [公式參考](https://www.digikey.com/en/articles/techzone/2011/may/using-an-accelerometer-for-inclination-sensing)
* Step 3 : 為避免雜訊干擾,需透過前面量測出來之數據來設立閥值進行校準
* Step 4 : 定義怎樣的移動算一步
* [相關函式](https://www.arduino.cc/en/Reference/MathHeader)
* 示範影片 :
<iframe width="560" height="315" src="https://www.youtube.com/embed/SSr-T8PbgZk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Lab4
### 計步器實作
* 實驗目標 : 完善你的簡易計步器,為其加上七段顯示器
* 實驗要求 : 七段顯示器第一開始顯示0,第ㄧ步增加1,第二步增加8,第三步增加27,以此類推,靜止時或輕輕移動時七段顯示器顯示的步數不會增加
<!--
#include <math.h>
#include <MPU6050.h>
#include <Wire.h>
int SEG_COM[4] = {10,11,12,13};
int SEG_data[11][8]={{1,1,1,1,1,1,0,0},
{0,1,1,0,0,0,0,0},
{1,1,0,1,1,0,1,0},
{1,1,1,1,0,0,1,0},
{0,1,1,0,0,1,1,0},
{1,0,1,1,0,1,1,0},
{1,0,1,1,1,1,1,0},
{1,1,1,0,0,0,0,0},
{1,1,1,1,1,1,1,0},
{1,1,1,0,0,1,1,0},
{1,1,1,1,1,1,1,1},
};
int number=0;
int disp[4]={0,0,0,0};
int i=0;
int j=0;
int nu=0;
MPU6050 accelgyro;
int16_t ax,ay,az,gx,gy,gz;
double xp,yp,zp=0;
void setup() {
// put your setup code here, to run once:
Wire.begin();
Serial.begin(9600);
accelgyro.initialize();
for(int j=2; j<=13 ; j++)
{
pinMode(j,OUTPUT);
digitalWrite(j,HIGH);
}
}
void loop() {
// put your main code here, to run repeatedly:
accelgyro.getMotion6(&ax , &ay, &az , &gx, &gy, &gz);
number_transfer(nu);
for(int z=0;z<=3;z++)
{ if(disp[z]!=10){
SEG_Drive(disp[z]);
digitalWrite(SEG_COM[z],LOW);
SEG_Drive(disp[z]);
digitalWrite(SEG_COM[z],HIGH);}
else
{digitalWrite(SEG_COM[z],HIGH);}
}
double x= atan(ax/ sqrt(square(ay) + square(az)))*180/3.14;
double y=atan(ay/ sqrt(square(ax) + square(az)))*180/3.14;
double z=atan(sqrt(square(ax) + square(ay))/az)*180/3.14;
if(( fabs(xp-x)+fabs(yp-y) > 85 or (fabs(xp-x)+fabs(yp-y)>80 and fabs(zp-z)>150)) and xp!=0 )
{
number++;nu=nu+number*number; Serial.println(number);delay(250);
xp=0;
}
else{
xp=x;
yp=y;
zp=z;
}
delay(10);
}
void number_transfer(int Num)
{
if(Num<10){
disp[0]= Num%10;
disp[1]=10;
disp[2]=10;
disp[3]=10;
}
else if(Num<100 and Num>=10){
disp[0]= Num%10;
disp[1]=(Num-Num%10)/10%10;
disp[2]=10;
disp[3]=10;
}
else if(Num<1000 and Num>=100){
disp[0]= Num%10;
disp[1]=(Num-Num%10)/10%10;
disp[2]=(Num-Num%100)/100%10;
disp[3]=10;
}
else{
disp[0]= Num%10;
disp[1]=(Num-Num%10)/10%10;
disp[2]=(Num-Num%100)/100%10;
disp[3]=(Num-Num%1000)/1000%10;}
}
void SEG_Drive(int numb)
{
i=numb;
digitalWrite(2,SEG_data[i][0]);
digitalWrite(3,SEG_data[i][1]);
digitalWrite(4,SEG_data[i][2]);
digitalWrite(5,SEG_data[i][3]);
digitalWrite(6,SEG_data[i][4]);
digitalWrite(7,SEG_data[i][5]);
digitalWrite(8,SEG_data[i][6]);
digitalWrite(9,LOW);
delay(1);
}
-->
* 示範影片 :
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZFsGQoQPg2E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<!-- ### 加分 LAB SPI -- 接收 slave 端的回饋值
- 實驗目的:SPI用 master 端與 slave 端溝通方式。
- 實驗目標:將 master 端的數值丟入 slave 端進行運算後,slave 端將計算後的數值送回 master 端並於 Serial Monitor 顯示計算結果。
- schematic:

- master:
```c=
#include <SPI.h>
void setup (void)
{
Serial.begin (115200);
Serial.println ();
digitalWrite(SS, HIGH);
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
}
byte transferAndWait (const byte what) //數值傳送至Slave端
{
byte a = ;
delayMicroseconds (20);
return a;
}
void loop (void)
{
byte a, b, c, d;
digitalWrite(SS, LOW);
/*在此填入加法指令與數值*/
transferAndWait ('a');
transferAndWait (0);
a = transferAndWait (17);
digitalWrite(SS, HIGH);
/*回傳值顯示在Serial Monitor上*/
Serial.println ("Adding results:");
Serial.println (a, DEC);
delay(1000);
digitalWrite(SS, LOW);
/*在此填入減法指令與數值*/
transferAndWait ('s');
transferAndWait (0);
a = transferAndWait (17);
digitalWrite(SS, HIGH);
/*回傳值顯示在Serial Monitor上*/
Serial.println ("Subtracting results:");
Serial.println (a, DEC);
delay (1000);
}
```
- slave:
```c=
volatile byte command = 0;
void setup (void)
{
/*設定主入從出*/
/*啟用slave模式*/
/*啟用中斷*/
}
ISR (SPI_STC_vect) //SPI中斷程序
{
byte c = SPDR;
switch (command)
{
case 0:
command = c; //將暫存器內的資料設給command
SPDR = 0; //清空暫存器
break;
/*收到加法or減法指令並在此進行計算*/
}
}
void loop (void)
{
if (digitalRead (SS) == HIGH) //若SPI通訊關閉,清除目前command
command = 0;
}
``` -->
## 課後問題
:::info
1. 請說明非同步傳輸中的"RS-232",“RS-422"以及"RS-485”,並討論這三者的差別為何?
2. MPU-6050 的接腳 XDA、XCL、AD0 功能為何?
3. 請問取random數字時不使用randomSeed()的差別在哪?
4. 下方為不呼叫 MPU-6050 library 來讀出 MPU-6050 數值之程式,
請為其標上 <font color="red">完整的註解</font>。
5. 心得
```c=
#include<Wire.h>
const int MPU_addr=0x68;
void setup(){
Wire.begin();
Wire.beginTransmission(MPU_addr);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission(true);
Serial.begin(38400);
}
void loop(){
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr,14,true);
AcX=Wire.read()<<8|Wire.read();
AcY=Wire.read()<<8|Wire.read();
AcZ=Wire.read()<<8|Wire.read();
GyX=Wire.read()<<8|Wire.read();
GyY=Wire.read()<<8|Wire.read();
GyZ=Wire.read()<<8|Wire.read();
Serial.print("AcX = "); Serial.print(AcX);
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);
}
```
:::
:::success
## 作業繳交格式
**W3結報_第X組.zip**
壓縮檔裡包含:
1.W3結報.pdf
2.資料夾:W3
Lab1.ino
Lab2.ino
...
:::