STM32 工控板通过 USB 与编码器 RS485 通信测试:0x1A 指令与 0x55 应答
一、功能说明
本文记录一个基于 USB + RS485 的通信测试程序。
系统由两部分组成:
-
工控板
-
通过 USB 与上位机串口助手通信;
-
通过 RS485 与编码器通信。
-
-
编码器
-
通过 RS485 接收工控板发送的数据;
-
收到指定命令后返回应答数据。
-
本次测试的通信逻辑如下:
| 方向 | 数据 | 说明 |
|---|---|---|
| 串口助手 → 工控板 | 0x1A |
USB 下发测试命令 |
| 工控板 → 编码器 | 0x1A |
工控板通过 RS485 转发命令 |
| 编码器 → 工控板 | 0x55 |
编码器收到 0x1A 后返回应答 |
| 工控板 → 串口助手 | 0x55 |
工控板收到编码器应答后通过 USB 返回 |
也就是说,如果 RS485 通信正常,串口助手向工控板发送 0x1A 后,最终可以收到工控板返回的 0x55。
二、整体通信流程
上位机 / 串口助手
|
| USB 发送 0x1A
v
工控板
|
| RS485 发送 0x1A
v
编码器
|
| RS485 回复 0x55
v
工控板
|
| USB 返回 0x55
v
上位机 / 串口助手
三、工控板程序
1. MyProcess.h
该文件主要定义测试命令、编码器应答值、USB 收发缓冲区以及相关函数声明。
#ifndef _MYPROCESS_H
#define _MYPROCESS_H
#include "stdint.h"
#include <string.h>
#define USB_RS485_TEST_CMD 0x1A
#define ENCODER_REPLY_OK 0x55
extern uint8_t process_usb_rxBuf[64];
extern uint8_t process_usb_txBuf[64];
extern volatile uint8_t g_WaitEncoderReply55;
extern volatile uint8_t g_UsbReply55Pending;
void Process_usb_reply(void);
void Encoder_485_SendByte(uint8_t data);
#endif
2. MyProcess.c
该文件实现 USB 命令处理以及 RS485 单字节发送。
当 USB 接收到 0x1A 后,工控板会:
-
设置等待编码器回复标志;
-
通过 RS485 向编码器发送
0x1A; -
清空 USB 接收缓冲区首字节,避免重复处理。
#include "MyProcess.h"
#include "usbd_cdc_if.h"
#include "usart.h"
#include "gpio.h"
uint8_t process_usb_rxBuf[64];
uint8_t process_usb_txBuf[64];
volatile uint8_t g_WaitEncoderReply55 = 0;
volatile uint8_t g_UsbReply55Pending = 0;
void Encoder_485_SendByte(uint8_t data)
{
HAL_GPIO_WritePin(UART4_RD_GPIO_Port, UART4_RD_Pin, GPIO_PIN_SET);
// RS485 发送使能
__HAL_UART_CLEAR_FLAG(&huart4, UART_FLAG_TC);
huart4.Instance->DR = data;
while (__HAL_UART_GET_FLAG(&huart4, UART_FLAG_TXE) == RESET) {}
while (__HAL_UART_GET_FLAG(&huart4, UART_FLAG_TC) == RESET) {}
HAL_GPIO_WritePin(UART4_RD_GPIO_Port, UART4_RD_Pin, GPIO_PIN_RESET);
// RS485 发送完成,切回接收模式
}
void Process_usb_reply(void)
{
if (process_usb_rxBuf[0] == USB_RS485_TEST_CMD)
{
g_WaitEncoderReply55 = 1;
Encoder_485_SendByte(USB_RS485_TEST_CMD);
process_usb_rxBuf[0] = 0;
}
}
3. USB 接收回调:usbd_cdc_if.c
USB 接收数据后,将数据复制到 process_usb_rxBuf,然后调用 Process_usb_reply() 进行处理。
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
if (*Len >= 1)
{
memset(process_usb_rxBuf, 0, sizeof(process_usb_rxBuf));
if (*Len > sizeof(process_usb_rxBuf))
{
*Len = sizeof(process_usb_rxBuf);
}
memcpy(process_usb_rxBuf, Buf, *Len);
Process_usb_reply();
}
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return USBD_OK;
}
4. UART4 接收中断:stm32f4xx_it.c
工控板通过 UART4 接收编码器返回的数据。
如果当前正在等待编码器回复,并且收到的数据为 0x55,则设置 USB 回复标志位。
void UART4_IRQHandler(void)
{
uint8_t rxData = 0;
if ((UART4->SR & UART_FLAG_RXNE) != RESET)
{
rxData = UART4->DR;
if (g_WaitEncoderReply55 == 1)
{
if (rxData == ENCODER_REPLY_OK)
{
g_WaitEncoderReply55 = 0;
g_UsbReply55Pending = 1;
// 主循环通过 USB 回复 0x55
}
}
}
}
5. main.c
主循环中不断检测 g_UsbReply55Pending。
当该标志被置位后,说明工控板已经成功收到编码器返回的 0x55,于是通过 USB 将 0x55 回复给串口助手。
#include "main.h"
#include "usart.h"
#include "usb_device.h"
#include "gpio.h"
#include "MyProcess.h"
void SystemClock_Config(void);
static void NVIC_Configuration(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_UART4_Init();
MX_USB_DEVICE_Init();
__HAL_UART_ENABLE_IT(&huart4, UART_IT_RXNE);
// 使能 UART 接收中断
NVIC_Configuration();
while (1)
{
if (g_UsbReply55Pending)
{
g_UsbReply55Pending = 0;
process_usb_txBuf[0] = ENCODER_REPLY_OK;
CDC_Transmit_FS(process_usb_txBuf, 1);
}
}
}
四、编码器程序
1. main.h
该文件定义 RS485 收发控制宏、测试命令、回复数据、DMA 通道以及相关函数声明。
#ifndef __MAIN_H__
#define __MAIN_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "n32l40x.h"
#include "n32l40x_gpio.h"
#include "n32l40x_dma.h"
#include <stdint.h>
#include <stdio.h>
#define RS485_ENABLE_TX() GPIO_SetBits(GPIOB, GPIO_PIN_6)
#define RS485_ENABLE_RX() GPIO_ResetBits(GPIOB, GPIO_PIN_6)
#define USB_RS485_TEST_CMD 0x1A
#define USB_RS485_TEST_REPLY 0x55
#define UART5_TX_DMA_CH DMA_CH3
#define UART5_TX_DMA_FLAG DMA_FLAG_TC3
#define UART5_TX_DMA_REMAP DMA_REMAP_UART5_TX
#define UART5_TX_DMA_IRQn DMA_Channel3_IRQn
#define UART5_TX_DMA_CLEAR_FLAGS \
(DMA_FLAG_GL3 | DMA_FLAG_TC3 | DMA_FLAG_HT3 | DMA_FLAG_TE3)
extern volatile uint8_t rs485_dma_tx_busy;
void Delay(uint32_t count);
void RCC_Configuration(void);
void GPIO_Configuration(void);
void USART_Configuration(void);
void NVIC_Configuration(void);
void DMA_Configuration(void);
void RS485_SendBuffer_DMA(uint8_t *buf, uint16_t len);
#ifdef __cplusplus
}
#endif
#endif
2. 编码器 main.c
编码器初始化 GPIO、UART5、DMA 和中断。
RS485 默认处于接收模式。
#include "main.h"
#include "n32l40x_gpio.h"
#include "n32l40x_usart.h"
#include "n32l40x_rcc.h"
#include "misc.h"
volatile uint8_t rs485_dma_tx_busy = 0;
void Delay(uint32_t count)
{
while (count--) __NOP();
}
void RCC_Configuration(void)
{
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO, ENABLE);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_UART5, ENABLE);
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_DMA, ENABLE);
}
void GPIO_Configuration(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Current = GPIO_DC_12mA;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF6_UART5;
GPIO_InitStructure.GPIO_Current = GPIO_DC_12mA;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF7_UART5;
GPIO_InitStructure.GPIO_Current = GPIO_DC_12mA;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
RS485_ENABLE_RX();
}
void USART_Configuration(void)
{
USART_InitType USART_InitStructure;
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 2500000;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_Init(UART5, &USART_InitStructure);
USART_ConfigInt(UART5, USART_INT_RXDNE, ENABLE);
USART_Enable(UART5, ENABLE);
}
void DMA_Configuration(void)
{
DMA_InitType DMA_InitStructure;
DMA_DeInit(UART5_TX_DMA_CH);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = (uint32_t)(UART5_BASE + 0x04);
DMA_InitStructure.MemAddr = (uint32_t)0;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.BufSize = 0;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(UART5_TX_DMA_CH, &DMA_InitStructure);
DMA_RequestRemap(UART5_TX_DMA_REMAP, DMA, UART5_TX_DMA_CH, ENABLE);
DMA_ConfigInt(UART5_TX_DMA_CH, DMA_INT_TXC, ENABLE);
DMA_ConfigInt(UART5_TX_DMA_CH, DMA_INT_ERR, ENABLE);
USART_EnableDMA(UART5, USART_DMAREQ_TX, ENABLE);
}
void NVIC_Configuration(void)
{
NVIC_InitType NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitStructure.NVIC_IRQChannel = UART5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = UART5_TX_DMA_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
USART_Configuration();
DMA_Configuration();
NVIC_Configuration();
RS485_ENABLE_RX();
while (1)
{
}
}
3. UART5 接收中断:n32l40x_it.c
编码器通过 UART5 接收 RS485 数据。
当接收到 0x1A 后,立即切换 RS485 为发送模式,发送 0x55,等待发送完成后再切换回接收模式。
#include "main.h"
#include "n32l40x_it.h"
#include "n32l40x_usart.h"
#include "n32l40x_dma.h"
void UART5_IRQHandler(void)
{
uint32_t sts = UART5->STS;
// 接收中断处理
if ((sts & USART_FLAG_RXDNE) != 0)
{
uint8_t cmd = (uint8_t)(UART5->DAT);
// 如果 DMA 正在发送,则忽略本次接收
if (rs485_dma_tx_busy)
{
return;
}
// 收到 0x1A 后回复 0x55
if (cmd == USB_RS485_TEST_CMD)
{
RS485_ENABLE_TX();
USART_ClrFlag(UART5, USART_FLAG_TXC);
USART_SendData(UART5, USB_RS485_TEST_REPLY);
while (USART_GetFlagStatus(UART5, USART_FLAG_TXC) == RESET)
{
}
RS485_ENABLE_RX();
return;
}
}
// 发送完成中断 TXC
if (rs485_dma_tx_busy && (sts & USART_FLAG_TXC) != 0)
{
USART_ConfigInt(UART5, USART_INT_TXC, DISABLE);
RS485_ENABLE_RX();
rs485_dma_tx_busy = 0;
USART_ConfigInt(UART5, USART_INT_RXDNE, ENABLE);
USART_ClrFlag(UART5, USART_FLAG_TXC);
return;
}
// 溢出处理
if ((sts & USART_FLAG_OREF) != 0)
{
(void)UART5->STS;
(void)UART5->DAT;
}
}
4. DMA 通道 3 中断
当前测试中,编码器接收到 0x1A 后是直接通过 UART 发送 0x55,但程序中保留了 DMA 发送完成中断处理,方便后续扩展多字节数据发送。
void DMA_Channel3_IRQHandler(void)
{
if (DMA_GetIntStatus(DMA_INT_TXC3, DMA) != RESET)
{
DMA_ClrIntPendingBit(DMA_INT_TXC3 | DMA_INT_GLB3, DMA);
while (!(UART5->STS & USART_FLAG_TXC))
{
}
DMA_EnableChannel(UART5_TX_DMA_CH, DISABLE);
USART_EnableDMA(UART5, USART_DMAREQ_TX, DISABLE);
RS485_ENABLE_RX();
rs485_dma_tx_busy = 0;
USART_ConfigInt(UART5, USART_INT_RXDNE, ENABLE);
return;
}
if (DMA_GetIntStatus(DMA_INT_ERR3, DMA) != RESET)
{
DMA_ClrIntPendingBit(DMA_INT_ERR3 | DMA_INT_GLB3, DMA);
DMA_EnableChannel(UART5_TX_DMA_CH, DISABLE);
USART_EnableDMA(UART5, USART_DMAREQ_TX, DISABLE);
RS485_ENABLE_RX();
rs485_dma_tx_busy = 0;
USART_ConfigInt(UART5, USART_INT_RXDNE, ENABLE);
}
}
五、关键标志位说明
1. 工控板侧
| 变量 | 作用 |
|---|---|
g_WaitEncoderReply55 |
工控板是否正在等待编码器回复 0x55 |
g_UsbReply55Pending |
是否需要通过 USB 回复上位机 0x55 |
process_usb_rxBuf |
USB 接收缓冲区 |
process_usb_txBuf |
USB 发送缓冲区 |
2. 编码器侧
| 变量 / 宏 | 作用 |
|---|---|
rs485_dma_tx_busy |
DMA 是否正在发送 |
RS485_ENABLE_TX() |
RS485 切换到发送模式 |
RS485_ENABLE_RX() |
RS485 切换到接收模式 |
USB_RS485_TEST_CMD |
测试命令 0x1A |
USB_RS485_TEST_REPLY |
测试回复 0x55 |
六、测试方法
1. 硬件连接
工控板和编码器之间通过 RS485 连接。
需要保证:
-
A、B 线连接正确;
-
双方共地;
-
RS485 收发控制引脚工作正常;
-
双方串口参数一致。
2. 串口参数
编码器侧 UART5 当前配置如下:
USART_InitStructure.BaudRate = 2500000;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
因此工控板 UART4 也需要配置为:
波特率:2500000
数据位:8
停止位:1
校验位:None
3. 上位机测试
在串口助手中通过 USB 虚拟串口发送:
1A
如果通信正常,串口助手应收到:
55
七、注意事项
1. RS485 方向控制必须正确
RS485 是半双工通信,发送和接收需要通过方向控制引脚切换。
工控板发送前:
HAL_GPIO_WritePin(UART4_RD_GPIO_Port, UART4_RD_Pin, GPIO_PIN_SET);
工控板发送完成后:
HAL_GPIO_WritePin(UART4_RD_GPIO_Port, UART4_RD_Pin, GPIO_PIN_RESET);
编码器发送前:
RS485_ENABLE_TX();
编码器发送完成后:
RS485_ENABLE_RX();
如果方向切换不正确,可能出现以下问题:
-
编码器收不到
0x1A; -
工控板收不到
0x55; -
RS485 总线被一直占用;
-
串口助手没有任何返回。
2. 双方波特率必须一致
编码器程序中 UART5 波特率为:
2500000
工控板 UART4 的波特率也必须设置为相同值,否则会导致接收数据错误。
3. USB 不建议在中断中直接发送
工控板收到编码器回复后,并没有在 UART4 中断中直接调用 CDC_Transmit_FS(),而是设置标志位:
g_UsbReply55Pending = 1;
然后在主循环中发送 USB 数据:
CDC_Transmit_FS(process_usb_txBuf, 1);
这样做可以避免在 UART 中断中直接操作 USB 带来的时序或阻塞问题。
4. 单字节通信适合测试链路
本程序只测试单字节命令和单字节应答,适合验证:
-
USB 通信是否正常;
-
工控板 RS485 发送是否正常;
-
编码器 RS485 接收是否正常;
-
编码器 RS485 回复是否正常;
-
工控板 USB 回传是否正常。
后续可扩展多个通信方式,如IIC、SPI、CAN等:
八、总结
本文实现了一个简单的 USB 转 RS485 通信测试流程。
整体逻辑为:
USB 收到 0x1A
↓
工控板通过 RS485 发送 0x1A
↓
编码器收到 0x1A 后回复 0x55
↓
工控板收到 0x55
↓
工控板通过 USB 返回 0x55
通过该程序,可以快速判断工控板与编码器之间的 RS485 通信是否正常。
如果串口助手发送 0x1A 后能够收到 0x55,说明 USB 通信、RS485 通信以及编码器应答逻辑均正常。
更多推荐



所有评论(0)