🌱 Lập trình vi điều khiển lõi ARM: Từ thanh ghi, thư viện đến các framework cấp cao

🌱 Lập trình vi điều khiển lõi ARM: Từ thanh ghi, thư viện đến các framework cấp cao

    Lập trình vi điều khiển lõi ARM là một hành trình thú vị, rất nhiều bạn hỏi khi bắt đầu học vi điều khiển (ví dụ STM32) thì tôi nên bắt đầu từ thanh ghi hay thư viện? Thực tế, việc này xuất phát từ mục tiêu mà bạn muốn học và sử dụng các vi điều khiển này.

    Từ việc thao tác trực tiếp với phần cứng đến sử dụng các công cụ trừu tượng hóa hiện đại. Với các cấp độ từ lập trình thanh ghi, CMSIS, thư viện Low-Level, STD, HAL, đến STM32CubeMX và các framework/thư viện cấp cao như FreeRTOS, mỗi cấp độ đều có ưu và nhược điểm riêng. Bài viết này sẽ giúp bạn hiểu rõ từng cấp độ, cách sử dụng, và cách chọn phương pháp phù hợp với dự án của mình.


Mục Lục


Tại sao cần phân loại như vậy và thư viện và Abstraction là gì?

    Thực chất thì lập trình thanh ghi hay thư viện cuối cùng khi compile, chương trình cũng sẽ trở thành binary và nạp xuống memory, CPU sẽ đọc các lệnh từ đây ra, truy cập vào thanh ghi của Vi điều khiển để làm các nhiệm vụ cấu hình - điều khiển. Vì vậy, dev cần hiểu các thư viện sinh ra nhằm mục đích đầu tiên là giúp người dùng dễ sử dụng để phát triển ứng dụng hơn! Cuối cùng thì ở bên trong các thư viện cũng sẽ có những đoạn code ghi vào thanh ghi.

    ↪ Video để bạn hiểu hơn về việc lập trình thanh ghi:

    Các thư viện sinh ra thực chất là một cách khác để user code (chỉ nói đến MCU) có thể truy xuất xuống các thanh ghi của ngoại vi mà dev không phải nhớ tên các thanh ghi, địa chỉ các thanh ghi, chỉ cần gọi các API mà thư viện cung cấp, thậm chí generate code bằng các giao diện như CubeMX.

    Ngoài việc cung cấp API, giao diện, bên trong các API của các thư viện thường triển khai thêm các công việc xử lý phần mềm, kiểm tra tham số người dùng, xử lý lỗi, gọi các thư viện trung gian, trừu tượng phần cứng, ...

STM32 Library Layers

    Abstraction - Từ khóa này cực kỳ quan trọng và thường xuyên được sử dụng trong embedded. Đối với MCU, có thể gọi là Hardware Abstraction (Dịch là trừu tượng phần cứng). Ý nghĩa là user code ở tầng trên, sẽ không cần quan tâm đến Hardware (Vi điều khiển) ở tầng dưới. Giúp code có thể porting dễ dàng hơn - Kiểu đang dùng STM32F4 cho project, viết Application sử dụng thư viện Abstraction tốt, thì sau này đổi sang con STM32F7 (nâng cấp lên), thì phần Application Code sẽ gần như không cần thay đổi gì cả!

STM32 Abstraction Layer

