Arduino Pro Mini 3,3 V và 5 V: nạp code bằng USB-TTL FTDI, ngủ sâu, đo điện áp pin, chạy pin lâu
Arduino Pro Mini bản 3,3 V 8 MHz và 5 V 16 MHz: nạp code bằng USB-TTL FTDI có DTR, chương trình ngủ sâu bằng watchdog đo điện áp pin, bỏ đèn và ổn áp để chạy pin hàng năm.
Arduino Pro Mini là mạch Arduino nhỏ (khoảng 18 × 33 mm) dùng chip ATmega328P như Uno, Nano, nhưng bỏ cổng USB để gọn và tiết kiệm điện. Mạch có hai bản: 3,3 V 8 MHz và 5 V 16 MHz. Muốn nạp code phải dùng bộ chuyển USB-TTL (thường gọi là FTDI). Nhờ không có chip USB, Pro Mini là lựa chọn quen thuộc cho thiết bị chạy pin: sau khi bỏ đèn nguồn, dòng lúc ngủ chỉ còn vài µA. Bài này so sánh hai bản, cách nối USB-TTL để nạp, chương trình ngủ sâu đo điện áp pin, và ước tính số ngày chạy pin theo từng cách sửa mạch.
1. Bản 3,3 V hay 5 V

- 3,3 V 8 MHz: hợp với module 3,3 V (nRF24L01, LoRa SX1278, nhiều cảm biến I2C) và chạy pin. Chip chạy 8 MHz vì ở 3,3 V không được phép chạy 16 MHz theo datasheet.
- 5 V 16 MHz: nhanh gấp đôi, hợp với cảm biến, module 5 V; tốn điện hơn.
- Nhận biết bản: xem chữ ghi trên mạch hoặc thạch anh (8.000 hay 16.000). Chân RAW nhận nguồn chưa ổn áp (thường tới khoảng 12 V), chân VCC là điện áp đã ổn áp (3,3 V hay 5 V).
Nano có sẵn cổng USB, hợp hơn khi học và thử nghiệm: xem bài bắt đầu với Arduino Nano CH340.
2. Nạp code bằng USB-TTL

