CẨM NANG STM32 CĂN BẢN CẦN BIẾT – FreeRTOS – blinky2

Chương trình blinky2

Chuyển đến thư mục demo blinky2:

$ cd ~/stm32f103c8t6/rtos/blinky2

Chương trình ví dụ này sử dụng API FreeRTOS để thực hiện một chương trình nhấp nháy trong môi trường RTOS. Bản kê 5-1 minh họa file main.c. Từ danh sách này, hãy chú ý đến các file được sử dụng:

  • FreeRTOS.h
  • task.h
  • libopencm3/stm32/rcc.h
  • libopencm3/stm32/gpio.h

Bạn đã thấy các file tiêu đề libopencm3 trước đây. File tiêu đề task.h định nghĩa các macro và các hàm liên quan đến việc tạo các tác vụ. Cuối cùng, là FreeRTOS.h, cái mà mọi dự án cần để tùy chỉnh và cấu hình FreeRTOS. Chúng ta sẽ kiểm tra điều đó sau khi chúng ta hoàn tất main.c.

Chương trình main.c cũng định nghĩa nguyên mẫu hàm cho hàm có tên là vApplicationStackOverflowHook() trong các dòng 11-13 của Bản kê 5-1. FreeRTOS không cung cấp một nguyên mẫu hàm cho nó, vì vậy chúng ta phải cung cấp cho nó ở đây để tránh trình biên dịch gửi lỗi về nó.

Bản kê 5-1. Phần đầu của mã nguồn stm32/rtos/blinky2/main.c

0001: /* Simple LED task demo, using timed delays:
0002:  *
0003:  * The LED on PC13 is toggled in task1.
0004:  */
0005: #include "FreeRTOS.h"
0006: #include "task.h"
0007:
0008: #include <libopencm3/stm32/rcc.h>
0009: #include <libopencm3/stm32/gpio.h>
0010:
0011: extern void vApplicationStackOverflowHook(
0012:   xTaskHandle *pxTask,
0013:   signed portCHAR *pcTaskName);

Bản kê 5-2 liệt kê định nghĩa của hàm tùy chọn vApplicationStackOverflowHook(). Hàm này có thể đã bị bỏ ra khỏi chương trình mà không gây ra sự cố. Nó được cung cấp ở đây để minh họa cách bạn định nghĩa nó, nếu bạn muốn sử dụng nó.

Bản kê 5-2. blinky2/main.c, Hàm vApplicationStackOverflowHook()

0017: void
0018: vApplicationStackOverflowHook(
0019:   xTaskHandle *pxTask __attribute((unused)),
0020:   signed portCHAR *pcTaskName __attribute((unused))
0021: ) {
0022:   for(;;);    // Loop forever here..
0023: }

Nếu hàm được định nghĩa, FreeRTOS sẽ gọi nó khi nó bị tràn ngăn xếp. Điều này cho phép người thiết kế ứng dụng quyết định sử dụng nó như thế nào. Ví dụ, bạn có thể nháy LED màu đỏ để biểu thị lỗi chương trình.

Bản kê 5-3 minh họa tác vụ thực hiện LED nhấp nháy. Nó chấp nhận một đối số void *, không được sử dụng trong ví dụ này. __attribute((unused)) là thuộc tính gcc để chỉ ra trình biên dịch mà đối số args không được sử dụng, và nó ngăn cản các cảnh báo về nó.

Bản kê 5-3. blinky2/main.c, Hàm task1()

0025: static void
0026: task1(void *args __attribute((unused))) {
0027:
0028:   for (;;) {
0029:       gpio_toggle(GPIOC,GPIO13);
0030:       vTaskDelay(pdMS_TO_TICKS(500));
0031:   }
0032: }

Phần thân của hàm task1() khá đơn giản. Ở phía trên cùng của vòng lặp, nó bật/tắt trạng thái on/off của GPIO PC13. Tiếp theo, delay được thực hiện trong 500 ms. Hàm vTaskDelay() yêu cầu một số tick để trì hoãn. Nó thuận tiện hơn để thay thế cho mili giây. Macro pdMS_TO_TICKS() chuyển đổi mili-giây sang (nhịp) tick theo cấu hình FreeRTOS của bạn.