Lập trình thanh ghi (Register-level)

    Đây là cấp độ thấp nhất, nơi bạn trực tiếp thao tác với các thanh ghi phần cứng của vi điều khiển (như GPIO, UART, ADC).

    Ví dụ: Cấu hình GPIOA Pin 5 làm output và điều khiển LED trên STM32 sử dụng thanh ghi

  1. typedef unsigned int UINT32;

  2. int main()
  3. {
  4. // Enable Clock for PortA - AHB1 -
  5. // Base = 0x40023800
  6. // RCC_AHB1ENR = Address offset: 0x30
  7. // 0x40023830 - bit 0 = 1 -> Pointer + Bitwise
  8. *(UINT32*)0x40023830 |= (1U << 0);

  9. // PA5 = Output / Push-Pull - PORTA Base Address = 0x40020000
  10. // GPIOA_MODER = 0x40020000 - 01: General purpose output mode Bit [11:10]
  11. *(UINT32*)0x40020000 &= ~(3U << 10);
  12. *(UINT32*)0x40020000 |= (1U << 10);

  13. while(1)
  14. {
  15. // Toggle LED - PA5
  16. // GPIOx_ODR = 0x40020014 - Bit 5
  17. *(UINT32*)0x40020014 ^= (1U << 5);

  18. // Delay
  19. }
  20. return 0;
  21. }

    Để bắt đầu với lập trình thanh ghi, bạn cần rất hiểu kiến trúc của vi điều khiển, biết cách đọc, tham khảo tài liệu Reference Manual để biết địa chỉ và chức năng của từng thanh ghi, từ đó dễ dàng thao tác, lập trình, tùy biến, tối ưu hóa theo cách của riêng bạn.

    ✘ Tuy nhiên dễ thấy nhược điểm của phương án này là Phức tạp, khó bảo trì, không dễ port sang chip khác. Nên phương án này có thể áp dụng để học tập và triển khai các thư viện cấp cao hơn hoặc một số trường hợp cần tối ưu hóa hiệu suất, tài nguyên hạn chế.

Lập trình Vi điều khiển lõi ARM sử dụng CMSIS

    Vì những lý do trên, các dòng vi điều khiển thường hỗ trợ các thư viện cấp cao hơn để người dùng dễ sử dụng, ít nhất là không cần phải ghi nhớ hoặc define lại các địa chỉ thanh ghi như trên!

    Đối với các vi điều khiển lõi ARM như STM32, CMSIS (Cortex Microcontroller Software Interface Standard) là tiêu chuẩn do ARM cung cấp, giúp chuẩn hóa giao diện lập trình, cung cấp các định nghĩa thanh ghi và hàm khởi tạo hệ thống để người dùng dễ dàng lập trình hơn (Vì ai cũng cần define lại mà nên ARM làm luôn một bản chuẩn hóa cho mọi người cùng sử dụng). Ở phần này đối với việc lập trình Vi điều khiển, mình chỉ nói đến khái niệm Peripheral Access Layer (CMSIS-PAL)

  • Cung cấp các định nghĩa tên, địa chỉ và các hàm truy cập thanh ghi cho Core và thiết bị ngoại vi.
  • Định nghĩa các hàm xử lý ngoại lệ (exception) và ngắt (interrupt).
  • Cung cấp các hàm khởi động, thiết lập hệ thống ban đầu như SystemInit().

    ↪ Ví dụ: Cấu hình GPIOA Pin 5 làm output và điều khiển LED trên STM32 sử dụng CMSIS

  1. #include "stm32f4xx.h"

  2. int main()
  3. {
  4. // Enable Clock for PortA - AHB1
  5. RCC_AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

  6. // PA5 = Output / Push-Pull
  7. GPIOA->MODER |= GPIO_MODER_MODER5_0;

  8. while(1)
  9. {
  10. // Toggle LED - PA5
  11. GPIOA->ODR ^= GPIO_ODR_OD5;
  12. }
  13. return 0;
  14. }

    CMSIS có ưu điểm là giúp code dễ đọc hơn, tương thích đa nền tảng ARM. Trong khi dev vẫn cần hiểu phần cứng, ít API cấp cao, nó là một bản y hệt như Register-Level Programming nhưng tốt hơn vì không cần define lại địa chỉ và cấu trúc thanh ghi!

Thư viện Low-Layer Driver (LL) của STM32

    Thư viện LL do STMicroelectronics cung cấp, cung cấp API để thao tác thanh ghi ở mức thấp nhưng dễ sử dụng hơn CMSIS.

