Ở phần 1 và phần 2 mình đã giới thiệu về giao thức Modbus và cấu hình STM32CubeMX cho project. Ở phần cuối này, mình sẽ hướng dẫn các bạn sử dụng thư viện FreeModbus và Demo 1 ví dụ để các bạn hiểu được cách làm cụ thể.
Chỉnh sửa portserial.c
Đây là file lớp giao tiếp (porting layer) giữa thư viện FreeModbus và phần cứng STM32. Nó định nghĩa các hàm cần thiết để FreeModbus có thể sử dụng UART làm kênh truyền thông, đồng thời điều khiển chân DIR/DE của IC MAX485 cho chuẩn RS485.
- Chọn cổng UART (ví dụ
huart2
) để làm kênh truyền Modbus. - Điều khiển chiều truyền/nhận dữ liệu qua chân DIR/DE.
- Cung cấp hàm gửi/nhận từng byte dữ liệu (
xMBPortSerialPutByte
,xMBPortSerialGetByte
). - Cho phép bật/tắt ngắt UART phục vụ cơ chế truyền thông theo chuẩn của FreeModbus.
Cụ thể, trong file portserial.c
, ta thấy có dòng:
extern UART_HandleTypeDef huart2;
Điều này có nghĩa là chương trình sẽ dùng UART2 được khai báo và khởi tạo ở nơi khác (trong file main.c
do CubeMX sinh ra). Nếu bạn muốn dùng cổng UART khác, ví dụ UART1 hoặc UART3, chỉ cần đổi lại thành:
extern UART_HandleTypeDef huart1; // hoặc huart3
và đồng thời chỉnh sửa trong CubeMX để khởi tạo UART tương ứng.
Ngoài ra, đoạn code có sử dụng chân GPIOA.4 để điều khiển DIR/DE của IC MAX485:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // chế độ truyền
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // chế độ nhận
Nếu trên mạch của bạn chân DIR/DE nối sang pin khác (ví dụ PB1), hãy sửa lại đúng cổng và chân GPIO trong hai dòng này.
Như vậy, để port sang phần cứng của bạn, chỉ cần:
- Đổi
huart2
thành UART bạn muốn dùng. - Sửa chân GPIOA.4 thành chân thực tế nối với DIR/DE của MAX485.
Chỉnh sửa file porttimer.c
Đây là file lớp giao tiếp (porting layer) giữa thư viện FreeModbus và phần cứng STM32. Nó định nghĩa các hàm cần thiết để FreeModbus sử dụng bộ định thời (timer) nhằm quản lý thời gian im lặng giữa các khung dữ liệu trong Modbus RTU.
- Chọn bộ timer (mặc định là TIM4) để tạo mốc thời gian.
- Cấu hình chu kỳ đếm 50 µs để FreeModbus tính toán timeout chính xác.
- Cung cấp các hàm bật/tắt timer (
vMBPortTimersEnable
,vMBPortTimersDisable
). - Khi timer hết hạn, sẽ gọi callback trong FreeModbus để báo kết thúc một khung truyền.
Trong file porttimer.c
có dòng:
extern TIM_HandleTypeDef htim4;
Điều này nghĩa là chương trình sẽ dùng TIM4 được khai báo và khởi tạo ở nơi khác (trong tim.c
do CubeMX sinh ra). Nếu bạn muốn dùng timer khác, ví dụ TIM2 hoặc TIM3, chỉ cần đổi lại thành:
extern TIM_HandleTypeDef htim2; // hoặc htim3
và đồng thời chỉnh sửa cấu hình trong CubeMX để khởi tạo timer tương ứng.
Mặc định code dùng TIM4 để tạo chu kỳ 50 µs nhờ công thức:
htim4.Init.Prescaler = (HAL_RCC_GetPCLK1Freq() / 1000000) - 1;
htim4.Init.Period = 50 - 1; // 50 µs
Nếu muốn thay đổi độ phân giải (ví dụ 100 µs), bạn chỉ cần điều chỉnh giá trị Period cho phù hợp.
👉 Tóm lại, khi port sang phần cứng mới, chỉ cần:
Cấu hình lại thông số timer trong CubeMX để tạo đúng chu kỳ 50 µs (hoặc giá trị bạn cần).
Đổi htim
thành timer bạn muốn dùng.
Trên đây là 2 file quan trọng nhất để có thể cấu hình phần cứng tương thích cho thư viện Modbus.
Enable Modbus RTU, ví dụ về cách sử dụng
Để chọn chế độ hoạt động là ModbusRTU, các bạn cần Enable nó ở file header mbconfig.h. Trong file này có 3 byte tương ứng với 3 chế độ, các bạn sử dụng chế độ nào thì các bạn ghi 1, còn lại ghi 0
Lúc này ở file mb.c, các bạn sẽ thấy ModbusRTU được định nghĩa, 2 chế độ kia bị ẩn đi do không được định nghĩa
Sử dụng Modbus
Sau khi hoàn tất quá trình cấu hình phần cứng, bật chế độ Modbus RTU, tại đây mình sẽ viết một chương trình demo sử dụng Modbus. Trước tiên, tạo fime mbtask.c
Đây là file nơi ta cấu hình thư viện FreeModbus và định nghĩa vùng nhớ cho các thanh ghi Modbus. File này chạy trong môi trường FreeRTOS (sử dụng CMSIS-OS).
- Khai báo địa chỉ bắt đầu và số lượng thanh ghi Input Register:
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 8
static USHORT usRegInputBuf[REG_INPUT_NREGS];
Đây là mảng lưu dữ liệu sẽ được trả về khi thiết bị slave nhận lệnh đọc input register.
- Trong hàm
ModbusRTUTask()
:- Gán giá trị mẫu cho
usRegInputBuf[]
. - Khởi tạo Modbus RTU bằng
eMBInit(MB_RTU, 1, 3, 19200, MB_PAR_NONE)
(slave ID = 1, UART3, baudrate 19200, không parity). - Gọi
eMBEnable()
để bật stack. - Trong vòng lặp
while(1)
:- Đọc trạng thái GPIOB.7, ghi vào
usRegInputBuf[0]
. - Điều khiển LED tại GPIOA.11 tùy theo trạng thái.
- Gọi
eMBPoll()
để FreeModbus xử lý yêu cầu từ master.
- Đọc trạng thái GPIOB.7, ghi vào
- Gán giá trị mẫu cho
- Hàm callback
eMBRegInputCB()
:- Được gọi khi master đọc Input Register.
- Copy dữ liệu từ
usRegInputBuf[]
sangpucRegBuffer
theo địa chỉ yêu cầu. - Nếu địa chỉ không hợp lệ → trả về
MB_ENOREG
. - Có thêm
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13)
để nháy LED mỗi lần có lệnh đọc.
- Các hàm callback khác (
eMBRegHoldingCB
,eMBRegCoilsCB
,eMBRegDiscreteCB
) hiện chưa được triển khai → luôn trả vềMB_ENOREG
(không hỗ trợ).
2. Khởi tạo hàm xử lý Modbus (ModbusRTUTask)
Trong StartDefaultTask()
, việc khởi tạo ModbusRTUTask được thực hiện bằng 2 bước:
osThreadDef(ModbusRTUTask, ModbusRTUTask, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
osThreadCreate(osThread(ModbusRTUTask), NULL);
osThreadDef(...)
- Đây là macro của CMSIS-OS (wrapper của FreeRTOS).
- Nó định nghĩa một thread mới với các tham số:
- Tên task:
ModbusRTUTask
. - Hàm thực thi:
ModbusRTUTask()
(đã viết trongmbtask.c
). - Độ ưu tiên:
osPriorityNormal
. - Số lượng instance:
0
(nghĩa là chỉ một task). - Dung lượng stack:
configMINIMAL_STACK_SIZE
.
- Tên task:
osThreadCreate(...)
- Tạo ra task thực sự trong FreeRTOS dựa trên cấu hình vừa định nghĩa.
- Tham số thứ hai (
NULL
) là con trỏ truyền vào cho hàm task (ở đây không cần).
Khi chạy xong 2 lệnh trên, ModbusRTUTask sẽ bắt đầu hoạt động như một thread độc lập, khởi tạo và duy trì giao thức Modbus RTU (bằng cách gọi eMBInit
, eMBEnable
, rồi xử lý vòng lặp với eMBPoll()
).
Các bạn có thể sử dụng phần mềm ModbusPoll để kiểm tra!
Toàn bộ file chương trình các bạn có thể tải về tại đây! Mọi thắc mắc hay cần hỗ trợ có thể ib qua Zalo hay email của mình. Thanks for watching!