You are on page 1of 117

ARDUINO CHO NGƯỜI MỚI BẮT ĐẦU

Quyển cơ bản

minht57 lab
Lời nói đầu

Vi điều khiển là một thiết bị có thể lập trình được nhằm thực hiện các tác vụ định
sẵn. Thường việc lập trình này dành cho các kỹ sư có trình độ kỹ thuật nhất định và
thường rất khó khăn đối với các bạn học sinh tiếp cận. Nhờ sự ra đời của nền tảng
Arduino để đơn giản hóa việc tiếp cận với vi điều dành cho một cá nhân chưa biết gì
về lập trình vi điều khiển mà vẫn có thể tạo ra hàng nghìn ứng dụng khác nhau.
Quyển sách “Arduino cho người mới bắt đầu” (quyển cơ bản) được thiết kế cho các
bạn chưa biết hoặc biết một ít về lập trình Arduino. Nội dung sách chủ yếu viết về
định hướng suy nghĩ liên quan đến lập trình vi điều khiển nhằm mục tiêu phát triển
lâu dài, tạo một nền tảng căn bản nhất cho các bạn làm quen với lập trình vi điều
khiển. Bên cạnh đó, cung cấp cho bạn một số mẹo, thông tin liên quan đến lập trình vi
điều khiển nói chung và Arduino nói riêng.
Quyển sách bao gồm 4 phần đi từ linh kiện điện tử cơ bản đến lập trình những module
quan trọng của Arduino.
Phần 1 (Linh kiện điện tử cơ bản) sẽ cho chúng ta một số kiến thức cơ bản về các loại
linh kiện điện tử thường dùng.
Phần 2 (Lập trình C cơ bản) sẽ cung cấp một số kiến thức cơ bản nhất về lập trình C hỗ
trợ cho lập trình cho vi điều khiển.
Phần 3 (Một số module ngoại vi quan trọng) sẽ cung cấp những kiến thức cốt lõi trong
lập trình vi điều khiển nói chung và Arduino nói riêng.
Phụ lục nhằm cung cấp thêm những thông tin, mạch điện bổ trợ.

minht57 lab
Hướng dẫn đọc sách

Bộ sách Arduino dành cho người mới bắt đầu gồm 3 quyển đi từ mức độ cơ bản đến
chuyên sâu bao gồm quyển cơ bản, quyển rất cơ bản và quyển không còn là cơ bản.
Bộ sách này sẽ cung cấp cho bạn một tư duy làm việc với một vi điều khiển hơn là chỉ
thực hành Arduino đơn thuần.
Bộ 3 quyển sách sẽ cung cấp cho bạn rất nhiều thông tin ở mức căn bản dưới dạng từ
khóa và tóm tắt vấn đề (vì nếu giải thích vấn đề rõ ràng, chuyên sâu thì sẽ rất lan man
và dài dòng). Nếu bạn quan tâm một vấn đề nào cụ thể thì cứ dựa vào những từ khóa
đã được nêu ra và tìm hiểu thêm trên internet hoặc sách vở.
Vì mục tiêu của quyển sách là hướng đến những bạn học lập trình Arduino định
hướng chuyên sâu nên sách sẽ không tập trung vào từng module cảm biến, thiết bị
chấp hành hay một dự án nào cụ thể. Mà cấu trúc sách đi theo hướng học một vi điều
khiển tổng quát mà Arduino chỉ là một ví dụ cụ thể.
Nếu bạn chưa có kiến thức về lập trình Arduino thì quyển cơ bản được khuyên là bạn
đọc theo thứ tự các chương từ đầu đến cuối. Nếu một vấn đề mà bạn đã biết, bạn cũng
được khuyên là đọc lướt qua để tránh bỏ sót những thông tin mà có thể bạn chưa biết
(vì không phải tất cả các thông tin quan trọng đều được in đậm hoặc nhấn mạnh).
Nếu bạn đã có kiến thức cơ bản về lập trình Arduino thì bạn có thể đọc một chương
bất kỳ mà bạn quan tâm. Nếu có thời gian thì bạn nên xem những chương mà bạn đã
biết để kiểm chứng cũng như xem liệu có kiến thức nào chưa biết hay không.
Nếu bạn cảm thấy văn phong hoặc cách tiếp cận của quyển sách không phù hợp với
bạn thì bạn có thể bỏ qua.
Trong quá trình viết và biên soạn sách không thể tránh khỏi những sai sót về mặt nội
dung cũng như hình thức. Nhóm tác giả rất mong nhận được sự góp ý của các bạn
đọc để quyển sách ngày càng hoàn thiện hơn và có thể truyền tải nội dung đến với
nhiều bạn đọc hơn.
Xin cảm ơn bạn đọc.

minht57 lab
Mục lục

Lời nói đầu ii

Hướng dẫn đọc sách iii

Mục lục iv

LINH KIỆN ĐIỆN TỬ CƠ BẢN 1


1 ĐIỆN TỬ CƠ BẢN 2
1.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Điện trở . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Tụ điện . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Cuộn cảm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Diode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.6 Transistor BJT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.7 MOSFET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.8 IC logic cần biết . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.9 Op-amp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

LẬP TRÌNH C CƠ BẢN 12


2 LẬP TRÌNH C CƠ BẢN 13
2.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Các thành phần cơ bản trong ngôn ngữ C . . . . . . . . . . . . . . . . . 13
2.3 Các hệ đếm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4 Biểu thức và phép toán . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5 Một số hàm C thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . 16
Hàm if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Hàm switch ... case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Vòng lặp for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Vòng lặp while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.6 Hàm/Chương trình con . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.7 Mảng một chiều . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.8 Con trỏ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

MỘT SỐ MODULE NGOẠI VI QUAN TRỌNG 27


3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 28
3.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Một số hàm thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . . 29
pinMode() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
digitalWrite() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
digitalRead() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3 Một số module mẫu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Đèn LED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Module Relay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Nút nhấn (button) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

4 TIME 37
4.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2 Một số hàm thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . . 37
delay() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
delayMicroseconds() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
millis() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
micros() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3 Ghi chú . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

5 UART 40
5.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2 Một số hàm thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Serial.begin() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Serial.end() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Serial.print()/Serial.println() . . . . . . . . . . . . . . . . . . . . . . . . . 41
Serial.write() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Serial.availableForWrite() . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Serial.available() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Serial.read() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Một số hàm khác . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Lưu ý . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.3 Một số module mẫu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Module bluetooth HC-05 . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Giao tiếp giữa 2 Arduino . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.4 Lời kết . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

6 ANALOG 51
6.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.2 Một số hàm thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . . 52
analogRead() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
analogReference() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
analogWrite() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.3 Một số module mẫu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Biến trở . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Module điều khiển động cơ L298N . . . . . . . . . . . . . . . . . . . . . 56
6.4 Lời kết . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

7 I2C 63
7.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.2 Một số hàm thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Wire.begin() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Wire.requestFrom() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Wire.beginTransmission() . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Wire.endTransmission() . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Wire.write() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Wire.available() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Wire.read() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Wire.setClock() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Wire.onReceive() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Wire.onRequest() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.3 Một số module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Module IMU MPU6050 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Module thời gian thực RTC . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.4 Lời kết . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

8 SPI 73
8.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
8.2 Một số hàm thông dụng . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
SPISettings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
SPI.begin() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
SPI.end() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
SPI.beginTransaction() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
SPI.endTransaction() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
SPI.transfer(), SPI.transfer16() . . . . . . . . . . . . . . . . . . . . . . . . 76
SPI.usingInterrupt() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
8.3 Một số module thường dùng . . . . . . . . . . . . . . . . . . . . . . . . 76
Thẻ nhớ SD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
8.4 Lời kết . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

9 NGẮT - INTERRUPT 81
9.1 Giới thiệu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2 Một số hàm thường dùng . . . . . . . . . . . . . . . . . . . . . . . . . . 81
interrupts() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
noInterrupts() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
attachInterrupt() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
detachInterrupt() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.3 Lời kết . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

PHỤ LỤC 84

A MỘT SỐ MODULE THÔNG DỤNG KHÁC 85


A.1 Module đo khoảng cách SRF-05 . . . . . . . . . . . . . . . . . . . . . . . 85
A.2 Cảm biến độ ẩm đất . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

B MỘT SỐ HÀM KHÁC 89


B.1 Hàm I/O nâng cao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
B.2 Hàm liên quan đến toán . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
B.3 Hàm làm việc với chuỗi . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

C MỘT SỐ KHÁI NIỆM CĂN BẢN 91


C.1 Một số khái niệm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
C.2 Một số từ ngữ chuyên ngành . . . . . . . . . . . . . . . . . . . . . . . . 92
C.3 Một số kiến thức cần biết . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Dòng sink/source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Push-pull và Open drain IO . . . . . . . . . . . . . . . . . . . . . . . . . 93
D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 94
D.1 Phần cứng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
D.2 Phần mềm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Cài đặt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Giới thiệu về Arduino IDE . . . . . . . . . . . . . . . . . . . . . . . . . . 99

E PHỤ LỤC MẠCH ĐIỆN 102


E.1 Mạch số 1: Diode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
E.2 Mạch số 2: Zener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
E.3 Mạch số 3: NPN Amplifier . . . . . . . . . . . . . . . . . . . . . . . . . . 104
E.4 Mạch số 4: Dùng BJT để đóng tắt relay . . . . . . . . . . . . . . . . . . . 105
E.5 Mạch số 5: IC số . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

Lời kết 109

Thông tin bản quyền 110


LINH KIỆN ĐIỆN TỬ CƠ BẢN
ĐIỆN TỬ CƠ BẢN 1
1.1 Giới thiệu . . . . . . . . . . . . 2
1.1 Giới thiệu 1.2 Điện trở . . . . . . . . . . . . . 2
1.3 Tụ điện . . . . . . . . . . . . . . 5
Chương này sẽ cung cấp một số kiến thức cơ bản về một số linh kiện 1.4 Cuộn cảm . . . . . . . . . . . . 7
điện tử. Các linh kiện này sẽ là những linh kiện thường gặp trong suốt 1.5 Diode . . . . . . . . . . . . . . . 8
quá trình học điện tử. 1.6 Transistor BJT . . . . . . . . . 8
1.7 MOSFET . . . . . . . . . . . . . 9
1.8 IC logic cần biết . . . . . . . 10
1.9 Op-amp . . . . . . . . . . . . 11
1.2 Điện trở

Linh kiện điện trở1 là linh kiện dùng để cản trở dòng điện (lưu ý, điện 1: Điện trở và linh kiện điện trở là khác
trở là đại lượng vật lý đặc trưng cho tính cản trở dòng điện của vật nhau về mặt khái niệm. Trong thực tế,
linh kiện điện trở luôn được gọi tắt là
liệu).
điện trở.
Công thức tính giá trị của điện trở:

𝑈
𝑅=
𝐼

◮ U: hiệu điện thế giữa hai đầu vật dẫn điện (đơn vị: Volt (V))
◮ I: cường độ dòng điện đi qua vẫn dẫn (đơn vị: Ampere (A))
◮ R: điện trở của vật dẫn (đơn vị: Ohm (Ω))

Công dụng
◮ Như tên điện trở, đây là linh kiện dùng để cản trở dòng điện
nhưng thật ra điện trở có nhiều công dụng như giảm điện áp
giữa tải với nguồn (ví dụ muốn cắm LED 3V vào nguồn 5V thì
cần mắc thêm điện trở để giảm áp), làm điện áp tham chiếu
bằng mạch chia áp, tạo các mạch khuếch đại/vi phân/tích phân
(khi kết hợp với tụ điện, cuộn cảm, op-amp), mạch lọc thông
thấp/thông cao (kết hợp với tụ điện, cuộn cảm), điện trở trong
mạch đo dòng,. . .
◮ Điện trở là linh kiện chuyển năng lượng từ điện sang nhiệt (tức
là trong lúc hoạt động sẽ sinh ra nhiệt) và thường lượng nhiệt
này không có ích. Mỗi điện trở sẽ có một công suất tối đa nên
khi dùng cần phải kiểm tra xem công suất mà điện trở phải chịu
có vượt qua công suất mặc định của điện trở không.
Các loại điện trở thường gặp:
◮ Điện trở cắm (through hole resistor)

• Chúng ta đọc giá trị điện trở dựa vào bảng màu (resistor
color code)
• Mỗi điện trở cắm thông thường đều có vạch màu để xác
định giá trị điện trở, đồng thời sai số của điện trở đó. Nếu
là điện trở có 4 vạch màu thì 2 vạch đầu tiên là giá trị, vạch
thứ 3 (vạch kế cuối) là hệ số nhân, vạch thứ 4 (vạch cuối
1 ĐIỆN TỬ CƠ BẢN 3

Hình 1.1: Hình ảnh điện trở chân cắm.


Nguồn từ Wikimedia và Wikimedia.

cùng) là sai số. Nếu là điện trở có 5 vạch màu thì 3 vạch
điều tiên là giá trị, vạch thứ 4 (vạch kế cuối) là hệ số nhân,
vạch thứ 5 (vạch cuối cùng) là sai số.
• Ví dụ điện trở có 4 vòng màu lần lượt là nâu-đen-cam-vàng
kim (gold): nâu có giá trị là 1, đen có giá trị là 0 thì giá trị
2 vạch đầu tiên là 10; cam là vạch thứ 3, cam có giá trị là 3
nên hệ số nhân là 103 ; vạch sai số là vàng kim, vàng kim có
sai số là ±5%. Giá trị điện trở là 10𝑥103 ± 5% = 10𝑘Ω ± 5%.
◮ Một số mẹo
• Một số khó khăn khi đọc giá trị điện trở là không xác định
được màu của điện trở và vạch nào là vạch đầu tiên hay
cuối cùng. Cách đơn giản là bạn đọc nhiều thì sẽ có kinh
nghiệm đọc giá trị điện trở hoặc bạn xác định dựa vòng
màu cuối cùng (vì thường vòng màu cuối cùng thường có
màu đặc biệt như vàng kim (gold)/bạch kim (siliver)).
• Ngoài bảng màu trên thì vẫn còn bảng màu đầy đủ hơn 2 .
2: Vì hình ảnh đó có bản quyền nên
Có 4 loại điện trở là 3/4/5/6 vòng màu, nhưng thường gặp mình không sao chép vào tài liệu của
nhất là điện trở 4 và 5 vòng màu. Ngoài ra, điện trở trên mình được
thực tế cũng có nhiều sai số khác nhau (0.05, 0.1, 0.25, 0.5,
1, 2, 5, 10, 20) nhưng trên thị trường thường gặp loại điện
trở 1% và 5%.
• Thông thường, điện trở 4 vòng màu và thân hình màu vàng
nhạt là điện trở 5%; điện trở 5 vòng màu và thân mình màu
xanh dương nhạt thì là điện trở 1%.
• Vậy việc đọc điện trở có cần thiết khi xung quanh chúng ta
có quá nhiều thiết bị có thể đo được điện trở (như VOM/Multi-
meter)? Câu trả lời là tùy thuộc vào bạn. Khi bạn tự đọc
được giá trị điện trở thì có những lợi ích như sau:
* Bạn có thể đọc được giá trị điện trở ngay lập tức mà
không cần dụng cụ đo.
* Đôi lúc chúng ta không thể đo giá trị của điện trở khi
điện trở đã hàn trong mạch (vì điện trở sẽ mắc song
song với mạch điện thì giá trị bạn đo giữa 2 đầu điện
trở là giá trị của điện trở cần đo và giá trị điện trở của
hệ thống tại 2 điểm đó) mà chúng ta cần ra bảng màu
hoặc cách ly điện trở ra khỏi mạch điện.
• Điện trở cắm thông thường sẽ có công suất 1/8W, 1/4W,
1/2W, 1W, 2W và 3W.
• Điện trở sẽ có một số giá trị tiêu chuẩn phù hợp cho từng
Ví dụ điện trở 5% sẽ có giá trị sau 1.0,
sai số khác nhau. Chúng sẽ có tiêu chuẩn giá trị điện trở 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7,
(standard resistor value) được chia thành E3 (>20%, có 3 3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8,
giá trị điện trở), E6 (20%, có 6 giá trị điện trở), E12 (10%, có 7.5, 8.2, 9.1. Giá trị điện trở 5% sẽ từ 10Ω
đến 22MΩ. Ví dụ với hệ số là 1.0 thì sẽ có
12 giá trị điện trở), E24 (5%, có 24 giá trị điện trở), E48 (2%, các giá trị 10Ω, 100Ω, 1kΩ, 10kΩ, 100kΩ,
1MΩ, 10MΩ và tương tự cho những giá
trị khác.
1 ĐIỆN TỬ CƠ BẢN 4

Hình 1.2: Bảng màu giá trị điện trở.


Nguồn từ Wikimedia.

có 48 giá trị điện trở), E96 (1%, có 96 giá trị điện trở), E192
(0.5%, 0.25%, <, có 192 giá trị điện trở).
◮ Điện trở dán SMD (SMD resistor)
• Giá trị của điện trở dán sẽ được in lên trên bề mặt (nếu có
thể). Các cách in trên điện trở có thể sẽ như sau:
3
* 103 = 10𝑥10 = 10𝑘Ω
0
* 100 = 10𝑥10 = 10Ω
1
* 1001 = 100𝑥10 = 1𝑘Ω
* 0 ≈ 0Ω
* 5𝑅6 = 5.6Ω
* 𝑅382 = 0.382Ω
* 068 = 0.068Ω
* 38𝐶 = 24.3𝑘Ω (tra theo chuẩn EIA-96)
• Kích thước điện trở:
* 0201: 0.024 x 0.012 inch, 1/20W
* 0402: 0.04 x 0.02 inch, 1/16W
* 0603: 0.06 x 0.03 inch, 1/10W
* 0805: 0.08 x 0.05 inch, 1/8W
* 1206: 0.12 x 0.06 inch, 1/4W
* 1210: 0.12 x 0.10 inch, 1/2W
* 1812: 0.18 x 0.12 inch, 1W
* 2010: 0.20 x 0.10 inch, 3/4W
* 2512: 0.25 x 0.12 inch, 1W
1 ĐIỆN TỬ CƠ BẢN 5

Hình 1.3: Điện trở dán SMD. Nguồn từ


Wikimedia.

• Một số mẹo:
* Nếu thấy điện trở in 3 chữ số thì có thể là điện trở 5%,
nếu in 4 chữ số thì có thể là điện trở 1% (lưu ý không
phải lúc nào cũng đúng).
◮ Biến trở (potentiometer)

Hình 1.4: Biến trở các loại. Nguồn từ


Wikimedia và Wikimedia.

• Biến trở là linh kiện có thể thay đổi được giá trị. Giá trị tối
đa của điện trở thông thường được ghi trực tiếp lên điện
trở.

1.3 Tụ điện

Chúng ta có thể hiểu đơn giản tụ điện là một linh kiện có thể chứa
được điện tích. Mỗi tụ điện sẽ có bản tụ ở 2 cực (được ngăn cách nhau
bởi 1 lớp điện môi, mỗi loại lớp điện môi khác nhau sẽ có một loại tụ
khác nhau) và các bản tụ này dùng để chứa điện tích. Tụ điện ngăn
cản không cho dòng điện một chiều đi qua nhưng cho phép dòng điện
xoay chiều đi qua (khái niệm đầy đủ hơn tại Wikipedia).

Hình 1.5: Một số loại tụ điện. Nguồn từ


Wikimedia.

Một số thông số cần lưu ý khi làm việc với tụ:


◮ Điện dung C (đơn vị Farad - F) thể hiện khả năng tích điện của
tụ. Điện dung càng lớn thì tích điện càng nhiều.
1 ĐIỆN TỬ CƠ BẢN 6

◮ Điện áp làm việc tối đa U (đơn vị Volt - V) là điện áp rơi tối đa


lên tụ có thể chịu được. Nếu điện áp hoạt động lớn hơn điện áp
làm việc thì gây chập tụ và nổ. Khi chọn điện áp làm việc cho tụ
hãy chọn tối thiểu có giá trị bằng 1.3 điện áp làm việc (ví dụ điện
áp làm việc 12V thì nên chọn tụ có điện áp 12𝑉 ∗ 1.3 = 15.6 ≈ 16𝑉).
◮ Công thức tính điện lượng Q:

𝑄 = 𝐶𝑈

• Thể hiện lượng điện tích được tích tụ ở bề mặt của tụ điện.
• Đơn vị là (Coulomd - C).
◮ Nhiệt độ làm việc (đơn vị C/F) là ngưỡng tối đa/tối thiểu của
nhiệt độ mà tụ có thể làm việc được. Thông thường chúng ta
không quan tâm đến thông số này, nhưng nếu thiết bị có chứa tụ
làm việc trong các môi trường khắc nghiệt (lò hơi, phòng lạnh,
ngoài trời,. . . ) thì hãy chú ý đến nhiệt độ hoạt động của môi
trường.
◮ Ngoài ra còn nhiều thông số khác nhưng tùy vào ứng dụng cụ
thể mà bạn nên tìm hiểu thêm như hệ số biến đổi điện dung theo
nhiệt độ/thời gian, dòng rò (leakage current), tần số làm việc,
sai số (tolerance), ESR (Equivalent Series Resistance). . .
Một số loại tụ phổ biến:
◮ Tụ hóa (Electrolytic Capacitors): là tụ điện phân cực. Điều lưu ý
khi sử dụng tụ là không được gắn ngược chiều âm dương của tụ
với nguồn (nếu ngược thì sẽ gây nổ).

Hình 1.6: Các loại tụ hóa (từ trái sang


phải): tụ hóa chân cắm (Radial lead Elec-
trolytic Capacitors), tụ tantan (SMD Tan-
talium Capacitor), tụ nhôm (Aluminum
Electrolytic Capacitor) . Nguồn từ Wiki-
media.

• Tụ hóa thường có giá trị lớn (có thể từ vài chục nF đến vài
F). Tụ có giá trị lớn được gọi là siêu tụ (supercapacitor).
• Cực của tụ hóa thường được đánh dấu trên tụ (tụ hóa chân cắm/tụ nhôm
sẽ đánh dấu cực âm, tụ tantalium sẽ đánh dấu cực dương).

