Khi lập trình vi điều khiển, nhiều trường hợp chúng ta cần làm việc với các dữ liệu ở dạng chuỗi ký tự, ví dụ như đóng gói các dữ liệu thành một chuỗi để lưu trữ hoặc gửi đi, nhận dữ liệu, bóc tách dữ liệu từ các bản tin nhận được để sử dụng. Xử lý chuỗi là một chủ đề quan trọng khi lập trình vi điều khiển, nhất là khi các hệ thống nhúng, IoTs ngày càng phát triển dẫn đến việc trao đổi dữ liệu giữa các thiết bị ngày càng nhiều. Xử lý chuỗi là một thử thách với rất nhiều bạn sinh viên / kỹ sư phần mềm nhúng – những người mới bắt đầu. Với tài liệu này, các kiến thức cơ bản liên quan đến khai báo, lưu trữ, xử lý chuỗi sẽ được tổng hợp lại và được mô tả, hướng dẫn thông qua những ví dụ, tình huống cụ thể, thường gặp.
Phần 1 – Một số khái niệm và các hàm chức năng
Phần 2 – Các hàm chức năng thường gặp
Tiếp tục với phần 1 và phần 2 của chuỗi bài viết, tại phần 3 (phần cuối) mình sẽ hướng dẫn các bạn thực hiện Ví dụ thực hành xử lý chuỗi trên Vi điều khiển STM32.
Phần 3. Hướng dẫn ví dụ thực hành xử lý chuỗi trên Vi điều khiển
3.1. Ví dụ thực hành
Vi điều khiển (device) và máy tính giao tiếp với nhau qua giao tiếp UART thông qua giao diện terminal (ví dụ Hercules). Trên thiết bị sẽ có một mã ID của thiết bị và có 2 đèn led (LED1 và LED2). Trên máy tính, thông qua giao diện terminal có thể cấu hình và đọc được ID của thiết bị, thay đổi trạng thái của các đèn LED. Viết chương trình điều khiển đơn giản sử dụng các lệnh sau để giao tiếp giữa máy tính và thiết bị:
- Cú pháp cài đặt mã ID cho thiết bị và phản hồi:
- Máy tính: +SET_DEV_ID:<ID>\r\n ; ID dài 6 ký tự.
- Thiết bị: +OK\r\n hoặc +ERROR\r\n
- Lấy mã ID của thiết bị
- Máy tính: +GET_DEV_ID\r\n
- Thiết bị: +ID:123456\r\nOK\r\n hoặc +ERROR:undefined\r\n hoặc +ERROR
- Máy tính: +GET_DEV_ID\r\n
- Cấu hình trạng thái của đèn LED
- Máy tính: +SET_LED_ STATE:n,s\r\n
- n là đèn led muốn cấu hình (n = 1, 2)
- s là trạng thái muốn cấu hình, s = 0: tắt LED, s = 1: bật LED.
- Thiết bị: +OKE\r\n hoặc +ERROR\r\n
- Máy tính: +SET_LED_ STATE:n,s\r\n
- Lấy trạng thái của đèn LED
- Máy tính: +GET_LED_STATE:n\r\n
- n là đèn LEDmuốn lấy trạng thái (n = 1, 2)
- Thiết bị: +LED:n,s\r\nOK\r\n
- n là đèn LEDmuốn lấy cấu hình (n = 1, 2)
- s là trạng thái (ON, OFF).
- Máy tính: +GET_LED_STATE:n\r\n
3.2. Phân tích bài toán
Chúng ta cần phải xem qua các command và phản hồi của nó để lựa chọn cách xử lý.
- Để nhận dạng các lệnh thì phải xem trong dữ liệu nhận được có các command hay không bằng cách sử dụng lệnh strstr() hoặc sử dụng lệnh so sánh chuỗi strcmp().
- Như các command ở trên, mình nhận thấy rằng các command đều có độ dài chuỗi sẽ không thay đổi, lúc này mình sẽ dựa vào đó để kiểm tra lệnh có đúng hay không, lúc này mình sẽ sử dụng hàm strlen().
- Ở các câu lệnh cấu hình thì sẽ chứa mã ID, đèn LED và trạng thái của LED cần cấu hình, chúng ta cần phải tách được các tham số và giá trị này để có thể xử lý đúng. Mình thường sử dụng hàm strtok() để ngắt chuỗi nhận được thành các token rồi xử lý.
- Một số phản hồi chứa nhiều định dạng khác nhau như số nguyên, mảng ký tự cần chuyển thành một chuỗi để truyền đi thì sử dụng hàm sprintf().
- Ngoài ra, mình còn sử dụng các hàm như atoi() để chuyển từ ký tự thành số nguyên.
3.3. Cấu hình phần cứng
- Khởi tạo giao tiếp UART với các tham số mặc định, bật ngắt.
- Cấu hình 2 chân OUTPUT cho 2 đèn LED.
3.4. Mã chương trình tham khảo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
#include "main.h" #include <string.h> #include <stdio.h> #include <stdlib.h> enum eCommand { SET_DEV_ID = 1, SET_LED_STATE, GET_DEV_ID, GET_LED_STATE, }; UART_HandleTypeDef huart2; char rxBuffer[50] = {0}; //Khai báo bộ đệm để chứa dữ liệu đến enum eCommand eCmd = 0; char deviceID[7] = {0}; //Lưu ID của thiết bị uint8_t ledOrder = 0; //Số thứ tự của led uint8_t ledState = 0; //Trạng thái của đèn led uint8_t rxFlag = 0; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART2_UART_Init(void); /* In phản hồi ra màn hình terminal */ void response(char *string); /* Xoá bộ đệm nhận dữ liệu */ void deleteRxData(); /* Kiểm tra dữ liệu nhận được là command nào */ void checkCommand(); /* Hàm cấu hình ID cho thiết bị */ void setDeviceID(); /* Hàm lấy ID của thiết bị */ void getDeviceID(); /* Hàm cấu hình trạng thái cho đèn led */ void setLedState(); /* Hàm lấy trạng thái của đèn led */ void getLedState(); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); HAL_Delay(2000); HAL_UARTEx_ReceiveToIdle_IT(&huart2, (uint8_t *) rxBuffer, 50); while (1) { if(rxFlag == 1) { commandHandler(); rxFlag = 0; } } } /* In phản hồi ra màn hình terminal */ void response(char *string) { HAL_UART_Transmit(&huart2, (uint8_t *) string, strlen(string), 1000); } /* Xoá bộ đệm nhận dữ liệu */ void deleteRxData() { memset(rxBuffer, '\0', 50); } /* Kiểm tra dữ liệu nhận được là command nào */ void checkCommand() { if( strstr(rxBuffer, "*SET_DEV_ID:") != NULL) eCmd = SET_DEV_ID; else if(strstr(rxBuffer, "*GET_DEV_ID") != NULL) eCmd = GET_DEV_ID; else if(strstr(rxBuffer, "*SET_LED_STATE:") != NULL) eCmd = SET_LED_STATE; else if(strstr(rxBuffer, "*GET_LED_STATE:") != NULL) eCmd = GET_LED_STATE; else eCmd = 0; } /* Hàm cấu hình ID cho thiết bị */ void setDeviceID() { /* Kiểm tra độ dài của command +SET_DEV_ID:123456\r\n */ if(strlen(rxBuffer) == 20) { /*Tách ID thiết bị */ //Cách 1: sử dụng strtok char *token = NULL; token = strtok(rxBuffer, ":"); //+SET_DEV_ID token = strtok(NULL, "\r"); //123456 memcpy(deviceID, token, 6); //deviceID = 123456; token = NULL; //Cách 2: sử dụng memcpy //memcpy(deviceID, rxBuffer + 12, 6); response("\r\n+OK\r\n "); } else { response("\r\n+ERROR:error command\r\n"); } } /* Hàm lấy ID của thiết bị */ void getDeviceID() { /* Kiểm tra độ dài command +GET_DEV_ID\r\n */ if(strlen(rxBuffer) == 13) { /* Kiểm tra độ dài của deviceID */ if(strlen(deviceID) == 0) { response("\r\n+ERROR:undefined\r\n"); } else { char *buffer = malloc(20 * sizeof(char));//cấp phát động memset(buffer, '\0', 20); //xoá bộ đệm buffer sprintf(buffer, "\r\n+ID:%s\r\nOK\r\n", deviceID); response(buffer); free(buffer); buffer = NULL; } } else { response("+ERROR:error command\r\n"); } } /* Hàm cấu hình trạng thái cho đèn led */ void setLedState() { /* Kiểm tra độ dài của command +SET_LED_STATE:n,s\r\n (n = 1, 2), (s = 0, 1)*/ if(strlen(rxBuffer) == 20) { /* Tách số thứ tự của led */ char *token = NULL; token = strtok(rxBuffer, ":"); //+SET_LED_STATE token = strtok(NULL, ","); //n ledOrder = atoi(token); /* Tách trạng thái của led */ token = strtok(NULL, "\r"); //s ledState = atoi(token); token = NULL; if((ledState == 0) || (ledState) == 1) { if(ledOrder == 1) { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, ledState); response("\r\nOK\r\n"); } else if(ledOrder == 2) { HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, ledState); response("\r\nOK\r\n"); } else response("\r\n+ERROR:undefined led\r\n"); } else { response("\r\n+ERROR:undefined state\r\n"); } } else { response("\r\n+ERROR:error command\r\n"); } } /* Hàm lấy trạng thái của đèn led */ void getLedState() { /* Kiểm tra độ dài command +GET_LED_STATE:n\r\n */ if(strlen(rxBuffer) == 18) { char *token = NULL; token = strtok(rxBuffer, ":"); //+SET_LED_STATE /* Tách thứ tự của led */ token = strtok(NULL, "\r"); //s ledOrder = atoi(token); token = NULL; char buffer[20] = {0}; if(ledOrder == 1) { sprintf(buffer, "\r\n+LED:%d,%d\r\nOK\r\n", ledOrder, HAL_GPIO_ReadPin(LED1_GPIO_Port, LED1_Pin)); response(buffer); } else if(ledOrder == 2) { sprintf(buffer, "\r\n+LED:%d,%d\r\nOK\r\n", ledOrder, HAL_GPIO_ReadPin(LED2_GPIO_Port, LED2_Pin)); response(buffer); } else response("\r\n+ERROR:undefined led\r\n"); } else { response("\r\n+ERROR:error command\r\n"); } } /* Hàm xử lý chính */ void commandHandler() { switch(eCmd) { case SET_DEV_ID: setDeviceID(); break; case GET_DEV_ID: getDeviceID(); break; case SET_LED_STATE: setLedState(); break; case GET_LED_STATE: getLedState(); break; default: break; } eCmd = 0; deleteRxData(); } /* Hàm gọi lại xử lý phục vụ ngắt UART */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if(huart -> Instance == USART2) { checkCommand(); rxFlag = 1; //HAL_UART_Transmit(huart, (uint8_t *) "\n", 1, 100); //HAL_UART_Transmit(huart, (uint8_t *) rxBuffer, strlen(rxBuffer), 100); HAL_UARTEx_ReceiveToIdle_IT(huart, (uint8_t *) rxBuffer, 50); } } |
3.5. Kết quả thực hiện
Hình 1. Cài đặt mã ID và lấy mã ID trên thiết bị
Hình 2. Cấu hình trạng thái LED và lấy trạng thái LED trên thiết bị
TỔNG KẾT
Qua chuỗi bài viết này, chúng ta đã tìm hiểu về khai báo chuỗi và xử lý chuỗi bằng những hàm thường sử dụng được hỗ trợ bởi các thư viện C, các ví dụ cụ thể của các hàm cũng đã được trình bày một cách chi tiết. Chuỗi bài viết cũng đưa ra một ví dụ trường hợp sử dụng trên vi điều khiển STM32. Có thể thấy, xử lý chuỗi là một chủ đề rất quan trọng khi lập trình vi điều khiển với các bài toán truyền, nhận, đóng gói, bóc tách, phân tích dữ liệu. Bên cạnh những vấn đề phổ biến được nêu ra trong bài viết, các bạn cũng có thể tìm hiểu thêm các vấn đề nâng cao như toàn vẹn dữ liệu với các phương pháp kiểm tra lỗi như CRC, checksum, hash, cũng như bảo mật dữ liệu với các kỹ thuật mã hóa và giải mã.
Chúc các bạn thành công!
H.V.Binh, N.T.Nhien & N.H.N.Thuong