Arduino 4×4薄膜鍵盤模組實驗(三):結合LCD顯示器的密碼輸入介面

本文將結合4×4薄膜按鍵以及文字型LCD顯示器,製作一個密碼輸入介面。程式一開始在LCD顯示兩列文字,第0列始終顯示“Knock, knock…”(敲、敲…)。第1列提示用戶輸入密碼(pin number):

若按下’*’鍵,可清除輸入的字元,重新輸入;按下’#’鍵,代表「確認」輸入。

按下”#”號之後,若密碼正確,則在第1列顯示“Welcome home!”(歡迎回家!):

若密碼錯誤,則在第1列顯示“***WRONG!!***”(錯誤!!):

I2C介面的LCD顯示器模組以及實驗材料

本單元的實驗材料如下:

  • Arduino Uno控制板,一片。
  • 4×4薄膜鍵盤,一個。
  • 1602文字型LCD顯示器模組,一個。
  • 自製LCD模組的序列轉接板或現成的I2C介面轉接板,一個。

文字型LCD顯示器模組,請依《超圖解Arduino互動設計入門》第九章「串列連接LCD顯示模組」一節說明,配合74LS164組裝好,或者,購買I2C介面的Hitachi HD44780U 1602 LCD點陣液晶模組,底下是I2C介面的轉接板和1602 LCD模組的外觀,轉接板和LCD模組上的排針和排插可能要自行焊接:

Hitachi HD44780U 1602 LCD點陣液晶模組

I2C介面的顯示器轉接板,都是依照LCD模組的接腳設計,兩者可直接焊在一起。結合LCD顯示器和I2C介面模組的模樣:

Hitachi HD44780U 1602 LCD點陣液晶模組

實驗電路接線

請依照下圖連接薄膜鍵盤和LCD序列顯示模組,即便是採用74LS164組裝的LCD模組的資料和時脈線,也能接在類比輸入埠,因為Arduino的類比輸入埠兼具數位輸出∕入功能

連接薄膜鍵盤和LCD序列顯示模組

實驗程式:清除LCD顯示字元

本單元程式需要補充說明的,應該只有清除顯示字元部份。清除顯示字元的方法,就是用「空白字元」蓋掉原本的字元。

用戶按下“*”鍵,程式需要從第1列第4行到第15行,填入空白字元:

清除LCD顯示字元

在顯示代表「歡迎回家」或者「錯誤!」的訊息之前,則需要清除整個第1列。筆者把清除文字的程式寫成名叫clearRow()的自訂函式,它將接收一個參數,代表從第1列的第n個位置開始清除。

執行 clearRow( 4 ),代表從第4個字元開始清除,函式裡的last值將是12,也就是從第4行開始,填入12個空白字元。

使用74LS164電路的LCD序列顯示器的完整程式碼如下,程式庫沿用書本第九章採行的LiquidCrystal_SR程式庫。

採用I2C介面的LCD顯示器程式

本單元程式採用的是這個I2C LCD顯示器程式庫,請注意,依I2C介面轉接板上的晶片或者韌體不同,驅動程式和I2C的位址可能不同,常見的位址有0x27, 0x38和0x3F,購買時請向商家確認。如果執行本單元的程式之後,LCD沒有顯示任何文字,請嘗試修改I2C位址,若是出現亂碼或者閃爍情況,請更換程式庫。

底下提供其他兩個具備I2C介面的1602 LCD程式庫當作參考:

不同程式庫的指令語法可能不一樣,請自行參閱程式庫的範例和說明。以本單元的程式庫為例,建立LCD物件的語法是:

並且經由底下的敘述初始化,以及選擇性地開啟背光:

完整的程式碼如下:

未完,待續…

延伸閱讀