Hình 1.7: Hình ảnh ký hiệu cực trên tụ


điện. Nguồn từ Wikimedia và Wikime-
dia.

◮ Tụ gốm (ceramic capacitor) là tụ điện không phân cực, thường


có giá trị nhỏ.
• Nếu là tụ gốm loại cắm thì thường giá trị sẽ in lên tụ (đơn
vị là pF). Nếu là tụ dán SMD thì giá trị tụ sẽ được ghi trên
reel (cách đóng gói tụ khi bán).
1 ĐIỆN TỬ CƠ BẢN 7

Hình 1.8: Tụ hóa. Nguồn từ Wikimedia


và Wikimedia.

• Các thông tin cụ thể về điện áp hoạt động, giá trị tụ, sai
số,. . . sẽ được ghi trên bao bì đóng gói của nhà sản xuất.
Nếu bạn có được một con tụ không có thông tin thì rất khó
để tìm được thông số của nó.
• Kích thước của tụ cũng khá đa dạng. Bạn tham khảo thêm
với từ khóa “EIA & Metric Standard Sizes” để biết thêm chi
tiết.
◮ Những loại tụ khác bạn có thể tìm hiểu tại đây.
◮ Chi tiết về đặc tính tụ, bạn đọc tham khảo chuỗi bài tại electronics-
tutorials

1.4 Cuộn cảm

Cuộn cảm là linh kiện có khả năng sinh ra từ trường khi có dòng điện
chạy qua. Cuộn cảm đặc trưng bằng độ tự cảm (từ dung) có đơn vị đo
là Henry (H). Cuộn cảm sẽ cho dòng điện một chiều đi qua nhưng sẽ
cản trở dòng điện xoay chiều.

Hình 1.9: Một số loại cuộn cảm. Nguồn


từ Wikimedia.

Ghi chú: thông thường thì tác giả ít khi thấy dùng cuộn cảm với các
ứng dụng cơ bản nên chúng ta sẽ không đi quá sâu ngoài khái niệm.
Nếu bạn đọc đang làm liên quan đến lọc nhiễu, làm mạch nguồn
buck/boost/. . . , mạch lọc tần số,. . . thì bạn nên đọc tài liệu chuyên
ngành cụ thể để hiểu thêm về đặc tính của cuộn cảm vì lý thuyết về
cuộn cảm nhìn chung là hơi khó so với mức căn bản.
1 ĐIỆN TỬ CƠ BẢN 8

1.5 Diode

Diode là linh kiện bán dẫn chỉ cho phép dòng điện đi theo một chiều
nhất định. Diode có 2 khối bán dẫn ghép với nhau là bán dẫn loại
P và bán dẫn loại N. Dòng điện dương sẽ chạy từ P (anode) sang N
(cathode).

Hình 1.10: Một số loại diode (từ trái


sang phải): diode thường, LED (Light
Emitting Diode), diode zener . Nguồn từ
Wikimedia.

Các loại bán dẫn thường thấy: diode chỉnh lưu, zener, LED (Light
Emitting Diode), Schottky Diode,. . . Ngoài ra vẫn còn nhiều loại diode
khác như Gunn Diode, PIN diode, step recovery diode, laser diode,
photo diode,. . .
Một số thông số cần quan tâm:
◮ Điện áp phân cực thuận là điện áp tối thiểu để dòng điện có
thể chạy qua diode (thường giá trị điện áp phân cực thuận từ
0.15V đến vài volt tùy vào từng loại diode, giá trị mà chúng ta
hay nghe là 0.7V. Giá trị này thông thường phụ thuộc vào loại
vật liệu được trộn vào để làm bán dẫn).
◮ Điện áp ngược cực đại là điện áp tối đa nếu mắc ngược cực của
diode vào nguồn mà không ảnh hưởng gì. Nếu lớn hơn điện
áp này thì diode sẽ bị đánh thủng (thường giá trị điện áp đánh
thủng từ vài trăm đến vài nghìn volt).
◮ Dòng điện tối đa mà diode có thể tải được.
◮ Tần số đóng ngắt tối đa mà diode có thể đáp ứng được. 3
3: Bạn sẽ quan tâm thông số này khi làm
việc với các mạch đóng ngắt có tần số (ví
dụ như mạch nguồn xung).
1.6 Transistor BJT

Transistor BJT là linh kiện bán dẫn chủ động thường được sử dụng
như một phần tử khuếch đại (amplify) hoặc khóa điện tử (switch elec-
tronic signals). Transistor là từ được kết hợp từ “transfer” và “resistor”
vì nó sẽ chuyển đổi một tín hiệu yếu từ mạch điện có trở kháng thấp
sang mạch điện có trở kháng cao (“The transistor is a semiconductor
device which transfers a weak signal from low resistance circuit to
high resistance circuit. The words trans mean transfer property and
istor mean resistance property offered to the junctions.”)
Transistor được ghép từ 3 lớp bán dẫn: N-P-N thì được gọi là NPN
transistor, P-N-P thì được gọi là PNP transistor. Transistor có 3 cực là
B (base), C (collector), E (emitter). Chiều mũi tên là chiều của dòng
điện.
Transistor sẽ có 3 vùng hoạt động lần lượt là vùng tắt, vùng khuếch
đại và vùng bão hòa. Tùy vào ứng dụng mà bạn cần cấu hình mạch
transistor phù hợp để transistor rơi vào đúng vùng hoạt động. Ví dụ
khi bạn cho transistor hoạt động ở chế độ khuếch đại thì phải phân
1 ĐIỆN TỬ CƠ BẢN 9

Hình 1.11: Transistor BJT. Nguồn từ


Wikimedia và Wikimedia.

cực làm sao để điểm hoạt động của transistor rơi vào vùng đấy. Nếu
bạn muốn transistor hoạt động như công tắc thì để transistor chuyển
đổi giữa vùng tắt và vùng bão hòa (bạn đọc thấy hứng thú thì tìm hiểu
thêm về lý thuyết transistor, mặc dù hay nhưng khó).
Ký hiệu như hình 1.12.

Hình 1.12: Ký hiệu BJT. Nguồn từ Wiki-


media.

Một số thông số cần lưu ý: điện áp phân cực thuận 𝑉𝐵𝐸 , điện áp tối
đa hoạt động, dòng diện tối đa, tần số hoạt động. Tùy vào ứng dụng
cụ thể mà bạn cần quan tâm đến những thông số nào (khi bạn tìm hiểu
một ứng dụng cụ thể thì thông thường các tài liệu sẽ đề cập những
thông số về transistor). Thông tin của transistor thì bạn nên tìm trên
datasheet của sản phẩm.

1.7 MOSFET

MOSFET là viết tắt của "Metal-Oxide Semiconductor Field-Effect Tran-


sistor", là transistor hiệu ứng trường.

Hình 1.13: Cấu tạo của MOSFET kênh


N. Nguồn từ Wikimedia.

MOSFET cũng có 2 loại là N-MOSFET và P-MOSFET. MOSFET có 3


cực được đặt tên là G (gate), S (source), D (drain).
Ký hiệu như hình 1.14.
1 ĐIỆN TỬ CƠ BẢN 10

Hình 1.14: Ký hiệu của MOSFET kênh N


(bên trái) và kênh P (bên phải). Nguồn
từ Wikimedia.

Ghi chú về sự khác nhau lớn nhất giữa transistor BJT và MOSFET:
BJT điều khiển bằng dòng (current-driven device) tại cực B, trong khi
đó MOSFET được điều khiển bằng áp (voltage-controlled device) được
đặt vào cực G.

1.8 IC logic cần biết

IC (viết tắt của Integrated Circuit) là một mạch điện thực hiện một
chức năng nào đó được đóng gói sẵn trong một con chip. IC có nhiều
loại khác nhau nhưng có thể chia thành 4 nhóm chính:
◮ Microcomputers là IC có chứa bộ vi xử lý.
◮ Memory là IC dùng để chứa dữ liệu.
◮ Standard logics ICs là IC để được thực hiện một chức năng cụ
thể (chúng ta đề cập loại IC này trong đề mục này).
◮ Custom Logic ICs là IC được thực hiện theo nhu cầu của người
dùng.
Thông tin thú vị: mạch điện là sự kết hợp của transistor, tụ điện, điện
trở, cuộn cảm,. . . Tất cả các thành phần này trong chip được làm bằng
semiconductor trên một tấm bán dẫn gọi là wafer.
IC logic thường có hai loại: TTL và CMOS. Cơ bản chức năng của các
ICs này là giống nhau nhưng sẽ khác nhau nhau về một số đặc tính
nhất định.
◮ TTL ICs (Transistor-transistor logic ICs) được chế tạo bằng BJT.
◮ CMOS ICs (Complementary metal oxide semiconductor ICs)
được chế tạo từ một cặp p/n – MOSFET với nhau.
◮ Nếu điện áp hoạt động của chip là 5V:
• TTL ICs:
* Mức tín hiệu đầu vào: thấp (low) là 0 – 0.8V, cao (high)
là 2 – 5V.
* Mức tín hiệu đầu ra: thấp (low) là 0 – 0.5V, cao (high)
là 2.7 – 5V.
• CMOS ICs:
* Mức tín hiệu đầu vào: thấp (low) là 0 – 1.5V, cao (high)
là 3.5 – 5V.
* Mức tín hiệu đầu ra: thấp (low) là 0 – 0.05V, cao (high)
là 4.95 – 5V.
Một dòng chip Standard logics ICs khá phổ biến là dòng 7400. Trên
thị trường, chúng ta thường thấy có 74HCxx và 74LSxx. Sự khác nhau
cơ bản: chip 74HCxx (CMOS – High speed) được chế tạo bằng công
1 ĐIỆN TỬ CƠ BẢN 11

nghệ CMOS, chip 74LSxx (Low power Shottky) được chế tạo bằng
công nghệ TTL. (Ngoài ra còn có nhiều loại khác, tìm hiểu tại đây).
Một số chip nên biết:
◮ 74x00: NAND 2 ngõ vào
◮ 74x01: NAND 2 ngõ vào có cực thu hở (open-collector).
◮ 74x02: NOR 2 ngõ vào
◮ 74x08: AND 2 ngõ vào
◮ 74x09: AND 2 ngõ vào có cực thu hở (open-collector)
◮ 74x14: cổng đảo (có Schmitt-trigger)
◮ 74x32: OR 2 ngõ vào
◮ 74x86: XOR 2 ngõ vào
◮ 74x595: thanh ghi dịch 8-bit, ngõ vào nối tiếp, ngõ ra song song
◮ 74x4511: BCD sang 7-segment (dùng với LED 7 đoạn)
◮ Ngoài ra còn rất nhiều loại khác, vui lòng đọc tại đây.

1.9 Op-amp

Op-amp (operational amplifier – mạch khuếch đại thuật toán) là một


mạch khuếch đại “DC-coupled” với hệ số khuếch đại cao, có đầu vào
vi sai và thông thường có đầu ra đơn.

Hình 1.15: Op-amp. Nguồn từ Wikime-


dia.

Ký hiệu:

Hình 1.16: Ký hiệu của op-amp. Nguồn


từ Wikimedia.

◮ V+: Đầu vào không đảo


◮ V-: Đầu vào đảo
◮ 𝑉𝑜𝑢𝑡 : Đầu ra
◮ 𝑉𝑆+ : Nguồn cung cấp điện dương
◮ 𝑉𝑆− : Nguồn cung cấp điện âm
Một số ứng dụng: op-amp có rất nhiều ứng dụng trong thực tế vì
opamp có thể thực hiện nhiều phép toán đảo, cộng, trừ, nhân, chia,
tích phân, đạo hàm tín hiệu. Mình sẽ liệt kê một số ứng dụng cơ bản
mà bạn có thể gặp ở mức độ cơ bản:
◮ Mạch đệm đảo/không đảo.
◮ Mạch khuếch đại đảo/không đảo.
◮ Mạch khuếch đại vi sai (sẽ gặp trong ứng dụng cân sử dụng cảm
biến loadcell).
◮ Mạch so sánh điện áp.
LẬP TRÌNH C CƠ BẢN
LẬP TRÌNH C CƠ BẢN 2
2.1 Giới thiệu . . . . . . . . . . . 13
2.1 Giới thiệu 2.2 Các thành phần cơ bản trong
ngôn ngữ C . . . . . . . . . . . . 13
C là ngôn ngữ lập trình cấu trúc được sử dụng rộng rãi trong lập trình 2.3 Các hệ đếm . . . . . . . . . . 14
nhúng. Chương này sẽ giúp bạn đọc nắm được những kiến thức cơ 2.4 Biểu thức và phép toán . . . 14
bản cần thiết phục vụ cho lập trình vi điều khiển. 2.5 Một số hàm C thường dùng 16
Hàm if . . . . . . . . . . . . . 16
Hàm switch ... case . . . . . . 17
Vòng lặp for . . . . . . . . . . 20
2.2 Các thành phần cơ bản trong ngôn ngữ C Vòng lặp while . . . . . . . . 22
2.6 Hàm/Chương trình con . . . 23
2.7 Mảng một chiều . . . . . . . 24
Từ khóa là từ có ý nghĩa xác định dùng để khai báo dữ liệu, viết
2.8 Con trỏ . . . . . . . . . . . . . 25
hàm,...
◮ Ví dụ: if, else, for, switch,...

Tên nhằm thể hiện rõ ý nghĩa của hằng, biến, mảng, con trỏ,... trong
chương trình.
Kiểu dữ liệu:

STT Kiểu dữ liệu Chiều dài Ngưỡng giá trị


1 unsigned char 1 byte 0 → 255
2 char 1 byte -128 → 127
3 unsigned int 2 bytes (hoặc 4 bytes) 0 → 65535 (hoặc 0 → (232 − 1))
4 int 2 bytes (hoặc 4 bytes) -32768 → 32768 (hoặc −231 → (231 − 1))
5 unsigned short 2 bytes 0 → 65535
6 short 2 bytes -32768 → 32768
7 unsigned long 8 bytes 0 → 264
8 long 8 bytes −2 → (263 − 1)
63

9 float 4 bytes 3.4 ∗ 10−38 → 3.4 ∗ 10−38


10 double 8 bytes 1.7 ∗ 10−308 → 1.7 ∗ 10308

Khai báo và khởi tạo:


◮ Khai báo (declaration)

int this_is_parameter;

Biến this_is_parameter được khai báo với kiểu dữ liệu int.


◮ Khởi tạo (initialization)

int this_is_parameter = 100;

Biến this_is_parameter được khai báo với kiểu dữ liệu int và


khởi tạo giá trị ban đầu là 100.
2 LẬP TRÌNH C CƠ BẢN 14

2.3 Các hệ đếm

Các hệ đếm cơ bản trong lập trình C: hệ nhị phân (hệ 2 – binary), hệ
bát phân (hệ 8 – octal), hệ thập phân (hệ 10 – decimal) và hệ thập lục
phân (hệ 16 – hexadecimal)

Decimal Hex Octal Binary


0 0 0 0
1 1 1 1
2 2 2 10
3 3 3 11
4 4 4 100
5 5 5 101
6 6 6 110
7 7 7 111
8 8 10 1000
9 9 11 1001
10 A 12 1010
11 B 13 1011
12 C 14 1100
13 D 15 1101
14 E 16 1110
15 F 17 1111
16 10 20 1 0000
32 20 40 10 0000
64 40 100 100 0000
128 80 200 1000 0000
256 100 400 1 0000 0000
512 200 1000 10 0000 0000
1024 400 2000 100 0000 0000

Hệ đếm là cách biểu diễn con số dưới các dạng khác nhau. Ví dụ số
mười được biểu diễn dưới dạng nhị phân là 10102 , dưới dạng bát phân
là 128 , dưới dạng thập phân là 1010 và dưới dạng thập lục phân là
𝐴16 .
Trong cuộc sống thường ngày, chúng ta dùng hệ thập phân, còn vi
điều khiển thi dùng hệ nhị phân. Từ đó trong lập trình, chúng ta dùng
hệ thập phân khi chúng ta muốn người đọc hiểu nhanh chóng (human-
readable), dùng hệ nhị phân khi muốn hiểu vi điều khiển đang hiểu
như thế nào, thập lục phân khi muốn viết gọn một chuỗi số nhị phân.

2.4 Biểu thức và phép toán

Biểu thức là sự phối hợp của những toán tử và toán hạng.

a=b+1
2 LẬP TRÌNH C CƠ BẢN 15

Giá trị b được cộng cho 1, sau đó kết quả được gán lại cho a. 5 5: Trong phép toán, b và 1 là toán hạng,
+ là toán tử.

index += 2;

Giá trị index được cộng cho 2, sau đó kết quả được gán lại cho
index (đây là cách viết gọn).
Các loại phép toán:
◮ Phép toán số học: cộng (+), trừ (-), nhân (*), chia (/), chia lấy dư
(%).
10 + 8 / 2 = 14
12 % 5 = 2
◮ Phép quan hệ: lớn hơn (>), lớn hơn hoặc bằng (>=), bé hơn (<),
bé hơn hoặc bằng (<=), bằng (==) và khác (!=).
10 > 2
→ có giá trị 1 (đúng)
20 == 30
→ có giá trị 0 (sai)
◮ Phép toán luận lý: phủ định (!), và (&&) và hoặc (||).
5 && (8 < 9)
→ có giá trị 1 (đúng)
1 || 0
→ có giá trị 1 (đúng)
1 && 0
→ có giá trị 0 (sai)
◮ Phép toán trên bit (bitwise): và (&), hoặc (|), xor (ˆ), dịch trái («),
dịch phải (»), đảo (∼).

bit x bit y x&y x|y xˆy x


0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0

210 >> 1 = 00102 >> 1 = 00012


210 << 1 = 00102 << 1 = 01002
◮ Các phép toán khác: phép gán (=), phép gán kết hợp (+=, -=, *=,
/=, %=, »=, «=, &=, |=, ˆ=), phép tăng giảm (++, - -).
◮ Độ ưu tiên của các phép toán6 :
6: Bạn đọc tham khảo tại dayn-
hauhoc.com.
2 LẬP TRÌNH C CƠ BẢN 16

Độ ưu tiên Các phép toán Trình tự


1 () [] -> Trái → Phải
2 ! ∼ & * ++ – Phải → Trái
3 * / % Trái → Phải
4 + - Trái → Phải
5 << >> Trái → Phải
6 < <= > >= Trái → Phải
7 == != Trái → Phải
8 & Trái → Phải
9 ^ Trái → Phải
10 | Trái → Phải
11 && Trái → Phải
12 || Trái → Phải
13 ? : Phải → Trái
14 = += -= *= /= %= <<= >>= &= ^= |= Phải → Trái

2.5 Một số hàm C thường dùng

Hàm rẽ nhánh có điều kiện (if ... else if ... else ....)

1 if (biểu thức luận lý 1) {


2 <khối lệnh 1>
3 }
4 else if (biểu thức luận lý 2) {
5 <khối lệnh 2>
6 }
7 ...
8 else if (biểu thức luận lý n) {
9 <khối lệnh n>
10 }
11 else {
12 <khối lệnh n + 1>
13 }

◮ Hàng 1..3: nếu (if ) biểu thức luận lý 1 đúng thì thực hiện khối lệnh
1 và thoát khỏi cấu trúc if
◮ Hàng 4..6: ngược lại nếu (else if ) biểu thức luận lý 2 đúng thì thực
hiện khối lệnh 2 và thoát khỏi cấu trúc if
◮ Hàng 8..10: ngược lại (else if ) nếu biểu thức luận lý n đúng thì thực
hiện khối lệnh n và thoát khỏi cấu trúc if
◮ Hàng 11..13: ngược lại (else) thực hiện khối lệnh n + 1.

Lưu ý:
◮ Từ khóa if, else if, else phải viết thường.
◮ Biểu thức luận lý đúng khi kết quả khác 0 (≠ 0) và sai khi kết quả
bằng 0 (== 0).
Một số dạng rút gọn của cấu trúc if
2 LẬP TRÌNH C CƠ BẢN 17

1 if (biểu thức luận lý 1) {


2 <khối lệnh 1>
3 }

hoặc

1 if (biểu thức luận lý 1) {


2 <khối lệnh 1>
3 }
4 else {
5 <khối lệnh 2>
6 }

Ví dụ: Link: https://onlinegdb.com/Sy_


yoAEWP

1 #include <stdio.h>
2

3 int main()
4 {
5 int a;
6 printf("Please add a: ");
7 scanf("%d", &a);
8

9 if(a>0) {
10 printf("%d is greater than 0", a);
11 }
12 else if (a == 0) {
13 printf("%d is equal to 0", a);
14 }
15 else {
16 printf("%d is less than 0", a);
17 }
18

19 return 0;
20 }

Kết quả:

Please add a: 6
6 is greater than 0

Hàm rẽ nhánh có điều kiện (switch ... case ...)

1 switch (biểu thức) {


2 case giá trị 1:
3 <khối lệnh 1>
2 LẬP TRÌNH C CƠ BẢN 18

4 break;
5 case giá trị 2:
6 <khối lệnh 2>
7 break;
8 ...
9 case giá trị n:
10 <khối lệnh n>
11 break;
12 default:
13 <khối lệnh n+1>
14 }

◮ Hàng 1: trong trường hợp biểu thức có giá trị là


◮ Hàng 2..4: giá trị 1 (case) thì thực hiện khối lệnh 1 và thoát ra khỏi
cấu trúc switch (break),
◮ Hàng 5..7: hoặc giá trị 2 (case) thì thực hiện khối lệnh 2 và thoát ra
khỏi cấu trúc switch (break),
◮ Hàng 9..11: hoặc giá trị n (case) thì thực hiện khối lệnh n và thoát
ra khỏi cấu trúc switch (break),
◮ Hàng 12..13: ngược lại (default) thì thực hiện khối lệnh n+1.

Lưu ý:
◮ Từ khóa switch, case, break, default phải viết thường.
◮ Biểu thức phải có kết quả là giá trị nguyên.

