Professional Documents
Culture Documents
Arduino Cho Ngi Mi BT Du Quyn Can
Arduino Cho Ngi Mi BT Du Quyn Can
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
Mục lục iv
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
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
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
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
• 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)
• 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).
𝑄 = 𝐶𝑈
• 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ổ).
• 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).
• 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
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.
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).
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
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.
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
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.
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
Ký hiệu:
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:
int this_is_parameter;
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)
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.
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 (∼).
Hàm rẽ nhánh có điều kiện (if ... else if ... else ....)
◮ 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
hoặc
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
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 }
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.
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ả:
Lưu ý:
◮ Từ khóa for phải viết thường.
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ả:
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.
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.
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
8 int main()
9 {
2 LẬP TRÌNH C CƠ BẢN 24
13 return 0;
14 }
Kết quả:
2+4=6
5 int main()
6 {
7 int result = sum(2,4);
8 printf("2 + 4 = %d", result);
9
10 return 0;
11 }
12
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
16 array[2] *= 3;
17
20 return 0;
21 }
Kết quả:
Value at index 2: 4
New value at index 2: 12
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 = ∥ → 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
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):
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.
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);
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);
◮ 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);
Đèn LED
Link: https://www.tinkercad.com/
things/3u2Pp26Xwzx
◮ 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
◮ 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.
1 void setup()
2 {
3 pinMode(13, OUTPUT);
4 }
5
6 void loop()
3 GENERAL PURPOSE INPUT/OUTPUT – GPIO 33
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 }
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ữ.
◮ 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
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.
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
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 }
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.
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
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
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);
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);
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();
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();
3 void loop() {
4 time = micros();
5 delay(1000); // wait a second
6 }
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).
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);
void setup() {
// opens serial port, sets data rate to 9600 bps
Serial.begin(9600);
}
void loop() {}
Serial.end()
Serial.end();
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);
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
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);
1 void setup() {
2 Serial.begin(9600); // open the serial port at 9600 bps:
3 }
4
5 void loop() {
5 UART 43
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()
Serial.availableForWrite();
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();
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()
Serial. read();
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ụ:
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
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
◮ 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.
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:
◮ 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.
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
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".
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
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 }
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.
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
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 }
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
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.
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
𝑇𝑂 𝑁
Đ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).
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
Cú pháp:
analogRead(pin);
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);
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);
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 }
Biến trở
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
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
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.
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.
◮ 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ơ.
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
52 }
53 }
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
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
• 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
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:
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);
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);
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
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);
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();
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();
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
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
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
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.
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.
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.
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
◮ Hai chân SDA, SCL lần lượt mắc vào hai chân A4, A5.
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
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
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
Để 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.
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:
◮ 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
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();
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();
SPI.beginTransaction()
SPI.beginTransaction(mySettings);
◮ mySettings: cấu hình được chọn dựa vào đối tượng SPISettings.
SPI.endTransaction()
SPI.endTransaction();
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);
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);
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
◮ 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.
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 }
Hình 8.7: Kết quả trả về khi đọc dữ liệu từ module SD Card.
Đâ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.
interrupts()
Chức năng: cho phép ngắt hoạt động khi ngắt đã bị tắt.
Cú pháp:
interrupts();
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();
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:
Cú pháp:
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()
detachInterrupt(digitalPinToInterrupt(pin));
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.
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.
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
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
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
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.
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.
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.
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 đó.
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).
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
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.
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.
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).
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:
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
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
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
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”.
◮ 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).
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).
◮ 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
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.
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
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.
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.
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.
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.
E.4 Mạch số 4: Dùng BJT để đóng tắt relay 24 24: Link: https://www.tinkercad.com/
things/4y9kZCtZCYl
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).
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.
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
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ả
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