- Dùng bộ chuyển USB-TTL có chân DTR (FT232RL, CH340, CP2102 loại 6 chân). Chọn mức 3,3 V cho bản 3,3 V (thường có jumper chọn 3,3 V / 5 V).
- Nối: GND–GND, VCC–VCC, TX của bộ chuyển vào RXI, RX vào TXO, DTR vào GRN (DTR). Pro Mini có sẵn tụ 100 nF từ DTR sang RESET: khi Arduino IDE mở cổng, DTR xuống thấp tạo xung reset để vào bootloader.
- Arduino IDE: Tools → Board → Arduino Pro or Pro Mini, Processor ATmega328P (3.3V, 8 MHz) hoặc (5V, 16 MHz) đúng bản, chọn cổng COM, rồi Upload.
Bộ chuyển không có DTR: bấm và giữ nút reset trên Pro Mini, nhả ra ngay khi IDE hiện "Uploading…". Các lỗi nạp như stk500_getsync() cùng nguyên nhân với Nano, xem bài Arduino Nano không nạp được code.
3. Chương trình ngủ sâu, đo điện áp pin
Chương trình dưới đây cho Pro Mini thức mỗi 8 giây bằng watchdog, nháy LED chân 13 trong 10 ms, rồi ngủ ở chế độ power-down (tắt ADC, tắt BOD trong lúc ngủ). Cứ 8 lần thức (khoảng 1 phút) in điện áp nguồn ra Serial 9600. Điện áp đo bằng mẹo so tham chiếu 1,1 V bên trong với VCC, nên không cần cầu phân áp hay chân analog: đo được điện áp pin khi pin cấp thẳng vào VCC.
/*
PYWORLD.VN - Arduino Pro Mini chạy pin: thức mỗi 8 giây bằng watchdog, đo điện áp nguồn bằng tham chiếu 1,1 V bên trong, nháy LED 10 ms rồi ngủ sâu
PYWORLD.VN - Ngủ chế độ power-down, tắt ADC và BOD khi ngủ; cứ 8 lần thức (khoảng 1 phút) in điện áp và số lần thức ra Serial 9600
PYWORLD.VN - Không cần mạch ngoài: điện áp đo được chính là VCC của chip (pin cấp thẳng vào chân VCC, bỏ ổn áp)
*/
#include <avr/sleep.h>
#include <avr/wdt.h>
const int CHAN_LED = 13;
const long HIEU_CHINH_1V1 = 1125300L; // 1,1 V × 1023 × 1000; đo VCC bằng đồng hồ rồi chỉnh số này cho đúng chip của bạn
volatile uint16_t soLanThuc = 0;
ISR(WDT_vect) {
soLanThuc++; // chỉ đánh thức chip, không reset
}
long doVcc() {
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // tham chiếu AVcc, đo kênh 1,1 V bên trong
delay(2); // chờ điện áp tham chiếu ổn định
ADCSRA |= _BV(ADSC);
while (ADCSRA & _BV(ADSC)) {
}
uint16_t ma = ADC;
return ma ? HIEU_CHINH_1V1 / ma : 0; // mV
}
void ngu8Giay() {
ADCSRA &= ~_BV(ADEN); // tắt ADC: bớt khoảng vài trăm µA
noInterrupts();
MCUSR &= ~_BV(WDRF);
WDTCSR = _BV(WDCE) | _BV(WDE); // trình tự thời gian bắt buộc để đổi cấu hình watchdog
WDTCSR = _BV(WDIE) | _BV(WDP3) | _BV(WDP0); // chỉ ngắt, chu kỳ khoảng 8 giây
wdt_reset();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_bod_disable(); // tắt mạch phát hiện sụt áp trong lúc ngủ
interrupts();
sleep_cpu();
sleep_disable();
ADCSRA |= _BV(ADEN);
}
void setup() {
Serial.begin(9600);
pinMode(CHAN_LED, OUTPUT);
for (byte chan = 2; chan <= 12; chan++) pinMode(chan, INPUT_PULLUP); // chân thả nổi làm tăng dòng ngủ
Serial.println(F("Pro Mini chay pin: thuc moi 8 giay"));
Serial.flush();
}
void loop() {
digitalWrite(CHAN_LED, HIGH);
delay(10);
digitalWrite(CHAN_LED, LOW);
if (soLanThuc % 8 == 0) {
Serial.print(F("Lan thuc "));
Serial.print(soLanThuc);
Serial.print(F(", VCC "));
Serial.print(doVcc());
Serial.println(F(" mV"));
Serial.flush(); // gửi hết trước khi ngủ, nếu không chữ bị cắt
}
ngu8Giay();
}

- Tham chiếu 1,1 V của từng chip lệch vài phần trăm. Đo VCC bằng đồng hồ, rồi sửa
HIEU_CHINH_1V1= 1125300 × (số đồng hồ / số chương trình in ra). - Watchdog đặt ở chế độ chỉ ngắt (WDIE), không reset; chu kỳ khoảng 8 giây nhưng sai lệch theo nhiệt độ và điện áp, không dùng làm đồng hồ chính xác.
Serial.flush()trước khi ngủ để chữ gửi hết; bỏ đi thì dòng in bị cắt giữa chừng.
4. Sửa mạch để chạy pin lâu

