單晶片lab4結報

tags: arduino
實驗日期 : 2021/10/14

上課教材

lab1

SPEC

利用I2C進行Arduino間通訊。

實現方法

完整I2C原理參見lab4課後習題 Question 1Wire.h的使用實例參見lab2課後習題 Question 4

  • master.ino
Wire.begin();

初始化Wire.h,未指定7-bit slave地址則為加入者為master。

if (Serial.available()) {
    Wire.beginTransmission(SLAVE_ADDRESS);
        while (Serial.available()) {
            Wire.write(Serial.read());
            delay(10);
        }
    Wire.endTransmission();
    }

當serial port有資料時,開啟slave地址,並讀入serial port的資料並送出資料到I2C的bus,最後再結束傳輸。

  • slave.ino
Wire.onReceive(handler)

當slave接收到master的傳輸觸發中斷程序,會被呼叫的由使用者自定義的函數。根據源碼void onReceive( void (*)(int) );,handler是使用function pointer實踐。

void myHandler(int numBytes){
    Serial.println("Receive Data:");
    while (Wire.available()) {
        Serial.print((char)Wire.read());
    }
    Serial.println("");
}

在這中斷程序,一開始會接受到從master讀入byte的個數int numBytesWire.available()會回傳可讀byte數,由於布林函數0以外的數字皆為正,因此Wire有資料會依序讀入Wire的資料到serial port並印出,直到Wire.available()回傳0,代表可讀的byte為0,才結束中斷程序。

lab2

SPEC

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

實現方法

熟悉16×2 I2C LCD模組使用,與<LiquidCrystal_I2C.h>函式庫成員函數的調用。參閱函式庫介紹How to control a character I2C LCD with Arduino

函數 作用
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字符範例
// 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
×
D
×
#
=
* 清除

實現方法

  1. 讀值
