12.2 C
London
Thứ Năm, Tháng 10 9, 2025

Modbus RTU STM32 (Phần 3)

Ở 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:

  1. Đổi huart2 thành UART bạn muốn dùng.
  2. 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).

  1. 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.
  • Hàm callback eMBRegInputCB():
    • Được gọi khi master đọc Input Register.
    • Copy dữ liệu từ usRegInputBuf[] sang pucRegBuffer 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); 
  1. 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 trong mbtask.c).
      • Độ ưu tiên: osPriorityNormal.
      • Số lượng instance: 0 (nghĩa là chỉ một task).
      • Dung lượng stack: configMINIMAL_STACK_SIZE.
  2. 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!

5/5 - (1 bình chọn)
Admin Peter
Admin Peterhttps://capitlab.com/
Coder & Hardware Designer $ Embedded Linux

Nội dung hấp dẫn

Giao thức I2C – Ví dụ với STM32

I2C (Inter-Integrated Circuit) là một giao thức truyền thông nối tiếp sử dụng 2 dây là SCL (tín hiệu clock) và SDA (tín hiệu dữ liệu)

Modbus RTU STM32 (Phần 2)

Giao thức Modbus trên STM32

Modbus RTU STM32 (Phần 1)

Trong các hệ thống điều khiển công nghiệp, Modbus...

STM32 HAL driver với cảm biến nhịp tim MAX30100 sử dụng STM32CUBE IDE

Cấu tạo và cách giao tiếp với cảm biến...

Thiết kế mạch cầu H tích hợp PID

Bài viết này mình sẽ chia sẻ cho...

Đặt mạch Trung Quốc, mua linh kiện hiếm

Bài viết nói về đặt mạch Trung Quốc và...

Nội dung liên quan

Modbus RTU STM32 (Phần 2)

Giao thức Modbus trên STM32

Modbus RTU STM32 (Phần 1)

Trong các hệ thống điều khiển công nghiệp, Modbus RTU là một trong những giao thức truyền thông phổ biến và dễ triển khai...

STM32 HAL driver với cảm biến nhịp tim MAX30100 sử dụng STM32CUBE IDE

Cấu tạo và cách giao tiếp với cảm biến nhịp tim MAX30100 Cảm biến MAX30100 là một module đo nhịp tim và nồng độ oxy...