打印
[其他ST产品]

RS485 Modbus协议 采集传感器数据

[复制链接]
307|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
硬件

1、传感器:为液压传感器,12vDC,RS485数据输出,采用Modbus协议通信
2、电路:根据传感器属性,电路主要是两部分,通信电路和电源电源
(1)485电路:由于485是半双工通信,需要控制收发,所以索性在把电路设计成自动收发电路



接跳线帽W1、W2即使用RS485收发,不接就是普通串口收发。RE和DE是收发使能,选择485模式:3_TXD常高,使能接受;当发送数据时,数据的起始位(下降沿)将3_TXD引脚电平拉低,将顺便使能发送。传感器接3_A、3_B。

使用特权

评论回复
沙发
初级工程渣|  楼主 | 2024-3-31 23:14 | 只看该作者
开关电路:主要是为了控制传感器开关,以降低功耗



测试485电路

1、485电路测试程序
对于单片机来说,其实仍然是串口通信,只不过经过485芯片实现了RS232电平→RS485电平。
(1)初始化串口3,并在初始化时开启串口接收中断

使用特权

评论回复
板凳
初级工程渣|  楼主 | 2024-3-31 23:15 | 只看该作者
void MX_USART3_UART_Init(void)
{

  huart3.Instance = USART3;
  huart3.Init.BaudRate = 9600;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }
  Usart3RecIT();//开启串口3接收中断
  start_capture();//发送请求帧
}

使用特权

评论回复
地板
初级工程渣|  楼主 | 2024-3-31 23:15 | 只看该作者
接收中断回调函数中保存串口数据
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

        if(huart->Instance == USART3){//RS485设备
                HAL_UART_Receive_IT(&huart3, &uart3Data, 1);
        uart3WriteByte(uart3Data);//接收到的数据写入缓存
                uart3Data** = 1;
        }       
}

使用特权

评论回复
5
初级工程渣|  楼主 | 2024-3-31 23:15 | 只看该作者
缓存数据:环形队列,不啰嗦
#define SENSOR_485_DATA_BUFFER_MAX_LENGTH  60
typedef struct {
        uint16_t   front;
        uint16_t   rear;
        uint8_t*   buffer;
        uint32_t   maxSize;
}Buffer_t;
static Buffer_t sensor485Buffer;
uint8_t  sensor485DataBuffer[SENSOR_485_DATA_BUFFER_MAX_LENGTH];//缓存数组

void uart3WriteByte(uint8_t data)
{
        Buffer_Puts(&sensor485Buffer,&data,1);//入队
}

bool Buffer_Puts(Buffer_t* buffer, uint8_t* data, uint16_t length)
{
        if (buffer->maxSize - Buffer_Size(buffer) <= length)//队满
                return false;
        for (uint16_t i = 0; i<length; ++i)//队列未满
        {
                buffer->rear = (buffer->rear + 1) % buffer->maxSize;
                buffer->buffer[buffer->rear] = data[i];//进队
        }
        return true;
}

使用特权