Script dưới đây ước tính số ngày chạy pin với chương trình ở mục 3, theo ba cách cấp nguồn. Dòng điện là giá trị giả định điển hình; đo mạch thật rồi thay vào.
# PYWORLD.VN - Ước tính số ngày chạy pin của Arduino Pro Mini 3,3 V chạy chương trình PRO_MINI_NGU_DO_PIN (thức mỗi 8 giây)
# PYWORLD.VN - So ba cách: mạch nguyên bản cấp qua RAW, bỏ đèn nguồn, bỏ cả đèn và ổn áp (pin LiFePO4 3,2 V cấp thẳng VCC)
# PYWORLD.VN - Chạy: python tinh_pin_pro_mini.py (dòng điện là giá trị giả định điển hình; đo mạch của bạn rồi thay vào)
CHU_KY_S = 8.0
THUC_S = 0.012 # 10 ms nháy LED + vài ms chạy chương trình
I_THUC_MA = 3.5 + 4.0 # chip ATmega328P 8 MHz khoảng 3,5 mA + LED chân 13 khoảng 4 mA trong lúc thức
I_CHIP_NGU_MA = 0.005 # power-down có watchdog: vài µA theo datasheet
# tên: (dòng lúc ngủ mA, pin, dung lượng mAh)
CACH = {
"Nguyen ban, cap qua RAW (den nguon + on ap)": (2.0 + 0.08 + I_CHIP_NGU_MA, "Li-ion 18650", 3000), # đèn nguồn ~2 mA, ổn áp ~80 µA
"Bo den nguon, van qua on ap": (0.08 + I_CHIP_NGU_MA, "Li-ion 18650", 3000),
"Bo den va on ap, cap thang VCC": (I_CHIP_NGU_MA, "LiFePO4 18650", 1500),
}
DUNG_DUOC = 0.8 # dùng 80 % dung lượng ghi trên pin
def dong_trung_binh(i_ngu_ma: float) -> float:
return (I_THUC_MA * THUC_S + i_ngu_ma * (CHU_KY_S - THUC_S)) / CHU_KY_S
if __name__ == "__main__":
for ten, (i_ngu, pin, mah) in CACH.items():
i = dong_trung_binh(i_ngu)
ngay = mah * DUNG_DUOC / i / 24
print(f"{ten:42s}: ngu {i_ngu * 1000:7.1f} uA, trung binh {i * 1000:7.1f} uA, {pin} {mah} mAh -> {ngay:6.0f} ngay ({ngay / 365:.1f} nam)")

- Chỉ riêng đèn báo nguồn đã làm pin hết sau vài tuần; bỏ đèn kéo dài lên vài năm. Ở mức vài µA, pin tự xả, nhiệt độ và độ ẩm mới là giới hạn thật.
- Cấp thẳng vào VCC thì điện áp pin phải nằm trong khoảng chip chịu được: chip 8 MHz cần từ khoảng 2,7 V, và mạch phát hiện sụt áp (BOD) của bản 3,3 V thường đặt 2,7 V. Pin LiFePO4 (3,2 V, khá phẳng khi xả) rất hợp; hai pin AA cấp thẳng chỉ dùng được phần đầu dung lượng; pin Li-ion 4,2 V khi đầy thì hợp với bản 5 V hơn, hoặc dùng ổn áp dòng tĩnh thấp.
- Theo dõi pin và cảnh báo pin yếu dựa trên điện áp đo ở mục 3; ngưỡng tùy loại pin.
Cách tính tương tự cho ESP32 có ở bài cùng cụm về dòng tiêu thụ ESP32; dùng ngủ sâu trên ESP32 có ở bài Deep Sleep trên ESP32.
5. Lỗi hay gặp
| Hiện tượng | Nguyên nhân | Cách xử lý |
|---|---|---|
| avrdude: stk500_getsync() not in sync | Nối TX–TX, thiếu DTR, chọn sai bản | Nối chéo TX–RXI, RX–TXO; bấm reset tay; chọn đúng 3.3V/5V |
| Serial ra ký tự lạ, delay chạy chậm gấp đôi | Bản 8 MHz nhưng chọn 16 MHz khi nạp | Chọn ATmega328P (3.3V, 8 MHz) rồi nạp lại |
| Dòng ngủ vẫn vài mA | Còn đèn nguồn, ổn áp, cảm biến vẫn cấp điện | Bỏ đèn, cấp cảm biến qua chân GPIO |
| Chip khởi động lại liên tục khi pin yếu | Điện áp dưới ngưỡng BOD | Thay pin; dùng pin có điện áp phù hợp |
| Hỏng module 3,3 V khi nạp code | Bộ chuyển USB-TTL để ở 5 V | Chuyển jumper sang 3,3 V trước khi nối |
Pin 18650 và khay pin: khay pin 18650 nhiều loại. Cần mạch có sẵn cổng USB thì dùng Arduino Nano.
Xem thêm