Máy tính bỏ túi bằng Arduino

Máy tính bỏ túi bằng Arduino

Máy tính bỏ túi bằng Arduino

Trong bài viết này chúng ta sẽ sử dụng Arduino để làm một chiếc máy tính bỏ túi có chức năng tính các biểu thức với các phép toán đơn giản như cộng, trừ, nhân, chia, cả số nguyên và số thập phân. Có báo lỗi khi biểu thức nhập không hợp lệ và thông báo nếu kết quả tính toán bị tràn. Trong bài có sử dụng module LCD I2C kết nối với module LCD16x02 một cách khác để board UnoX có thể điều khiển LCD hiển thị nội dung thông qua I2C giúp tiết kiệm chân cho UnoX.

Chuẩn bị

Chức năng các linh kiện được sử dụng

  • Board UnoX:

    • Đọc giá trị từ module Keypad4x4 để xác định phím nào được nhấn.
    • Tính toán các phép toán được nhập, kiểm tra lỗi nếu có.
    • Điều khiển LCD hiển thị nội dung.
  • Module LCD I2c:

    • Giao tiếp với UnoX thông qua I2C sau đó điều khiển module LCD16x02 hiển thị nội dung.
  • Module LCD16x02:

    • Hiển thị nội dung nhập từ Keypad4x4 và hiển thị kết quả khi tính toán xong.
  • Module Keypad4x4:

    • Bao gồm 16 phím, giúp người sử dụng có thể nhập các toán hạng bằng các phím từ 0 đến 9 và các toán tử đơn cơ bản +,-,x,:, dấu ”.” thập phân và cuối cùng là dấu ”=” để kết thúc việc nhập và tính toán kết quả.

Kết nối module LCD I2C với module LCD16x02

  • Chúng ta có thể sử dụng breadboard để kết nối module LCD I2C với module LCD16x02, hoặc có thể hàn trực tiếp 2 module này với nhau thông qua 1 header đực(như hình bên)

Kết nối UnoX với module Keypad4x4

Sử dụng các header đực-đực để kết nối UnoX với module Keypad4x4 theo bảng đấu nối và hình bên cạnh

UnoXKeypad4x4
10R1
9R2
8R3
7R4
6C1
5C2
4C3
3C4

Kết nối UnoX với module LCD I2C

Sử dụng các header đực-cái để kết nối UnoX với module LCD I2C theo bảng đấu nối và hình bên cạnh

UnoXLCD I2C
GNDGND
5VVCC
A4SDA
A5SCL

Giới thiệu các thư viện được sử dụng

Thư viện LiquidCrystal_I2C.h link

  • Thư viện LiquidCrystal_I2C.h được sử dụng để điều khiển module LCD thông qua module I2C LCD
  • Để sử dụng, đầu tiên ta cần khởi tạo 1 đối tượng lcd với các thông số cấu hình như là địa chỉ module I2C (thương thường là 0x3F, hoặc 0x13) và kích thước module LCD như sau: LiquidCrystal_I2C lcd(0x3F, 16, 2)
  • Trong hàm setup(), khởi động LCD với lệnh lcd.begin().
  • Hàm lcd.print() giúp chúng ta in nội dung cần hiển thị ra LCD.
  • Lệnh lcd.setCursor(x,y) gíup đặt vị trí con trỏ của LCD, x tương ứng với cột và y tương ứng với hàng. Ví dụ, (x,y) = (0,1) tương ứng với việc di chuyển con trỏ đến vị trí hàng 0, cột 1.

Thư viện Keypad.h

  • Thư viện Keypad.h được sử dụng để điều khiển module Keypad4x4. Đầu tiên ta cần khởi tạo một đối tượng keypad bằng cách truyền vào đối tượng này các chân mà UnoX sử dụng để kết nối với keypad4x4, ma trận các kí tự nút nhấn của module keypad (sẽ được giải thích cụ thể hơn trong source code tham khảo).
  • Để xác định nút nào được nhấn ta dùng hàm keypad.getKey().

Thư viện expression_parser.h link

  • Thư viện expression_parser.h được sử dụng để tính các phép toán được nhập vào, hàm cần sử dụng là parse_expression(ex), trong đó ex là một chuỗi chức biểu thức cần tính, ví dụ “2+5x9”, hàm sẽ trả về kết quả là một số kiểu double.