Ví dụ: Link: https://onlinegdb.com/


ryB1TC4bw

1 #include <stdio.h>
2

3 int main()
4 {
5 int month = 0;
6 printf("Please input month: ");
7 scanf("%d", &month);
8

9 switch(month)
10 {
11 case 1:
12 printf("January\n");
13 break;
14 case 2:
15 printf("February\n");
16 break;
17 case 3:
18 printf("March\n");
19 break;
20 case 4:
21 printf("April\n");
22 break;
23 case 5:
24 printf("May\n");
25 break;
26 case 6:
27 printf("June\n");
28 break;
29 case 7:
2 LẬP TRÌNH C CƠ BẢN 19

30 printf("July\n");
31 break;
32 case 8:
33 printf("August\n");
34 break;
35 case 9:
36 printf("September\n");
37 break;
38 case 10:
39 printf("October\n");
40 break;
41 case 11:
42 printf("November\n");
43 break;
44 case 12:
45 printf("December\n");
46 break;
47 default:
48 printf("Input is wrong\n");
49 }
50

51 switch(month)
52 {
53 case 1:
54 case 2:
55 case 3:
56 printf("Month %d is in Q1\n", month);
57 break;
58 case 4:
59 case 5:
60 case 6:
61 printf("Month %d is in Q2\n", month);
62 break;
63 case 7:
64 case 8:
65 case 9:
66 printf("Month %d is in Q3\n", month);
67 break;
68 case 10:
69 case 11:
70 case 12:
71 printf("Month %d is in Q4\n", month);
72 break;
73 }
74

75 return 0;
76 }

Kết quả:

Please input month: 2


February
Month 2 is in Q1
2 LẬP TRÌNH C CƠ BẢN 20

Vòng lặp for

1 for (biểu thức 1; biểu thức 2; biểu thức 3) {


2 <khối lệnh>
3 }

◮ Hàng 1: đầu tiên sẽ thực hiện biểu thức 1,


◮ Hàng 1..2: kiểm tra biểu thức 2 nếu đúng thì thực hiện khối lệnh.
Sau đó thực hiện biểu thức 3.
◮ Hàng 1..2: kiểm tra biểu thức 2 nếu vẫn còn đúng thì thực hiện
khối lệnh. Sau đó thực hiện biểu thức 3.
◮ Hàng 1..2: kiểm tra biểu thức 2 nếu vẫn còn đúng thì thực hiện
khối lệnh. Sau đó thực hiện biểu thức 3.
◮ Hàng 1: Kiểm tra biểu thức 2 nếu sai thì thoát khỏi vòng for.

Lưu ý:
◮ Từ khóa for phải viết thường.

Một số từ khóa đi với vòng lặp:


◮ break: dùng để thoát vòng lặp gần nhất chứa nó.
◮ continue: dùng để tiếp tục vòng lặp gần nhất, bỏ qua các lâu lệnh
phía sau lệnh continue.
Ví dụ 1: Link: https://onlinegdb.com/
HJCwC0E-P

1 #include <stdio.h>
2

3 int main()
4 {
5 int idx = 0;
6 for(idx = 0; idx < 10; idx++) {
7 printf("Loop %d\n", idx);
8 }
9 printf("---\n");
10 for(idx = 0; idx < 10; idx++) {
11 printf("Loop %d\r", idx);
12 }
13 return 0;
14 }

Kết quả:

Loop 0
Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
Loop 6
Loop 7
Loop 8
Loop 9
2 LẬP TRÌNH C CƠ BẢN 21

---
Loop 9

Ví dụ 2: Link: https://onlinegdb.com/
B19tyyBZD

1 #include <stdio.h>
2

3 int main()
4 {
5 int break_point = -1;
6 printf("Input a point [0..9]: ");
7 scanf("%d", &break_point);
8 if ((break_point > 9) || (break_point < 0)) {
9 printf("Input is wrong");
10 return 0;
11 }
12

13 int idx = 0;
14 for(idx = 0; idx < 10; idx++) {
15 if(idx == break_point) {
16 break;
17 }
18 printf("Loop %d\n", idx);
19 }
20 printf("---\n");
21 for(idx = 0; idx < 10; idx++) {
22 if(idx == break_point) {
23 continue;
24 }
25 printf("Loop %d\n", idx);
26 }
27 return 0;
28 }

Kết quả:

Input a point [0..9]: 5


Loop 0
Loop 1
Loop 2
Loop 3
Loop 4
---
Loop 0
Loop 1
Loop 2
Loop 3
Loop 4
Loop 6
Loop 7
Loop 8
Loop 9
2 LẬP TRÌNH C CƠ BẢN 22

Vòng lặp while

1 while(điều kiện){
2 <khối lệnh>;
3 }

◮ Hàng 1..2: Nếu điều còn đúng thì thực hiện khối lệnh, nếu điều
kiện sai thì thoát ra khỏi vòng lặp.

Ví dụ: Link: https://onlinegdb.com/


SyByxkH-v

1 #include <stdio.h>
2

3 int main()
4 {
5 int idx = 10;
6 while(idx--) {
7 printf("Loop %d\n", idx);
8 }
9 printf("---\n");
10 idx = 10;
11 while(--idx) {
12 printf("Loop %d\n", idx);
13 }
14

15 return 0;
16 }

Kết quả:

Loop 9
Loop 8
Loop 7
Loop 6
Loop 5
Loop 4
Loop 3
Loop 2
Loop 1
Loop 0
---
Loop 9
Loop 8
Loop 7
Loop 6
Loop 5
Loop 4
Loop 3
Loop 2
Loop 1
2 LẬP TRÌNH C CƠ BẢN 23

Thông tin

Trong vi điều khiển luôn cần một vòng lập vô tận (while(1), while(true),
for(;;)) để vi điều khiện lặp đi lặp lại một công việc nào đó chúng ta
lập trình (thật ra để vi điều khiển luôn được kiểm soát nằm trong
hoạt động của chương trình).
Nếu chúng ta lập trình trên vi điều khiển mà không liên quan đến
hệ điều hành (ví dụ RTOS) thì chỉ cần duy nhất một vòng lập vô
tận duy nhất vì khi vào vòng lặp thì không bao giờ thoát ra được.
Khi chúng ta sử dụng các vòng lập for, while, do. . . while thì cần phải
kiểm soát số lần lặp để tránh trường hợp rơi vào vòng lập vô tận.

Lưu ý về vòng lặp vô tận:


◮ Vòng lặp vô tận

1 unsigned char index = 100;


2 for(index = 100; index >= 0; index--) {
3 ...
4 }

◮ Vòng lặp được thực hiện 101 lần

1 char index = 100;


2 for(index = 100; index >= 0; index--) {
3 ...
4 }

2.6 Hàm/Chương trình con

Hàm được dùng để thực hiện một công việc nào đó cụ thể mà người
dùng muốn đặt ra. Người lập trình thường viết chương trình lớn thành
nhiều chương trình con để cho chương trình đỡ phức tạp và dễ dàng
trong đọc hiểu, bảo trì code. Chương trình con có thể gọi một chương
trình con khác. Nếu chương trình con gọi chính nó thì gọi là phương
pháp đệ quy. Lưu ý khi lập trình đệ quy là phải có điều kiện thoát,
nếu không chương trình sẽ lập vô tận và sinh ra các kết quả không
mong muốn.
Ví dụ 1: Link: https://onlinegdb.com/
rkLte1BZw

1 #include <stdio.h>
2

3 int sum(int a, int b)


4 {
5 return (a+b);
6 }
7

8 int main()
9 {
2 LẬP TRÌNH C CƠ BẢN 24

10 int result = sum(2,4);


11 printf("2 + 4 = %d", result);
12

13 return 0;
14 }

Kết quả:

2+4=6

Ví dụ 2: Hàng 3 được gọi là prototype function.


Prototype function bắt buộc khai báo khi
chương trình con được định nghĩa sau
1 #include <stdio.h> hàm main.
2

3 int sum(int a, int b);


4

5 int main()
6 {
7 int result = sum(2,4);
8 printf("2 + 4 = %d", result);
9

10 return 0;
11 }
12

13 int sum(int a, int b)


14 {
15 return (a+b);
16 }

Kết quả trả về giống như trên.

2.7 Mảng một chiều

Mảng là tập hợp nhiều phần tử có chung kiểu dữ liệu. Chỉ số của các
phần tử trong mảng luôn bắt đầu là 0 và tăng dần 1 đơn vị cho các
phần tử tiếp theo.
Ví dụ: tạo ra mảng 10 phần tử có địa chỉ liên tiếp nhau có chung kiểu
dữ liệu là int.
int array[10];
Để tham chiếu phần tử trong mảng, ta thực hiện cú pháp sau:
int value = array[2];

Lưu ý

Khi khai báo mảng cần phải xác định số lượng phần tử trong mảng
nhằm tránh gây ra các sai sót về bộ nhớ.
2 LẬP TRÌNH C CƠ BẢN 25

Ví dụ:

1 #include <stdio.h>
2

3 #define NUMBER_OF_ARRAY 10
4

5 int main()
6 {
7 int array[NUMBER_OF_ARRAY];
8 int idx = 0;
9 for(idx = 0; idx < NUMBER_OF_ARRAY; idx++)
10 {
11 array[idx] = 2 * idx;
12 }
13

14 printf("Value at index 2: %d\n", array[2]);


15

16 array[2] *= 3;
17

18 printf("New value at index 2: %d\n", array[2]);


19

20 return 0;
21 }

Kết quả:

Value at index 2: 4
New value at index 2: 12

2.8 Con trỏ

Con trỏ sẽ chứa địa chỉ dùng để truy cập các biến/hàm thông qua địa
chỉ này. Ví dụ:
◮ int par; → Khai báo biến par có kiểu dữ liệu là int.
◮ int * px; → Khai báo con trỏ sẽ trỏ đến ô nhớ có kiểu dữ liệu là
int.
◮ px = &par; → Lấy địa chỉ của biến par gán cho con trỏ px.
◮ (* px) = 10; → Gán 10 vào ô nhớ được chứa trong con trỏ px (tức
là gán giá trị cho par thông qua con trỏ px).
Link: https://onlinegdb.com/
1 #include <stdio.h> BkdMXyrWD
2

3 int main()
4 {
5 int parameter = 100;
6

7 printf("Value of parameter: %d\n", parameter);


8 printf("Value of address of parameter: 0x%x\n", &parameter);
9
2 LẬP TRÌNH C CƠ BẢN 26

10 int *px = &parameter;


11 printf("px is pointer. Value of px: 0x%x\n", px);
12 printf("Value at address in pointer: %d\n", (*px));
13

14 (*px) = 10;
15 printf("New value at address in pointer: %d\n", (*px));
16 printf("Value of parameter: %d\n", parameter);
17

18 return 0;
19 }

Kết quả (lưu ý địa chỉ của biến parameter có thể không giống bên
dưới):

Value of parameter: 100


Value of address of parameter: 0x39d73204
px is pointer. Value of px: 0x39d73204
Value at address in pointer: 100
New value at address in pointer: 10
Value of parameter: 10
MỘT SỐ MODULE NGOẠI VI QUAN
TRỌNG
GENERAL PURPOSE
INPUT/OUTPUT – GPIO 3
3.1 Giới thiệu . . . . . . . . . . . 28
3.1 Giới thiệu 3.2 Một số hàm thường dùng . 29
pinMode() . . . . . . . . . . . 29
General Purpose Input/Output (GPIO) là module dùng để đọc tín digitalWrite() . . . . . . . . . 29
hiệu vào hay xuất tín hiệu ra một hoặc một số chân vi điều khiển. digitalRead() . . . . . . . . . 30
GPIO chỉ làm việc với 2 mức điện áp (điện áp thấp và điện áp cao) 3.3 Một số module mẫu . . . . . 30
Đèn LED . . . . . . . . . . . . 30
tương ứng với mức logic 1 (mức điện áp cao – tùy thuộc vào vi điều
Module Relay . . . . . . . . . 32
khiển: có thể là 1.8V, 3.3V, 5V,...) và mức logic 0 (mức điện áp thấp –
Nút nhấn (button) . . . . . . 33
thường là 0V).
Xuất tín hiệu logic ra ngoài (output) là đưa điện áp mức cao (mức
logic 1) hay điện áp mức thấp (mức logic 0). Đọc tín hiệu điện áp vào
có kết quả là mức logic 0/1 (input) nhằm để xác định xem điện áp tại
chân vi điều khiển tương ứng là mức thấp hay cao.
Một số đặc tính khi chân của vi điều khiển được cấu hình là INPUT:
cấu hình mặc định của các chân Arduino (Atmega) là input, vì thế
chúng ta không cần khai báo khi hàm pinMode() khi sử dụng chúng
như một chân input. Chân vi điều khiển được cấu hình bằng cách
này sẽ ở trạng thái trở kháng cao (high-impedance state) – khoảng
100MΩ.
Thêm trở kéo lên/xuống cho một chân của vi điều khiển khi được
cấu hình là INPUT: một điều như không bắt buộc nhưng được xem
là thông lệ (nên được xem là bắt buộc) là phải xác định trạng thái của
chân (được cấu hình là input) nếu không có tín hiệu input từ ngoài
vào. Việc này được thực hiện bằng cách thêm một điện trở kéo lên (lên
mức điện áp cao - +5V đối với Arduino Atmega) hoặc điện trở kéo
xuống (xuống đất – 0V). Giá trị điện trở này thường được dùng là 10
kΩ.
Đặc tính của chân của vi điều khiển khi được cấu hình là INPUT_-
PULLUP: bên trong vi điều khiển Atmega đã có một điện trở 20K
được kéo lên nguồn có thể truy cập bằng phần mềm. Để truy cập được
điện trở này thì ta khai báo pinMode() với biến là INPUT_PULLUP.
Đặc tính của chân của vi điều khiển khi được cấu hình là OUTPUT:
khi được cấu hình là OUTPUT thì chân này ở trạng thái trở kháng
thấp (low-impedance state). Điều này có nghĩa là vi điều khiển có
thể cung cấp một lượng dòng điện lớn (vài milliampere đến vài chục
milliampere) ra ngoài. Các chân của vi điều khiển Atmega có thể dùng
mạch kiểu source (cung cấp dòng điện dương) hoặc kiểu sink (cung
cấp dòng điện âm) lên đến 40mA. Với lượng dòng điện này có thể đủ
làm sáng LED (phải mắc nối tiếp điện trở để hạn dòng) hoặc một vài
cảm biến nhưng không đủ dòng để bật tắt relay (rờ-le), motor.
Lưu ý: việc ngắn mạch hoặc để các thiết bị tiêu thụ dòng cao trên chân
của Arduino có thể hỏng một chân hoặc cả con vi điều khiển. Vì vậy,
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 29

khi kết nối bất cứ thiết bị nào vào vi điều khiển phải xem xét đến dòng
điện tiêu thụ.
Ví dụ: dòng điện tiêu thụ của đèn LED khoảng 20mA, dòng tối đa mà
một chân vi điều khiển chịu được dòng điện tối đa là 40mA.Một đèn
LED có thể nối trực tiếp với chân vi điều khiển, nhưng không nên mắc
song song từ 2 LED trở lên vì sẽ gây hại đến vi điều khiển (có thể chết
chân hoặc chết cả vi điều khiển). Giải pháp là hãy dùng mạch đệm
dòng hoặc dùng BJT/FET để điều khiển.

3.2 Một số hàm thường dùng

pinMode()

Chức năng: dùng để cấu hình một chân là input hay output.
Cú pháp:

pinMode(pin, mode);

◮ pin: chân ta muốn cấu hình


◮ mode: INPUT, OUTPUT hoặc INPUT_PULLUP

Kết quả trả về: không.


Ví dụ: thiết lập chân 13 là ouput.

pinMode(13, OUTPUT); // sets the digital pin 13 as output

Lưu ý: các chân analog vẫn có thể sử dụng như digital (input/out-
put).

digitalWrite()

Chức năng: ghi mức logic HIGH (cao) hoặc LOW (thấp) ra chân được
chỉ định.
Cú pháp:

digitalWrite(pin, value);

◮ pin: chân ta muốn ghi mức giá trị.


◮ value: HIGH hoặc LOW.

Kết quả trả về: không.


Lưu ý:
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 30

◮ Nếu không thiết lập pinMode() là OUTPUT và kết nối đèn LED
vào pin này, khi gọi hàm digitalWrite(HIGH) thì đèn LED có thể sẽ
sáng mờ. Nguyên nhân là ta không thiết lập pinMode là OUTPUT
thì mặc định là INPUT và gọi hàm digitalWrite(HIGH) sẽ bật điện
trở nội kéo lên nên sẽ xem như đèn LED mắc nối tiếp với trở hạn
dòng có giá trị lớn.
◮ Các chân analog vẫn có thể sử dụng như chân digital (ngoại trừ
chân A6, A7 của board Arduino Mini, Arduino Nano, Pro Mini).

digitalRead()

Chức năng: đọc giá trị logic từ chân được chỉ định.
Cú pháp:

digitalRead(pin);

◮ pin: chân mà ta muốn đọc

Giá trị trả về: HIGH hoặc LOW.


Lưu ý:
◮ Nếu chân được đọc tín hiệu mà không được nối vào đâu thì giá
trị trả về có thể là HIGH hoặc LOW (và có thể thay đổi đột ngột).
◮ Các chân analog vẫn có thể sử dụng như chân digital (ngoại trừ
chân A6, A7 của board Arduino Mini, Arduino Nano, Pro Mini).

3.3 Một số module mẫu

Đèn LED

Chức năng: thường dùng để làm đèn tín hiệu.


Cách lập trình: ta sẽ xem xét hai mạch sau để cùng bật một đèn LED.

Link: https://www.tinkercad.com/
things/3u2Pp26Xwzx

Hình 3.1: Điều khiển LED (tích cực cao)


3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 31

◮ Theo như hình trên, đèn LED sáng khi điện áp xuất ra từ Arduino
là điện áp cao.

1 void setup()
2 {
3 // Set pin 13 as OUTPUT
4 pinMode(13, OUTPUT);
5 }
6

7 void loop()
8 {
9 // Turn the LED on (HIGH is the voltage level)
10 digitalWrite(13, HIGH);
11 }

Link: https://www.tinkercad.com/
things/74dtpbF6plb

Hình 3.2: Điều khiển LED (tích cực thấp)

◮ Theo như hình trên, đèn LED sáng được khi điện áp xuất ra từ
Arduino là điện áp thấp.

1 void setup()
2 {
3 // Set pin 13 as OUTPUT
4 pinMode(13, OUTPUT);
5 }
6

7 void loop()
8 {
9 // Turn the LED on (HIGH is the voltage level)
10 digitalWrite(13, LOW);
11 }

◮ Để bật/tắt một thiết bị, chúng ta cần biết được đặc tính của thiết
bị đó là để hoạt động cần mức điện áp thấp (mức logic 0) hay
điện áp cao (mức logic 1).

Thông tin thêm: Tại sao điện trở nối tiếp LED trong hai ví dụ trên là
220Ω?
◮ Yêu cầu là cần bật một LED màu đỏ. Chúng ta cần xem các thông
số kỹ thuật liên quan đến LED đỏ là điện áp hoạt động và dòng
điện định mức. Sau quá trình tìm kiếm thì có được thông số như
sau: 𝑉𝐷 = 2V, I = 20mA.
◮ Dòng điện tối đa một chân của Arduino là 40 mA, mà dòng định
mức của LED là 20 mA thì một chân Arduino có thể tải được
trực tiếp một LED. Điện áp đang sử dụng là 5V mà điện áp 𝑉𝐷
của LED đỏ là 2V nên cần thêm một con trở mắc nối tiếp.
◮ Điện áp giữa hai đầu điện trở là 𝑈 𝑅 = 5V – 𝑉𝐷 = 5V – 2V = 3V.
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 32

◮ Dòng điện qua điện trở bằng dòng điện qua LED (do mắc nối
tiếp): 𝐼 𝑅 = 𝐼 𝐷 . Do đây chỉ là đèn LED tín hiệu nên không cần quá
sáng, vì vậy chúng ta có thể giảm dòng điện lại để tiết kiệm điện
năng hơn. Thông thường thì để đèn LED ở khoảng 10 – 15 mA.
Trong ví dụ này chọn 15 mA.
◮ Giá trị điện trở: R = 𝑈 𝑅 / 𝐼 𝑅 = 5V / 15mA = 200Ω.
◮ Vì trên thị trường điện trở 200Ω không thông dụng, mà điện trở
220Ω lại thông dụng hơn nên chọn giá trị điện trở 220Ω.

Module Relay

Chức năng: dùng để bật tắt các thiết bị có công suất lớn/điện áp
lớn/dòng điện lớn.
Sơ đồ chân:
◮ Hiện nay, trên thị trường có rất nhiều loại module relay khác
nhau với sơ đồ nguyên lý khác nhau (cách ly hoặc không cách
ly/tích cực mức thấp hoặc cao), điện áp hoạt động khác nhau
(5V, 12V hoặc 24V) nên khi sử dụng ta cần chú ý đọc kỹ hướng
dẫn trước khi sử dụng.
◮ Thông thường, module sẽ có 2 domino (hoặc 1 header và 1
domino) để điều khiển module. Một domino để cấp nguồn (sẽ
có 2 chân DC+ và DC- hoặc GND và VCC) và tín hiệu điều khiển
bật tắt relay (IN). Domino còn lại dùng để điều khiển các thiết
bị công suất với 3 ngõ COM (cổng chung), NO (normal open
– thường mở) và NC (normal close – thường đóng). Nếu relay
được kích (hoạt động) thì COM sẽ nối với NO, ngược lại nếu
relay không được kích thì COM sẽ nối với NC.

Hình 3.3: Cách kết nối với Arduino

Mô phỏng Link: https://www.tinkercad.com/


things/lc8CaKsOHyI
Cách lập trình: tương tự như lập trình với LED.

1 void setup()
2 {
3 pinMode(13, OUTPUT);
4 }
5

