Bài 15: Bootloader STM8S với IAR.
Chào các bạn, hôm nay mình sẽ hướng dẫn các bạn một số bước và lưu ý để viết chương trình code bootloader và application cho STM8S.
- Bootloader và Application là gì.
- Bootloader là phần code được viết sẵn và nạp trong một vùng nhớ flash cố định, code này hầu như không thay đổi và hoạt động phải ổn định(bạn cứ tưởng tượng bootloader cho một sản phẩm đã bán đến khách hàng mà update FW bị lỗi và phải thu hồi về để nạp lại bootloader thì hơi chuối…).Code này thường chiếm ít dung lượng của bộ nhớ vì mục đích của nó chỉ dung để cập nhật Firmware và cung cấp một số ít thông tin cần thiết. phần code này thường được nạp bằng mạch nạp(debugger) và nạp một lần duy nhất trước khi Release.
- Application Firmware là phần code Firmware sẽ thường xuyên thay đổi, ví dụ sản phẩm có tính năng A, người dùng muốn thêm tính năng B, giả sử phần cứng của bạn đã hỗ trợ tính năng B, bạn chỉ cần viết thêm phần code của tính năng B và update lại Application FW thông qua phần code Bootloader.
- Bootloader : phần này sẽ chứa code của bootloader, mục đích chính để update FW, code build cho phần phải nhỏ hơn 2 kbyte. Tùy thuộc vào khả năng viết code và ứng dụng nó sẽ có dung lượng khác nhau, size code của phần này càng nhỏ càng tốt, để tiết kiệm vùng nhớ cho Application.
- Aplication : vùng nhớ này sẽ chứa code thay đổi theo từng version và chiếm dung lượng lớn nhất. Size của code buid ra không được vượt quá dung lượng quy định là 5.75 kbyte.
- Config : vùng này sẽ chứa thông số config như version bootloader, phần bảo mật để cho phép upload code khi có yêu cầu, các thông số hoạt động cần thiết cho bootloader…
- Một số điểm lưu ý khi viết chương trình bootloader và application.
- Làm sao để nhảy từ vùng nhớ application sang bootloader và ngược lại.
- Nhảy tới vùng nhớ application từ code bootloader : Chúng ta sẽ sử dụng ngôn ngữ máy để nhảy tới địa chỉ mong muốn, ở đây vùng nhớ application của mình bắt đầu từ 0x00 8800(tương ứng bootloader từ 0x00 8000 -> 0x00 87FF = 2Kbyte). Lệnh để nhảy tới tương ứng là asm("JPF $8800").
- Nhảy tới vùng nhớ bootloader từ application: Vùng nhớ bootloader được quy định bắt đầu từ 0x00 8000 nên việc nhảy tới vùng nhớ này tương ứng với việc reset MCU(thường là soft reset). Có nhiều cách để reset MCU, ở đây mình dùng cho phép Independent watchdog(IWDG) hoạt động và chờ cho đến khi bộ IWDG tràn, lúc này MCU sẽ được reset. Cú pháp như sau:
- Chuyển đổi các vector ngắt khi chương trình thực thi ở vùng nhớ application.
- Cấu hình IDE để thay đổi địa chỉ bắt đầu build khác địa chỉ mặc địnhs 0x00 8000.
- Tăng tốc độ xóa và ghi trong bộ nhớ flash.
- Define trực tiếp trong thẻ config:
- Hoặc bỏ command trong file stm8s.h.
- Tối ưu code để code bootloader chiếm dung lượng nhỏ nhất.
- Tối ưu thuật toán, biến để giảm dung lượng code nhỏ nhất.
- Sử dụng thanh ghi thay thế cho các function viết sẵn: Bạn có thể xem chi tiết các function, xóa bỏ các define kiểm tra tính safety code, sử dụng các thanh ghi trong chính function con để giảm size code. Chúng ta chỉ nên modify các function này khi cần tối ưu code flash, nếu còn nhiều bộ nhớ thì không nên modify function của nhà sản xuất vì nó sẽ giảm độ an toàn cũng như hiệu năng của code.
- Tối ưu phép tính toán xor, or, and … với các giá trị tương đồng.
- Demo chạy thử.
- 1 tool update dùng để chạy trên máy tính.
- 1 file bootloader.
- 2 file application chứa 2 version khác nhau và tốc độ thực thi led trên chân PD2 lần lượt là 500ms và 2000ms.
- 1 module STM8S003F3P6, 1 module UART-USB, 1 mạch nạp stlink_v2.
- B1. Nạp file code bootloader thông ST visual programmer bằng mạch nạp stlink.
- B1. Nạp file code bootloader thông ST visual programmer bằng mạch nạp stlink.
- B2. Kết nối board với máy tính thông qua module USB_UART.
- B3. Mở tool và chọn cổng UART mà máy tính đã nhận.
- B4.Chọn đến file hex application với thời gian nháy led là 100ms và version là 1.0 và nhấn nút Upgrade_8s.
- B5. Chọn 1 file application khác với thời gian nháy led là 2000ms và version là 1.2. Kết quả sau khi update là:
Để hiểu về bootloader, trước tiên bạn cần hiểu thứ tự thực thi code và vùng nhớ của vi điều khiển, ở đây là STM8S003F3P6. Với MCU này thì bộ nhớ Flash(bộ nhớ lưu trữ code) sẽ là 8Kbyte, và địa chỉ lưu code sẽ là từ 0x00 8000 đến 0x00 9FFF. Chương trình cũng sẽ load từ địa chỉ 0x00 8000 đến 0x00 9FFF và khi bị reset thì MCU cũng sẽ load chương trình theo thứ tự này.
Bootloader là một ứng dụng được lập trình sẵn dụng để cập nhật Firmware cho các MCU mà không cần tới mạch nạp. Bootloader sử dụng một trong các chuẩn truyền thông có sẵn(One wire, UART, USB, SPI, CAN, I2C …) để cập nhật lại bộ nhớ flash giúp thực thi các ứng dụng một cách linh hoạt hơn. Bootloader và Application thường sử dụng cho các sản phẩm có phần giao tiếp như Bluetooth, wifi, usb, Rs232, zigbee, rs232… Ví dụ : bạn muốn cập nhật Firmware từ xa cho một sản phẩm mà không cần tháo dỡ phần cứng hoặc không cần đến tận nơi có phần cứng đó.
Tùy thuộc từng dự án và mục đích sử dụng bộ nhớ flash sẽ chia làm nhiều phần khác nhau. Ở đây, mình ví dụ STM8S003F3P6 sẽ chia code bootloader là 2kbyte, và application sẽ là 5.75kbyte, config sẽ là 0.25kbyte. Thì sơ đồ vùng nhớ flash sẽ như sau:
Lưu đồ giải thuật của việc sử dụng bootloader và application như sau :
Ở đây mình viết bootloader và application bằng IAR cho STM8, một số lưu ý khi viết chương trình như sau:
IWDG->KR = IWDG_KEY_ENABLE; while (1); // Wait until reset occurs from IWDG
Địa chỉ các vector ngắt mặc định bắt đầu từ 0x82000000, khi chuyển sang vùng nhớ application chúng ta cũng phải chuyển đổi vector ngắt để khi nhảy tới vùng nhớ application ngắt vẫn thực thi bình thường và không còn thực thi ở vùng nhớ bootloader. Mình có search việc thực thi này và tham khảo một số page khác. Vector ngắt của mình sẽ bắt đầu địa chỉ 0x82008800. Việc config sẽ thực thi ở code bootloader và khai báo như sau:
__root const long reintvec[]@".intvec"= { 0x82008080,0x82008804,0x82008808,0x8200880c, 0x82008810,0x82008814,0x82008818,0x8200881c, 0x82008820,0x82008824,0x82008828,0x8200882c, 0x82008830,0x82008834,0x82008838,0x8200883c, 0x82008840,0x82008844,0x82008848,0x8200884c, 0x82008850,0x82008854,0x82008858,0x8200885c, 0x82008860,0x82008864,0x82008868,0x8200886c, 0x82008870,0x82008874,0x82008878,0x8200887c, };
Trong config của iar có thể thay đổi địa chỉ build bắt đầu việc load file .icf, file này có chức năng cấu hình vùng nhớ, eeprom, stack heap size…. File mặc định nếu chưa cấu hình gì sẽ nằm trong thư mục cài đăt iar. Ở trên máy của mình sẽ là C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.0\stm8\config\lnkstm8s003f3.icf với MCU sử dụng là STM8S003F3P6.
Việc đổi địa chỉ tiến hành như sau: copy file .icf tương ứng với project bạn sử dụng vào trong thư mục chứa project, sửa địa chỉ mong muốn rồi lưu lại.
Vào config project và chỉnh sửa lại link đến file .icf vừa thêm.
Bình thường chúng ta sẽ sử dụng các function có sẵn trong thư viện “stm8s_flash” để thao tác với bộ nhớ flash, tuy nhiên tốc độ thực thi của chúng khá chậm, nhà sản xuất của đã hỗ trợ một số function khác dùng RAM để thực thi(chứa IN_RAM ở đầu function), tốc độ này nhanh hơn nhiều lần so với các function thông thường, tuy nhiên nó sẽ tiêu tốn RAM của vi điều khiển. Nếu sử dụng không tốt có thể dẫn tới treo vi điều khiển. Các function thao tác với flash cũng nằm trong file thư viện “stm8s_flash”. Để sử dụng tính năng này chúng ta phải define trong chương trình, bạn có thể sử dụng 1 trong 2 cách sau:
Ví dụ: Các biến toàn cục có giá trị tối đa là 255 thì chỉ nên sử dụng uint8_t không nên sử dụng uint16_t hay uint32_t.
Ví dụ: Một function Init_GPIO sẽ có chi tiết các code như sau:
Khi các bạn đã xác định rõ chân GPIO là chân input/output, interrupt/none interrupt, pull up/push pull thì không cần if else và bỏ đi đoạn check safety code. Code được viết lại như sau :
Ví dụ: Các phép toán or với giá trị 0 thì nên được xóa đi ví dụ: CLK->CKDIVR |= (uint8_t)CLK_PRESCALER_HSIDIV1; với define CLK_PRESCALER_HSIDIV1 = (uint8_t)0x00. Phép tính sẽ là CLK->CKDIVR |= 0x00; -> giá trị thanh ghi này không đổi -> chúng ta sẽ command luôn phép tính này để không tốn bộ nhớ cho phép tính này: //CLK->CKDIVR |= (uint8_t)CLK_PRESCALER_HSIDIV1;
Ở đây mình dùng Visual studio viết 1 tool update Firmware từ máy tính thông qua module UART USB. Các file cần chuẩn bị như sau:
Lưu ý: các bạn cần config và chọn đúng mạch nạp trước khi nạp bootloader.
Sau khi update xong các bạn đọc version là 1.0 và quan sát led ở chân PD2 sẽ đổi trạng thái với thời gian là 100ms.
Note: Vì tính bảo mật và là thành quả của việc nghiên cứu nên mình sẽ không update source code như các hướng dẫn trước mong các bạn thông cảm. Bạn nào trong quá trình làm gặp khó khăn gì cứ comment hay inbox mình, mình sẵn lòng hỗ trợ trong thời gian ngắn nhất có thể. Bài viết có thể có một số thông tin chưa đúng, mong nhận đươc sự góp ý của các bạn. Chúc các bạn thành công!!!