Tác vụ này, giả định rằng tất cả các thiết lập cần thiết đã được thực hiện trước. Điều này được thực hiện bởi chương trình chính, được minh họa trong Bản kê 5-4.

Bản kê 5-4. blinky2/main.c, Hàm main()

0034: int
0035: main(void) {
0036:
0037:   rcc_clock_setup_in_hse_8mhz_out_72mhz(); // For "blue pill"
0038:
0039:   rcc_periph_clock_enable(RCC_GPIOC);
0040:   gpio_set_mode(
0041:       GPIOC,
0042:       GPIO_MODE_OUTPUT_2_MHZ,
0043:       GPIO_CNF_OUTPUT_PUSHPULL,
0044:       GPIO13);
0045:
0046:   xTaskCreate(task1,"LED",100,NULL,configMAX_PRIORITIES-1,NULL);
0047:   vTaskStartScheduler();
0048:
0049:   for (;;);
0050:   return 0;
0051: }

Chương trình main() được định nghĩa là trả về dạng int trong dòng 34 và 35, mặc dù chương trình chính sẽ không trả về giá trị trong MCU này. Điều này chỉ đơn giản là đáp ứng các trình biên dịch mà nó phù hợp với tiêu chuẩn POSIX (Portable Operating System Inferface: Giao diện Hệ điều hành di động). Câu lệnh return trong dòng 50 không được thực hiện.

Dòng 37 minh họa một điểm mới – thiết lập tốc độ xung CPU. Đối với thiết bị Blue Pill của bạn, thông thường bạn cần gọi hàm này để có hiệu năng tốt nhất. Nó cấu hình xung nhịp sao cho HSE (high-speed external oscillator: bộ dao động bên ngoài tốc độ cao) sẽ sử dụng một tinh thể 8 MHz, nhân với 9 bởi một PLL (phase-locked loop: vòng lặp khóa pha), để đạt được tốc độ xung CPU là 72 MHz. Nếu không có hàm gọi này, chúng ta sẽ dựa vào xung nhịp RC (xung điện trở/tụ điện).

Dòng 39 kích hoạt xung nhịp GPIO cho cổng C. Đây là bước đầu tiên trong các bước thiết lập cho GPIO PC13, là chân điều khiển LED tích hợp. Các dòng 40-44 xác định các bước còn lại sao cho PC13 là một chân output, ở 2 MHz, trong cấu hình push/pull.

Dòng 46 tạo ra một tác vụ mới, sử dụng hàm có tên là task1(). Chúng ta cung cấp cho tác vụ một tên tượng trưng là “LED”, bạn có thể chọn tùy ý. Đối số thứ ba xác định có bao nhiêu ngăn xếp kiểu word được yêu cầu cho không gian ngăn xếp. Đối với nền tảng STM32, một word là bốn byte. Việc ước tính không gian ngăn xếp thường phức tạp và có nhiều cách để tính nó (xem Chương 21, “Khắc phục sự cố”). Bây giờ, chúng ta chấp nhận rằng 400 byte (100 từ) là đủ.

Đối số thứ tư trong dòng 46 trỏ tới bất kỳ dữ liệu nào bạn muốn chuyển cho tác vụ của mình. Nó không cần thiết ở đây, vì vậy chúng ta gán nó bằng NULL. Con trỏ này được chuyển tới đối số args trong task1(). Đối số thứ năm xác định mức độ ưu tiên của tác vụ. Chúng ta chỉ có một tác vụ trong ví dụ này (ngoài tác vụ chính). Chúng ta chỉ đơn giản là đặt mức độ ưu tiên cao cho nó. Đối số cuối cùng cho phép một tác vụ xử lý được trả về nếu chúng ta cung cấp con trỏ cho nó. Chúng ta không cần xử lý trả về, do đó gán nó bằng NULL.