26 thoughts on “Arduino 4×4薄膜鍵盤模組實驗(三):結合LCD顯示器的密碼輸入介面

  1. 老師:
    請問我用144*32的LCD顯示字元正確,但是一直無法顯示下一行,而用16*2的LCD可換下一行,但是字元都是亂碼?
    我是用74LS164IC的電路.

    #include
    #include // 引用LCD序列顯示器的程式庫
    LiquidCrystal_SR lcd(8,7,TWO_WIRE);
    void setup() {

    lcd.begin(16,2); // 初始化lcd物件

    lcd.home();

    lcd.write(“kiss me”);
    lcd.setCursor(0, 1); // 切換到第2行
    lcd.print(“PIN:”);
    lcd.cursor();
    }
    void loop(){
    }

  2. 使用原作者的範例用144*32的中文LCD,會出現一個笑臉圖左右移動,但是用16*2的LCD則是亂碼?

    1. hi dull-boy:

      因為144×32顯示器是「繪圖型LCD」,它採用的晶片和文字型LCD完全不同,所以程式庫不一樣,程式指令也可能不同。

      thanks,
      jeffrey

  3. 可是我用16*2的LCD會出現亂碼,原作者的函式庫3個版本我都用了,都是亂碼…
    只有144*32的LCD搭配最早版本的函式庫,能正常顯示字元,但是無法出現第2行的字串..

  4. 那104電容我覺得有接沒接都一樣,都試過了,並接在ic電源接腳,就同等並接在整個電路的電源,,兩個LCD我用並列傳輸的方式,都能正常顯示喔

  5. 老師抱歉再問你一下
    那如果使用A來設定新的一組密碼的話,那要如何讓程式判定新的密碼?

    1. 因為密碼存在passcode變數,所以程式碼還是一樣:

      thanks,
      jeffrey

  6. 老師不好意思,我想請問我已經寫出有關按鍵C的設定可是要如何核對舊的1234這組密碼如果核對正確再輸入新的密碼,我現在卡在這個問題點試過很多方法都會直接跳到上一段輸入正確的地方,拜託老師幫我指點了,謝謝老師。

    1. 請先寫下你預期的程式流程,例如:

      輸入密碼
      按下 ‘C’ 鍵
      若密碼正確
      則在LCD第0列顯示 “New Password:”
       輸入新的密碼
       輸入完畢後再次按下 ‘C’ 鍵儲存

      若密碼不正確,則顯示”WRONG!!”然後返回初始狀態。

      如果是這樣的話,
      程式要判斷 ‘C’ 鍵被按了幾次,
      你可以用一個變數來紀錄按下的次數,
      假設這個變數叫做 pressC,預設為0

      用戶首先輸入密碼,接著按下C鍵,底下的條件式將被執行:

      if (pressC == 0) {
       代表C鍵第一次被按下
       比對輸入值與passcode值

       pressC ++;
      } else {
       代表第2次按下C鍵
       把輸入值存入passcode // 這就紀錄新密碼啦~

       pressC = 0; // 清除按鍵紀錄
      }

      have fun!
      jeffrey

  7. 老師,我想再問一下pressC要宣告在一開始的部分還是放在if判斷句中就好了呢?
    可是如果用pressC那不就(key==’C’)這行就不必使用了嗎?
    謝謝老師好幾次的回答小弟感激不盡。

    1. ㄟ…如果搞不清楚的話,變數宣告就放在程式開頭,至於要不要(key==’C’),就看你的程式需要,還是那句話…你試試看就知道了。

      thanks,
      jeffrey

  8. void checkPinCode() {
    clearRow(0); // 從第0個字元開始清除LCD畫面
    lcd.noCursor();
    lcd.setCursor(0, 1); // 切換到第2行
    // 比對密碼
    if (inputCode == passcode) {
    lcd.clear();
    lcd.print(” Unlocked “);
    }else if(inputCode !=passcode){
    lcd.clear();
    lcd.print(“***False!!***”);
    ccount++;
    if(ccount>=3)//如果輸入密碼錯誤達到3次立即發出警報
    {
    lcd.clear();
    lcd.print(“****ALARM!!****”);
    tone(buzzer,1000);
    delay(1000);
    }
    }
    resetLocker();
    delay(3000);
    }
    老師我想問一下如果在ALARM時候不想讓他復歸可以加上什麼呢?

  9. else if(key==’C’){
    lcd.clear();
    lcd.print(“old password:”);
    lcd.setCursor(0, 1);
    lcd.cursor();
    if (inputCode == passcode) {
    lcd.clear();
    lcd.print(“newpassword”);
    lcd.setCursor(0, 1);
    lcd.cursor();
    pressC++;
    lcd.cursor();
    inputCode = “”;
    inputCode=passcode;
    pressC==0;
    }
    resetLocker;
    delay(300);
    }
    老師是這樣存進去passcode的嗎?
    如果不是的話可以可以幫我點明該如何存進去passcode
    謝謝老師!!

    1. 大致像這樣,程式未驗證:

      good luck!
      jeffrey

  10. 老師這是我改出來的部分程式驗證過也是可以的,也謝謝老師的教導,學生我感激不盡!!

  11. 老師我想請教您,程式上基本上沒有錯誤我把RFID(3)的程式貼上來我的密碼鎖裡面後就會顯示exit status 1
    a function-definition is not allowed here before ‘{‘ token在我的void setup()上面請問老師我該如何地去修正它?
    #include
    #include
    #include
    #include
    #include
    #include
    #include // 引用I2C序列顯示器的程式庫
    #include
    #include
    #define KEY_ROWS 4 // 薄膜按鍵的列數
    #define KEY_COLS 4 // 薄膜按鍵的行數
    #define LCD_ROWS 2 // LCD顯示器的列數
    #define LCD_COLS 16 // LCD顯示器的行數
    #define RST_PIN 13 // Reset腳
    #define SS_PIN 53 // 晶片選擇腳
    const int buzzer = 3;
    char keymap[KEY_ROWS][KEY_COLS] = { // 設置按鍵模組
    {‘1’, ‘2’, ‘3’, ‘A’},
    {‘4’, ‘5’, ‘6’, ‘B’},
    {‘7’, ‘8’, ‘9’, ‘C’},
    {‘*’, ‘0’, ‘#’, ‘D’}};
    byte rowPins[KEY_ROWS] = {12, 11, 10, 9};
    byte colPins[KEY_COLS] = {8, 7, 6, 5};
    Keypad keypad = Keypad(makeKeymap(keymap), rowPins, colPins, KEY_ROWS, KEY_COLS);
    int pressC = 0;
    int pressB = 0;
    byte ccount=0;
    unsigned long time;
    int i = 0;
    String passcode = “1234”; // 預設密碼
    String inputCode = “”; // 暫存用戶的按鍵字串
    bool acceptKey = true; //是否接受用戶按鍵輸入的變數,預設為「接受」
    // LCD顯示器
    LiquidCrystal_I2C lcd(0x27,2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
    #define GPIO_ADDR 0x27
    void clearRow(byte n) {
    byte last = LCD_COLS – n;
    lcd.setCursor(n, 1); // 移動到第2行,”PIN:”之後
    for (byte i = 0; i =3)//如果輸入密碼錯誤達到3次立即發出警報
    {
    lcd.clear();
    lcd.print(“****ALARM!!****”);
    tone(buzzer,1000);
    delay(1000);
    }
    }
    delay(3000);
    resetLocker();
    }
    void changePinCode() {
    lcd.clear();
    lcd.noCursor();
    lcd.print(“New PW:”); // 請輸入新密碼
    lcd.cursor();
    acceptKey = true;
    inputCode = “”;
    pressC++; // 重設LCD顯示文字和輸入狀態
    }
    MFRC522 mfrc522(SS_PIN, RST_PIN); // 建立MFRC522物件

    MFRC522::MIFARE_Key key; // 儲存金鑰

    byte sector = 15; // 指定讀寫的「區段」,可能值:0~15
    byte block = 1; // 指定讀寫的「區塊」,可能值:0~3
    byte blockData[16] = “Keep Hacking!”;// 最多可存入16個字元
    // 若要清除區塊內容,請寫入16個 0
    //byte blockData[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

    // 暫存讀取區塊內容的陣列,MIFARE_Read()方法要求至少要18位元組空間,來存放16位元組。
    byte buffer[20];
    int ledPin = 12;

    MFRC522::StatusCode status;

    void writeBlock(byte _sector, byte _block, byte _blockData[]) {
    if (_sector 15 || _block 3) {
    // 顯示「區段或區塊碼錯誤」,然後結束函式。
    Serial.println(F(“Wrong sector or block number.”));
    return;
    }

    if (_sector == 0 && _block == 0) {
    // 顯示「第一個區塊只能讀取」,然後結束函式。
    Serial.println(F(“First block is read-only.”));
    return;
    }

    byte blockNum = _sector * 4 + _block; // 計算區塊的實際編號(0~63)
    byte trailerBlock = _sector * 4 + 3; // 控制區塊編號

    // 驗證金鑰
    status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
    // 若未通過驗證…
    if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F(“PCD_Authenticate() failed: “));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
    }

    // 在指定區塊寫入16位元組資料
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockNum, _blockData, 16);
    // 若寫入不成功…
    if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F(“MIFARE_Write() failed: “));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
    }

    // 顯示「寫入成功!」
    Serial.println(F(“Data was written.”));
    }

    void readBlock(byte _sector, byte _block, byte _blockData[]) {
    if (_sector 15 || _block 3) {
    // 顯示「區段或區塊碼錯誤」,然後結束函式。
    Serial.println(F(“Wrong sector or block number.”));
    return;
    }

    byte blockNum = _sector * 4 + _block; // 計算區塊的實際編號(0~63)
    byte trailerBlock = _sector * 4 + 3; // 控制區塊編號

    // 驗證金鑰
    status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
    // 若未通過驗證…
    if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F(“PCD_Authenticate() failed: “));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
    }

    byte buffersize = 18;
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockNum, _blockData, &buffersize);

    // 若讀取不成功…
    if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F(“MIFARE_read() failed: “));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
    }

    // 顯示「讀取成功!」
    if(status == MFRC522::STATUS_OK){
    Serial.println(F(“Data was read.”));
    }

    void setup() {
    pinMode(buzzer,OUTPUT);
    Serial.begin(9600);
    lcd.begin(16,2);
    lcd.backlight(); // 開啟背光
    SPI.begin(); // 初始化SPI介面
    mfrc522.PCD_Init(); // 初始化MFRC522卡片
    resetLocker();
    Serial.println(F(“Please scan MIFARE Classic card…”));

    // 準備金鑰(用於key A和key B),出廠預設為6組 0xFF。
    for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
    }
    void loop() {
    // 查看是否感應到卡片
    if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return; // 退回loop迴圈的開頭
    }
    // 選取一張卡片
    if ( ! mfrc522.PICC_ReadCardSerial()) { // 若傳回1,代表已讀取到卡片的ID
    return;
    }
    writeBlock(sector, block, blockData); // 區段編號、區塊編號、包含寫入資料的陣列
    readBlock(sector, block, buffer); // 區段編號、區塊編號、存放讀取資料的陣列
    Serial.print(F("Read block: ")); // 顯示儲存讀取資料的陣列元素值
    for (byte i = 0 ; i < 16 ; i++) {
    Serial.write (buffer[i]);
    if(i==16)
    {i=0;}
    }
    Serial.println();
    mfrc522.PICC_HaltA();
    // Stop encryption on PCD
    mfrc522.PCD_StopCrypto1();

    char key = keypad.getKey();

    // 若目前接受用戶輸入,而且有新的字元輸入…
    if (acceptKey && key != NO_KEY) {
    if (key == '*') { // 清除畫面
    clearRow(9); // 從第4個字元開始清除
    inputCode = "";
    } else if (key == '#') { // 比對輸入密碼
    checkPinCode();
    } else if(key == 'A'){ //進入密碼重設與鎖上介面
    lcd.clear();
    lcd.print("C.RESET PASSWORD");
    } else if(key == 'B'){
    if(pressB == 0){
    lcd.clear();
    lcd.print("password:");
    lcd.cursor();}
    if(inputCode == passcode){
    pressB++;
    }
    if(pressB==1){
    lcd.clear();
    lcd.print(" correct ");
    noTone(buzzer);
    ccount=0;
    }if(pressB==2){
    pressB=0;
    resetLocker();
    }
    }else if(key=='D'){ //重新復歸
    resetLocker();
    } else if (key == 'C') {
    if(pressC == 0){
    lcd.clear();
    lcd.print("old PW:");
    lcd.cursor();
    }if(inputCode == passcode){
    pressC ++;
    } if (pressC == 1) {
    changePinCode(); // 比對輸入密碼
    } else if(pressC == 2){
    passcode = inputCode; // 儲存新密碼
    } else if(pressC == 3){
    pressC =0;
    resetLocker();
    }
    }else {
    inputCode += key; // 儲存用戶的按鍵字元
    lcd.print('*');
    }
    }
    }

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *