---
# System prepended metadata

title: 單晶片lab4結報
tags: [arduino]

---

# 單晶片lab4結報
###### tags: `arduino`
###### 實驗日期 : 2021/10/14
## 上課教材
- [I^2^C、LCD、四則運算](https://hackmd.io/@mzTjnf74ST6xKOB4FrGblQ/SksjSGqWK)

## lab1
### SPEC
利用I^2^C進行Arduino間通訊。


### 實現方法
完整I^2^C原理參見[lab4課後習題
Question 1](#Question-1)，`Wire.h`的使用實例參見[lab2課後習題 Question 4](https://hackmd.io/2coEqM37QrSNsro2JQl6Qg?view#Question-4)。

- master.ino
```cpp
Wire.begin();
```
初始化`Wire.h`，未指定7-bit slave地址則為加入者為master。
```cpp
if (Serial.available()) {
    Wire.beginTransmission(SLAVE_ADDRESS);
        while (Serial.available()) {
            Wire.write(Serial.read());
            delay(10);
        }
    Wire.endTransmission();
    }
```
當serial port有資料時，開啟slave地址，並讀入serial port的資料並送出資料到I^2^C的bus，最後再結束傳輸。

- slave.ino
```cpp
Wire.onReceive(handler)
```
當slave接收到master的傳輸[觸發中斷程序](https://www.arduino.cc/en/Reference/WireOnReceive)，會被呼叫的由使用者自定義的函數。根據源碼`void onReceive( void (*)(int) );`，handler是使用function pointer實踐。
```cpp
void myHandler(int numBytes){
    Serial.println("Receive Data:");
    while (Wire.available()) {
        Serial.print((char)Wire.read());
    }
    Serial.println("");
}
```
在這中斷程序，一開始會接受到從master讀入byte的個數`int numBytes`。`Wire.available()`會回傳可讀byte數，由於布林函數`0`以外的數字皆為正，因此Wire有資料會依序讀入Wire的資料到serial port並印出，直到`Wire.available()`回傳0，代表可讀的byte為0，才結束中斷程序。

## lab2
### SPEC
自己的學號字串以LCD跑馬燈顯示。

### 實現方法
熟悉16×2 I^2^C LCD模組使用，與`<LiquidCrystal_I2C.h>`函式庫成員函數的調用。參閱函式庫介紹[How to control a character I2C LCD with Arduino](https://www.makerguides.com/character-i2c-lcd-arduino-tutorial/)。
|函數|作用|
|:-:|:-:|
|LiquidCrystal_I2C lcd(addr, col, row)|創建物件|
|lcd::init()|初始化|
|lcd::backlight()|開啟背光|
|lcd::begin(cols, rows);|定義LCD的長寬(n列×n行)|
|lcd::clear()|清空LCD|
|lcd::setCursor(col, row);|設定輸入值起始位置|
|lcd::print(text string);|在螢幕上顯示資料|
|lcd::scrollDisplayRight();|將畫面向右捲動，可搭配delay()使用|
|lcd::scrollDisplayLeft();|將畫面向左捲動，可搭配delay()使用|
|lcd::home()|使光標移至左上角|
|lcd::cursor()|顯示光標|
|lcd::noCursor()|關閉上一個功能|
|lcd::blink()|顯示一整塊長方形方格的光標|
|lcd::noBlink()|關閉上一個功能|
|lcd::display()|顯示所有print到LCD的東西|
|lcd::noDisplay()|關閉上一個功能|
|lcd::write()|與print()不同點在於是直接寫進LCD，而非人所看到的資料，例如`lcd.write(64);`是印ASCII Code 64號的`@`符號到LCD上|
|lcd::autoscroll()|下一位輸入值會把前一位輸入值往旁邊一個推|
|lcd::noAutoscroll()|關閉上一個功能|
|lcd::leftToRight()|之後的文字會從光標右邊開始寫|
|lcd::rightToLeft()|之後的文字會從光標左邊開始寫|
|lcd::lcd.createChar(custom characters, byte[]);|先自定義5 x 8 led matrix的像素點陣列，並重新定義字元|
- 自定義LCD字符範例
```cpp
// Make custom characters
byte Heart[] = {
    B00000,
    B01010,
    B11111,
    B11111,
    B01110,
    B00100,
    B00000,
    B00000
};

// Create new characters
lcd.createChar(0, Heart);

//use the number of the custom character that we want to display
lcd.setCursor(0, 1);
lcd.write(0);
```

## lab3
### SPEC
利用keypad與LCD實現二項加減乘除。

### 實現方法
此部分為lab4功能的子集合。

## lab4
### SPEC
利用keypad與LCD實現四則運算計算機。
|輸入值|功能|
|:-:|:-:|
|`A`|$+$|
|`B`|$-$|
|`C`|$\times$|
|`D`|$\times$|
|`#`|$=$|
|`*`|清除|

### 實現方法
1. 讀值
```cpp
if (enter == '#') {
    infix[count] = '\0';
    break;
} else if (enter == 'A') {
    infix[count] = '+';
```
當有輸進值`enter != NO_KEY`時，對輸入字串進行替換，以符合原始資料，並顯示在LCD上`lcd.print(infix[count]);`，當出現`#`則代替為終止字符，並跳出無窮迴圈。

2. 計算
```cpp
double pfix_parser(char *ifix);
```
四則運算為資料結構(DS)介紹stack功能章節實作。
- infix轉postfix<br>
    - 目的 :<br>
    不用處理運算子先後順序問題，只要依序由運算式由前往後讀取即可。
    - 方法 : 取出中序式的字元
        1. 遇運算元直接輸出。(在字元結尾插入`'n'`因此可接受多位數值)
        3. 堆疊運算子與左括號`(`。
        4. 堆疊中運算子**優先順序若大於**等於讀入的運算子優先順序的話，直接輸出堆疊中的運算子，再將讀入的運算子置入堆疊。
        5. 遇**右括號`)`輸出**堆疊中的運算子至左括號`(`。

- postfix轉value<br>
把運算元push進或pop出stack直接計算。

## 課後習題
### Question 1
簡述I^2^C如何藉由`SCL`與`SDA`傳送資料 ?

### Answer 1
I2C 是序列式的傳輸，只有兩條線，一條叫做`SDA`送資料，另一條叫做`SCL`傳clock。
```cpp
Wire.begin(); 
//初始化Wire.h並作為master or slave加入I2C bus
//如未指定7-bit slave地址(函數重載)，則為加入者為master
Wire.beginTransmission(SLAVE_ADDRESS); 
//傳slave地址
```
1. 傳slave地址，決定跟哪個slave溝通
    - Start condition：`SCL=high + SDA falling`。
    - **傳7-bit地址**，因此最大可以有128個slave(但實際上有16個系統擴充位址是保留的)。
    - **第8個bit傳`R/W bit`**，指定要讀(0：write)或寫(1:Read)資料。
    - Stop condition：`SCL=high + SDA raising`。
2. Slave `ACK`一次確認<br>
    - `SDA`主動權由master交給slave控制。
    - 如果有**成功收到**先前的訊號**slave要把SDA拉低**。
```cpp
Wire.write(INNER_REGISTER_ADDR); //指定slave內部寄存器地址
```
3. Slave內部暫存器地址
    - Start condition：`SCL=high + SDA falling`。
    - 傳送8-bit地址。
    - Stop condition：`SCL=high + SDA raising`。
4. Slave `ACK`一次確認
    - `SDA`主動權由master交給slave控制。
    - 如果成功有收到先前的訊號slave要把`SDA`拉低。
```cpp
Wire.write(0); //寫入1個byte的數據
Wire.endTransmission(false);
//If false, endTransmission() sends a 
//restart message after transmission.
Wire.requestFrom(SLAVE_ADDRESS, DATA_SIZE, true);
if (Wire.available()) {
    Serial.print("Data returned: ");
    while (Wire.available()) {
    //return the number of bytes available for reading.
        Serial.print( (char)Wire.read() );
    }
  Serial.println();
}
Wire.endTransmission(); //結束傳輸
```
5. 傳送data
    - Start condition：`SCL=high + SDA falling`。
    - 由前面`R/W bit`去判斷資料要傳送還是發送，每次傳送8-bit資料。
    - 如果未傳輸完，則由下一次再繼續傳8-bit。
    - Stop condition：`SCL=high + SDA raising`。
6. Slave `ACK`一次確認
    - `SDA`主動權由master交給slave控制。
    - 如果成功有收到先前的訊號slave要把`SDA`拉低。
7. 結束傳輸
    - `SCL`先拉高，隨後`SDA`跟著拉高。


- 資料來源 : [How I2C Communication Works and How To Use It with Arduino](https://www.youtube.com/watch?v=6IAkYpmA1DQ)
### Question 2
上課的時候提到可以有多個master，請問若同時有兩個以上的master送出訊號會發生什麼事？並且描述該如何解決。

### Answer 2
在各種bus arbitration機制中，最基本的禮貌就是"先聽再說"：當你要說話時，先聽聽有沒有別人在說話，如果有的話就閉嘴，如果沒有別人在說話，你才可以說話。根據 I^2^C 的規格說明，需要兩個機制：時脈同步(clock synchronization)和匯流排仲裁(bus arbitration)。
- **時脈同步(clock synchronization)**<br>
時序可分為兩種master clock和slave clock，而在時脈同步系統中，master經由網路伺服器、GPS接收器等裝置得到準確時序，再將此時序連接到各個slave端，以確保整個系統的時序是精準且同步的。master一天之中會多次更新時脈訊號給slave，避免slave時序延遲。

- **匯流排仲裁(bus arbitration)**<br>
在匯流排中用於初始化資料傳輸的裝置稱為**主匯流排(bus master**)。當一個系統會有超過一個主匯流排時，這些裝置間會互相分享匯流排系統，即當目前的主匯流排已經交出主導權時，其餘的匯流排可以獲得處理器的控制權，bus仲裁即為主匯流排間互相交換主控權的過程。<br>
其中有兩種Bus仲裁方式，分別為 :
    1. **集中式仲裁(Centralised Arbitration)**
    ![](https://i.imgur.com/JOKAPtt.png)<br>
    只有一個匯流排會負責執行仲裁，這個匯流排有可能是處理器或是連接至匯流排的控制器，集中式仲裁主要有三個方式：<br>
        - **菊鍊(Daisy Chaining)**：<br>
![](https://i.imgur.com/eeCl70N.png)<br>
所有主匯流排裝置都使用同一條線路去向仲裁裝置(在此圖為控制器)請求控制權，假設匯流排是空閒的情況則仲裁會回傳**允許控制匯流排系統(bus grant)** 的資訊，此資訊會從線路回傳並經過沿路的主匯流排裝置，直到遇到第一個發出控制權請求的裝置。此時這個主匯流排裝置會阻擋bus grant訊號的傳遞，並且啟動**bus busy線路**後獲取控制權。
<br><br>
        - **輪詢法(Polling Method)**:<br>
![](https://i.imgur.com/lgqH6RP.png)<br>
仲裁裝置(在此圖為控制器)在此方法中主要用於為主匯流排產生address，而**位址線路(address line)** 的數量取決於連接至此系統的主匯流排數量。舉例來說，假設有8個連接至系統的主匯流排，則至少需要3個位址線路。<br><br>
仲裁裝置會根據發出請求的匯流排產生一組的主匯流排位址，而若發出請求的主匯流排認得此位址，則他就會啟動bus busy線路並開始使用匯流排系統。
<br><br>
        - **獨立請求(Independent Request)**:<br>
![](https://i.imgur.com/fhVv5fB.png)<br>
在此方法中每組主匯流排都有各自獨立的請求線路和獲取線路，其中每組匯流排系統都有各自分配的優先權，仲裁裝置(在此圖為控制器)會有內建的優先權解碼器，並且會選取最高優先權的請求，然後允許此主匯流排控制匯流排系統。
<br><br>
    2. **分散式仲裁(Distributed Arbitration)**
    ![](https://i.imgur.com/AMWL8jW.png)<br>
    所有裝置會被列為可能選擇的下一個主匯流排，並被各自分配4-bit的識別數字，假設有一或多個裝置請求匯流排系統的控制權，則他們會斷言(assert)**開始仲裁(start-arbitration)** 的信號並且把識別數字輸出到仲裁線路上(由ARB0到ARB3)，由於4條仲裁線路都是Open-Collector，因此即便有一個以上的裝置也一樣可以輸出自己的4-bit識別碼。若有一個裝置要把1輸出到bus line上、另一個要輸出0到同個線路時，bus line的狀態會為0。
<br><br>
裝置會透過inverter buffer讀取每個仲裁線路的狀態。(因此若讀取到bus line狀態為0，則會轉為1)假設同時有兩個或以上的裝置放置識別數字在bus line上時，則需要透過bus line的狀態辨別出最高順位的識別數字。
<br><br>
舉例來說：假設裝置A的數字為1 (0001)，裝置B的數字為6 (0110)且兩裝置同時請求使用匯流排，將兩者訊號輸出後會發現bus line的狀態為1000，但透過inverter buffer兩個裝置會看到狀態為0111。此時兩個裝置會自己比較這個code和自身辨別數字的不同(**從最高有效位開始**)，若裝置發現**有哪個部分不同**(A:0001 <-> bus:0111)，則會關閉從這個bit到最低位bit的輸出，將這些部分全部輸出0(以圖片而言，即為A裝置從ARB2關閉至ARB0)，因此A裝置會輸出0000(invert後變成1111)，則此時bus line狀態變為1001(兩裝置看到的為0110)，因此最後判定裝置B獲得主控權。
<br><br>
這種去中心化的仲裁系統擁有很高的可靠性，因為他不會取決於任何一個單一裝置。

- ref :
1. [【Maker電子學】I2C 界面解密 — PART 6 仲裁協定](https://makerpro.cc/2020/07/i2c-interface-part6/)<br>
2. [What is bus arbitration? Explain any two techniques of bus arbitration. OR Explain Bus Contention and different method to resolve it](https://www.ques10.com/p/8794/what-is-bus-arbitration-explain-any-two-techniqu-2/?)<br>
3. [SYNCHRONIZED CLOCK SYSTEMS EXPLAINED](https://sapling-inc.com/synchronized-clock-systems-explained/)

### Question 3
電算機為何總和超過`32767`時字串會溢位?如何解決?
### Answer 3
Arduino中，`int`的資料型態為`signed 16bits`，因此當數值超過$2^{15} = 32,768$時，會發生overflow。解決方法是變更資料型態為`float`、`double`、`long`。

---
Arduino中常見的資料型態 :
|  資料型態    |      說明     |  記憶體長度 |              數值範圍              |
|:----------:|:-------------:|:---------:|:---------------------------------:|
|   `boolean`|      布林     |   8  bits  |  true   (1, HIGH), false(0, LOW)  |
|    `byte`  |     位元組    |   8   bits  |               0~255              |
|    `char`  |      字元     |   8  bits  |              -128~127             |
|   `short`  |     短整數    |   16  bits  |           -32768~32767           |
|    `word`  |       字     |   16   bits |              0~65535             |
|    `long`  |     長整數    |   32   bits |       -2147483648~2147483647     |
|    `float` |     浮點數    |   32   bits |          +-3.4028235e+38         |
|   `double` |  倍精度浮點數  |   32   bits |          +-3.4028235e+38         |



double與float皆相同，根據[官方doc](https://www.arduino.cc/reference/en/language/variables/data-types/double/)定義
> **Double precision floating point number**. On the Uno and other ATMEGA based boards, this occupies 4 bytes. That is, **the double implementation is exactly the same as the float**, with no gain in precision.
<br><br>
On the Arduino Due, doubles have 8-byte (64 bit) precision.

![](https://i.imgur.com/aFNOsKL.png)
雙浮點精度由3個部分包含 :
|組成|功能|
|:-:|:-:|
|sign bit (符號)|正負號|
|exponent (指數)|次方數|
|mantissa (尾數)|精確度|

## 心得
### 劉永勝
I^2^C傳輸協定，在之前用`MPU6050`時，已有簡單的討論，其優點在於接線數較少，實作中遇到了一些內建函數回傳值的問題，輸出回傳值後即了解，slave端接收訊號時，會計算接收位元組數進行回傳。

LCD的部分，相較於七段顯示器，LCD較好操作。LCD模組配有掃描式驅動IC，並配合每pixel之電容，可讓面板上的數值持續顯示，不像七段顯示器要用迴圈的方式才能使數值常駐顯示。而四則運算的原理，在資料結構中有學過，先將輸入字元重新排列後，再以push、pop的方式處理。直接使用資料結構之程式碼，在調整LCD顯示問題即可完成。

### 李宇洋
此次實驗為操作LCD與利用I^2^C進行Arduino間通訊，使用的是`Wire.h`函式庫，比較上次實驗是利用SPI進行通訊，主要使用的函式庫式是`SPI.h`。因為LCD本身有自定義的函式庫，只需要事先利用`LiquidCrystal_I2C.h`函式庫設定LCD位址，再把需要顯示的字元print出就可以顯示在螢幕上。

### 陳旭祺
做個小整理。<br>
延續[lab2學UART](https://hackmd.io/@G8HrHAUqQyCt9mHFYW05UA/rJSzxDQ-K#UART-Protocol)(Serial Monitor)、[lab3學SPI](https://hackmd.io/@9ScCWm6PQhCqJjg8JfJKzQ/SJ2Ig1nVt#Introduction-of-SPI)(傳字串)，這次[lab4學I^2^C](https://hackmd.io/@mzTjnf74ST6xKOB4FrGblQ/SksjSGqWK#Introduction-of-I2C)(傳字串)三大傳輸協定是算是告一段落，原理介紹文章可參閱[【Maker 電子學】作者Bird](https://makerpro.cc/author/birdlg/)，而對應相關模組 :
|介面|外接線|模組|
|:-:|:-:|:-:|
|UART|RX(接收)、TX(發送)|藍芽`HC-05`(lab3)|
|I^2^C|SDA(資料)、SCL(時脈)|三軸陀螺儀與三軸加速計`MPU6050`(lab2)、顯示器`16*2 I2C LCD`(lab4)|
|SPI|MOSI(主出從入)、MISO(主入從出)、SCLK(時脈)、SS(選擇1)、SS2(選擇2)...|無|

3者對應使用到的函式庫 : 
- [`<SoftwareSerial.h>`](https://www.arduino.cc/en/Reference/softwareSerial) enables serial communication with a digital pin other than the serial port.The native serial support happens via a piece of hardware (built into the chip) called a UART.
- [`<Wire.h>`](https://www.arduino.cc/en/reference/wire) communicate with I2C / TWI devices.
- [`<SPI.h>`](https://www.arduino.cc/en/reference/SPI)
communicate with SPI devices, with the Arduino as the master device.