STM32 Low Layer Drivers

    ↪ Ví dụ: Cấu hình GPIOA Pin 5 làm output và điều khiển LED trên STM32 sử dụng thư viện LL.

  1. #include "stm32f4xx_ll_gpio.h"
  2. #include "stm32f4xx_ll_rcc.h"

  3. int main()
  4. {
  5. // Enable Clock for PortA - AHB1
  6. LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);

  7. // PA5 = Output / Push-Pull
  8. LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_5, LL_GPIO_MODE_OUTPUT);
  9. // LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_5); // Optional: Set PA5 HIGH initially

  10. while(1)
  11. {
  12. // Toggle LED - PA5
  13. LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);
  14. }
  15. return 0;
  16. }

  • Ưu điểm: Hiệu suất cao do cũng không xử lý phức tạp, code dễ bảo trì hơn CMSIS.
  • Nhược điểm: Chỉ hỗ trợ chip STM32, vẫn cần hiểu phần cứng.

Thư viện Standard Peripheral (STD)

    Thư viện STD là thế hệ cũ của ST, cung cấp API trừu tượng hơn LL nhưng kém mạnh mẽ hơn HAL.

    ↪ Ví dụ: Cấu hình GPIOA Pin 5 làm output và điều khiển LED trên STM32 sử dụng thư viện STD.

  1. #include "stm32f4xx.h"
  2. #include "stm32f4xx_gpio.h"
  3. #include "stm32f4xx_rcc.h"

  4. int main(void)
  5. {
  6. // Configure GPIO Pin
  7. GPIO_InitTypeDef GPIO_InitStructure;

  8. // Enable Clock for GPIOA
  9. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

  10. // PA5 = output, push-pull
  11. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  12. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  13. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  14. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  15. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  16. GPIO_Init(GPIOA, &GPIO_InitStructure);

  17. while(1)
  18. {
  19. // Toggle LED pin PA5
  20. GPIO_ToggleBits(GPIOA, GPIO_Pin_5);
  21. for(volatile uint32_t i = 0; i < 1000000; i++); // Simple Delay
  22. }
  23. return 0;
  24. }

  • Ưu điểm: Dễ dùng, trừu tượng phần cứng hơn so với thư viện LL.
  • Nhược điểm: Không còn được phát triển, chỉ hỗ trợ STM32 cũ như STM32F1, F2.

    Truy cập STD Lib của hãng ST

Thư viện HAL (Hardware Abstraction Layer)

    HAL (Hardware Abstraction Layer) là thư viện cấp cao của ST, trừu tượng hoàn toàn phần cứng, cung cấp API thân thiện để cấu hình ngoại vi.

    Ví dụ: Cấu hình GPIOA Pin 5 làm output và điều khiển LED trên STM32 sử dụng thư viện HAL.

  1. #include "stm32f4xx_hal.h"

  2. int main(void)
  3. {
  4. HAL_Init();

  5. // Enable Clock for GPIOA
  6. __HAL_RCC_GPIOA_CLK_ENABLE();

  7. // PA5 = output, push-pull
  8. GPIO_InitTypeDef GPIO_InitStruct = {0};
  9. GPIO_InitStruct.Pin = GPIO_PIN_5;
  10. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  11. GPIO_InitStruct.Pull = GPIO_NOPULL;
  12. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  13. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  14. while(1)
  15. {
  16. // Toggle LED pin PA5
  17. HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
  18. HAL_Delay(500); // Delay 500ms
  19. }
  20. }

  • Ưu điểm: Dễ dùng, hỗ trợ nhiều chip STM32, tích hợp tốt với CubeMX, tiết kiệm thời gian phát triển các ứng dụng.
  • Nhược điểm: Code triển khai cồng kềnh nên hiệu suất thấp hơn LL trong nhiều trường hợp.
    Đối với các dòng vi điều khiển khác trong phát triển chip/sản phẩm, đều hỗ trợ các thư viện giống như LL và HAL, mục tiêu là trừu tượng hóa phần cứng và hỗ trợ các hãng phát triển sản phẩm, các thư viện của các hãng cũng khá tương đồng về mặt cấu trúc. Vì vậy, việc biết cách sử dụng các thư viện này cũng là khá cần thiết khi bạn làm việc với các layer trên (Middleware, Application, ...). 