评论回复
6
初级工程渣|  楼主 | 2024-3-31 23:18 | 只看该作者
这个接口,是为了向传感器发送指令,请求传感器数据,指令需要查看传感器指令定义,属于协议那部分,先测试
void start_capture(){
    uint8_t TxData[10]= "1111111111";
        HAL_UART_Transmit(&huart3,TxData,10,0xffff);
    HAL_Delay(100);

使用特权

评论回复
7
初级工程渣|  楼主 | 2024-3-31 23:18 | 只看该作者
在Keil中Debug,用串口助手向单片机发送数据,查看数组sensor485DataBuffer接收到了,接收是可以了

使用特权

评论回复
8
初级工程渣|  楼主 | 2024-3-31 23:19 | 只看该作者
但是我遇到了问题,上位机没有接受到2-(4)发送的“1111111111”,单片机发送出现了问题

(1)分别检查TX、R15、R16都有信号出来

使用特权

评论回复
9
初级工程渣|  楼主 | 2024-3-31 23:19 | 只看该作者
第一反应是485芯片发送使能没有成功,检查RE和DE引脚,果然一直是低电平,说明三极管一直导通,没有阻塞过

使用特权

评论回复
10
初级工程渣|  楼主 | 2024-3-31 23:19 | 只看该作者
无奈之下,干脆用反相器替换了三极管

使用特权

评论回复
11
初级工程渣|  楼主 | 2024-3-31 23:20 | 只看该作者
加入Modbus协议

1、协议原理
(1)以上测试说明:链路层硬件协议√;
(2)但是问题来了,只有硬件协议可以和传感器通信吗? 当然不行,传感器又不是电脑,它没有上位机:你点一下发送就把数据发出去了。这个时候需要单片机来告知它发送。所以我们还需要:链路层软件协议Modbus协议√;
(3)我们选取Modbus协议中对我们编程有帮助的几点:

以帧的形式通信,有ASCII和RTU两种模式,帧中的地址、功能码等都是一个或多个字节,每个字节是一个8位串口数据


如果一个串口连接多个4856设备,可以通过地址区分不同485设备,当然串口资源充足也可以挂载在多个串口上

RTU模式通过两帧数据的时间间隔,区分前后两帧数据,如果串口数据间隔大于3.5个字节,那么就认为一帧数据结束了,在9600bps/s波特率下,传输3.5个字节时间大概为4ms;ASCII模式读回车换行就行了

如果要读取传感器状态、数据,都需要发送请求帧

数据需要CRC校验

具体指令规则需查看传感器指令文档

使用特权

评论回复
12
初级工程渣|  楼主 | 2024-3-31 23:20 | 只看该作者
根据传感器指令文档编程

(1)打开传感器开关

void open_sensor(){
        //控制电源开关的引脚拉高
}

使用特权

评论回复
13
初级工程渣|  楼主 | 2024-3-31 23:20 | 只看该作者
发送读取命令

void start_read(){
    uint8_t TxData[10]= "010300000001840A";
        HAL_UART_Transmit(&huart3,TxData,10,0xffff);
    HAL_Delay(100);
}

使用特权

评论回复
14
初级工程渣|  楼主 | 2024-3-31 23:20 | 只看该作者
读取数据

在串口接收中断中接收数据,如果下一个串口数据在4ms之内到来,那么重新计时,定时器中断不会生效;如果没来,则认为接收完一帧数据,程序会进入定时器中断,这个很关键
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
                if(huart->Instance == USART3){
                          HAL_TIM_Base_Stop_IT(&htim5);
                          HAL_UART_Receive_IT(&huart3, &uart3Data, 1);
              uart3WriteByte(uart3Data);
              HAL_TIM_Base_Start_IT(&htim5);//开始计时4ms,即modbus设备在9600波特率下传输一帧数据的时间间隔
                          uart3Data** = 1;
                }
}

使用特权

评论回复
15
初级工程渣|  楼主 | 2024-3-31 23:21 | 只看该作者
在定时器中断中进行处理数据

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim==(&htim5))
  {
                //TIM_ClearITPendingBit(TIM5,TIM_IT_Update);  //标准库需要清除TIMx更新中断标志,HAL库不需要,直接写业务逻辑就行
                 HAL_TIM_Base_Stop_IT(&htim5);
                 Modbus_Work();//数据处理函数
                 Uart3BufClear();
                 RS485_RX_CNT++;
  }
}

使用特权

评论回复
16
初级工程渣|  楼主 | 2024-3-31 23:21 | 只看该作者
数据CRC校验

CRC原理就不啰嗦了
不同种类的CRC校验的多项式并不统一,这里是0xA001,文档中会说明


int16_t CRC_16( int8_t *vptr, int8_t len)
{
    uint16_t MODBUSCRC = 0xffff;
    uint16_t POLYNOMIAL = 0xa001;
    uint8_t i, j;

    for (i = 0; i < len; i++)
    {
        MODBUSCRC ^= vptr[i] ;
        for (j = 0; j < 8; j++)
        {
            if ((MODBUSCRC & 0x0001) != 0)
            {
                MODBUSCRC >>= 1;
                MODBUSCRC ^= POLYNOMIAL;
            }
            else
            {
                MODBUSCRC >>= 1;
            }
        }
    }
    return MODBUSCRC;
}

使用特权

评论回复
17
初级工程渣|  楼主 | 2024-3-31 23:22 | 只看该作者
数据解析

int Modbus_Work(void)
{
        double depth;
        uint16_t  depth_high;
        uint16_t  depth_low;
        int len = strlen((char*)(sensor485DataBuffer+1));
        if(sensor485DataBuffer[1] == 0x01)//从机地址正确则进行换算
        {
                if((CRC_16((int8_t *)(sensor485DataBuffer+1),len))==0x0000){//CRC校验
                        depth_high = sensor485DataBuffer[3];
                        depth_low = sensor485DataBuffer[4];
                        depth = (double)((depth_high << 8) + depth_low)/1000;
                }
        }memset(sensor485DataBuffer,0,len+1);
        return depth;//这就是水深值了
}

使用特权

评论回复
18
初级工程渣|  楼主 | 2024-3-31 23:22 | 只看该作者
注意事项
1、RS485通信,一定要A接A,B接B,不是反接
2、采集线过长,要考虑使用屏蔽线

使用特权

评论回复
19
药无尘| | 2024-4-1 13:44 | 只看该作者
自动收发电路响应时间是多少,如果串口速度过快能反应过来吗

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

47

主题

516

帖子

0

粉丝