Học FreeRTOS, p1

 

Tôi bắt đầu series này với tư cách là người hoàn toàn mới về RTOS nói chung và FreeRTOS nói riêng. Tôi sẽ liệt kê các bình luận và code khi đọc các chương của cuốn Using The FreeRTOS Realtime Kernel.

Tại sao FreeRTOS? Sau một thời gian làm trong lĩnh vực hệ thống nhúng, và IOT, tôi nghĩ nếu một phần cứng không chạy được Linux nhúng, nó nên chạy với FreeRTOS và lwIP? Bạn có thể đưa luận điểm có những nơi mà FreeRTOS+lwIP không phù hợp, Contiki hay RIOT phù hợp hơn. Nhưng qủa thật khả năng kết nối trực tiếp và bảo mật cao từ node đến cloud mang lại sự tiện lợi vô cùng lớn so với việc sử dụng border router.

Do tôi không muốn phụ thuộc vào phần cứng khi làm quen với RTOS này, nên đã setup một môi trường mô phỏng trên Linux, ở https://github.com/nqd/freertos_linux_devl. Trong môi trường này v8.2.3 được sử dụng, lwIP v1.4.1 về sau cũng được tích hợp vào.

Môi trường này không tồn tại nếu không có bản port tuyệt vời của William Davy và fork của megakilo.

Những dòng lệnh sau sẽ chuẩn bị môi trường cho phần này và những phần sau.


git clone --recursive https://github.com/nqd/learn_freertos.git

cd freertos_linux_devl

make -f Makefile.tools

Nếu bạn tò mò, Makefile.tools để lấy freertos v8.2.3 xuống.

Bài đầu tiên, tạo task.


cd ../chapter1

make

./create_task.out

Bình luận

Nếu thay phần vTaskDelay(1000 / portTICK_RATE_MS); bằng vòng lặp for.

Với For delay

Cho mainDELAY_LOOP_COUNT gía trị 10000, kế qủa là task 1 được in ra liên tiếp, rồi đến task 2 và ngược lại.


Task 1 is running
Task 1 is running
...
Task 1 is running
Task 1 is running
Task 1 is running
Task 2 is running
Task 2 is running
...
Task 2 is running

Sau đó tăng mainDELAY_LOOP_COUNT lên 1000000 (tùy thuộc vào tốc độ của PC đang chạy chương trình), kết qủa Task 1/2 được in ra luân phiên nhau.

Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 2 is running
Task 1 is running

...

Nguyên nhân? Xem hình dưới. Mỗi task chạy một khoảng delta(T) = t2-t1, ứng với thời gian giữa các tick của kernel. mainDELAY_LOOP_COUNT càng nhỏ thì càng nhiều vòng lặp được thực hiện trong khoảng delta(T) này, dẫn đến Task x is running xuất hiện liên tục nhiều hơn.

freertos-task1-task2-same-priority-delay

Nếu cho task 2 có priority cao hơn, ví dụ bằng cách #define mainTASK2_PRIORITY (tskIDLE_PRIORITY + 2). Lúc này task 1 không có cơ hội được thực thi.


Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running

freertos-task-priorities

Việc delay bằng vòng lặp for dẫn đến các task có mức độ ưu tiên thấp hơn không có điều khiện thực thi. Đều này nên tránh khi viết chương trình.

vTaskDelay, không For

Quay trở lại với delay bằng vTaskDelay. Chương trình cho kết qủa


Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running

một cách tuần tự dù Task 2 có độ ưu tiên cao hơn Task 1. Vì sao?

Lúc này xuất hiện khái niệm Blocked/Ready state.

freertos-tasks

Mỗi task nằm trong một trong các trạng thái

  • Blocked là một tráng thái “Not Running”. Lúc này task đang chờ một trong hai sự kiện: Time hoặc Synchronization. Liên quan đến thời gian như Task X sẽ chuyển đến trạng thái Block trong 100 ms. Synchronization khi nó đang tương tác với các Task khác (semaphore, queue, …) hoặc interrupt.
  • Suspended là một trạng thái “Not Running”. Một Task vào trạng thái này khi được gọi với vTaskSuspend(), và thoát ra với vTaskResume() hoặc xTaskResumeFromISR(). Task với trạng thái này sẽ được được schedule bởi kernel.
  • Ready cũng là một trạng thái “Not Running”. Task này sẵng sàng để chạy, nhưng chưa chuyển sang Running.
  • Running là trạng thái Task được CPU thực thi.