6 void loop()
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 33

Hình 3.4: Cách kết nối với Arduino (mô


phỏng)

7 {
8 digitalWrite(13, HIGH);
9 delay(1000); // Wait for 1000 millisecond(s)
10 digitalWrite(13, LOW);
11 delay(1000); // Wait for 1000 millisecond(s)
12 }

Nút nhấn (button)

Chức năng: dùng để chọn chế độ hoạt động, nhận một tín hiệu điều
khiển từ người dùng mà vi điều khiển có thể hiểu được.
Sơ đồ chân: nút nhấn được chia thành hai loại cơ bản là push button
và switch.
◮ Push button: sau khi tác động lực thì nút nhấn quay về trạng thái
ban đầu, trạng thái đóng/mở có thể được giữ hoặc không giữ.

Hình 3.5: Nút nhấn push-button. Nguồn


từ Wikimedia và Wikimedia.

◮ Switch: sau khi tác động lực để thay đổi trạng thái thì nút nhấn
không quay về trạng thái ban đầu, trạng thái của nút nhấn được
giữ khi ngừng tác dụng lực

Hình 3.6: DIP switch. Nguồn từ Wiki-


media.
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 34

Hình 3.7: Nút nhấn tích cực thấp

Hình 3.8: Sơ đồ mạch điện của nút nhấn


(tích cực thấp)

Cách lập trình: chúng ta cùng xem xét hai mạch đọc nút nhấn khác
nhau.
Link: https://www.tinkercad.com/
Theo sơ đồ nguyên lý như hình 3.8: things/3IN5fVNqBmI

◮ Xét trạng thái tại chân D2, giả sử chân D2 chỉ nhận mức tín hiệu
điện áp và không có dòng điện đi vào chân D2.
◮ Nếu S1 không được nhấn thì điện áp tại chân D2 là 5V. 𝐼 𝑅1 = 0𝐴
vì đã giả sử không có dòng điện vào chân D2.

𝑈𝐷2 = 5𝑉 − 𝐼 𝑅1 𝑅1 = 5𝑉 − 0( 𝐴).10(𝑘Ω) = 5𝑉

◮ Ngược lại, nếu S1 được nhấn thì chân D2 được nối với 0V nên
điện áp đọc vào là 0V.

Trạng thái nút nhấn Giá trị điện áp Mức logic


Không nhấn 5V HIGH
Nhấn 0V LOW

1 int buttonState = 0;
2

3 void setup()
4 {
5 // Set pin 2 as INPUT
6 pinMode(2, INPUT);
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 35

7 // Set pin 13 as OUTPUT


8 pinMode(13, OUTPUT);
9 }
10

11 void loop()
12 {
13 // Read the state of the pushbutton value
14 buttonState = digitalRead(2);
15 // Check if pushbutton is pressed. if it is, the
16 // buttonState is LOW
17 if (buttonState == LOW) {
18 // Turn LED on
19 digitalWrite(13, HIGH);
20 } else {
21 // Turn LED off
22 digitalWrite(13, LOW);
23 }
24 delay(10); // Delay a little bit to improve simulation performance
25 }

Hình 3.9: Nút nhấn tích cực ca

Link: https://www.tinkercad.com/
Ta xét mạch điện thứ 2 như hình 3.10, ta có sơ đồ nguyên lý như sau: things/9tb62dzleah
◮ Tương tự, ta cũng xét trạng thái tại chân D2.
◮ Nếu S1 không được nhấn thì điện áp tại chân D2 là 0V. 𝐼 𝑅1 =0A
vì đã giả sử không có dòng điện vào chân D2.

𝑈𝐷2 = 𝐼 𝑅1 𝑅1 = 0( 𝐴).10(𝑘Ω) = 0𝑉

◮ Ngược lại, nếu S1 được nhấn thì chân D2 được nối với 5V nên
điện áp đọc vào là 5V.

Trạng thái nút nhấn Giá trị điện áp Mức logic


Không nhấn 0V LOW
Nhấn 5V HIGH

1 int buttonState = 0;
2

3 void setup()
4 {
5 // Set pin 2 as INPUT
6 pinMode(2, INPUT);
7 // Set pin 13 as OUTPUT
8 pinMode(13, OUTPUT);
9 }
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 36

Hình 3.10: Sơ đồ mạch điện của nút


nhấn (tích cực cao)

10

11 void loop()
12 {
13 // Read the state of the pushbutton value
14 buttonState = digitalRead(2);
15 // Check if pushbutton is pressed. if it is, the
16 // buttonState is HIGH
17 if (buttonState == HIGH) {
18 // Turn LED on
19 digitalWrite(13, HIGH);
20 } else {
21 // Turn LED off
22 digitalWrite(13, LOW);
23 }
24 delay(10); // Delay a little bit to improve simulation performance
25 }
TIME 4
4.1 Giới thiệu . . . . . . . . . . . 37
4.1 Giới thiệu 4.2 Một số hàm thường dùng . 37
delay() . . . . . . . . . . . . . 37
Time (thời gian) trong lập trình vi điều khiển khá quan trọng vì giúp delayMicroseconds() . . . . 38
cho vi điều khiển nhận biết thời gian cần phải thực hiện/thời gian đã millis() . . . . . . . . . . . . . 38
thực hiện một nhiệm vụ do người lập trình đưa ra. micros() . . . . . . . . . . . . 39
4.3 Ghi chú . . . . . . . . . . . . 39

4.2 Một số hàm thường dùng

delay()

Chức năng: dùng để dừng chương trình trong khoảng thời gian xác
định (đơn vị: milliseconds).
Cú pháp:

delay(ms);