Tạo một tác vụ đơn lẻ là không đủ để bắt đầu chạy nó. Bạn có thể tạo một số tác vụ trước khi khởi động bộ lập lịch trình tác vụ. Khi bạn gọi hàm FreeRTOS vTaskStartScheduler(), các tác vụ sẽ bắt đầu từ địa chỉ hàm mà bạn đã đặt tên trong đối số một.

Chương trình quan tâm việc lựa chọn các hàm gọi trước khi bắt đầu công việc lên lịch. Một số hàm nâng cao hơn chỉ có thể được gọi sau khi trình lập lịch biểu đang chạy. Vẫn còn những thứ khác chỉ có thể được gọi trước khi bộ lập lịch chạy. Kiểm tra tài liệu FreeRTOS khi cần thiết.

Khi bộ lập lịch tác vụ đang chạy, nó không trả về từ dòng 47 của Bản kê 5-4 trừ khi bộ lập lịch trình bị dừng lại. Trong trường hợp nó quay trở lại, thông thường phải đặt một vòng lặp (dòng 49) sau hàm gọi để ngăn không cho nó trả về (dòng 50).

=> Bài viết được trích từ sách : Cẩm nang STM32 (tập 1)

Build và Thử nghiệm blinky2

Với bộ lập trình của bạn được nối với thiết bị của bạn, hãy thực hiện như sau:

$ make clobber
$ make
# make flash

Dòng lệnh make clobber xóa bất kỳ thành phần được build hoặc build một phần để làm cho nó hoàn toàn biên dịch lại trong dự án. Dòng lệnh make flash sẽ gọi tiện ích st-flash để ghi chương trình mới vào thiết bị của bạn. Nhấn nút Reset nếu cần, nhưng nó có thể tự khởi động.

Code cho thấy LED tích hợp sẽ chuyển trạng thái mỗi 500 ms. Nếu bạn có máy hiện sóng, bạn có thể xác nhận rằng điều này thực sự xảy ra (tín hiệu ở chân PC13). Điều này không chỉ xác nhận rằng chương trình hoạt động như dự định, mà còn xác nhận rằng file FreeRTOS.h của chúng ta đã được cấu hình đúng cách.

=> xem thêm : Lập trình Arm STM32

Thực thi

Chương trình ví dụ khá đơn giản, nhưng chúng ta sẽ giải thích các đoạn code quan trọng:

  • Hàm task1() thực thi để bật tắt LED tích hợp. Điều này được hẹn giờ bởi timer thông qua việc sử dụng vTaskDelay().
  • Hàm main() được gọi là vTaskStartScheduler(). Điều này cho phép điều khiển bộ lập lịch FreeRTOS, khởi động và chuyển các tác vụ khác nhau. Các chủ đề chính sẽ tiếp tục thực hiện trong FreeRTOS (trong lịch trình) trừ khi một tác vụ dừng lên lịch trình.

Tác vụ task1() có một ngăn xếp được phân bổ từ vùng lưu trữ để thực thi (chúng ta đã cho nó giới hạn 100 từ). Nếu tác vụ đó đã từng bị xóa, bộ nhớ này sẽ được trả về vùng lưu trữ. Tác vụ chính hiện đang thực hiện trong bộ lập lịch FreeRTOS, sử dụng ngăn xếp nó được đưa ra.

Trong khi những điều này giống các điểm cơ bản để thực hiện, điều quan trọng là phải biết là tài nguyên được phân bổ ở đâu. Các ứng dụng lớn hơn cần phải phân bổ bộ nhớ và CPU một cách cẩn thận để không có tác vụ nào bị bỏ sót. Điều này cũng phác thảo cấu trúc điều khiển tổng thể đang hoạt động. Việc sử dụng đa tác vụ ưu tiên đòi hỏi phải có cách phân chia nhiệm vụ mới. Việc chia sẻ dữ liệu giữa các tác vụ yêu cầu phải sử dụng các nguyên tắc an toàn cho chủ đề. Ví dụ đơn giản này đơn giản hóa vấn đề này vì chỉ có một tác vụ. Các dự án sau này sẽ yêu cầu giao tiếp giữa các tác vụ.

=> Sách Arduino, ESP8266, STM32 : Sách tự động hóa