Quay trở lại chương trình, Task 2 với ưu tiên cao hơn, được thực thi trước. Khi nó được chuyển sang trạng thái Blocked với hàm vTaskDelay, Task 1 được kernel gọi thực thi. Sau đó Task 1 cũng được chuyển sang trạng thái Blocked này.

Kernel sang trạng thái idle, và vApplicationIdleHook được gọi. Chương trình có thể đặt cpu sang chế độ ngủ nếu năng lượng là vấn đề của hệ thống.

Sau khoảng thời gian delay, Task 2 chuyển sang trạng thái Running, in ra terminal, rồi lại chuyển sang Blocked. Tương tự như vậy với Task 1.

Xác nhận: Các hình ở trên được lấy từ sách Using The FreeRTOS Realtime Kernel của Richard Barry.

chạy unity trên chip nhúng

Trong khi đọc cuốn Test Driven Development for Embedded C tôi được giới thiệu làm quen với unity. Unity là test framework gọn nhẹ, viết hoàn toàn bằng C; điều này có nghĩa là  nó có thể dịch chạy trên PC và cho cả trên MCU nhúng.

Ngoài việc viết test unit chạy trên PC, tôi cũng muốn các test unit này chạy trực tiếp trên MCU. Tại sao? Tôi nghĩ code sẽ chạy trên MCU, tại sao không test trực tiếp trên đối tượng hoạt động, đặt biệt khi trình một trình biên dịch phổ biến cho hệ thống nhúng cũng có nhiều lỗi.

Để khởi đầu cho hành trình này, tôi sử dụng CC3200 MCU với arm-none-eabi-gcc 4.8.2. Để tiết kiệm thời gian chạy các test tôi load chương trình trực tiếp xuống RAM thông qua JTAG để chạy thay vì flash. Nguyên nhân: quá trình test càng chậm thì tôi càng ít muốn chạy test, điều này không tốt cho quá trình phát triển TDD, khi lời khuyên là chạy test cho mỗi lần commit.

Unity có một vài file, nên liên kết đến nó sẽ khá đơn giản:

UNITY_ROOT=$(PROJECTROOT)/tools/unity

UNITY_DIR = \
$(UNITY_ROOT)/src \
$(UNITY_ROOT)/extras/fixture/src

VPATH += $(UNITY_DIR)
IPATH += $(UNITY_DIR)

Bài test đầu tiên của tôi là kiểm tra flash driver của CC3200. Và khi dịch thì báo lỗi như bên dưới.

/usr/lib/gcc/arm-none-eabi/4.8.2/../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-writer.o): In function `_write_r':
/build/buildd/newlib-2.1.0/build/arm-none-eabi/armv7e-m/newlib/libc/reent/../../../../../../newlib/libc/reent/writer.c:58: undefined reference to `_write'
/usr/lib/gcc/arm-none-eabi/4.8.2/../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-closer.o): In function `_close_r':
/build/buildd/newlib-2.1.0/build/arm-none-eabi/armv7e-m/newlib/libc/reent/../../../../../../newlib/libc/reent/closer.c:53: undefined reference to `_close'
/usr/lib/gcc/arm-none-eabi/4.8.2/../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-fstatr.o): In function `_fstat_r':
/build/buildd/newlib-2.1.0/build/arm-none-eabi/armv7e-m/newlib/libc/reent/../../../../../../newlib/libc/reent/fstatr.c:62: undefined reference to `_fstat'
/usr/lib/gcc/arm-none-eabi/4.8.2/../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-isattyr.o): In function `_isatty_r':
/build/buildd/newlib-2.1.0/build/arm-none-eabi/armv7e-m/newlib/libc/reent/../../../../../../newlib/libc/reent/isattyr.c:58: undefined reference to `_isatty'
/usr/lib/gcc/arm-none-eabi/4.8.2/../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-lseekr.o): In function `_lseek_r':
/build/buildd/newlib-2.1.0/build/arm-none-eabi/armv7e-m/newlib/libc/reent/../../../../../../newlib/libc/reent/lseekr.c:58: undefined reference to `_lseek'
/usr/lib/gcc/arm-none-eabi/4.8.2/../../../arm-none-eabi/lib/armv7e-m/libc.a(lib_a-readr.o): In function `_read_r':
/build/buildd/newlib-2.1.0/build/arm-none-eabi/armv7e-m/newlib/libc/reent/../../../../../../newlib/libc/reent/readr.c:58: undefined reference to `_read'

 