if (enter == '#') {
    infix[count] = '\0';
    break;
} else if (enter == 'A') {
    infix[count] = '+';

當有輸進值enter != NO_KEY時,對輸入字串進行替換,以符合原始資料,並顯示在LCD上lcd.print(infix[count]);,當出現#則代替為終止字符,並跳出無窮迴圈。

  1. 計算
double pfix_parser(char *ifix);

四則運算為資料結構(DS)介紹stack功能章節實作。

  • infix轉postfix

    • 目的 :
      不用處理運算子先後順序問題,只要依序由運算式由前往後讀取即可。
    • 方法 : 取出中序式的字元
      1. 遇運算元直接輸出。(在字元結尾插入'n'因此可接受多位數值)
      2. 堆疊運算子與左括號(
      3. 堆疊中運算子優先順序若大於等於讀入的運算子優先順序的話,直接輸出堆疊中的運算子,再將讀入的運算子置入堆疊。
      4. 右括號)輸出堆疊中的運算子至左括號(
  • postfix轉value
    把運算元push進或pop出stack直接計算。

課後習題

Question 1

簡述I2C如何藉由SCLSDA傳送資料 ?

Answer 1

I2C 是序列式的傳輸,只有兩條線,一條叫做SDA送資料,另一條叫做SCL傳clock。

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一次確認
    • SDA主動權由master交給slave控制。
    • 如果有成功收到先前的訊號slave要把SDA拉低
Wire.write(INNER_REGISTER_ADDR); //指定slave內部寄存器地址
  1. Slave內部暫存器地址
    • Start condition:SCL=high + SDA falling
    • 傳送8-bit地址。
    • Stop condition:SCL=high + SDA raising
  2. Slave ACK一次確認
    • SDA主動權由master交給slave控制。
    • 如果成功有收到先前的訊號slave要把SDA拉低。
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(); //結束傳輸
  1. 傳送data
    • Start condition:SCL=high + SDA falling
    • 由前面R/W bit去判斷資料要傳送還是發送,每次傳送8-bit資料。
    • 如果未傳輸完,則由下一次再繼續傳8-bit。
    • Stop condition:SCL=high + SDA raising
  2. Slave ACK一次確認
    • SDA主動權由master交給slave控制。
    • 如果成功有收到先前的訊號slave要把SDA拉低。
  3. 結束傳輸
    • SCL先拉高,隨後SDA跟著拉高。

Question 2

上課的時候提到可以有多個master,請問若同時有兩個以上的master送出訊號會發生什麼事?並且描述該如何解決。

Answer 2

在各種bus arbitration機制中,最基本的禮貌就是"先聽再說":當你要說話時,先聽聽有沒有別人在說話,如果有的話就閉嘴,如果沒有別人在說話,你才可以說話。根據 I2C 的規格說明,需要兩個機制:時脈同步(clock synchronization)和匯流排仲裁(bus arbitration)。

  • 時脈同步(clock synchronization)
    時序可分為兩種master clock和slave clock,而在時脈同步系統中,master經由網路伺服器、GPS接收器等裝置得到準確時序,再將此時序連接到各個slave端,以確保整個系統的時序是精準且同步的。master一天之中會多次更新時脈訊號給slave,避免slave時序延遲。

  • 匯流排仲裁(bus arbitration)
    在匯流排中用於初始化資料傳輸的裝置稱為主匯流排(bus master)。當一個系統會有超過一個主匯流排時,這些裝置間會互相分享匯流排系統,即當目前的主匯流排已經交出主導權時,其餘的匯流排可以獲得處理器的控制權,bus仲裁即為主匯流排間互相交換主控權的過程。
    其中有兩種Bus仲裁方式,分別為 :

    1. 集中式仲裁(Centralised Arbitration)
      只有一個匯流排會負責執行仲裁,這個匯流排有可能是處理器或是連接至匯流排的控制器,集中式仲裁主要有三個方式:
      • 菊鍊(Daisy Chaining)

        所有主匯流排裝置都使用同一條線路去向仲裁裝置(在此圖為控制器)請求控制權,假設匯流排是空閒的情況則仲裁會回傳允許控制匯流排系統(bus grant) 的資訊,此資訊會從線路回傳並經過沿路的主匯流排裝置,直到遇到第一個發出控制權請求的裝置。此時這個主匯流排裝置會阻擋bus grant訊號的傳遞,並且啟動bus busy線路後獲取控制權。

      • 輪詢法(Polling Method):

        仲裁裝置(在此圖為控制器)在此方法中主要用於為主匯流排產生address,而位址線路(address line) 的數量取決於連接至此系統的主匯流排數量。舉例來說,假設有8個連接至系統的主匯流排,則至少需要3個位址線路。

        仲裁裝置會根據發出請求的匯流排產生一組的主匯流排位址,而若發出請求的主匯流排認得此位址,則他就會啟動bus busy線路並開始使用匯流排系統。

      • 獨立請求(Independent Request):

        在此方法中每組主匯流排都有各自獨立的請求線路和獲取線路,其中每組匯流排系統都有各自分配的優先權,仲裁裝置(在此圖為控制器)會有內建的優先權解碼器,並且會選取最高優先權的請求,然後允許此主匯流排控制匯流排系統。

    2. 分散式仲裁(Distributed Arbitration)
      所有裝置會被列為可能選擇的下一個主匯流排,並被各自分配4-bit的識別數字,假設有一或多個裝置請求匯流排系統的控制權,則他們會斷言(assert)開始仲裁(start-arbitration) 的信號並且把識別數字輸出到仲裁線路上(由ARB0到ARB3),由於4條仲裁線路都是Open-Collector,因此即便有一個以上的裝置也一樣可以輸出自己的4-bit識別碼。若有一個裝置要把1輸出到bus line上、另一個要輸出0到同個線路時,bus line的狀態會為0。

      裝置會透過inverter buffer讀取每個仲裁線路的狀態。(因此若讀取到bus line狀態為0,則會轉為1)假設同時有兩個或以上的裝置放置識別數字在bus line上時,則需要透過bus line的狀態辨別出最高順位的識別數字。

      舉例來說:假設裝置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獲得主控權。

      這種去中心化的仲裁系統擁有很高的可靠性,因為他不會取決於任何一個單一裝置。
  • ref :

  1. 【Maker電子學】I2C 界面解密 — PART 6 仲裁協定
  2. What is bus arbitration? Explain any two techniques of bus arbitration. OR Explain Bus Contention and different method to resolve it
  3. SYNCHRONIZED CLOCK SYSTEMS EXPLAINED

Question 3

電算機為何總和超過32767時字串會溢位?如何解決?

Answer 3

Arduino中,int的資料型態為signed 16bits,因此當數值超過

215=32,768時,會發生overflow。解決方法是變更資料型態為floatdoublelong


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定義

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.

On the Arduino Due, doubles have 8-byte (64 bit) precision.

雙浮點精度由3個部分包含 :

組成 功能
sign bit (符號) 正負號
exponent (指數) 次方數
mantissa (尾數) 精確度

心得

劉永勝

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

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

李宇洋

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

陳旭祺

做個小整理。
延續lab2學UART(Serial Monitor)、lab3學SPI(傳字串),這次lab4學I2C(傳字串)三大傳輸協定是算是告一段落,原理介紹文章可參閱【Maker 電子學】作者Bird,而對應相關模組 :

介面 外接線 模組
UART RX(接收)、TX(發送) 藍芽HC-05(lab3)
I2C SDA(資料)、SCL(時脈) 三軸陀螺儀與三軸加速計MPU6050(lab2)、顯示器16*2 I2C LCD(lab4)
SPI MOSI(主出從入)、MISO(主入從出)、SCLK(時脈)、SS(選擇1)、SS2(選擇2)

3者對應使用到的函式庫 :

  • <SoftwareSerial.h> 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> communicate with I2C / TWI devices.
  • <SPI.h> communicate with SPI devices, with the Arduino as the master device.