◮ ms: số milliseconds cần dừng chương trình (kiểu dữ liệu: un-


signed long
Kết quả trả về: không.
Ví dụ:

1 delay(1000); // wait for a second

Lưu ý: trong thời gian thực hiện hàm delay thì vi điều khiển không
thực hiện bất cứ chương trình nào khác như đọc giá trị cảm biến, tính
toán hay thao tác trên các chân. Tuy nhiên, hàm delay sẽ không vô
hiệu các ngắt (interrupt) nên các giao tiếp bằng phần cứng vẫn hoạt
động (đường nhận dữ liệu), phát xung PWM hay trạng thái các Pin
không thay đổi. Vì vậy, chúng ta nên thật sự lưu ý khi dùng các hàm
delay. Ngoài ra, hàm delay cũng được dùng như một cách chống rung
phím bằng phần mềm.
4 TIME 38

delayMicroseconds()

Chức năng: dùng để dừng chương trình trong khoảng thời gian xác
định (đơn vị: microseconds).
Cú pháp:

delayMicroseconds(us);

◮ us: số microseconds cần dừng chương trình (kiểu dữ liệu: un-


signed long)
Kết quả trả về: không.
Ví dụ:

1 delayMicroseconds(1000); // wait for a millisecond

Lưu ý: hàm này làm việc chính xác khi us > 3 microsenconds.

millis()

Chức năng: trả về số milliseconds kể từ khi bắt đầu hoạt động. Giá trị
này sẽ bị tràn và quay về 0 khi đạt giá trị tối đa (khoảng 50 ngày).
Cú pháp:

time = millis();

Kết quả trả về: số milliseconds (kiểu dữ liệu: unsigned long).


Ví dụ:

1 unsigned long time;


2

3 void loop() {
4 time = millis();
5 delay(1000); // wait a second
6 }

Lưu ý: kiểu dữ liệu là unsigned long nên khi ta thực hiện phép toán thì
phải đảm bảo chung kiểu dữ liệu để không gây ra lỗi sai về giá trị tối
đa của các kiểu dữ liệu.
4 TIME 39

micros()

Chức năng: trả về số microseconds kể từ khi bắt đầu hoạt động. Giá
trị này sẽ bị tràn và quay về 0 khi đạt giá trị tối đa (khoảng 70 phút).
Cú pháp:

time = micros();

Kết quả trả về: số microseconds (kiểu dữ liệu: unsigned long).


Ví dụ:

1 unsigned long time;


2

3 void loop() {
4 time = micros();
5 delay(1000); // wait a second
6 }

4.3 Ghi chú

Khi sử dụng các hàm delay thì ta cần phải cân nhắc có nên sử dụng
hay không vì nó sẽ làm ảnh hưởng đến tốc độ chương trình. Ta cũng
có thể sử dụng các phương pháp khác để thay thế các hàm delay như
tính toán thời gian dựa vào việc lấy các hàm giá trị thời gian thực.
UART 5
5.1 Giới thiệu . . . . . . . . . . . 40
5.1 Giới thiệu 5.2 Một số hàm thường dùng . 40
Serial.begin() . . . . . . . . . 40
UART là một chuẩn giao tiếp nối tiếp để giao tiếp giữa các thiết bị với Serial.end() . . . . . . . . . . 41
nhau. (Lý thuyết về UART sẽ được giải thích ở quyển 2) Serial.print()/Serial.println() 41
Serial.write() . . . . . . . . . 42
Trong Arduino, UART được dùng để giao tiếp giữa board Arduino với Serial.availableForWrite() . 43
các module khác có cùng giao thức hoặc giữa board Arduino với các Serial.available() . . . . . . . 43
board vi điều khiển khác. Serial.read() . . . . . . . . . . 44
Một số hàm khác . . . . . . . 44
Giao thức UART gồm 2 dây tín hiệu là TX và RX. Khi 2 board muốn Lưu ý . . . . . . . . . . . . . . 45
giao tiếp với nhau thì phải mắc chéo (nghĩa là TX board này sẽ nối với 5.3 Một số module mẫu . . . . . 45
RX board kia, RX board này nối với TX board kia). Module bluetooth HC-05 . . 45
Giao tiếp giữa 2 Arduino . . 47
Một thông số quan trọng là tốc độ truyền (baudrate), các thông số này
5.4 Lời kết . . . . . . . . . . . . . 49
là những số được quy định trước 1200, 2400, 4800, 9600, 19200, 38400,
57600, 115200,...
Một số thông số liên quan đến giao tiếp UART: baudrate, số stop
bit (1/2), số bytes dữ liệu (5/6/7/8(/9)), kiểm tra lỗi parity (odd-
/even/none).

5.2 Một số hàm thường dùng

Serial.begin()

Chức năng: thiết lập baudrate (và các thông số khác) cho giao tiếp nối
tiếp. Đây là thiết lập baudrate cho module phần cứng (chân 0 và 1 trên
vi điều khiển). Thông số khác như số lượng data, parity và stop bits
(giá trị mặc định là 8 data bits, no parity và 1 stop bit).
Cú pháp:

Serial.begin(speed);
Serial.begin(speed, config);

◮ Serial: đối tượng cổng nối tiếp (serial port)


◮ speed: baudrate (bits per second – kiểu dữ liệu: long)
◮ config: SERIAL_5N1, SERIAL_6N1, SERIAL_7N1, SERIAL_8N1
(giá trị mặc định), SERIAL_5N2, SERIAL_6N2, SERIAL_7N2, SE-
RIAL_8N2, SERIAL_5E1, SERIAL_6E1, SERIAL_7E1, SERIAL_-
8E1, SERIAL_5E2, SERIAL_6E2, SERIAL_7E2, SERIAL_8E2, SE-
RIAL_5O1, SERIAL_6O1, SERIAL_7O1, SERIAL_8O1, SERIAL_-
5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2
5 UART 41

Kết quả trả về: không.


Ví dụ:

void setup() {
// opens serial port, sets data rate to 9600 bps
Serial.begin(9600);
}

void loop() {}

Serial.end()

Chức năng: ngừng giao tiếp nối tiếp.


Cú pháp:

Serial.end();

Kết quả trả về: không.

Serial.print()/Serial.println()

Chức năng: in dữ liệu ra thông qua cổng nối tiếp với các ký tự nhìn
thấy được (human-readble ASCII text). Với lệnh Serial.println() thì sau
dữ liệu in ra thì có kèm theo 2 ký tự: ký tự trả về đầu dòng (a carriage
return character - ASCII 13 – ’\r’) và ký tự xuống hàng (a newline
character – ASCII 10 – ’\n’).
Cú pháp:

Serial.print(val);
Serial.print(val, format);

◮ Serial: đối tượng cổng nối tiếp


◮ val: giá trị cần in ra (với bất kỳ kiểu dữ liệu nào)
◮ format: định dạng dữ liệu của val (xem ví dụ)

Kết quả trả về: số bytes dữ liệu được ghi ra cổng nối tiếp (kiểu dữ liệu:
size_t).
Ví dụ: Link: https://www.tinkercad.com/
things/6L8YcnI8993
5 UART 42

1 void setup() {
2 Serial.begin(9600); // open the serial port at 9600 bps:
3 }
4

5 void loop() {
6 Serial.println("--------------------------");
7 Serial.print(78); // gives "78"
8 Serial.println(); // enter new line
9 Serial.print(1.23456); // gives "1.23"
10 Serial.println(); // enter new line
11 Serial.print('N'); // gives "N"
12 Serial.println(); // enter new line
13 Serial.print("Hello world."); // gives "Hello world."
14 Serial.println(); // enter new line
15

16 Serial.println(78, BIN); // gives "1001110"


17 Serial.println(78, OCT); // gives "116"
18 Serial.println(78, DEC); // gives "78"
19 Serial.println(78, HEX); // gives "4E"
20 Serial.println(1.23456, 0); // gives "1"
21 Serial.println(1.23456, 2); // gives "1.23"
22 Serial.println(1.23456, 4); // gives "1.2346"
23 delay (1000); // wait a sencond
24 }

Serial.write()

Chức năng: ghi dữ liệu nhị phân (binary data) thông qua cổng nối
tiếp. Dữ liệu này được gửi dạng byte hoặc chuỗi các bytes.
Cú pháp:

Serial.write(val);
Serial.write(str);
Serial.write(buf, len);

◮ Serial: đối tượng cổng nối tiếp


◮ val: giá trị cần gửi đi – 1 byte
◮ str: chuỗi các ký tự gửi đi – chuỗi các bytes
◮ buf : mảng các byte cần gửi
◮ len: chiều dài chuỗi dữ liệu cần gửi
Kết quả trả về: số bytes dữ liệu được ghi ra cổng nối tiếp (kiểu dữ liệu:
size_t).
Ví dụ:

1 void setup() {
2 Serial.begin(9600); // open the serial port at 9600 bps:
3 }
4

5 void loop() {
5 UART 43

6 Serial.write(45); // send a byte with the value 45


7

8 int bytesSent = Serial.write(“ hello” ); /* Send the string “hello” and return the length of the string.*/
9 delay (1000); // wait a sencond
10 }

Lưu ý: nếu bộ đệm (buffer) đủ khoảng trống để truyền thì hàm Se-
rial.write() sẽ trả về trước khi tất cả các ký tự được gửi, các ký tự gửi
đi sẽ được lưu trữ trong bộ đệm. Nếu độ đệm truyền bị đầy thì hàm
này sẽ khóa (block) mãi cho đến khi đủ khoảng trống cho việc truyền.
Để tránh trường hợp bị khóa (block) thì nên kiểm tra trước số byte còn
trống trong bộ đệm bằng hàm Serial.availableForWrite().

Serial.availableForWrite()

Chức năng: số bytes còn lại trong bộ đệm truyền.


Cú pháp:

Serial.availableForWrite();

◮ Serial: đối tượng cổng nối tiếp

Kết quả trả về: số bytes có sẵn để ghi.

Serial.available()

Chức năng: số bytes có sẵn trong bộ đệm nhận (bộ đệm tối đa là 64
bytes).
Cú pháp:

Serial.available();

◮ Serial: đối tượng cổng nối tiếp

Kết quả trả về: số bytes có sẵn để đọc.


Ví dụ:

1 void setup() {
2 Serial.begin(9600); // open the serial port at 9600 bps:
3 }
4

5 void loop() {
6 // reply only when you receive data:
7 if (Serial.available() > 0) {
8 // Read data at here. Please see next example.
9 }
10 }
5 UART 44

Serial.read()

Chức năng: đọc một byte dữ liệu đến.


Cú pháp:

Serial. read();

◮ Serial: đối tượng cổng nối tiếp

Kết quả trả về: ký tự đầu tiên trong chuỗi dữ liệu nhận được (kiểu dữ
liệu: int).
Ví dụ:

1 int incomingByte = 0; // for incoming serial data


2

3 void setup() {
4 // opens serial port, sets data rate to 9600 bps
5 Serial.begin(9600);
6 }
7

8 void loop() {
9 // send data only when you receive data:
10 if (Serial.available() > 0) {
11 // read the incoming byte:
12 incomingByte = Serial.read();
13

14 // say what you got:


15 Serial.print("I received: ");
16 Serial.println(incomingByte, DEC);
17 }
18 }

Một số hàm khác

Ngoài các hàm trên, Serial vẫn còn nhiều hàm khác sử dụng cho những
trường hợp khác

◮ if(Serial): kiểm tra cổng nối tiếp có sẵn sàng chưa.


◮ Serial.find(): tìm xem trong bộ đệm có ký tự muốn tìm hay không
bằng cách đọc dữ liệu trong bộ đệm đến khi tìm được ký tự
mong muốn.
◮ Serial.findUntil(): tìm chuỗi ký tự cần tìm có tồn tại trong bộ đệm
hay không bằng cách đọc dữ liệu trong bộ đệm đến khi tìm được
chuỗi mục tiêu hoặc chuỗi để kết thúc sự tìm kiếm.
◮ Serial.flush(): đợi cho việc truyền hoàn tất cho tất cả các ký tự
trong bộ đệm.
5 UART 45

◮ Serial.parseFloat(): trả về giá trị có kiểu dấu chấm động phù hợp
đầu tiên trong bộ đệm. Các ký tự không phải số hoặc dấu trừ sẽ
bị bỏ qua. Hàm này sẽ chấm dứt khi gặp ký tự đầu tiên không
phải dấu chấm động hoặc hết thời gian (thời gian này được thiết
lập bởi hàm Serial.setTimeout()).
◮ Serial.parseInt(): tương tự như hàm Serial.parseFloat() nhưng giá
trị cần đọc là kiểu int.
◮ Serial.peek(): trả về ký tự tiếp theo trong bộ đệm mà không có xóa
nó đi ra khỏi bộ đệm.
◮ Serial.readBytes(): đọc về số byte dữ liệu được xác định trước.
◮ Serial.readBytesUntil(): đọc về số byte dữ liệu được xác định trước
và phát hiện ký tự kết thúc hoặc hết thời gian định trước.
◮ Serial.readString(): đọc dữ liệu trong bộ đệm dưới kiểu dữ liệu là
String.
◮ Serial.readStringUntil(): đọc dữ liệu trong bộ đệm dưới kiểu dữ
liệu là String cho đến khi ký tự kết thúc được phát hiện hoặc hết
thời gian định trước.
◮ Serial.setTimeout(): thiết lập thời gian tối đa cho việc đợi dữ liệu
từ cổng nối tiếp. Giá trị mặc định là 1 giây.
◮ serialEvent(): hàm này được gọi khi có dữ liệu đến

Để biết thêm thông tin chi tiết, vui lòng truy cập vào link này.

Lưu ý

Khi lập trình C/C++, chúng ta cần phân biệt ký tự ‘a’ và chuỗi “a”.
Trường hợp ‘a’ (dấu nháy đơn) thì đây là ký tự a có kích thước một
byte. Trường hợp “a” (dấu nháy kép) thì đây là chuỗi ký tự có kích
thước hai bytes vì cuối chuỗi phải có chứa 1 byte kết thúc chuỗi ‘\0’.
Khi bạn nhập ‘ab’, compiler có thể ngầm hiểu đây là chuỗi mà không
báo lỗi, nếu compiler không hỗ trợ thì sẽ thông báo lỗi.

◮ ’\r’ (CR – Cariage Return): di chuyển con trỏ về đầu hàng mà


không xuống dòng.
◮ ’\n’ (LF – Line Feed): di chuyển con trỏ xuống hàng tiếp theo.

5.3 Một số module mẫu

Module bluetooth HC-05

Hình 5.1: Module bluetooth HC-05.


Nguồn từ Eprolabs
5 UART 46

Chức năng: là module chuyển đổi chuẩn giao tiếp nối tiếp sang blue-
tooth . Khi máy tính kết nối module này thì máy tính nhận là một cổng Giao tiếp bằng bluetooth là giao tiếp tầm
COM ảo. gần (dưới 10m).

Sơ đồ chân:

Hình 5.2: Kết nối module bluetooth HC-


05 với Arduino.

◮ Kết nối như hình 5.2, ta có thể không cần thiết phải nối chân
KEY với 3.3V.
◮ Thông số mặc định của bluetooth là HC-05, mật khẩu kết nối là
1234 và baudrate mặc định là 9600.
◮ Chúng ta có thể thay đổi cấu hình của module bluetooh bằng
chế độ AT command, baudrate cho chế độ này là 38400. Bạn có
thể tìm kiếm trên mạng bằng từ khóa “hc05 at commands”.
◮ Lưu ý, TX và RX phải được mắc chéo.

Cách lập trình:

1 int incomingByte = 0; // for incoming serial data


2

3 void setup() {
4 Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
5 }
6

7 void loop() {
8 Serial.println("minht57 lab");
9 // send data only when you receive data:
10 if (Serial.available() > 0) {
11 // read the incoming byte:
12 incomingByte = Serial.read();
13

14 // say what you got:


15 Serial.print("I received: ");
16 Serial.println(incomingByte, DEC);
17 }
18 delay(1000);
19 }

Lưu ý: đây là chuẩn bluetooth 2.0 nên chỉ giao tiếp 1 – 1 (tức là một
master và 1 slave giao tiếp với nhau). Nếu bạn muốn tìm hiểu về nhiều
module giao tiếp với nhau có thể tham khảo với từ khóa “module
bluetooth 4.0” hoặc “module bluetooth BLE”.
Thông tin thêm:
5 UART 47

◮ Để hai thiết bị có thể giao tiếp được với nhau thì cần phải có dây
kết nối giữa chúng. Trong trường hợp cần sự linh hoạt và không
cần dây kết nối rờm rà thì cần phải có một giao thức không
dây để truyền tải dữ liệu như bluetooth, wifi,. . . Vì việc kết nối
với phần cứng để truyền nhận tín hiệu không dây thường phức
tạp, trải qua nhiều bước cấu hình nên các nhà sản xuất đã chế
tạo các module bluetooth/wifi và giao tiếp với các module này
thông qua các chuẩn giao tiếp phổ biến trên vi điều khiển như
UART/SPI.
◮ Giao tiếp với các module này thường thông qua tập lệnh "AT
command". AT (ATtention) command là bộ lệnh hướng dẫn khi
sử dụng để giao tiếp với module. Ví dụ khi làm việc với module
HC-05, chúng ta muốn thay đổi tên bluetooth thì công việc cần
làm là: vào chế độ AT command → gửi chuỗi "AT+NAME=AHIHI_-
DONGOC".

Giao tiếp giữa 2 Arduino

Ví dụ 1: board Arduino 1 sẽ gửi một chuỗi ký tự sang board Arduino Link: https://www.tinkercad.com/
2. Khi board Arduino 2 nhận được sẽ in ra ngoài. things/kSSCq6eIkV7

Hình 5.3: Giao tiếp nối tiếp giữa 2 board


Arduino

Chương trình Arduino 1

1 void setup() {
2 Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
3 }
4

5 void loop() {
6 Serial.println("minht57 lab, Vietnam");
7 delay(1000);
8 }

Chương trình Arduino 2


5 UART 48

1 char incomingByte = 0; // for incoming serial data


2

3 void setup() {
4 Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
5 }
6

7 void loop() {
8 // send data only when you receive data:
9 while (Serial.available() > 0) {
10 // read the incoming byte:
11 incomingByte = Serial.read();
12 Serial.print(incomingByte);
13 }
14 }

Ví dụ 2: board Arduino 1 sẽ đọc tín hiệu nút nhấn và gửi trạng thái Link: https://www.tinkercad.com/
của nút nhấn sang board Arduino 2. Board Arduino 2 sẽ bật/tắt đèn things/lxCfz5KzuRK
LED theo trạng thái nút nhấn nhận được từ board Arduino 1.

Hình 5.4: Giao tiếp nối tiếp giữa 2 Ar-


duino

Chương trình Arduino 1

1 int buttonState = 0;
2

3 void setup() {
4 // Set pin 2 as INPUT
5 pinMode(2, INPUT);
6 // Open serial port, sets data rate to 9600 bps
7 Serial.begin(9600);
8 }
9

10 void loop() {
11 // Read the state of the pushbutton value
12 buttonState = digitalRead(2);
13 // Check if pushbutton is pressed. If it is, the buttonState is HIGH
14 if (buttonState == HIGH) {
15 Serial.println('1');
5 UART 49

16 }
17 else
18 {
19 Serial.println('2');
20 }
21

22 delay(10); // Delay a little bit to improve simulation performance


23 }

Chương trình Arduino 2

1 char string;
2

3 void setup() {
4 // Set pin 13 as OUTPUT
5 pinMode(13, OUTPUT);
6 // Opens serial port, sets data rate to 9600 bps
7 Serial.begin(9600);
8 }
9

10 void loop() {
11 /*
12 // send data only when you receive data:
13 while (Serial.available() > 0) {
14 // read the incoming byte:
15 incomingByte = Serial.read();
16

17 }*/
18 string = Serial.read();
19 if (string == '1') {
20 // Turn LED on
21 digitalWrite(13, HIGH);
22 }
23 else if (string == '2') {
24 // Turn LED off
25 digitalWrite(13, LOW);
26 }
27 }

5.4 Lời kết

UART (hay giao tiếp nối tiếp) là một chuẩn giao tiếp rất phổ biến và
thông dụng trong lập trình vi điều khiển vì tính đơn giản để sử dụng
truyền dữ liệu nhỏ.
Chúng ta nên phân biệt rằng giao tiếp nối có rất nhiều chuẩn và UART
là một trong số đó nên cẩn thận khi nói các khái niệm:
◮ UART là giao tiếp nối tiếp (đúng).
◮ Giao tiếp nối tiếp chỉ là UART (sai).
5 UART 50

Có một chuẩn giao tiếp cũng khá phổ biến trong công nghiệp là
RS232/RS485, vì mức điện áp hoạt động của 2 chuẩn giao tiếp là khác
nhau nên khi vi điều khiển giao tiếp với các module có chuẩn giao
tiếp này thì chúng ta cần thêm một module chuyển đổi từ UART sang
RS232/RS485.
Lưu ý rằng đối tượng Serial là đối tượng hoạt động với ngoại vi phần
cứng bên trong chip (chân 0 và chân 1) và một số board Arduino chỉ
có 1 module giao tiếp nối tiếp, nếu ứng dụng của chúng ta muốn hoạt
động nhiều module giao tiếp nối tiếp hơn thì có các bạn có thể tham
khảo thư viện “SoftwareSerial”10 . Khi bạn sử dụng thư viện này thì 10: Các chức năng cơ bản của hai thư
không có các chức năng liên quan đến phần cứng như interrupt hay viện Serial và SoftwareSerial là giống
nhau.
event.
ANALOG 6
6.1 Giới thiệu . . . . . . . . . . . 51
6.1 Giới thiệu 6.2 Một số hàm thường dùng . 52
analogRead() . . . . . . . . . 52
Tín hiệu số (digital signal) là tín hiệu mà được thể hiện bằng một chuỗi analogReference() . . . . . . 53
con số rời rạc. Tại bất kỳ thời điểm nào, nó chỉ có một giá trị trong tập analogWrite() . . . . . . . . . 54
giá trị hữu hạn của nó. Các mạch điên tử số thông dụng mà chúng ta 6.3 Một số module mẫu . . . . . 55
Biến trở . . . . . . . . . . . . . 55
thường thấy thì có 2 mức điện áp 0V và VCC. Người ta gọi là tín hiệu
Module điều khiển động cơ
nhị phân (binary signal).
L298N . . . . . . . . . . . . . . . 56
6.4 Lời kết . . . . . . . . . . . . . 62

Hình 6.1: Tín hiệu số (digital). Nguồn từ


Wikimedia.

Tín hiệu tương tự (analog signal) là tín hiệu có giá trị thay đổi liên tục
theo thời gian.

Hình 6.2: Tín hiệu tương tự (analog).


Nguồn từ Wikimedia.

Như chúng ta đã biết, vi điều khiển chỉ có thể xử lý bằng tín hiệu số.
Nhưng các tín hiệu trong môi trường đều là tín hiệu tương tự và vi
điều khiển muốn hiểu giá trị của các tín hiệu này thì phải có module
chuyển đổi từ tín hiệu tương tự sang tín hiệu số. Người ta đã nghiên
cứu ra một số phương pháp chuyển từ tín hiệu tương tự sang tín hiệu
số được gọi chung là ADC (Analo-Digital Converter). Ngược lại khi
muốn chuyển từ tín hiệu số sang tín hiệu tương tự thì có module DAC
(Digital-Analog Converter).
Ngoài hai module ADC và DAC, module điều chế độ rộng xung (PWM
- Pulse Width Modulation) cũng được Arduino hỗ trợ như một tính
năng analog dùng để xuất xung với độ rộng xung thay đổi trong một
chu kỳ xác định trước.
6 ANALOG 52

Hình 6.3: Xung PWM.

𝑇𝑂 𝑁
Điện áp trung bình được tín theo công thức 𝑉 = 𝑇𝐶 𝑦𝑐𝑙𝑒 𝑉𝐶𝐶 . Một số
ứng dụng phổ biến của PWM có thể kể đến như: điều khiền động cơ
(vi trí, tốc độ, moment), điều khiển điện áp (buck/boost/. . . ), thay đổi
độ sáng của đèn,. . .
Lưu ý: phân biệt điện áp trung bình và dạng tín hiệu của xung PWM,
Ví dụ khi PWM với 𝑇𝑂 𝑁 =50% thì điện áp trung bình là 0.5𝑉𝐶𝐶 (khi lấy
VOM đo), nhưng dạng tín hiệu thực tế là dạng xung có 2 mức điện áp
là 0V và 𝑉𝐶𝐶 (dùng oscilloscope).

6.2 Một số hàm thường dùng

analogRead()

Chức năng: đọc giá trị analog từ một chân được chỉ định trước.
Board Arduino có chứa nhiều kênh analog (mỗi nguồn tín hiệu là
một kênh).
◮ Tín hiệu trả về có độ phân giải 10-bit, có nghĩa là vi điều khiển
đọc điện áp từ 0V – VCC thì giá trị trả về từ 0 đến 1023 (210 − 1).
Nếu tín hiệu điện áp nhỏ hơn 0V thì giá trị đọc được vẫn là 0,
tương tự nếu điện áp lớn VCC thì giá trị trả về là 1023.
◮ Giá trị ngưỡng điện áp tham chiếu có thể thay đổi khi sử dụng
hàm analogReference().
◮ Các board Arduino như UNO, Nano, Mini, Mega thì thời gian
đọc tín hiệu analog ngõ vào khoảng 100 us.
6 ANALOG 53

Board Operating voltage Usable pins Max resolution


Uno 5V A0..A5 10 bits
Mini, Nano 5V A0..A7 10 bits
Mega, Mega2560 5V A0..A14 10 bits
Micro 5V A0..A11 10 bits
Leonardo 5V A0..A11 10 bits
Zero 3.3V A0..A5 12 bits
Due 3.3V A0..A11 12 bits

Cú pháp:

analogRead(pin);

◮ pin: chân analog muốn đọc.

Kết quả trả về: giá trị analog đọc được.


Ví dụ:

1 /* potentiometer wiper (middle terminal) connected to analog pin 3 outside


2 leads to ground and +5V */
3 int analogPin = A3;
4 // variable to store the value read
5 int val = 0;
6

7 void setup() {
8 Serial.begin(9600); // setup serial
9 }
10 void loop() {
11 val = analogRead(analogPin); // read the input pin
12 Serial.println(val); // debug value
13 }

Lưu ý: nếu chân analog không được kết nối với bất kỳ nguồn tín hiệu
nào thì giá trị trả về sẽ bị dao động không biết trước.

analogReference()

Chức năng: cấu hình điện áp tham chiếu cho chân analog ngõ vào.
Cú pháp:

analogReference(type);

◮ type: loại tham chiếu được sử dụng.


• Arduino AVR Boards (Uno, Mega, etc.)
* DEFAULT: điện áp 5V nếu dùng board Arduino 5V
hoặc điện áp 3.3V nếu dùng board Arduino 3.3V.
* INTERNAL: điện áp tham chiếu nội, bằng 1.1V trên
chip ATmega168 or ATmega328P và 2.56V trên chip
ATmega8 (không có sẵn trên board Arduino Mega).
6 ANALOG 54

* INTERNAL1V1: điện áp tham chiếu nội 1.1V (chỉ có


trên board Arduino Mega).
* INTERNAL2V56: điện áp tham chiếu nội 2.56V (chỉ có
trên board Arduino Mega).
* EXTERNAL: sử dụng điện áp trên chân AREF để tham
chiếu ( từ 0 đến 5V).
• Arduino SAMD Boards (Zero, etc.)
* AR_DEFAULT: điện áp tham chiếu 3.3V
* AR_INTERNAL: điện áp tham chiếu nội 2.23V
* AR_INTERNAL1V0: điện áp tham chiếu nội 1.0V
* AR_INTERNAL1V65: điện áp tham chiếu nội 1.65V
* AR_INTERNAL2V23: điện áp tham chiếu nội 2.23V
* AR_EXTERNAL: sử dụng điện áp trên chân AREF để
tham chiếu.
• Arduino SAM Boards (Due)
* AR_DEFAULT: giá trị mặc định là 3.3V, đây là điện áp
tham chiếu duy nhất trên board Arduino Due.
Kết quả trả về: không.
Lưu ý:
◮ Sau thay đổi điện áp tham chiếu, vài giá trị trả về đầu tiên sẽ
không chính xác.
◮ Không sử dụng điện áp tham chiếu ngoài (thông qua chân AREF)
với mức điện áp nhỏ hơn 0V hay lớn hơn 5V.
◮ Nếu sử dụng điện áp tham chiếu ngoài, chúng ta cần thiết lập
điện áp tham chiếu là EXTERNAL trước khi gọi analogRead().
Bằng không thì sẽ gây ngắn mạch điện áp tham chiếu nội và điện
áp tham chiếu ngoại gây ra “hư hỏng” vi điều khiển.

analogWrite()

Chức năng: ghi ra dạng sóng PWM đến chân được chỉ định.
◮ Sau khi gọi hàm analogWrite(), chân được chỉ định sẽ tạo ra
sóng PWM với duty cycle được chỉ định mãi tới khi gọi hàm
analogWrite() lần nữa. Tần số PWM ở hầu hết các chân là 490Hz,
riêng chân 5 và 6 trên Uno (hoặc các board tương tự) có tần số
xấp xỉ 980Hz.
Cú pháp:

analogWrite(pin, value);

◮ pin: chân để ghi xung PWM.


◮ value: duty cycle, giá trị từ 0 đến 255.

Kết quả trả về: không.


Ví dụ: thiết lập độ sáng đèn tương ứng với giá trị biến trở.
6 ANALOG 55

1 // LED connected to digital pin 9


2 int ledPin = 9;
3 // potentiometer connected to analog pin 3
4 int analogPin = 3;
5 // variable to store the read value
6 int val = 0;
7

8 void setup() {
9 pinMode(ledPin, OUTPUT); // sets the pin as output
10 }
11

12 void loop() {
13 val = analogRead(analogPin); // read the input pin
14 /* analogRead values go from 0 to 1023, analogWrite values from 0 to 255 */
15 analogWrite(ledPin, val / 4);
16 }

6.3 Một số module mẫu

Biến trở

Hình 6.4: Biến trở xoay. Nguồn từ Wiki-


media và electronics-tutorials.

Sơ đồ chân:
◮ Biến trở có 3 chân (như hình 6.4) với giá trị là R, giá trị điện trở
𝑅12 và 𝑅23 sẽ thay đổi giá trị khi xoay núm vặn.

𝑅12 + 𝑅23 = 𝑅

◮ Nếu ta nối chân 1 và 3 của biến trở vào nguồn điện 5V, ta có
mạch chia áp với giá trị điện áp tại chân số 2 thay đổi từ 0V đến
5V.
Link: https://www.tinkercad.com/
things/8jh2P36bep4

Hình 6.5: Arduino và biến trở


6 ANALOG 56

Cách lập trình:

1 int sensorValue = 0;
2 float voltage = 0;
3

4 void setup()
5 {
6 pinMode(A0, INPUT);
7 Serial.begin(9600);
8 }
9

10 void loop()
11 {
12 // Reads the input on analog pin 0:
13 sensorValue = analogRead(A0);
14 // Prints out the value you read:
15 Serial.println(sensorValue);
16

17 // Calculates voltages
18 voltage = sensorValue * 5.0 / 1023;
19 Serial.print("Volt (V): ");
20 Serial.println(voltage, 2);
21 Serial.println("------------");
22

23 delay(10); // Delay a little bit to improve simulation performance


24 }

Lưu ý:
◮ Biến trở có nhiều hình dạng khác nhau (như hình 6.6) và cách
hoạt động khác nhau nhưng mục đích chung vẫn là thay đổi giá
trị điện trở.
◮ Biến trở có nhiều giá trị điện trở tối đa khác nhau nên tùy vào
ứng dụng mà ta chọn giá trị điện trở phù hợp.

Hình 6.6: Biến trở các loại. Nguồn từ


Wikimedia.

Module điều khiển động cơ L298N 12

Chức năng: 12: Bài viết lấy nguồn từ howtomecha-


tronics
◮ Để điều khiển tốc độ động cơ (chạy nhanh hay chậm) thì chúng
ta cần thay đổi giá trị điện áp cho động cơ và động cơ là một
thiết bị tiêu thụ điện với điện áp cao lẫn dòng điện cao nên việc
vi điều khiển điều khiển trực tiếp động cơ là không thể. Để hỗ
trợ vấn đề này, chúng ta cần một mạch lái (mạch điều khiển –
6 ANALOG 57

driver) động cơ để nhận tín hiệu điều khiển từ vi điều khiển và


cho ngõ ra điện áp tương ứng.
◮ Mạch điều khiển động cơ L298N là một module thông dụng điều
khiển 2 động cơ DC 12V riêng biệt.

Hình 6.7: Module điều khiển động cơ


L298N. Nguồn từ howtomechatronics.

Sơ đồ chân:
◮ Hai domino MotorA và MotorB được nối với động cơ.
◮ Chân 12V-GND để cấp nguồn cho động cơ.
◮ Chân 5V-GND để cấp nguồn cho mạch điều khiển động cơ.
Chúng ta có thể không cấp nguồn 5V thì có thể dùng jumper để
lấy điện áp từ nguồn 12V.

Hình 6.8: Chi tiết các chân module.

◮ Các chân ENA, IN1, IN2 để điều khiển MotorA, tương tự các
chân ENB, IN3, IN4 để điều khiển MotorB.
◮ Các chân INx để điều khiển chiều quay của động cơ, chân ENx
để cấp xung thay đổi tốc độ động cơ.

Hình 6.9: Chiều của dòng điện khi IC


L298N hoạt động.

Cách lập trình:


◮ Dùng biến trở để thay đổi tốc độ động cơ:

1 /* Arduino DC Motor Control - PWM | H-Bridge | L298N - Example 01


2

3 by Dejan Nedelkovski, www.HowToMechatronics.com


4 */
6 ANALOG 58

Hình 6.10: Mạch điện dùng biến trở để


thay đổi tốc độ động cơ.

6 #define enA 9
7 #define in1 6
8 #define in2 7
9 #define button 4
10

11 int rotDirection = 0;
12 int pressed = false;
13

14 void setup() {
15 pinMode(enA, OUTPUT);
16 pinMode(in1, OUTPUT);
17 pinMode(in2, OUTPUT);
18 pinMode(button, INPUT);
19 // Set initial rotation direction
20 digitalWrite(in1, LOW);
21 digitalWrite(in2, HIGH);
22 }
23

24 void loop() {
25 // Read potentiometer value
26 int potValue = analogRead(A0);
27 // Map the potentiometer value from 0 to 255
28 int pwmOutput = map(potValue, 0, 1023, 0 , 255);
29 // Send PWM signal to L298N Enable pin
30 analogWrite(enA, pwmOutput);
31

32 // Read button - Debounce


33 if (digitalRead(button) == true) {
34 pressed = !pressed;
35 }
36 while (digitalRead(button) == true);
37 delay(20);
38

39 // If button is pressed - change rotation direction


40 if (pressed == true & rotDirection == 0) {
41 digitalWrite(in1, HIGH);
42 digitalWrite(in2, LOW);
43 rotDirection = 1;
44 delay(20);
45 }
46 // If button is pressed - change rotation direction
47 if (pressed == false & rotDirection == 1) {
48 digitalWrite(in1, LOW);
49 digitalWrite(in2, HIGH);
50 rotDirection = 0;
51 delay(20);
6 ANALOG 59

52 }
53 }

◮ Dùng joytick để điều khiển xe:

Hình 6.11: Mạch điện dùng joytick để


điều khiển xe

1 /* Arduino DC Motor Control - PWM | H-Bridge | L298N


2 Example 02 - Arduino Robot Car Control
3 by Dejan Nedelkovski, www.HowToMechatronics.com
4 */
5

6 #define enA 9
7 #define in1 4
8 #define in2 5
9 #define enB 10
10 #define in3 6
11 #define in4 7
12

13 int motorSpeedA = 0;
14 int motorSpeedB = 0;
15

16 void setup() {
17 pinMode(enA, OUTPUT);
18 pinMode(enB, OUTPUT);
19 pinMode(in1, OUTPUT);
20 pinMode(in2, OUTPUT);
21 pinMode(in3, OUTPUT);
22 pinMode(in4, OUTPUT);
23 }
24

25 void loop() {
26 int xAxis = analogRead(A0); // Read Joysticks X-axis
27 int yAxis = analogRead(A1); // Read Joysticks Y-axis
28

29 // Y-axis used for forward and backward control


30 if (yAxis < 470) {
31 // Set Motor A backward
32 digitalWrite(in1, HIGH);
33 digitalWrite(in2, LOW);
34 // Set Motor B backward
35 digitalWrite(in3, HIGH);
36 digitalWrite(in4, LOW);
37 // Convert the declining Y-axis readings for going backward from
38 // 470 to 0 into 0 to 255 value for the PWM signal for increasing
39 // the motor speed
6 ANALOG 60

40 motorSpeedA = map(yAxis, 470, 0, 0, 255);


41 motorSpeedB = map(yAxis, 470, 0, 0, 255);
42 }
43 else if (yAxis > 550) {
44 // Set Motor A forward
45 digitalWrite(in1, LOW);
46 digitalWrite(in2, HIGH);
47 // Set Motor B forward
48 digitalWrite(in3, LOW);
49 digitalWrite(in4, HIGH);
50 // Convert the increasing Y-axis readings for going forward from
51 // 550 to 1023 into 0 to 255 value for the PWM signal for increasing
52 // the motor speed
53 motorSpeedA = map(yAxis, 550, 1023, 0, 255);
54 motorSpeedB = map(yAxis, 550, 1023, 0, 255);
55 }
56 // If joystick stays in middle the motors are not moving
57 else {
58 motorSpeedA = 0;
59 motorSpeedB = 0;
60 }
61

62 // X-axis used for left and right control


63 if (xAxis < 470) {
64 // Convert the declining X-axis readings from 470 to 0 into increasing
65 // 0 to 255 value
66 int xMapped = map(xAxis, 470, 0, 0, 255);
67 // Move to left - decrease left motor speed, increase right motor speed
68 motorSpeedA = motorSpeedA - xMapped;
69 motorSpeedB = motorSpeedB + xMapped;
70 // Confine the range from 0 to 255
71 if (motorSpeedA < 0) {
72 motorSpeedA = 0;
73 }
74 if (motorSpeedB > 255) {
75 motorSpeedB = 255;
76 }
77 }
78 if (xAxis > 550) {
79 // Convert the increasing X-axis readings from 550 to 1023 into 0 to
80 // 255 value
81 int xMapped = map(xAxis, 550, 1023, 0, 255);
82 // Move right - decrease right motor speed, increase left motor speed
83 motorSpeedA = motorSpeedA + xMapped;
84 motorSpeedB = motorSpeedB - xMapped;
85 // Confine the range from 0 to 255
86 if (motorSpeedA > 255) {
87 motorSpeedA = 255;
88 }
89 if (motorSpeedB < 0) {
90 motorSpeedB = 0;
91 }
92 }
93 // Prevent buzzing at low speeds (Adjust according to your motors.
94 // My motors couldn't start moving if PWM value was below value of 70)
95 if (motorSpeedA < 70) {
96 motorSpeedA = 0;
97 }
98 if (motorSpeedB < 70) {
99 motorSpeedB = 0;
6 ANALOG 61

100 }
101 analogWrite(enA, motorSpeedA); // Send PWM signal to motor A
102 analogWrite(enB, motorSpeedB); // Send PWM signal to motor B
103 }

Lưu ý:
◮ Nếu cắm jumper thì không cấp nguồn 5V.
◮ Chúng ta nên tách nguồn cho mạch công suất và mạch điều
khiển riêng biệt nhau để tránh những ảnh hưởng không mong
muốn (không cắm jumper).
Mô phỏng:
◮ Điều khiển 2 động cơ dùng cầu H L293D (cơ bản chức năng
giống IC L298N)
Link: https://www.tinkercad.com/
things/lcY0bEVbJJb

Hình 6.12: Điều khiển 2 động cơ dùng


cầu H L293D.

◮ Dùng biến trở điều khiển động cơ dùng cầu H L293D


Link: https://www.tinkercad.com/
things/a7huWRaYEVl

Hình 6.13: Dùng biến trở điều khiển


động cơ dùng cầu H L293D.

◮ Dùng biến trở điều khiển servo


Link: https://www.tinkercad.com/
things/1v07egCxb9v

Hình 6.14: Dùng biến trở điều khiển


servo.

• Thông tin về servo: muốn điều khiển servo thì phải đưa
một xung có tần số 20ms (50Hz) và chu kỳ từ 1 – 2 ms tương
ứng với góc xoay −90◦ đến 90◦ .
6 ANALOG 62

6.4 Lời kết

Tín hiệu analog là tín hiệu rất quan trọng vì nhiều cảm biến sẽ trả về
tín hiệu điện áp để xử lý.
Xung PWM là một tín hiệu điều khiển hiệu quả trong nhiều ứng dụng
cần sự thay đổi điện áp dưới sự điều khiển của vi điều khiển.
I2C 7
7.1 Giới thiệu . . . . . . . . . . . 63
7.1 Giới thiệu 7.2 Một số hàm thường dùng . 63
Wire.begin() . . . . . . . . . . 63
I2C là một chuẩn giao tiếp 2 dây (SDA và SCL) cho phép kết nối Wire.requestFrom() . . . . . 64
nhiều thiết bị với nhau (lên đến 127 thiết bị) trên cùng một bus (đường Wire.beginTransmission() . 64
truyền). Các chân tín hiệu SDA và SCL phải có điện trở kéo lên nguồn Wire.endTransmission() . . 64
Wire.write() . . . . . . . . . . 65
Chuẩn giao tiếp I2C là giao tiếp kiểu chủ/tớ (master/slave). Tất cả các Wire.available() . . . . . . . 65
giao tiếp đều do master khởi tạo. Trên cùng một bus truyền có thể có Wire.read() . . . . . . . . . . 66
nhiều chủ hoặc tớ. Wire.setClock() . . . . . . . . 66
Wire.onReceive() . . . . . . . 67
Các ứng dụng thường dùng khi giao tiếp giữa vi điều khiển và các Wire.onRequest() . . . . . . 67
module thì vi điều khiển luôn đóng vai trò là master. 7.3 Một số module . . . . . . . . 68
Module IMU MPU6050 . . . 68
Thư viện hỗ trợ giao tiếp I2C là Wire (một số tài liệu sẽ ghi là TWI thay
Module thời gian thực RTC 69
cho I2C). Lưu ý, thư viện này sử dụng bộ đệm 32 bytes nên nếu truyền
7.4 Lời kết . . . . . . . . . . . . . 72
vượt quá số bytes này thì những bytes vượt quá sẽ bị bỏ qua.
Các chân hỗ trợ I2C trên một số dòng board Arduino:

Board I2C/TWI pins


Uno A4 (SDA), A5 (SCL)
Mega2560 20 (SDA), 21(SCL)
Leonardo 2 (SDA), 3 (SCL)
Due 20 (SDA), 21 (SCL)

7.2 Một số hàm thường dùng

Wire.begin()

Chức năng: khởi tạo kết nối và tham gia vào mạng I2C. Hàm này
thường được gọi một lần duy nhất.
Cú pháp:

Wire.begin();
Wire.begin(address);

◮ address: 7-bit địa chỉ của slave (tự chọn), nếu không được khai
báo thì tham gia bus với vai trò như master.
Kết quả trả về: không.
7 I2C 64

Wire.requestFrom()

Chức năng: yêu cầu dữ liệu từ slave. Các bytes sau đó có thể được truy
xuất với các hàm available() và read().
Cú pháp:

Wire.requestFrom(address, quantity);
Wire.requestFrom(address, quantity, stop);

◮ address: 7-bit địa chỉ của thiết bị cần yêu cầu.


◮ quantity: số bytes yêu cầu.
◮ stop: true thì sẽ gửi tin nhắn ngừng (stop message) sau yêu cầu
và giải phóng bus. Ngược lại, nếu false thì sẽ tiếp tục gửi tin
nhắn khởi động lại (restart message) sau yêu cầu, trường hợp
này thì vẫn giữ bus ở trạng thái tích cực.
Kết quả trả về: số bytes trả về từ slave.

Wire.beginTransmission()

Chức năng: bắt đầu quá trình truyền dữ liệu đến địa chỉ I2C được chỉ
định. Sau đó, dùng hàm write() để ghi các bytes cần truyền và bắt đầu
truyền bằng hàm endTransmission().
Cú pháp:

Wire.beginTransmission(address);

◮ address: 7-bit địa chỉ của thiết bị cần truyền tới.

Kết quả trả về: không.

Wire.endTransmission()

Chức năng: kết thúc quá trình truyền tải đến địa chỉ thiết bị được thiết
lập trước.
Cú pháp:

Wire.endTransmission();
Wire.endTransmission(stop);

◮ stop: true thì sẽ gửi tin nhắn ngừng (stop message) sau quá trình
truyền và giải phóng bus. Ngược lại, nếu false thì sẽ tiếp tục gửi
tin nhắn khởi động lại (restart message) sau quá trình truyền,
trường hợp này thì vẫn giữ bus ở trạng thái tích cực.
Kết quả trả về:
◮ 0: truyền thành công.
7 I2C 65

◮ 1: dữ liệu quá dài so với bộ đệm.


◮ 2: nhận NACK khi truyền địa chỉ
◮ 3: nhận NACK khi truyền dữ liệu
◮ 4: lỗi khác.

Wire.write()

Chức năng: ghi dữ liệu từ slave đến master hoặc từ master tới slave.
Cú pháp:

Wire.write(value) ;
Wire.write(string) ;
Wire.write(data, length);

◮ value: giá trị của 1 byte cần gửi.


◮ string: chuỗi cần gửi.
◮ data: mảng các dữ liệu.
◮ length: số byte cần gửi.
Kết quả trả về: số bytes đã gửi.
Ví dụ:

1 #include <Wire.h>
2 byte val = 0;
3 void setup()
4 {
5 Wire.begin(); // join i2c bus as a master
6 }
7

8 void loop()
9 {
10 Wire.beginTransmission(44); // transmit to device #44 (0x2c)
11 // device address is specified in datasheet
12 Wire.write(val); // sends value byte
13 Wire.endTransmission(); // stop transmitting
14 val++; // increment value
15 if(val == 64) // if reached 64th position (max)
16 {
17 val = 0; // start over from lowest value
18 }
19 delay(500);
20 }

Wire.available()

Chức năng: trả về số bytes để có thể truy xuất bằng hàm read(). Hàm
này được gọi ở master sau khi gọi hàm requestFrom() hoặc ở slave
trong chương trình xử lý onReceive().
7 I2C 66

Cú pháp:

Wire.available();

Kết quả trả về: số bytes có sẵn để đọc.

Wire.read()

Chức năng: đọc một byte đã được truyền từ slave đến master sau khi
gọi requestFrom() hoặc được truyền từ master đến slave.
Cú pháp:

Wire.read();

Kết quả trả về: byte tiếp theo nhận được.


Ví dụ:

1 #include <Wire.h>
2

3 void setup()
4 {
5 Wire.begin(); // join i2c bus (address optional for master)
6 Serial.begin(9600); // start serial for output
7 }
8

9 void loop()
10 {
11 Wire.requestFrom(2, 6); // request 6 bytes from slave device #2
12

13 while(Wire.available()) // slave may send less than requested


14 {
15 char c = Wire.read(); // receive a byte as character
16 Serial.print(c); // print the character
17 }
18

19 delay(500);
20 }

Wire.setClock()

Chức năng: điều chỉnh tần số clock cho giao tiếp I2C.
Cú pháp:

Wire.setClock(clockFrequency);
7 I2C 67

◮ clockFrequency: giá trị của tần số giao tiếp (đơn vị là Hz). Chú ý
nên xem giới hạn của tất cả phần cứng trong mạng để chọn tần
số hợp lý.
Kết quả trả về: không.

Wire.onReceive()

Chức năng: đăng ký tên một chương trình con để khi có sự kiện nhận
dữ liệu thì chương trình con này sẽ được gọi.
Cú pháp:

Wire.onReceive(handler);

◮ handler: tên chương trình con được gọi. Hàm này nên có dạng
như sau

void TWIReceiveHandler(int numOfBytes) {


...
}

Kết quả trả về: không.

Wire.onRequest()

Chức năng: đăng ký một chương trình con để khi có một yêu cầu từ
master thì hàm này sẽ được gọi.
Cú pháp:

Wire.onRequest(handler);

◮ handler: tên chương trình con được gọi. Hàm này nên có dạng
như sau

void TWIRequestHandler(int numOfBytes) {


...
}

Kết quả trả về: không.


7 I2C 68

7.3 Một số module

Module IMU MPU6050

Chức năng:
◮ Là một cảm biến trả về các giá trị gia tốc (accelerometer – hay gọi
là accel) và con quay hồi chuyển (gyroscope – hay gọi là gyro).
◮ IMU thường dùng trong các ứng dụng theo dõi chuyển động.

Hình 7.1: Module MPU 6050. Nguồn từ


Wikimedia.

Sơ đồ chân:
◮ Hai chân VCC-GND được nối vào nguồn 5V.
◮ Hai chân SDA, SCL lần lượt mắc vào các chân A4, A5.
◮ Có thể không cần đến chân INT.

Hình 7.2: Sơ đồ kết nối MPU 6050 với


Arduino.

Cách lập trình:


◮ Đọc về giá trị thô chưa qua xử lý:

1 // MPU-6050 Short Example Sketch


2 // By Arduino User JohnChi
3 // August 17, 2014
4 // Public Domain
5 #include<Wire.h>
6 const int MPU_addr=0x68; // I2C address of the MPU-6050
7 int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
8 void setup(){
9 Wire.begin();
10 Wire.beginTransmission(MPU_addr);
11 Wire.write(0x6B); // PWR_MGMT_1 register
12 Wire.write(0); // set to zero (wakes up the MPU-6050)
7 I2C 69

13 Wire.endTransmission(true);
14 Serial.begin(9600);
15 }
16 void loop(){
17 Wire.beginTransmission(MPU_addr);
18 // starting with register 0x3B (ACCEL_XOUT_H)
19 Wire.write(0x3B);
20 Wire.endTransmission(false);
21 // request a total of 14 registers
22 Wire.requestFrom(MPU_addr,14,true);
23 // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
24 AcX=Wire.read()<<8|Wire.read();
25 // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
26 AcY=Wire.read()<<8|Wire.read();
27 // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
28 AcZ=Wire.read()<<8|Wire.read();
29 // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
30 Tmp=Wire.read()<<8|Wire.read();
31 // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
32 GyX=Wire.read()<<8|Wire.read();
33 // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
34 GyY=Wire.read()<<8|Wire.read();
35 // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
36 GyZ=Wire.read()<<8|Wire.read();
37 Serial.print("AcX = "); Serial.print(AcX);
38 Serial.print(" | AcY = "); Serial.print(AcY);
39 Serial.print(" | AcZ = "); Serial.print(AcZ);
40 //equation for temperature in degrees C from datasheet
41 Serial.print(" | Tmp = "); Serial.print(Tmp/340.00+36.53);
42 Serial.print(" | GyX = "); Serial.print(GyX);
43 Serial.print(" | GyY = "); Serial.print(GyY);
44 Serial.print(" | GyZ = "); Serial.println(GyZ);
45 delay(333);
46 }

Lưu ý:
◮ Code mẫu trên rất đơn giản và chưa được xử lý, bạn nên tìm
thêm các nguồn tài liệu khác với từ khóa “MPU6050 Arduino”
để có những nguồn tài liệu đầy đủ hơn.
◮ Đây là một cảm biến khá khó để xử lý vì có cần phải nhiều thuật
toán khác kết hợp thì mới ra kết quả tốt hơn. Nếu bạn có làm đề
tài liên quan thì có thể tìm hiểu bộ lọc Kalman (Kalman filter) và
bộ lọc bù (complementary filter) để ra kết quả tốt nhất.

Module thời gian thực RTC

Chức năng:
◮ Lưu trữ thời gian thực tế ngay cả khi vi điều khiển không hoạt
động.
◮ Thường được dùng trong các ứng dụng điều khiển thiết bị theo
thời gian hoặc một số ứng dụng cần đến thời gian thực.
Sơ đồ chân:
◮ Hai chân VCC-GND nối với nguồn 5V.
7 I2C 70

Hình 7.3: Module RTC DS1307. Nguồn


từ ElectronicWings.

◮ Hai chân SDA, SCL lần lượt mắc vào hai chân A4, A5.

Hình 7.4: Sơ đồ kết nối module RTC


DS1307 với Arduino.

Cách lập trình:

1 #include <Wire.h>
2 // Address of DS1307
3 const byte DS1307 = 0x68;
4 // Number of bytes that need to read from DS1307
5 const byte NumberOfFields = 7;
6 // Time variable
7 int second, minute, hour, day, wday, month, year;
8

9 void setup()
10 {
11 Wire.begin();
12 // Setup time: 07:00:10 Sat 27/04/2019
13 setTime(07, 00, 10, 7, 27, 4, 19);
14 Serial.begin(9600);
15 }
16

17 void loop()
18 {
19 // Read data from DS1307
20 readDS1307();
21 // Display time to Serial monitor
22 digitalClockDisplay();
23 delay(1000);
24 }
25

26 void readDS1307()
27 {
28 Wire.beginTransmission(DS1307);
29 Wire.write((byte)0x00);
30 Wire.endTransmission();
31 Wire.requestFrom(DS1307, NumberOfFields);
32

33 second = bcd2dec(Wire.read() & 0x7f);


7 I2C 71

34 minute = bcd2dec(Wire.read() );
35 hour = bcd2dec(Wire.read() & 0x3f); // 24h mode
36 wday = bcd2dec(Wire.read() );
37 day = bcd2dec(Wire.read() );
38 month = bcd2dec(Wire.read() );
39 year = bcd2dec(Wire.read() );
40 year += 2000;
41 }
42 // Convert from BCD (Binary-Coded Decimal) to Decimal
43 int bcd2dec(byte num)
44 {
45 return ((num/16 * 10) + (num % 16));
46 }
47 // Convert from Decimal to BCD
48 int dec2bcd(byte num)
49 {
50 return ((num/10 * 16) + (num % 10));
51 }
52

53 void digitalClockDisplay(){
54 // digital clock display of the time
55 Serial.print(hour);
56 printDigits(minute);
57 printDigits(second);
58 Serial.print(" ");
59 Serial.print(day);
60 Serial.print(" ");
61 Serial.print(month);
62 Serial.print(" ");
63 Serial.print(year);
64 Serial.println();
65 }
66

67 void printDigits(int digits)


68 {
69 Serial.print(":");
70 if(digits < 10)
71 {
72 Serial.print('0');
73 }
74 Serial.print(digits);
75 }
76

77 /* Set up time for DS1307 */


78 void setTime(byte hr, byte min, byte sec, byte wd, byte d, byte mth, byte yr)
79 {
80 Wire.beginTransmission(DS1307);
81 Wire.write(byte(0x00)); // Reset pointer
82 Wire.write(dec2bcd(sec));
83 Wire.write(dec2bcd(min));
84 Wire.write(dec2bcd(hr));
85 Wire.write(dec2bcd(wd)); // day of week: Sunday = 1, Saturday = 7
86 Wire.write(dec2bcd(d));
87 Wire.write(dec2bcd(mth));
88 Wire.write(dec2bcd(yr));
89 Wire.endTransmission();
90 }
7 I2C 72

7.4 Lời kết

Một trong những ưu điểm của giao tiếp I2C là có thể giao tiếp rất
nhiều thiết bị trên cùng một bus vật lý. Nhưng mỗi thời điểm trên bus
chỉ tồn tại duy nhất một kết nối giữa một master và một slave.
SPI 8
8.1 Giới thiệu . . . . . . . . . . . 73
8.1 Giới thiệu 8.2 Một số hàm thông dụng . . 74
SPISettings . . . . . . . . . . 74
SPI (Serial Peripheral Interface) là chuẩn giao tiếp dữ liệu nối tiếp SPI.begin() . . . . . . . . . . 75
đồng bộ được sử dụng bởi vi điều khiển cho giao tiếp với một hoặc SPI.end() . . . . . . . . . . . . 75
nhiều thiết vị ngoại vi trong một khoảng cách ngắn hoặc giữa các vi SPI.beginTransaction() . . . 75
SPI.endTransaction() . . . . 75
điều khiển với nhau. Với chuẩn giao tiếp SPI thì luôn có một master
SPI.transfer(),
(thường là vi điều khiển) để giao tiếp với các ngoại vi.
SPI.transfer16() . . . . . . . . . . 76
Kết nối giữa master và các thiết bị ngoại vi có 3 dây chung và 1 dây SPI.usingInterrupt() . . . . . 76
riêng nối từ master đến từng thiết bị ngoại vi: 8.3 Một số module thường dùng 76
Thẻ nhớ SD . . . . . . . . . . 76
◮ MISO (Master Input Slave Output): dữ liệu sẽ được gửi từ slave 8.4 Lời kết . . . . . . . . . . . . . 80
và master sẽ nhận
◮ MOSI (Master Output Slave Input): dữ liệu sẽ được gửi từ master
và slave sẽ nhận
◮ SCK (Serial Clock): xung nhịp để đồng bộ dữ liệu truyền, xung
này được master sinh ra.
◮ SS (Slave Select): dây tín hiệu dùng để master bật/tắt chọn một
thiết bị.
Khi chân SS ở mức thấp thì quá trình giao tiếp sẽ bắt đầu. Ngược lại
khi chân SS ở mức cao thì slave sẽ bỏ qua dữ liệu trên bus truyền. Điều
này sẽ cho phép master có thể giao tiếp với nhiều slave khi chia sẻ
chung các đường dây MISO, MOSI, CLK. Lưu ý tại một thời điểm thì
chỉ có một slave được chọn.
Một số điều cần lưu ý khi sử dụng một thiết bị SPI mới:
◮ Tốc độ tối đa mà thiết bị có thể được sử dụng.
◮ Dữ liệu được dịch theo chuẩn MSB (Most Significant Bit) hay
LSB (Least Significant Bit). Hầu hết các chip SPI đều sử dụng
MSB.
◮ Chân clock ở trạng thái rãnh (idle) là thấp hay cao. Lấy mẫu ở
cạnh lên hay cạnh xuống của xung nhịp. Điều này để chọn chế
độ (mode) hoạt động phù hợp.
◮ Để tránh mắc sai lầm khi lập trình cũng như giao tiếp các thiết bị,
bạn đọc cần phải đọc thật kỹ hướng dẫn sử dụng (user manual)
và thông tin kỹ thuật (datasheet).
SPI sẽ có 4 chế độ truyền dữ liệu. Chế độ dữ liệu sẽ điều khiển liệu dữ
liệu được dịch vào/ra ở cạnh lên/xuống của xung nhịp (gọi là clock
phase), và liệu chân xung nhịp ở trạng thái rãnh là mức thấp hay cao
(gọi là clock polarity).
Chân kết nối trên Arduino như bảng thứ 2 (trang tiếp theo).
Lưu ý rằng bạn đọc có thể sử dụng các chân SPI trên hàng chân ICSP
như hình 8.1.
8 SPI 74

Clock Polarity Clock Phase


Chế độ Output Edge Data Capture
(CPOL) (CPHA)
Cạnh xuống Cạnh lên
SPI_MODE0 0 0
(Falling) (Rising)
Cạnh lên Cạnh xuống
SPI_MODE1 0 1
(Rising) (Falling)
Cạnh lên Cạnh xuống
SPI_MODE2 1 0
(Rising) (Falling)
Cạnh xuống Cạnh lên
SPI_MODE3 1 1
(Falling) (Rising)

Arduino/ SS SS Mức điện áp


MOSI MISO SCK
Genuino Board (slave) (master) (level)
Uno or Duemilanove 11 or ICSP-4 12 or ICSP-1 13 or ICSP-3 10 - 5V
Mega1280 or Mega2560 51 or ICSP-4 50 or ICSP-1 52 or ICSP-3 53 - 5V
Leonardo ICSP-4 ICSP-1 ICSP-3 - - 5V
Due SPI-4 SPI-1 SPI-3 - 4, 10, 52 3,3V
Zero ICSP-4 ICSP-1 ICSP-3 - - 3,3V
101 11 or ICSP-4 12 or ICSP-1 13 or ICSP-3 10 10 3,3V
MKR1000 8 10 9 - - 3,3V

Hình 8.1: Chân ISCP trên Arduino


board.

Để có thể sử dụng được SPI trên Arduino, bạn cần khai báo thư viện
SPI “#include <SPI.h>”. Thư viện này chỉ hỗ trợ chế độ master.

8.2 Một số hàm thông dụng

SPISettings

Chức năng: SPISettings là đối tượng dùng thể sử dụng cho việc cấu
hình SPI module. Đối tượng có 3 tham số và được đưa vào hàm
SPI.beginTransaction() cho việc tính toán các thông số cho SPI module.
Biến tham số đầu vào có thể ở dạng biến hoặc dạng hằng số, nhưng
chúng ta được khuyên là đưa trực tiếp hằng số vào các tham số để có
tốc độ cấu hình nhanh hơn.
Cú pháp:

SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0));