Nguyên nhân do Unity sử dụng UNITY_OUTPUT_CHAR để in kết quả. Theo mặt định, UNITY_OUTPUT_CHAR liên kết đến putchar() để kết quả hiển thị lên terminal nhờ libc.a khi dịch trên PC. Trong trường hợp CC3200 (và hầu hết các hệ thống nhúng khác), OS nhúng không cung cấp sẵn implement cho hàm này, dẫn đến lỗi khi dịch. Tôi cần chỉ dẫn unity sử dụng hàm khác để in ra cổng UART.

Nhìn vào unity_internals.h, tôi thấy cách để liên kết con trỏ UNITY_OUTPUT_CHAR này mà không thay đổi thư viện unity bẳng lệnh tiền xử lý

#ifdef UNITY_INCLUDE_CONFIG_H
#include "unity_config.h"
#endif

...

#ifndef UNITY_OUTPUT_CHAR
//Default to using putchar, which is defined in stdio.h
#include <stdio.h>
#define UNITY_OUTPUT_CHAR(a) putchar(a)
#else
//If defined as something else, make sure we declare it here so it's ready for use
extern int UNITY_OUTPUT_CHAR(int);
#endif

Vậy thêm cờ ở  Makefile

CFLAGS+=-DUNITY_INCLUDE_CONFIG_H

, đồng thời thêm file unity_config.h

#define UNITY_OUTPUT_CHAR(x) PRINT(x)

PRINT(x) được thực thi ở đâu đó trong chương trình, nhằm đẩy byte x ra cổng UART.

 

Đây là kết quả khiêm tốn cho một số test:

Unity test run 1 of 1
......

-----------------------
6 Tests 0 Failures 0 Ignored

 

Kết luận: Unity là một công cụ nhỏ rất tiện lợi để test chương trình, có thể chạy trên PC hoặc trên MCU nhờ vào việc nó sư dụng C thuần túy và sự gọn nhẹ của thư viện này.

debug custom cc3200 board with launchpad

Kudos for TI that they are supporting GCC and FreeRTOS as first citizens in their Internet on Chip cc3200. TI provides the linker, startup code which run out of box. I can also manage to run GDB on the launchpad also.

Last days I get a couple of custom cc3200 boards. The board has 4 pins for JTAG. My first attempt is to use the JTAG debugging of the launchpad, since I don’t want to go through flashing step. With make debug, the program is loaded to RAM, and execute from here; and debugging with GDB is so valuable compare to, say, printf.

The hardware connection is deadly easy. Launchpad give 4 JTAG pin rough jumper J6-J9, in the power side; connect them direct to MCU JTAG pin. Done.

This is the test, running freertos-demo example.

cc3200_jtag

Pic: Connect a custom board to Launchpad with serial and jtag.

30 buck for the launchpad, with JTAG, a dev board, is a good spending isn’t it.

cc3200 paho client breaks freertos task scheduling

Paho MQTT client already provides an adaptation layer for CC3200 at MQTTClient-C/src/cc3200. In basic, the embedded-C Paho requires 2 resources from the device: timer and socket.

cc3200

The CC3200 porting runs out of the box; however, when you run with FreeRTOS, the task scheduling of the OS may broken.

Why? Look at MQTTCC3200.c, we find that the timer is got from Systick interrupt handling, but Systick is occupied already for FreeRTOS scheduling. It seems that most of Cortex M3/M4 FreeRTOS use Systick for kernel timing. Good practice is to avoid the use of it in app layer.

Work around? Need timer? Get from OS kernel itself, which retrieve from Systick,  at xTaskGetTickCount().

Không, IFTTT

Tôi làm làm việc với một công ty camera, họ có khoảng vài trăm ngàn user. Lãnh đạo nghe ngóng thấy IoT có vẻ hot, liếc qua thấy các công ty Cam khác đều có nhãn IFTTT. Nhiệm vụ được đặt ra: gắn mác IFTTT lên sản phẩm.

IFTTT cung cấp cơ chế nếu – thì cho các dịch vụ mạng. Tuy nhiên phải nó IFTTT khá tệ trong việc cung cấp thông tin cho các dịch vụ khác tích hợp vào. Điều khiến tôi khá thất vọng là cho đến thời điểm tôi viết bài này, không một hướng dẫn nào của IFTTT cho developer để làm việc với dịch vụ của họ. Cái duy nhất tôi thấy là Partnership Inquiries.

