Featured image of post 翻斗式雨量计

翻斗式雨量计

翻斗式雨量计

RS485通信


手册

image-20260327214643578

格式:

image-20260327214430138

  • 寄存器地址

image-20260327214849977

  • 查询雨量

image-20260327214918578

RS485的代码

RS485.c

 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
#include "rs485.h"

/************************************************
函数名称:RS485_Init
功能:初始化 RS485 控制引脚 + 串口1
参数:波特率 如 4800/9600/115200
************************************************/
void RS485_Init(u32 baudrate)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;

    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    //==================== PA8 推挽输出 ====================
    GPIO_InitStruct.GPIO_Pin = RS485_CTRL_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(RS485_CTRL_PORT, &GPIO_InitStruct);

    //==================== PA9 TX 复用推挽 ====================
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    //==================== PA10 RX 上拉输入 ====================
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    //==================== 串口配置 8N1 ====================
    USART_InitStruct.USART_BaudRate = baudrate;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStruct);

    USART_Cmd(USART1, ENABLE); // 使能串口

    RS485_RECEIVE(); // 默认进入接收模式
}

/************************************************
函数名称:RS485_SendData
功能:RS485 发送一包数据
参数:buf-发送缓冲区 len-长度
************************************************/
void RS485_SendData(u8 *buf, u8 len)
{
    u8 i;

    RS485_SEND();      // 切换为发送模式
    Delay_us(20);      // 等待电平稳定

    for(i=0; i<len; i++)
    {
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        USART_SendData(USART1, buf[i]);
    }

    while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    Delay_us(100);     // 等待最后一字节发送完成

    RS485_RECEIVE();   // 切回接收模式
}

/************************************************
函数名称:RS485_ReceiveData
功能:接收一包数据
参数:buf-接收缓冲区  timeout-超时时间
返回值:接收到的数据长度
************************************************/
u8 RS485_ReceiveData(u8 *buf, u16 timeout)
{
    u16 cnt = 0;
    u8 len = 0;

    RS485_RECEIVE(); // 确保是接收模式

    while(timeout--)
    {
        if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
        {
            buf[len++] = USART_ReceiveData(USART1);
            cnt = 0;
        }
        else
        {
            cnt++;
            if(cnt > 800) break; // 空闲超时,代表一帧结束
        }
    }
    return len;
}

RS485.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef __RS485_H
#define __RS485_H

#include "stm32f10x.h"
#include "delay.h"

//==================== 引脚定义 ====================
#define RS485_CTRL_PIN      GPIO_Pin_8
#define RS485_CTRL_PORT     GPIOA
#define RS485_CTRL_RCC      RCC_APB2Periph_GPIOA


//==================== 收发控制 ====================
#define RS485_RECEIVE()     GPIO_ResetBits(RS485_CTRL_PORT, RS485_CTRL_PIN)
#define RS485_SEND()        GPIO_SetBits(RS485_CTRL_PORT, RS485_CTRL_PIN)


//==================== 对外函数 ====================
void RS485_Init(u32 baudrate);       // 初始化 RS485(串口+控制脚)
void RS485_SendData(u8 *buf, u8 len); // 发送一包数据
u8   RS485_ReceiveData(u8 *buf, u16 timeout); // 接收一包数据

#endif

雨量计的代码

rain.c

  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
#include "rain.h"
#include "rs485.h"

// Modbus CRC16 校验
u16 CRC16_Modbus(u8 *buf, u8 len)
{
    u16 crc = 0xFFFF;
    for(u8 i=0; i<len; i++)
    {
        crc ^= buf[i];
        for(u8 j=0; j<8; j++)
        {
            if(crc & 0x01)
            {
                crc >>= 1;
                crc ^= 0xA001;
            }
            else
            {
                crc >>= 1;
            }
        }
    }
    return crc;
}

// 发送标准Modbus帧
void Modbus_SendFrame(u8 addr, u8 func, u16 reg, u16 data)
{
    u8 buf[8];
    buf[0] = addr;          // 从机地址
    buf[1] = func;          // 功能码
    buf[2] = (reg >> 8) & 0xFF;  // 寄存器高8位
    buf[3] = reg & 0xFF;        // 寄存器低8位
    buf[4] = (data >> 8) & 0xFF;//数据/寄存器长度 高字节
    buf[5] = data & 0xFF;      // 数据/寄存器长度 低字节
    // 计算CRC
    u16 crc = CRC16_Modbus(buf, 6);
    buf[6] = crc & 0xFF;         // CRC低字节
    buf[7] = (crc >> 8) & 0xFF;  // CRC高字节

    // RS485 发送
    RS485_SendData(buf, 8);
}

// ==============================================
// 函数功能:读取雨量值
// 返回值:雨量(单位:mm)
// ==============================================
float Rain_ReadValue(u8 addr)
{
    u8 recv_buf[16] = {0};

    // 发送指令:0x03读寄存器, 地址0x0000, 长度1
    Modbus_SendFrame(addr, 0x03, 0x0000, 0x0001);

    // 接收回复
    u8 len = RS485_ReceiveData(recv_buf, 500);

    // 数据校验
		if (len == 7
    && recv_buf[0] == addr
    && recv_buf[1] == 0x03
    && recv_buf[2] == 0x02)
    {
				// 校验CRC
        u16 recv_crc = (recv_buf[6] << 8) | recv_buf[5];
        u16 calc_crc = CRC16_Modbus(recv_buf, 5);
        
        if(recv_crc == calc_crc)
        {
					u16 rain_val = (recv_buf[3] << 8) | recv_buf[4];
					return rain_val / 10.0f;
        }
    }

    // 读取失败返回-1
    return -1.0f;
}