Generate code với STM32CubeMX

    STM32CubeMX là công cụ đồ họa của ST, cho phép cấu hình vi điều khiển qua giao diện và tự động sinh mã HAL hoặc LL.

    ↪ Ví dụ: Cấu hình GPIOA Pin 5 làm output và điều khiển LED trên STM32 sử dụng STM32CubeMX để generate code theo thư viện HAL.

    Cách làm này có ưu điểm là giảm thời gian phát triển các ứng dụng, giảm lỗi cấu hình khi code, đặc biệt là các project sử dụng số lượng ngoại vi lớn. Tuy nhiên nhược điểm là phải phụ thuộc vào Tools. Một số mảng cũng hỗ trợ tool generate code điển hình như Autosar với phần mềm nổi tiếng EB Tresos. 

> Ví dụ về triển khai việc generate code bằng giao diện.

Các Framework/RTOS cấp cao

    Các framework như Arduino, Mbed OS, hoặc RTOS như FreeRTOS cung cấp mức trừu tượng cao nhất, phù hợp cho ứng dụng phức tạp hoặc IoT. Hiểu đơn giản thì các API do các framework/thư viện cấp cao này cung cấp, một hàm sẽ bao gồm nhiều hàm của các thư viện kể trên và để phục vụ cho một mục đích cụ thể.

    Tuy nhiên, phần này thường đặc thù cho từng ứng dụng cụ thể chứ không cùng loại với các phương án kể trên.

    STM32CubeMX là công cụ đồ họa của ST, cho phép cấu hình vi điều khiển qua giao diện và tự động sinh mã HAL hoặc LL.

    ↪ Ví dụ: Chạy Task nháy LED trên chân PA5 vi điều khiển STM32 sử dụng freeRTOS.

  1. #include "FreeRTOS.h"
  2. #include "task.h"
  3. #include "stm32f401xe.h"

  4. void GPIO_Init(void) {
  5. RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  6. GPIOA->MODER |= (0x01 << 10);
  7. }

  8. void LED_Task(void *pvParameters) {
  9. while (1) {
  10. GPIOA->ODR ^= (1 << 5);
  11. vTaskDelay(500 / portTICK_PERIOD_MS);
  12. }
  13. }

  14. int main(void) {
  15. SystemCoreClockUpdate();
  16. GPIO_Init();

  17. xTaskCreate(LED_Task, "LED", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
  18. vTaskStartScheduler();

  19. while (1) {
  20. }
  21. }

So sánh và lựa chọn cấp độ phù hợp

    Nhìn chung, cấp độ phù hợp của các thành phần trên tùy thuộc vào hoàn cảnh và công việc mọi người đang làm. Chẳng hạn, nếu đang làm sản phẩm và dùng chip của hãng sssss, thì bạn chắc chắn cần dùng thư viện mà nó cung cấp. Nhưng khi một số function trong thư viện không phù hợp và cần customize thì lúc đó dev có thể code thanh ghi. 

MCU Library Selection

    Dưới đây là bảng so sánh:

Cấp độĐộ phức tạpHiệu suấtTính trừu tượngỨng dụng
Thanh ghiCaoCao nhấtThấp nhấtTối ưu hóa
CMSISCaoCaoThấpHiệu suất, đa nền tảng
Low-LevelTrung bìnhCaoTrung bìnhHiệu suất + bảo trì
STDTrung bìnhTrung bìnhTrung bìnhSTM32 cũ
HALThấpTrung bìnhCaoPhát triển nhanh
CubeMXThấpTrung bìnhCaoPhát triển nhanh
Framework/RTOSThấp nhấtThấpCao nhấtIoT, phức tạp

    Đối với người mới học và làm các ứng dụng về Embedded thì có thể bắt đầu với các thư viện cấp cao như STD, HAL, sử dụng CubeMX. Còn nếu bạn đã từng làm việc và muốn hiểu về vi điều khiển thì có thể lập trình sử dụng thanh ghi, sử dụng CMSIS.

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

Đăng nhận xét

Mới hơn Cũ hơn
//