Mã nguồn

Ý tưởng thực hiện:

  • Ta sẽ sử dụng hàm keypad.getKey() để xác định phím được nhấn của keypad4x4, sau đó sử dụng biến s kiểu chuỗi (String) để lưu các kí tự mà người dùng đã nhập, chuỗi này là một biểu thức cần tính toán. Khi người dùng bấm phím ”#”, ta sẽ đưa chuỗi biểu thức này vào hàm parse_expression(s) rồi lấy kết quả xuất ra màn hình LCD thông qua hàm lcd.print(result), với result là kết quả phép tính.
#include "expression_parser.h"
#include <Keypad.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x3F, 16, 2);
// Khai báo ma trận bàn phím
const byte ROWS = 4; //4 dòng
const byte COLS = 4; //4 cột
// Khai báo ma trận kí tự nút nhấn, bao gồm các số và toán tử
char keys[ROWS][COLS] = {
  {'1', '2', '3', '+'},
  {'4', '5', '6', '-'},
  {'7', '8', '9', '*'},
  {'.', '0', '=', '/'}
};

//Các chân kết nối giữa UnoX với module Keypad4x4 
byte rowPins[ROWS] = {10, 9, 8, 7}; 
byte colPins[COLS] = {6, 5, 4, 3}; 

//Khai báo đối tượng điều khiển keypad, sau đó truyền vào ma trận phím, và các chân kết nối 
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

String s = "";	//Biến lưu trữ biểu thức được nhập
char ex[16];

void setup() {
  Serial.begin(9600);
  lcd.begin();					//Khởi động lcd
  lcd.backlight();			//Bật đèn nền
  lcd.clear();					//Xóa các kí tự rác
  Serial.println("..:Calculator:..");
  lcd.print("..:Calculator:..");
  delay(2000);
  lcd.clear();
  lcd.setCursor(0, 0);	//Đưa con trỏ về (0, 0)
}

int cur = 0;
bool enter = false;

void loop() {
  char key = keypad.getKey(); 	//Đọc giá trị phím nhấn.
	// Kiểm tra nêu phím đã được nhấn.
  if ((key != '=') && (key != NO_KEY) ) {
    if (!enter) {								//Nếu chưa kết thúc một lần nhập.
      Serial.println(key);
      if(key=='/')
        lcd.print(':');
      else 
      if (key=='*')
        lcd.print('x');
      else
        lcd.print(key);
      s = s + key;							//Đưa phím vừa được nhấn vào biến s.
      cur++;										
      lcd.setCursor(cur, 0);		//Dịch con trỏ nhập đi 1 ô.
    } else {                    
      // Ngược lại, khi đã hoàn thành việc tính toán thì cho phép người dùng nhập lại.
      lcd.setCursor(0, 0);			
      lcd.print("                ");
      enter = false;
      lcd.setCursor(0, 0);
      cur = 0;
      lcd.print(key);
      s = "";
      s = s + key;
      cur++;
      lcd.setCursor(cur, 0);
    }
  } else if (key == '=') {    //Nếu người dùng nhập "=", thì tính toán và hiển thị kết quả.
    enter = true;
    // Kiểm tra nhập sai
    if ((s[0] == '=') || (s[0] == '/') || (s[0] == '*') || (s[0] == '.')) {
      lcd.setCursor(0, 1);
      lcd.print("Error");
    } else {
      lcd.setCursor(0, 1);
      lcd.print("                ");
      s.toCharArray(ex, 16);
      Serial.println(s);
      double re = parse_expression(ex); // Tính kết quả.
      String check = String(re);
      if (check.length() > 15) {
        lcd.setCursor(0, 1);
        lcd.print("Overflow");
      } else {
        Serial.println(re);
        lcd.setCursor(0, 1);
        lcd.print("=");
        lcd.print(re);                 // Xuất kết quả ra màn hình LCD.
      }
    }
  }
}

Kết quả

  • Sau khi hoàn thành, ta tiến hành nhập thử một số biểu thức để kiểm tra hoạt động của sản phẩm.