// ==============================================
// 函数功能:清除雨量数据
// 参数:addr - 设备地址
// 返回值:0-成功  -1-失败
// ==============================================
u8 Rain_ClearData(u8 addr)
{
    u8 recv_buf[16] = {0};

    // 1. 发送清零指令(0x06写寄存器, 地址0x0000, 数值0x005A)
    Modbus_SendFrame(addr, 0x06, 0x0000, 0x005A);

    // 2. 等待并接收应答帧,应答帧固定为6字节
    u8 len = RS485_ReceiveData(recv_buf, 500);

    // 3. 严格校验应答
    // 应答格式:地址(0) 功能码(1) 起始寄存器(2-3) 清除命令(4-5) CRC(6-7)
    if (len == 8
     && recv_buf[0] == addr
     && recv_buf[1] == 0x06
     && recv_buf[2] == 0x00 && recv_buf[3] == 0x00 // 起始寄存器匹配
     && recv_buf[4] == 0x00 && recv_buf[5] == 0x5A)// 清除命令匹配
    {
        // 校验CRC
        u16 recv_crc = (recv_buf[7] << 8) | recv_buf[6];
        u16 calc_crc = CRC16_Modbus(recv_buf, 6);
        
        if(recv_crc == calc_crc)
        {
            return 0; // 清零成功
        }
    }
    
    return -1; // 清零失败
}
// ==============================================
// 函数功能:修改设备地址
// 参数:old_addr - 旧设备地址	new_addr	- 新设备地址
// 返回值:0-成功  -1-失败
// ==============================================
u8 Rain_SetAddr(u8 old_addr, u8 new_addr)
{
		u8 recv_buf[16] = {0};
	
    Modbus_SendFrame(old_addr, 0x06, 0x07D0, new_addr);
		u8 len = RS485_ReceiveData(recv_buf, 500);
		
		if (len == 8
     && recv_buf[0] == old_addr
     && recv_buf[1] == 0x06
     && recv_buf[2] == 0x07 && recv_buf[3] == 0xD0 // 起始寄存器匹配
     && recv_buf[4] == 0x00 && recv_buf[5] == new_addr)// 
    {
        // 校验CRC
        u16 recv_crc = (recv_buf[7] << 8) | recv_buf[6];
        u16 calc_crc = CRC16_Modbus(recv_buf, 6);
        
        if(recv_crc == calc_crc)
        {
            return 0; // 设置成功
        }
    }
    
    return -1; // 设置失败
}

// ==============================================
// 函数功能:修改波特率
// 参数:0=2400 1=4800 2=9600 3=19200 ...
// 返回值:0-成功  -1-失败
// ==============================================
u8 Rain_SetBaud(u8 addr, u8 baud_opt)
{
    u8 recv_buf[16] = {0};

    // 发送指令:功能06,寄存器07D1,数据=00+baud_opt(高字节0)
    Modbus_SendFrame(addr, 0x06, 0x07D1, (0x00 << 8) | baud_opt);

    u8 len = RS485_ReceiveData(recv_buf, 500);

    // 校验应答帧(固定8字节)
    if (len == 8
     && recv_buf[0] == addr
     && recv_buf[1] == 0x06
     && recv_buf[2] == 0x07 && recv_buf[3] == 0xD1  // 寄存器地址匹配
     && recv_buf[4] == 0x00                         // 高字节固定0
     && recv_buf[5] == baud_opt)                     // 低字节=波特率选项
    {
        // CRC校验
        u16 recv_crc = ((u16)recv_buf[7] << 8) | recv_buf[6];
        u16 calc_crc = CRC16_Modbus(recv_buf, 6);

        if (recv_crc == calc_crc)
        {
            return 0;  // 设置成功
        }
    }
    return -1;  // 设置失败
}
// ==============================================
// 函数功能:查询地址
// 返回值:0-成功  -1-失败
// ==============================================
u8 Rain_FindAddr(void)
{
    u8 recv_buf[16] = {0};

    // 发送广播查询指令:地址0xFF,功能03,寄存器07D0,长度1
    Modbus_SendFrame(0xFF, 0x03, 0x07D0, 0x0001);

    u8 len = RS485_ReceiveData(recv_buf, 500);

    // 校验应答帧
    if (len == 7
     && recv_buf[0] == 0xFF
     && recv_buf[1] == 0x03
     && recv_buf[2] == 0x02)  // 有效字节数=2
    {
        // CRC校验
        u16 recv_crc = ((u16)recv_buf[6] << 8) | recv_buf[5];
        u16 calc_crc = CRC16_Modbus(recv_buf, 5);

        if (recv_crc == calc_crc)
        {
            // 地址值:高字节0,低字节为真实地址
            return recv_buf[4];
        }
    }
    return 0;  // 未找到设备
}

rain.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#ifndef __RAIN_H
#define __RAIN_H

#include "stm32f10x.h"

// 函数声明
float Rain_ReadValue(u8 addr);        // 读取雨量
u8 Rain_ClearData(u8 addr);         // 清零雨量
u8 Rain_SetAddr(u8 old_addr, u8 new_addr); // 修改地址
u8 Rain_SetBaud(u8 addr, u8 baud_opt);     // 修改波特率
u8 Rain_FindAddr(void);// 查询地址
#endif
最后更新于 2026-03-28 13:56
...