SPISettings mySettting(speedMaximum, dataOrder, dataMode);

◮ speedMaximum: tốc độ tối đa cho giao tiếp. Nếu tốc độ chip tối
đa là 20MHz thì sử dụng 20000000.
◮ dataOrder: MSBFIRST or LSBFIRST
◮ dataMode: SPI_MODE0, SPI_MODE1, SPI_MODE2, or SPI_MODE3

Kết quả trả về: không.


8 SPI 75

SPI.begin()

Chức năng: khởi tạo SPI bus bằng việc thiết lập các chân SCK, MOSI,
SS là ngõ ra (output), kéo chân SCK và MOSI xuống mức thấp và SS ở
mức cao.
Cú pháp:

SPI.begin();

Kết quả trả về: không.

SPI.end()

Chức năng: tắt SPI bus (chế độ của các chân được giữ không đổi).
Cú pháp:

SPI.end();

Kết quả trả về: không.

SPI.beginTransaction()

Chức năng: khởi tạo SPI bus sử dụng SPISettings.


Cú pháp:

SPI.beginTransaction(mySettings);

◮ mySettings: cấu hình được chọn dựa vào đối tượng SPISettings.

Kết quả trả về: không.

SPI.endTransaction()

Chức năng: dừng sử dụng SPI bus.