Điền vào form này, chờ vài ngày, không có trả lời. Điền lại lần 2 cũng không thấy trả lời. WTF.

Gởi email cho họ trực tiếp, tôi nhận được trả lời như sau:

Thank you for your interest in IFTTT. We’ve received your request to join our partner platform. Given the influx of Channel submissions, we aren’t in a position to move forward at this time. However, because you’ve shared your information with us, we’ll be sure to be in touch when we have more to share.

Nghĩa là sẽ chờ không biết bao giờ stack của họ mới được giải phóng, oppp.

Nhu cầu về tự động dịch vụ đến thời điểm này khá phổ biến đến mức có một số khác tựa như IFTTT ra đời, kể đến như Zapier, WeWiredWeb, Elastic.io.

Liếc qua Zapier, họ có ít các app được kết nối hơn, nhưng tài liệu hỗ trợ cho devl thì tuyệt vời. Zapier cung cấp chi tiết cách thức để tôi có thể tích hợp với backend của mình, thông qua một loạt ví dụ. Tôi có thể tạo private app trước khi public nó. Điểm cộng cho Zapier.

nhìn ké vào bên trong amazon dash

Cách đây hơn 1 năm, tôi và một người bạn tên Mike có dự định tạo một thiết bị nhỏ trợ giúp cho việc mua sắm, dịch vụ trong ngôi nhà tiện hơn. Ví dụ nếu sữa (hoặc giấy toilet) hết, một lệnh order gởi đến một kênh mua sắm nào đấy được lựa chọn sẵn, sau khi xác nhận, bên cung cấp sẽ chuyển sản phẩm đến nhà của tôi. Tôi lúc đó chưa nghĩ đến việc tạo ra ngôi nhà thông minh, nó chỉ là một công cụ hỗ trợ.

Sau khi bắt tay vào làm prototype, tôi phát hiện ra amazon đang rục rịch ra mắt một sản phẩm tương tự, sau này có tên là dash. Chúng tôi ngưng việc prototype ngay sau đó.

amazon_dash_button_bathroom_placement_-640x640

Amazon Dash (AD) được cung cấp cho người dùng miễn phí. Rất thú vị khi Ted Benson thông báo đã hack được AD này. Tôi thì không nghĩ đây là hack, khi Ted Benson chỉ lắng nghe các gói ARP, rồi ghi nhận thông điệp này lên google sheet.
Lục thêm một chút nữa, tôi tìm thấy em AD này bị mổ bụng bởi Matthew Witheiler.

Đi vào kỹ thuật một xíu, AD chạy với một SiP USI (Avnet) 850101, gồm wireless module BCM43362 và MCU STM32F205. Partical có bán một module tên Photon với gía $19, cao hơn một chút so với ESP8266, sử dụng cùng SiP. Tôi sẽ không chạy Linux trên này, thay vào đó FreeRTOS, lwIP sẽ phù hợp hơn. AD sử dụng một pin AAA, Amazon báo nó sẽ sử dụng được khoảng vài năm. Đều này sẽ đạt được khi AD luôn ở trang thái ngủ. Sau khi hết pin, tôi sẽ làm gì? Amazon chắc sẽ bảo rằng hãy quẳng nó đi.

AD đưa ra một minh chứng rằng phần cứng IoT nên rất rẻ, thay vào đó họ sẽ bán dịch vụ (hàng hóa) kèm theo. Tôi sẽ rất mong chờ ai đó thật sực hack được AD, và viết lại chương trình lên đó.

*Adafruit đã có bài viết về cách lập trình lại chương trình cho AD.

profiles for fota client js

My first fota-client-js program is deadly simple. When I come to work with multiple fota servers, I found that fix setup of domain server, key in fota-client.js can not be stand.

A little bit improvement, fota client by default will look for setting at $HOME/.fotaclient-config.json to choose the server. I can add many profile in this config file, as long as a profile contains HOST, PROTOCOL, and APIKEY. An example

{
   "DEMO": {
      "HOST": "103.253.146.183",
      "PROTOCOL": "http",
      "APIKEY": "mykey"
   },
  "LOCAL": {
      "HOST": "myip",
      "PROTOCOL": "http",
      "APIKEY": "mykey"
   }
}

Makefile is updated also. A full register will now be like
make register FOTA_CONFIGFILE=file FOTA_PROFILE=DEMO

Still simple enough to use.