Sau khi mình đã giới thiệu tổng quan về Firmware Over The Air – FOTA và thiết lập cơ bản để sử dụng tính năng FOTA ở bài viết trước. Trong bài này, mình sẽ tiếp tục giải thích các bước để viết chương trình Bootloader và một số xử lý trên chương trình ứng dụng sau khi được chuyển qua từ chương trình Bootloader. Nếu bạn chưa đọc phần 1 thì nhấn vào đây để đọc qua trước nhé.
[HỌC ONLINE: LẬP TRÌNH VI ĐIỀU KHIỂN STM32, VI XỬ LÝ ARM CORTEX – M]
Phần 2: Chương trình Bootloader và các chương trình ứng dụng khi sử dụng tính năng FOTA
1. Chương trình Bootloader
Nhiệm vụ của chương trình Bootloader là kiểm tra các điều kiện để lựa chọn thực thi một trong các chương trình: FOTA, Factory Firmware hoặc Current Firmware mỗi khi CPU reset. Vị trí của chương trình Bootloader thường được bắt đầu tại địa chỉ đầu tiên của bộ nhớ Flash, đây là địa chỉ mặc định sẽ được CPU thực thi sau khi reset. Với dòng vi điều khiển STM32 thì vị trí bắt đầu của bộ nhớ Flash là 0x0800 0000.
Chương trình này sẽ được người dùng lập trình và nạp thủ công vào vi điều khiển.
Dưới đây là chi tiết mô hình bộ nhớ khi sử dụng chương trình Bootloader mà mình sử dụng với vi điều khiển STM32F103RCT6:
Giá trị của thanh ghi Stack Pointer (SP) và thanh ghi Program Counter (PC) trong CPU có liên quan trực tiếp đến quá trình khởi động và thực thi chương trình của vi điều khiển, trong đó:
Stack Pointer(SP): là một thanh ghi lưu trữ giá trị trỏ tới vùng nhớ hiện tại trong bộ nhớ Stack. Mỗi chương trình sẽ có giá trị khởi tạo SP khác nhau vì giá trị này cũng giá trị địa chỉ cuối cùng của SRAM được chương trình sử dụng. Giá trị khởi tạo của SP sẽ được lưu tại địa chị bắt đầu của chương trình.
Program Counter (PC): Là một thanh ghi chứa địa chỉ câu lệnh tiếp theo sẽ được CPU thực thi. Thanh ghi này sẽ tự động tăng lên mỗi khi thực hiện xong một lệnh (trong một số trường hợp như branch, jump hoặc interrupt sẽ thay đổi PC theo cách khác).
Khi muốn chuyển sang chương trình mới, thanh ghi PC phải lấy giá trị từ địa chỉ bắt đầu chương trình + 4. Vì đây là nơi lưu trữ địa chỉ của Reset Handler, và hàm main() chương trình sẽ được thực thi từ đây.
Để một chương trình được thực thi thì cần có bảng vector ngắt và mặc định khi không cấu hình thay đổi thì bảng vector ngắt này nằm ở phần đầu tiên của bộ nhớ của bộ nhớ Flash. Với chương trình bootloader thì chúng ta không cần thay đổi vị trí vector ngắt vì Bootloader nằm ngay ở vị trí đầu tiên của bộ nhớ Flash.(Address = 0x08000000). Còn các chương trình còn lại gồm FOTA, Current Firmware và Factory Firmware nằm ở các vị trí khác trên bộ nhớ Flash nên chúng ta cần thay đổi địa chỉ bảng vector ngắt tương ứng với vị trí bắt đầu của từng chương trình.
Lần lượt các bước thực hiện trong chương trình Bootloader:
Bước 1: Lấy địa chỉ chương trình mà bạn muốn chuyển tới lưu vào một biến.
Các bạn có thể tạo các câu lệnh điều kiện để tới một địa chỉ cụ thể. Trong bài viết này, mình sẽ tạo trực tiếp một biến gán địa chỉ của chương trình Factory Firmware.
uint32_t diachibatdauchuongtrinh = 0x08001000;
Bước 2: Tắt hết các ngoại vi, xóa hết các cờ ngắt
HAL_RCC_DeInit();
Bước 3: Xóa hết các Pending Interrupt Request, đồng thời tắt System Tick
HAL_DeInit();
Bước 4: Tắt hết fault harder nếu trong chương trình Bootloader có sử dụng
SCB->SHCSR &= ~( SCB_SHCSR_USGFAULTENA_Msk |\
SCB_SHCSR_BUSFAULTENA_Msk | \
SCB_SHCSR_MEMFAULTENA_Msk ) ;
Bước 5: Đặt giá trị Main Stack Pointer:
__set_MSP(*((volatile uint32_t*) diachibatdauchuongtrinh));
Bước 6: Gán địa chỉ của hàm main() của chương trình đó cho thanh ghi PC
Chúng ta sẽ đọc giá trị tại Reset_Handler và sử dụng con trỏ hàm như ví dụ dưới để gán giá trị đó cho thanh ghi PC.
uint32_t JumpAddress = *((volatile uint32_t*) (diachibatdauchuongtrinh + 4));
void (*reset_handler)(void) = (void*)JumpAddress;
reset_handler();
Rồi sau đó hiệu chỉnh và đưa hình lưu đồ thuật toán trong chương trình Bootloader:
2. Các chương trình FOTA, Factory Firmware, Current Firmware
Sau khi chương trình Bootloader thực hiện xong ghi giá trị cho Stack Pointer và Program Counter thì chương trình sẽ nhảy tới một trong các chương trình FOTA, Factory Firmware hoặc Current Firmware tùy điều kiện.
Điểm chung của các chương trình này là bắt buộc phải dời bảng vector ngắt ngay khi bắt đầu thực thi các chương trình ứng dụng. Như đã giải thích ở mục 1, ta biết rằng địa chỉ đầu tiên của một chương trình cũng là địa chỉ của bảng vector ngắt của chương trình đó. Đồng thời, một chương trình này thì không thể tham chiếu và sử dụng được địa chỉ bảng Vector ngắt của chương trình khác được và ngược lại.
Ví dụ dưới đây, mình sẽ dời bảng vector ngắt của Factory Firmware. Có 2 cách để dời bảng vector ngắt:
Cách 1: Ngay sau khi vào hàm main() ta thêm đoạn chương trình:
/* USER CODE BEGIN 1 */
SCB->VTOR = (uint32_t)0x08001000;
/* USER CODE END 1 */
Cách 2: Vào điều chỉnh VECT_TAB_OFFSET trong system_stm32f1xx.c (Lấy ví dụ của chương trình Factory Firmware):
Sau đó chúng ta có thể lập trình như chương trình bình thường. Đơn giản phải không nào
=================================================
Qua bài viết này, hi vọng các bạn có thể hiểu về chương trình Bootloader và một số xử lý ban đầu ở chương trình ứng dụng. Bài tiếp theo mình sẽ giới thiệu về cấu trúc của một file chương trình hexadecimal và cách cập nhật chương trình cho thiết bị thông qua chương trình FOTA.
Xem tiếp phần 3: Hiểu tập tin chương trình ở định dạng Intel HEX được sử dụng trong quá trình FOTA
Theo dõi thêm:
– Cơ bản về cấu trúc và tính năng Vi xử lý ARM Cortex – Mx.
– Phân biệt các khái niệm Processor Core, Processor và Microcontroller trong Hệ thống nhúng
– Tổng hợp các bài hướng dẫn Lập trình vi điều khiển STM32F1
– Tổng hợp hướng dẫn Lập trình vi điều khiển STM32F4
Chúc các bạn thành công!
Nhóm TAPIT ARM R&D