Cú pháp:

SPI.endTransaction();

Kết quả trả về: không.


8 SPI 76

SPI.transfer(), SPI.transfer16()

Chức năng: trao đổi dữ liệu bằng chuẩn SPI được dựa trên việc truyền
và nhận đồng thời. Dữ liệu nhận được từ kết quả về của việc gọi hàm
transfer(), transfer16().
Cú pháp:

receivedVal = SPI.transfer(val);
receivedVal16 = SPI.transfer16(val16);
SPI.transfer(buffer, size);

◮ val: byte dữ liệu được gửi ra ngoài bus.


◮ val16: 2 byte dữ liệu sẽ được gửi ra ngoài bus.
◮ buffer: chuỗi dữ liệu được gửi đi.
◮ size: kích thước của chuỗi dữ liệu.
Kết quả trả về: dữ liệu nhận được (receivedVal, receivedVal16). Nếu
1 byte dữ liệu được gửi thì nhận được về 1 byte dữ liệu, nếu 2 bytes
được gửi thì nhận về 2 bytes.

SPI.usingInterrupt()

Chức năng: nếu chương trình của bạn được thực hiện cùng với ngắt
(interrupt), việc gọi hàm này để đăng ký với thư viện SPI số thứ tự
của ngắt. Điều này cho phép hàm beginTransaction() ngăn chặn các
xung đột bằng cách tắt các ngắt này đi và được bật lại khi gọi hàm
endTransaction().
Cú pháp:

SPI.usingInterrupt(interruptNumber);

◮ interruptNumber: số thứ tự của ngắt.

Kết quả trả về: không.

8.3 Một số module thường dùng

Thẻ nhớ SD

Giới thiệu: lưu trữ dữ liệu là một trong những công việc quan trọng
nhất trong khi lập trình các ứng dụng. Có một vài cách để lưu trữ dữ
liệu theo kích thước và kiểu dữ liệu. Thẻ nhớ (mirco) SD là một trong
những các dữ liệu lưu trữ phổ biến.

◮ Thẻ nhớ cho phép giao tiếp với bộ nhớ bên trong để đọc/ghi dữ
liệu thông qua giao tiếp SPI.
◮ Để sử dụng thẻ nhớ thì bạn nên khai báo thư việc SD “#include
<SD.h>” để giúp bạn thao tác với SD card dễ dàng hơn.
8 SPI 77

Hình 8.2: Module SD Card.

◮ Bên dưới là giải thích mô tả cho các command trong thư viện. Bạn
đọc có thể đọc chi tiết tại https://www.arduino.cc/en/reference/
SD.

Hình 8.3: Các câu lệnh trong thư viện


SD Card.

Cách cấu hình mạch như hình 8.5.


Cách lập trình:
◮ Ghi dữ liệu:

1 #include <SPI.h> #include <SD.h> File myFile;


2 void setup() {
3 // Open serial communications and wait for port to open:
4 Serial.begin(9600);
5 while (!Serial) {
6 ; // wait for serial port to connect. Needed for native USB port only
7 }
8 Serial.print("Initializing SD card...");
9 if (!SD.begin(10)) {
10 Serial.println("initialization failed!");
11 while (1);
12 }
13 Serial.println("initialization done.");
14 // open the file. note that only one file can be open at a time,
15 // so you have to close this one before opening another.
16 myFile = SD.open("test.txt", FILE_WRITE);
17 // if the file opened okay, write to it:
18 if (myFile) {
19 Serial.print("Writing to test.txt...");
20 myFile.println("This is a test file :)");
8 SPI 78

Hình 8.4: Các chân trong module SD


Card.

21 myFile.println("testing 1, 2, 3.");
22 for (int i = 0; i < 20; i++) {
23 myFile.println(i);
24 }
25 // close the file:
26 myFile.close();
27 Serial.println("done.");
28 } else {
29 // if the file didn't open, print an error:
30 Serial.println("error opening test.txt");
31 }
32 }
33 void loop() {
34 // nothing happens after setup
35 }

• Kết quả trả về như hình 8.6.


◮ Đọc dữ liệu:

1 #include <SPI.h> #include <SD.h> File myFile;


2 void setup() {
3 // Open serial communications and wait for port to open:
4 Serial.begin(9600);
5 while (!Serial) {
6 ; // wait for serial port to connect. Needed for native USB port only
7 }
8 Serial.print("Initializing SD card...");
9 if (!SD.begin(10)) {
10 Serial.println("initialization failed!");
11 while (1);
12 }
13 Serial.println("initialization done.");
14 // open the file for reading:
15 myFile = SD.open("test.txt");
16 if (myFile) {
17 Serial.println("test.txt:");
8 SPI 79

Hình 8.5: Cách kết nối giữa Arduino và


module SD card.

Hình 8.6: Kết quả trả về khi ghi dữ liệu


vào module SD Card.

18 // read from the file until there's nothing else in it:


19 while (myFile.available()) {
20 Serial.write(myFile.read());
21 }
22 // close the file:
23 myFile.close();
24 } else {
25 // if the file didn't open, print an error:
26 Serial.println("error opening test.txt");
27 }
28 }
29 void loop() {
30 // nothing happens after setup
31 }

• Kết quả trả về như hình 8.7.


Lưu ý: nếu có một chuỗi các thông tin cần lưu cùng một thời điểm
(thời gian thực, nhiệt độ, độ ẩm, tín hiệu điều khiển động cơ) thì bạn
nên lưu kiểu định dạng csv (comma-separated values). Kiểu file csv
đơn giản là tất cả các dữ liệu tại một thời điểm được lưu trên dùng một
hàng cách nhau bởi dấu phẩy (,). Bạn có thể mở file này bằng phần
mềm Excel để xem cũng như dùng các chức năng của Excel như vẽ đồ
thị, lọc,...
8 SPI 80

Hình 8.7: Kết quả trả về khi đọc dữ liệu từ module SD Card.

8.4 Lời kết

Đây là một chuẩn giao tiếp tầm ngắn khá nhanh, truyền dữ liệu tốc
độ cao dành cho vi điều khiển. Vì thế thì các module lưu trữ bộ nhớ
thường dùng chuẩn giao tiếp SPI để giao tiếp hoặc các module có
truyền tải lượng dữ liệu lớn như Ethernet.
NGẮT - INTERRUPT 9
9.1 Giới thiệu . . . . . . . . . . . 81
9.1 Giới thiệu 9.2 Một số hàm thường dùng . 81
interrupts() . . . . . . . . . . 81
Ngắt (interrupt) là một tính năng rất quan trọng của vi điều khiển. Nó noInterrupts() . . . . . . . . . 81
có thể can thiệp vào bất cứ thời điểm nào của vi điều khiển khi có một attachInterrupt() . . . . . . . 82
sự kiện ngắt xảy ra. detachInterrupt() . . . . . . . 83
9.3 Lời kết . . . . . . . . . . . . . 83
Interrupts cho phép những công việc quan trọng được thực hiện ngầm
(mặc định là được cho phép). Một số hàm sẽ không hoạt động khi tắt
interrupt và các dữ liệu truyền đến vi điều khiển có thể bị bỏ qua.

9.2 Một số hàm thường dùng

interrupts()

Chức năng: cho phép ngắt hoạt động khi ngắt đã bị tắt.
Cú pháp:

interrupts();

Kết quả trả về: không.


Ví dụ:

1 void setup() {}
2 void loop() {
3 noInterrupts();
4 // critical, time-sensitive code here
5 interrupts();
6 // other code here
7 }

noInterrupts()

Chức năng: tắt ngắt. Chúng ta tắt ngắt trong trường hợp có những
công việc nghiêm khắc về thời gian vì ngắt có thể ảnh hưởng một phần
nhỏ đến thời gian thực hiện các công việc khác. Lưu ý khi tắt ngắt thì
một số hàm sẽ không hoạt động.
Cú pháp:
9 NGẮT - INTERRUPT 82

noInterrupts();

Kết quả trả về: không.

attachInterrupt()

Chức năng:
◮ Đăng ký chương trình con mà chương trình này được gọi khi có
một sự thay đổi từ một chân digital bên ngoài.
◮ Các chân hỗ trợ interrupt:

Board Các chân digital được sử dụng cho ngắt


Uno, Nano, Mini, other 328-based 2, 3
Uno WiFi Rev.2 tất cả các chân digital
Mega, Mega2560, MegaADK 2, 3, 18, 19, 20, 21
Micro, Leonardo, other 32u4-based 0, 1, 2, 3, 7
Zero tất cả các chân digital, ngoại trừ chân 4
MKR Family boards 0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Due tất cả các chân digital
tất cả các chân digital
101
(Các chân 2, 5, 7, 8, 10, 11, 12, 13 chỉ hoạt động với chế độ CHANGE)

Cú pháp:

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

◮ pin: chân sẽ xét điều kiện để vào chương trình con.


◮ ISR: hàm được gọi khi có ngắt xảy ra.
◮ mode: định nghĩa các chế độ để gọi vào ngắt.
• LOW: xảy ra ngắt khi chân có mức điện áp.
• CHANGE: xảy ra ngắt khi tại chân vi điều khiển có sự thay
đổi giá trị logic.
• RISING: xảy ra khi có sự chuyển mức điện áp từ thấp lên
cao.
• FALLING: xảy ra khi có sự thay đổi mức điện áp từ cao
xuống thấp.
Kết quả trả về: không.
Ví dụ:

1 const byte ledPin = 13;


2 const byte interruptPin = 2;
3 volatile byte state = LOW;
4

5 void setup() {
6 pinMode(ledPin, OUTPUT);
7 pinMode(interruptPin, INPUT_PULLUP);
8 attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
9 NGẮT - INTERRUPT 83

9 }
10

11 void loop() {
12 digitalWrite(ledPin, state);
13 }
14

15 void blink() {
16 state = !state;
17 }

Lưu ý:
◮ Khi gọi hàm ngắt thì hàm delay() sẽ không hoạt động, giá trị trả
về millis() sẽ không thay đổi, dữ liệu nhận về từ các giao tiếp có
thể bị mất. Vì vậy, ta dùng hàm này khi thật sự cần thiết và cực
kỳ quan trọng.
◮ Nên khai báo các biến dạng volatile trong chương trình ngắt này.
◮ Phải lập trình hàm được đính kèm càng ngắn càng tốt và thời
gian thực hiện phải ngắn.

detachInterrupt()

Chức năng: tắt ngắt được đính kèm.


Cú pháp:

detachInterrupt(digitalPinToInterrupt(pin));

◮ pin: chân được đính kèm interrupt cần tắt.

Kết quả trả về: không.

9.3 Lời kết

Interrupt là phần khá quan trọng nhưng cũng rất thận trọng khi sử
dụng. Nếu bạn cảm thấy không tự tin thì có thể tìm cách giải quyết
khác không cần dùng interrupt.
PHỤ LỤC
MỘT SỐ MODULE THÔNG
DỤNG KHÁC A
A.1 Module đo khoảng cách SRF-
A.1 Module đo khoảng cách SRF-05 05 . . . . . . . . . . . . . . . . . . 85
A.2 Cảm biến độ ẩm đất . . . . . 87
Chức năng: đo khoảng cách dùng sóng siêu âm.

Hình A.1: Module SRF-05. Nguồn từ


eProLabs.

Nguyên lý hoạt động: kích chân Trig một xung lớn hơn 10us thì module
sẽ phát ra môi trường 8 xung sóng siêu âm. Sau đó chân ECHO sẽ
chuyển trạng thái từ thấp lên cao, chân ECHO xuống thấp lại khi có
xung phản xạ lại (gặp vật cản thì xung sẽ phản xạ lại) hoặc sau 30ms
nếu không có vật cản. Thời gian của xung ECHO ở mức cao là thời
gian để sóng âm đi từ module đến vật cản cộng với thời gian sóng
phản xạ từ vật cản về module.

Hình A.2: Sơ đồ thời gian của module


SRF-05. Nguồn từ DFROBOT.

Sơ đồ chân được nối như hình A.3.


◮ Hai chân VCC-GND được nối vào nguồn 5V.
◮ Chân Trig nối với chân 12, chân Echo nối vào chân 13.

Chương trình mẫu:

1 /*
2 VCC to +5V
3 GND to ground
4 TRIG to digital pin 12
A MỘT SỐ MODULE THÔNG DỤNG KHÁC 86

Hình A.3: Sơ đồ kết nối của module SRF-


05 và Arduino.

5 ECHO to digital pin 13


6 */
7

8 const int TRIG_PIN = 12;


9 const int ECHO_PIN = 13;
10

11 void setup()
12 {
13 // initialize serial communication:
14 Serial.begin(9600);
15 pinMode(TRIG_PIN,OUTPUT);
16 pinMode(ECHO_PIN,INPUT);
17 }
18

19 void loop()
20 {
21 long duration, distanceCm, distanceIn;
22

23 digitalWrite(TRIG_PIN, LOW);
24 delayMicroseconds(2);
25 digitalWrite(TRIG_PIN, HIGH);
26 delayMicroseconds(10);
27 digitalWrite(TRIG_PIN, LOW);
28 duration = pulseIn(ECHO_PIN,HIGH);
29

30 // convert the time into a distance


31 distanceCm = duration / 29.1 / 2 ;
32 distanceIn = duration / 74 / 2;
33

34 if (distanceCm <= 0)
35 {
36 Serial.println("Out of range");
37 }
38 else
39 {
40 Serial.print(distanceIn);
41 Serial.print("in: ");
42 Serial.print(distanceCm);
43 Serial.print("cm");
44 Serial.println();
45 }
46 delay(1000);
47 }

Lưu ý: kết quả đo khoảng cách phụ thuộc rất nhiều vào bề mặt vật
thể.
A MỘT SỐ MODULE THÔNG DỤNG KHÁC 87

A.2 Cảm biến độ ẩm đất

Chức năng: đo độ ẩm đất nhưng không đưa ra giá trị cụ thể. Ta cần
phải dùng phương pháp thực nghiệm để điều chỉnh cũng như lấy
thông số cho việc lập trình cảm biến.

Hình A.4: Module đo độ ẩm đất. Nguồn


từ eProLabs.

Sơ đồ chân được nối như hình A.5.


◮ Hai chân VCC-GND nối với nguồn 5V.
◮ Chân D0 cho kết quả trả về dạng digital (HIGH/LOW). Trên
module có biến trở điều chỉnh giá trị so sánh.
◮ Chân A0 trả về kết quả dạng analog (dao động từ 0V - VCC).

Hình A.5: Sơ đồ kết nối giữa cảm biến


độ ẩm đất và Arduino.

Chương trình mẫu:

1 int sensor_pin = A0;


2 int output_value ;
3

4 void setup()
5 {
6 Serial.begin(9600);
7 Serial.println("Reading From the Sensor ...");
8 delay(2000);
9 }
10

11 void loop()
12 {
13 output_value= analogRead(sensor_pin);
14 output_value = map(output_value,550,0,0,100);
A MỘT SỐ MODULE THÔNG DỤNG KHÁC 88

15 Serial.print("Mositure : ");
16 Serial.print(output_value);
17 Serial.println("%");
18 delay(1000);
19 }
MỘT SỐ HÀM KHÁC B
B.1 Hàm I/O nâng cao . . . . . . 89
B.1 Hàm I/O nâng cao B.2 Hàm liên quan đến toán . . 89
B.3 Hàm làm việc với chuỗi . . 89
pulseIn(pin, value <, timeout>) trả về độ dài của xung ở mức HIGH
hoặc LOW (value) tại chân pin. Giá trị trả về là 0 nếu không có
xung mức value trước thời gian timeout.
shiftIn(dataPin, clockPin, bitOrder) dịch chuyển từng bit khi có sự
thay đổi tại chân clockPin thành một byte tại chân dataPin, thứ
tự của các bit trong một byte được sắp xếp theo bitOrder (MSB-
FIRST/LSBFIRST).
shiftOut(dataPin, clockPin, bitOrder, value) xuất giá trị từng bit ra
chân dataPin khi có sự thay đổi tại chân clockPin theo thứ
bitOrder (MSBFIRST/LSBFIRST) với giá trị value.

B.2 Hàm liên quan đến toán


abs(x) trả về trị tuyệt đối của x.
constrain(x, a, b) trả về giá trị của x bị chặn bởi cận dưới a và cận trên
b.
map(value, fromLow, fromHigh, toLow, toHigh) chuyển đổi giá trị
value từ phạm vi giá trị này (fromLow - fromHigh) thành phạm
vi giá trị khác (toLow - toHigh).
max(x, y) trả về lớn hơn trong 2 số x và y.
min(x, y) trả về nhỏ hơn trong 2 số x và y.
pow(base, exponent) tính lũy thừa base mũ exponent.
sq(x) bình phương x.
sqrt(x) căn bậc 2 của x.
sin(rad) sin của rad.
cos(rad) cos của rad.
tan(rad) tan của rad.

B.3 Hàm làm việc với chuỗi


char *strcpy(char *dich, char *nguon) sao chép chuỗi nguồn vào chuỗi
đích.
char *strncpy(char *dich, char *nguon, int n) sao chép n ký tự đầu
tiên của chuỗi nguồn sang chuỗi đích.
int strlen(char *s) trả về độ dài của chuỗi s.
char *strcat(char *dich,char *nguon) ghép chuỗi nguồn vào sau chuỗi
đích.
char *strncat(char *dich,char *nguon,int n) ghép n kí tự đầu tiên của
chuỗi nguồn vào chuỗi đích.
int strcmp(char *s1,char *s2) so sánh hai chuỗi s1 và s2.
char *strlwr(char *s) chuyển tất cả các ký tự về chữ thường.
B MỘT SỐ HÀM KHÁC 90

char *strupr(char *s) chuyển tất cả các ký tự về chữ hoa.


void *memset (void *ptr, int value, size_t num) ghi num bytes của
bộ nhớ bắt đầu từ địa chỉ ptr thành giá trị value.
void *memcpy (void *dich, const void *nguon, size_t num) sao chép
num bytes từ địa chỉ nguon đến địa chỉ dich.
MỘT SỐ KHÁI NIỆM CĂN
BẢN C
C.1 Một số khái niệm . . . . . . 91
C.1 Một số khái niệm C.2 Một số từ ngữ chuyên ngành 92
C.3 Một số kiến thức cần biết . 92
Vi xử lý (MPU – MicroProcessing Unit/CPU – Central Processing Dòng sink/source . . . . . . 92
Unit): là một máy tính, đơn vị tính toán thực hiện các phép toán logic Push-pull và Open drain IO 93
theo chương trình được lập trình trước. Vi xử lý sẽ cần phối hợp với
các ngoại vi thiết yếu bên ngoài mới có thể hoạt động được.

Hình C.1: Vi xử lý và các ngoại vi.


Nguồn từ sylvainavenel.esy.es.

Vi điều khiển: chứa một hoặc nhiều lõi vi xử lý và các module khác
(RAM, ROM, Timer, ...) trên cùng một chip.

Hình C.2: Vi điều khiển. Nguồn từ syl-


vainavenel.esy.es.

Arduino: là nền tảng vi điều khiển mã nguồn mở được sử dụng rộng


rãi trên thế giới. Đây là dòng vi điều khiển khá đơn giản để bắt đầu
vào con đường lập trình nhúng.
C MỘT SỐ KHÁI NIỆM CĂN BẢN 92

Ngôn ngữ lập trình: là ngôn ngữ tuân theo các cú pháp đặt trước được
dùng để lập trình ra các chương trình phần mềm/nhúng để thực hiện
một mục đích nào đó.

C.2 Một số từ ngữ chuyên ngành


