Bài 47.124 phút đọc
Kiến trúc MCU
Sau bài này bạn sẽ làm được
- Kể tên các khối chính của một vi điều khiển và vai trò từng khối
- So sánh tài nguyên của MCU với máy tính trên tám tiêu chí
- Nêu sáu quy tắc bắt buộc của lập trình nhúng và lý do từng quy tắc
- Ước lượng được RAM và Flash mà một chương trình cần
Mọi thứ đã học về bit, con trỏ, volatile và bố cục bộ nhớ tới đây mới được dùng thật. Lập trình nhúng không có khái niệm nào mới, nó chỉ siết chặt mọi ràng buộc tới mức bạn buộc phải hiểu đúng.
#Bên trong một vi điều khiển
Vi điều khiển
Một con chip gói trọn bộ xử lý, bộ nhớ chương trình, bộ nhớ dữ liệu và các ngoại vi vào một khối. Khác với bộ vi xử lý của máy tính, nó không cần chip nhớ hay chip cầu bên ngoài để chạy.
| Khối | Vai trò | Dung lượng điển hình |
|---|---|---|
| Lõi CPU | Chạy lệnh, thường là ARM Cortex-M từ M0 tới M7 | 16 tới 480 MHz |
| Flash | Chứa mã chương trình và hằng số, giữ được khi mất điện | 16 KB tới 2 MB |
| SRAM | Chứa biến, ngăn xếp, vùng cấp phát động | 2 KB tới 512 KB |
| Ngoại vi | GPIO, UART, SPI, I2C, ADC, timer, DMA | Vài chục khối |
| NVIC | Bộ điều khiển ngắt lồng nhau, thuộc lõi Cortex-M | Tới 240 nguồn ngắt |
#Tám khác biệt so với lập trình máy tính
| Máy tính | Vi điều khiển | |
|---|---|---|
| RAM | Vài gigabyte | Vài kilobyte tới vài trăm KB |
| Bộ nhớ chương trình | Vài trăm gigabyte | Vài chục KB tới vài MB |
| Hệ điều hành | Có, lo hết mọi thứ | Thường không, chạy trần |
| malloc | Dùng thoải mái | Thường bị cấm hẳn, xem mục sau |
| printf | Miễn phí | Tốn 5 tới 10 KB Flash, phải cân nhắc |
| Số thực | Có bộ tính số thực phần cứng | Thường không có, chậm hàng chục lần |
| Gỡ lỗi | GDB, in ra màn hình | SWD, UART, hoặc một cái đèn |
| Khi lỗi | Chương trình sập, khởi động lại | Thiết bị chết ngoài hiện trường |
Số thực đắt tới mức nào
do-so-thuc.c
/* Trên Cortex-M0, hoàn toàn không có bộ tính số thực.
Mọi phép float được mô phỏng bằng thư viện phần mềm. */
float a = 1.5f, b = 2.5f;
float c = a * b; /* gọi __aeabi_fmul, hàng trăm chu kỳ */
/* Trên Cortex-M4F có bộ tính số thực 32 bit,
nhưng chỉ cho float, còn double vẫn bằng phần mềm. */
double d = 1.5 * 2.5; /* vẫn gọi thư viện, dù có FPU */
/* Cách đúng: dùng số nguyên với thang cố định, xem Bài 50.3 */
int32_t a_x100 = 150; /* 1.50 nhân 100 */
int32_t b_x100 = 250; /* 2.50 nhân 100 */
int32_t c_x100 = a_x100 * b_x100 / 100; /* 375, tức 3.75 */terminal
./do-phep-tinh --cortex-m0
phep tinh so chu ky int32 nhan 1 int32 chia 12 float cong 120 float nhan 145 float chia 480 double nhan 890 sqrt(float) 1240
# Cùng phép tính trên Cortex-M4F có bộ tính số thực
./do-phep-tinh --cortex-m4f
float cong 1 float nhan 1 float chia 14 double nhan 890 <- van bang phan mem
#Sáu quy tắc bắt buộc
#Đo tài nguyên chương trình dùng
Xem chương trình chiếm bao nhiêu
$ arm-none-eabi-size firmware.elf
text data bss dec hex filename
18240 108 4372 22720 58c0 firmware.elf
/* text : mã và hằng số, nằm trong FLASH
data : biến toàn cục có giá trị khởi tạo khác 0
chiếm chỗ ở CẢ Flash lẫn SRAM, xem Bài 14.3
bss : biến toàn cục bằng 0 hoặc chưa khởi tạo
chỉ chiếm SRAM
Flash dùng = text + data = 18240 + 108 = 18348 byte
SRAM dùng = data + bss = 108 + 4372 = 4480 byte
cộng thêm NGĂN XẾP, mà size không tính */Đo mức dùng ngăn xếp cao nhất
do-ngan-xep.c
#include <stdint.h>
extern uint32_t _sstack, _estack; /* do tệp linker định nghĩa */
#define MAU 0xC0DEC0DEu
/* Gọi ngay đầu main, trước khi ngăn xếp kịp sâu.
Đổ đầy vùng ngăn xếp bằng một mẫu nhận ra được. */
void ngan_xep_do_day(void)
{
uint32_t *p = &_sstack;
uint32_t hien_tai;
/* Dừng cách vị trí ngăn xếp hiện tại một khoảng an toàn */
while (p < (&hien_tai - 16))
*p++ = MAU;
}
/* Gọi bất cứ lúc nào sau đó. Đếm số từ còn nguyên mẫu,
phần còn lại là mức dùng cao nhất từ trước tới giờ. */
uint32_t ngan_xep_da_dung(void)
{
uint32_t *p = &_sstack;
while (p < &_estack && *p == MAU) ++p;
return (uint32_t)((&_estack - p) * sizeof(uint32_t));
}terminal
> stack
ngan xep cap phat : 4096 byte da dung cao nhat : 1284 byte (31 phan tram) con trong : 2812 byte
# Sau khi gọi một hàm dùng printf
> printf-test; stack
da dung cao nhat : 2216 byte (54 phan tram) printf mot minh an them 932 byte ngan xep
#Chọn vi điều khiển
| Dòng | Lõi | RAM và Flash điển hình | Hợp với |
|---|---|---|---|
| STM32F0, C0 | Cortex-M0 | 4 tới 32 KB RAM, 16 tới 256 KB Flash | Điều khiển đơn giản, giá rẻ nhất |
| STM32F4 | Cortex-M4F | 128 tới 384 KB RAM, 512 KB tới 2 MB Flash | Đa số dự án, có bộ tính số thực |
| STM32H7 | Cortex-M7 | 1 MB RAM, 2 MB Flash | Xử lý ảnh, âm thanh, tính toán nặng |
| ESP32 | Xtensa hoặc RISC-V | 520 KB RAM, 4 MB Flash ngoài | Cần Wi-Fi hoặc Bluetooth sẵn |
| AVR, dòng Arduino Uno | AVR 8 bit | 2 KB RAM, 32 KB Flash | Học, làm mẫu nhanh |
| RP2040 | Hai lõi Cortex-M0 | 264 KB RAM, Flash ngoài | Hai lõi, khối vào ra lập trình được |
Tự làm thử
- Tra bảng dữ liệu của một vi điều khiển bạn có, ghi lại dung lượng Flash, SRAM, tần số lõi và số ngoại vi mỗi loại.
- Biên dịch một chương trình trống rồi chạy
arm-none-eabi-size, sau đó thêm một lời gọiprintfvà đo lại. - Viết hàm nhân hai số dùng
floatrồi dùng số nguyên thang cố định, đếm số lệnh máy sinh ra bằngarm-none-eabi-objdump -d. - Cài hàm đổ đầy mẫu và đo mức dùng ngăn xếp cao nhất, rồi thử với một hàm đệ quy sâu dần.
- Biên dịch cùng một hàm với
uint8_tvà vớiuint_fast8_tcho biến cục bộ, so kích thước mã sinh ra.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Vi điều khiển gói lõi, Flash, SRAM và ngoại vi vào một chip, tất cả nằm trong cùng một không gian địa chỉ.
- SRAM là tài nguyên eo hẹp nhất, và ngăn xếp chỉ vài kilobyte, tức nhỏ hơn máy tính khoảng một nghìn lần.
- Không cấp phát động trong vòng lặp chính và không đệ quy, vì cả hai đều làm hỏng thứ không đoán trước được.
- Số thực chậm hàng chục tới hàng trăm lần khi không có FPU, và một hằng số thiếu hậu tố
fkéo cả phép tính sangdouble. - Lệnh
sizekhông tính ngăn xếp, nên phải tự đo bằng cách đổ đầy mẫu và đếm phần còn nguyên.