Analog tương tự.
Build dịch mã chương trình.
Code chương trình.
CPU – Central Processing Unit vi xử lý.
Debug gỡ lỗi.
Decleration khai báo.
Digital số.
Func/Function hàm.
GPIO – General Peripheral Input/Output các tín hiệu vào/ra của ngoại
vi với mục đích chung.
IDE - Integrated Development Environment môi trường phát triển
tích hợp.
Initialization khởi tạo.
IO - Input and Output vào và ra.
MCU – MicroController Unit vi điều khiển.
Pin chân trên vi điều khiển.
Program nạp code.
PWM – Pulse-Width Modulation điều chế độ rộng xung.
SoC – System on chip một con chip sẽ chứa nhiều module để có thể
xây dựng một hệ thống.
Var/Variable biến.

C.3 Một số kiến thức cần biết

Dòng sink/source

Dòng source (source current/sourcing) là dòng điện cấp cho tải (load)
do thiết bị (device) cấp ra.
Dòng sink (sink current/sinking) là dòng diện từ nguồn (power sup-
ply) thông qua tải (load) rồi đến thiết bị (device).

Hình C.3: Dòng source (bên trái) và


dòng sink (bên phải). Nguồn từ allabout-
circuits.

Ví dụ về một IC cấp nguồn cho LED theo kiểu dòng source/sink như
hình C.4.
Các chip/thiết bị trong thực tế sẽ có nhiều loại khác nhau như chỉ hỗ
trợ dòng source, chỉ hỗ trợ dòng sink hay hỗ trợ cả dòng source và sink.
C MỘT SỐ KHÁI NIỆM CĂN BẢN 93

Hình C.4: Điều khiển LED bằng dòng


source (bên trái) và dòng sink (bên phải).
Nguồn từ allaboutcircuits.

Vì thế khi nối một tải vào thiết bị cần nên lưu tâm thiết bị hỗ trợ dòng
source/sink/both. Lưu ý: bạn nào làm việc với PLC (Programmable
Logic Controller) thì có thể sẽ gặp khái niệm này nhiều.

Push-pull và Open drain IO

Open drain: là cấu hình mạch bên trong con chip chỉ có 1 transistor
được nối xuống mức thấp. Muốn sử dụng được mạch này thì phải có
một điện trở ngoài kéo lên mức cao.

Hình C.5: Sơ đồ mạch open-drain bên


trong con chip. Nguồn từ 96boards.org.

Push-pull: là cấu hình mạch sẽ có một transistor nối mức cao và một
transistor nối với mức thấp (trong cùng một thời điểm thì chỉ có một
con transistor hoạt động).

Hình C.6: Sơ đồ mạch push-pull bên


trong con chip. Nguồn từ 96boards.org.
TỔNG QUAN VỀ NỀN TẢNG
ARDUINO D
D.1 Phần cứng . . . . . . . . . . . 94
D.1 Phần cứng D.2 Phần mềm . . . . . . . . . . . 94
Cài đặt . . . . . . . . . . . . . 94
Hiện nay trên thị trường có rất nhiều dòng arduino khác nhau với các Giới thiệu về Arduino IDE 99
đặc tính và sức mạnh khác nhau như Arduino Uno R3, Arduino Mega
2560, Arduino Nano CH40,...

Hình D.1: Một số loại board Arduino


thông dụng trên thị trường (thứ tự từ
trái qua phải lần lượt là Arduino Uno,
Arduino Nano, Arduino Mega 2560).
Nguồn từ Wikimedia , Wikimedia và
Wikimedia.

Trong tài liệu này, chúng ta sẽ dùng Arduino Uno R3 trong suốt phần
còn lại của tài liệu. Một số thông số kỹ thuật cần biết:

Vi điều khiển ATMega328


Nguồn USB 5V
Nguồn ngoài 7-12V
Số chân digital 14 (6 PWMs)
Số chân analog 6
Dòng tối đa trên GPIO 40 mA
Dòng tối đa cấp từ
150 mA
chân nguồn 3.3V
32 KB
Flash
(0.5 KB cho boot loader)
SRAM 2 KB
EEPROM 1 KB

D.2 Phần mềm

Arduino IDE là môi trường để lập trình và nạp code cho các dòng
Arduino. Arduino IDE được xây dựng trên nền tảng Java nên hỗ trợ
hầu hết các hệ điều hành hiện nay.

Cài đặt

Bước 1: Để có thể dùng được Arduino IDE, chúng ta cần download


JRE (Java Runtime Enviroment) cho máy tính. Bạn vào trang chủ của
Oracle (www.oracle.com) và chọn file download phù hợp với hệ điều
hành đang sử dụng.
D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 95

Hình D.2: Cài đặt JRE.

Bước 2: Truy cập vào trang chủ của Arduino (https://www.arduino.


cc-/en/Main/Software) và chọn file cài đặt phù hợp với hệ điều hành
đang sử dụng.

Hình D.3: Tải file cài đặt.

Sau khi giải nén tập tin tải về, mở file arduino.exe để khởi động chương
trình.
D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 96

Hình D.4: Giải nén tập tin tải về và khởi


chạy ứng dụng.

Ta có giao diện chương trình như sau:

Hình D.5: Giao diện chương trình Ar-


duino.

Bước 3: Cài đặt driver: Sử dụng cáp USB kết nối Arduino với máy tính,
máy tính sẽ tự động nhận driver.
D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 97

Hình D.6: Máy tính sẽ nhận board Ar-


duino Uno như một cổng COM (ở đây là
COM3, có thể khác nhau vì thứ tự cổng
COM sẽ do máy tính cấp phát).

Nếu máy tính hiển thị thông báo “Device driver software was not
successfully installed” thì thực hiện các bước sau:
◮ Bước 3.1: Mở cửa sổ Run (phím windows + R).
◮ Bước 3.2: Gõ devmgmt.msc. Cửa sổ được hiển thị như hình bên
dưới. Sau đó, bấm chuột phải vào thiết bị là Arduino và chọn
“Update Driver Software”.

Hình D.7: Cập nhật phần mềm cho


driver.

◮ Bước 3.3: Chọn “Browse my computer for driver software”.


D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 98

Hình D.8: Cửa sổ cập nhật phần mềm


cho driver.

◮ Bước 3.4: Chọn nút nhấn Browse... và chọn đến nơi chứa driver
(nằm trong thư mục tải về của Arduino).

Hình D.9: Chọn thư mục chứa driver.

◮ Bước 3.5: Chọn Next và đợi cài đặt driver.


◮ Bước 3.6: Sau khi thành công, chúng ta sẽ thấy xuất hiện cổng
COM như ở đầu bước 3.
Bước 4: Chọn loại board tương ứng. Chúng ta mở chương trình Ar-
duino IDE và vào Tools → Board → Chọn board đang sử dụng (ở đây
là Arduino Uno).
D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 99

Hình D.10: Chọn board đang sử dụng.

Bước 5: Cấu hình cổng COM để nạp code cho Arduino. Chọn Tools
→ Serial Port → COMxx (chọn cổng COM tương ứng với board Ar-
duino).

Hình D.11: Chọn cổng COM đang được


kết nối với board. Nếu có nhiều board
cùng một lúc thì phải chọn cổng COM
phù hợp với board muốn nạp chương
trình.

Giới thiệu về Arduino IDE

Giao diện được chia thành 3 vùng chính:


D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 100

Hình D.12: Giao diện chính của chương


trình Arduino.

◮ Vùng 1: các phím chức năng

Hình D.13: Ý nghĩa các phím chức năng.

◮ Vùng 2: cửa sổ để viết chương trình


• Chương trình (code) sẽ được viết tại đây. Ở đây có 2 hàm
quan trọng là setup() và loop().
• Hàm setup() sẽ được khởi chạy một lần duy nhất. Chức
năng của hàm này dùng để khởi tạo các biến, khai báo chức
năng, khởi tạo các thông số cho các module trong Arduino.
• Hàm loop() là nơi chương trình được chạy và lặp đi lặp lại
cho đến khi tắt nguồn vi điều khiển.
D TỔNG QUAN VỀ NỀN TẢNG ARDUINO 101

◮ Vùng 3: hiển thị các thông tin liên quan đến chương trình
• Là cửa sổ để hiển thị các thông tin về việc build chương
trình, nạp chương trình thành công xuống vi điều khiển và
các cảnh báo khác liên quan đến chương trình và vi điều
khiển của chúng ta. Lưu ý, mọi thông báo và trạng thái
của cả quá trình viết chương trình (write code), xây dựng
chương trình (build code) và nạp chương trình (program
code) đều được hiển thị tại đây. Của sổ này còn được gọi là
của sổ debug.
PHỤ LỤC MẠCH ĐIỆN E
Nếu mạch điện có đường dẫn đến tinkercad thì bạn có thể theo đường E.1 Mạch số 1: Diode . . . . . . 102
dẫn để mô phỏng online. E.2 Mạch số 2: Zener . . . . . . 103
E.3 Mạch số 3: NPN Amplifier 104
E.4 Mạch số 4: Dùng BJT để đóng
tắt relay . . . . . . . . . . . . . 105
E.1 Mạch số 1: Diode 21 E.5 Mạch số 5: IC số . . . . . . 107

21: Link: https://www.tinkercad.com/


things/fhiDjTwTB8E

Hình E.1: Sơ đồ nối dây chứng minh


hoạt động của diode.

Mạch điện trên để chứng minh diode chỉ cho dòng điện đi theo một
chiều và chỉ hoạt động khi điện áp tại hai đầu diode lớn hơn 𝑉𝐷 (𝑉𝐷 =
0.7𝑉). Bạn thay đổi điện áp cấp vào thì sẽ thấy diode hoạt động như
thế nào.

Hình E.2: Trường hợp diode chưa dẫn


(bên trái) và được dẫn (bên phải).

Mạch điện dưới là diode được gắn với một nguồn sin có biên độ là
2V, tần số 1Hz, và offset là 0V. Khi nào điện áp lớn hơn 0.7V thì đèn
sáng. Kết quả nhận được là đèn sẽ nhấp nháy với thời gian tắt lâu hơn
sáng.
E PHỤ LỤC MẠCH ĐIỆN 103

E.2 Mạch số 2: Zener 22 22: Link: https://www.tinkercad.com/


things/kPAmPITjooS

Hình E.3: Sơ đồ nối dây chứng minh


hoạt động của zener.

Khi phân cực thuận, zener hoạt động như một diode bình thường.
Khi phân cực nghịch và điện áp cấp vào zener nhỏ hơn điện áp zener
(zener voltage) thì zener sẽ cho phép dòng điện đi qua và điện áp được
giữ ở mức điện áp zener.

Hình E.4: Đặc tuyến V-I của zenner.


Nguồn từ Wikimedia.

Cấu hình của mạch: đầu cathod của zener đang được mắc vào cực
dương, đầu anode đang được mắc vào cực âm nhằm để cho diode
hoạt động ở chế độ phân cực ngược. Mức điện áp zener đang được
cấu hình là 5.1V.

Hình E.5: Trường hợp zener chưa ghim


áp (bên trái) và đã ghim áp (bên phải).

Giải thích:
E PHỤ LỤC MẠCH ĐIỆN 104

◮ Khi điện áp cấp vào nhỏ hơn điện áp zener thì không có dòng
điện đi qua diode, điện áp 2 đầu zener bằng điện áp nguồn.
◮ Khi điện áp cấp vào lớn hơn điện áp zener thì có dòng điện đi
qua zener, điện áp tại hai đầu zener gần bằng điện áp zener.
Ứng dụng thường thấy khi dùng zener là dùng để ghim áp, xén áp.

E.3 Mạch số 3: NPN Amplifier 23 23: Link: https://www.tinkercad.com/


things/f6lU2TGnzld

Hình E.6: Sơ đồ nối dây mạch khuếch


đại dùng NPN.

Cấu hình: mạch được đưa vào là tín hiệu hình sin có biên độ 0.5V, tần
số 50Hz. Kết quả ngõ ra của mạch vẫn là tín hiệu hình sin (ngược pha
với tín hiệu vào) có cùng tần số nhưng biên độ là 4V.

Hình E.7: Hình ảnh tín hiệu trước


khuếch đại (bên trái) và sau khuếch đại
(bên phải). Chú ý vào biên độ của oscil-
loscope.
E PHỤ LỤC MẠCH ĐIỆN 105

E.4 Mạch số 4: Dùng BJT để đóng tắt relay 24 24: Link: https://www.tinkercad.com/
things/4y9kZCtZCYl

Hình E.8: Sơ đồ nối dây mạch đóng tắt


thiết bị bằng relay.

Việc kích để đóng cắt relay thì cần dòng khá lớn nên thông thường
thì sẽ được kích thông qua một con BJT. Dòng kích không cần quá
lớn. Bạn có thể dùng mạch tương tự để bật/tắt các thiết bị khác mà vi
điều khiển không thể trực tiếp điều khiển (do giới hạn dòng hoặc do
nhiễu).

Hình E.9: Trạng thái đóng và ngắt mạch


bằng điều khiển relay.

Sau đây là sơ đồ mạch (schematic) của một module relay

Hình E.10: Sơ đồ mạch dây của một


module relay. Nguồn từ sunfounder.cc.

Vì hai sơ đồ giống nhau nên mình chỉ xem xét sơ đồ trên. Ta thấy rằng
sơ đồ trên giống với cách mà chúng ta mô phỏng mạch điện đóng ngắt
E PHỤ LỤC MẠCH ĐIỆN 106

relay. Module này là điều khiển relay tích cực thấp (nghĩa là nếu tín
hiệu IN1/IN2 là mức thấp thì module sẽ bật).
Chức năng của các linh kiện:
◮ J5: (jumper) để xác định xem nguồn của mạch điều khiển và
nguồn của mạch công suất có dùng chung hay không. Nếu
jumper được cắm thì 2 nguồn dùng chung.
◮ IN1: là đèn LED dùng để báo hiệu là module có đang được kích
hay không. Đèn sáng là module đang được kích.
◮ 817C: là opto dùng để cách ly giữa khối tín hiệu (bên phải) và
khối công suất (bên trái). Khi làm việc với relay (có chứa cuộn
dây) thì sẽ sinh ra nhiễu dội ngược về đường tín hiệu, nên mạch
cần opto để cách ly tín hiệu.
◮ R2: là điện trở hạn dòng cho cực B.
◮ Q1: là bjt dùng để điều khiển bật/tắt relay.
◮ K1: là relay.
◮ D1: là diode chống dòng ngược được sinh ra từ cuộn dây.
Nhận xét: phần thiết kế mạch này khá tốt nhưng vẫn còn điểm có thể
cải thiện. Nếu bạn đọc có thiết kế mạch thì nên sử dụng nguồn cho
phần tín hiệu và phần công suất khác nhau (hoặc đấu kiểu hình sao
về nguồn) thì cách ly được hoàn toàn nhiễu từ phần công suất gây ra.
Nhưng nếu cần dùng chung một nguồn thì đất của hai khối cần được
đi dây riêng và nối với nhau bằng điện trở 0Ω.
Ghi chú: Vì relay có thể hoạt động để điều khiển các thiết bị có điện áp
cao (điện AC 220V chẳng hạn) nên có một vài lưu ý khi layout mạch:
◮ Không nên đổ đất (Polygon Pour hoặc Copper pour) mà nên là
những đường dây đơn.
◮ Nếu mạch điện hoạt động điện áp cao thì PCB nên được cắt hẳn
để tách biệt giữa mạch công suất lớn và mạch điều khiển (đôi lúc
bạn sẽ thấy module relay sẽ được cắt ra một khoảng giữa board
để cách ly điện áp cao với điện áp thấp).
Thông tin thêm:
◮ Mạch dùng relay để điều khiển đóng tắt các thiết bị thì cần có
cuộn dây để kích từ bên trong. Khi cuộn dây được cấp điện thì
cuộn dây sinh ra từ trường được giữ bên trong cuộn dây. Khi
cuộn dây không còn được cấp điện, từ trường bên trong cuộn
dây giảm sẽ sinh ra dòng điện cảm ứng. Chính dòng điện cảm
ứng này sẽ sinh ra nhiễu cho mạch điện. Vì vậy D1/D2 được
dùng để xả dòng điện cảm ứng vì cuộn cảm và diode sẽ tạo
thành một mạch kín và giải phóng năng lượng này thông qua
điện trở bên trong cuộn dây.
◮ Vi điều khiển thì thông thường chỉ hoạt động ở mức điện áp
thấp (1.8V, 3.3V, 5V), trong khi đó relay được kích ở mức điện
áp cao hơn (5V, 12V, 24V,. . . ) nên cần phải được kích thông qua
một BJT. Vì thông thường relay sẽ điều khiển các thiết bị có công
suất/điện áp lớn nên cũng thường sinh ra nhiễu cho các tín hiệu
điều khiển có mức điện áp nhỏ (ví dụ khi đóng/cắt thiết bị 220V
thì có thể sẽ sinh ra tia lửa điện) nên việc cách ly phần tín hiệu
điều khiển và phần công suất là bắt buộc.
E PHỤ LỤC MẠCH ĐIỆN 107

◮ Khi một mạch đang hoạt động có phần công suất thì phần công
suất nên đặt ngoài cùng của board, không có các mạch điện
dễ nhiễu gần khu vực này (như vi điều khiển, mạch đo, mạch
RF/Wifi/. . . ).
◮ Một số vấn đề có thể gặp khi dùng vi điều khiển điều khiển
ralay:
• Relay gây ra ảnh hưởng đến các tín hiệu khác.
• Vi điều khiển reset mỗi khi bật tắt relay.
• Module truyền thông (wifi/RF/. . . ) bị reset/mất gói khi
bật/tắt relay.
◮ Cách xử lý:
• Hãy để mạch relay sang một khu vực tách biệt với các phần
còn.
• Dùng máy đo sóng (oscilloscope) đo dây tín hiệu, nguồn,
đất xem có bị ảnh hưởng nhiễu mỗi khi bật/tắt relay hay
không.

E.5 Mạch số 5: IC số 25 25: Link: https://www.tinkercad.com/


things/6FPsXKSVnjL

Hình E.11: Sơ đồ nối dây các IC số


thông dụng (74HC00, 74HC02, 74HC08,
74HC32, 74HC86).

Mạch điện trên gồm 5 loại ICs khác nhau là NAND, NOR, AND, OR
và XOR. Có một cái người ta gọi là bảng chân trị/bảng sự thật/truth
table. Bảng này dùng để thể hiện mối quan hệ giữa ngõ vào và ngõ
ra.

Input Output
Input 1 Input 2 NAND NOR AND OR XOR
0 0 1 1 0 0 0
0 1 1 0 0 1 1
1 0 1 0 0 1 1
1 1 0 0 1 1 0

IC CD4511 là IC dùng để chuyển BCD thành mã led 7 đoạn.26 26: Link: https://www.tinkercad.com/
things/bhSgzYQaYam
E PHỤ LỤC MẠCH ĐIỆN 108

Hình E.12: IC 4511 chuyển từ mã BCD


thành LED 7 đoạn.

BCD (binary-coded decimal) là mã hóa nhị phân của số thập phân, tức
là dịch từng số thập phân ra nhị phân. Lưu ý số nhị phân này không
phải số nhị phân quy ước. Bạn đọc nên tìm hiểu thêm các phép toán
với BCD để không nhầm lẫn với các hệ thông thường.
LỜI KẾT

Chúng ta đã đi qua hầu hết các tính năng căn bản của Arduino và đã thực hiện một số ví dụ đơn giản,
nhóm tác giả hy vọng các bạn có thể nắm được các kiến thức của Arduino để bạn có thể tạo ra những sản
phẩm như mong muốn.
Trong quyển sách này, tác giả không hướng dẫn bạn đọc làm bất kỳ một dự án nào hoàn chỉnh mà chỉ
cung cấp cho bạn những kiến thức riêng lẻ vì số lượng dự án thì nhiều vô số kể nên các tác giả không thể
hướng dẫn chi tiết từng dự án một. Vì thế, nội dung sách chỉ cho bạn những kiến thức cốt lõi nhất để bạn
có thể tự phát triển ứng dụng bạn mong muốn.
Cho đến thời điểm hiện tại, Arduino được xem là một nền tảng vi điều khiển dân dụng phổ biến nhất
trên thế giới nên số tượng tài liệu trên mạng nhiều vô số kể và lượng kiến thức trong quyển sách này chỉ
là một phần rất nhỏ nhoi trong lượng kiến thức ấy. Vì thế, cá nhân các bạn cần phải chủ động tìm hiểu
thêm để có những kiến thức rộng hơn và hiểu được bản chất của tư duy vi điều khiển.
Nếu bạn đọc có bất cứ góp ý/thắc mắc liên quan đến nội dung quyển sách thì đừng ngần ngại gửi mail
về nhóm tác giả theo thông tin tác giả bên dưới.
THÔNG TIN TÁC GIẢ VÀ BẢN QUYỀN

Tác giả

Người biên soạn: minht57 lab


Email: minht57.lab@gmail.com
Định hướng lĩnh vực nghiên cứu: lập trình nhúng (embedded software) và robotics.

Bản quyền

Quyển sách được phát hành dưới dạng e-book và miễn phí nên tác giả sẽ không cam kết tính đúng đắn
của toàn bộ nội dung nếu bạn sử dụng các kiến thức này vào các công việc có lợi nhuận. Tác giả cũng
không chịu bất kỳ trách nhiệm liên quan đến vấn đề bản quyền thương mại của bất kỳ sản phẩm nào mà
đọc giả lấy thông tin từ sách.
Tài liệu được biên soạn nhằm mục đích phát triển cộng đồng nên tài liệu được sử dụng với các mục đích
phi lợi nhuận thì không cần sự đồng ý của tác giả. Nếu bạn có sử dụng các thông tin trong quyển sách
này vui lòng trích dẫn đến sách (nếu thông tin trong sách đang được lấy nguồn từ bên ngoài thì vui lòng
bạn trích dẫn bài viết gốc không cần trích dẫn quyển sách này).
Bạn phải xin phép tác giả khi sử dụng sách liên quan đến bất kỳ hoạt động thương mại nào.

minht57 lab

You might also like