一、系统总体架构
复制代码
┌─────────────────────────────────────────────────────────┐
│ 水表集中抄表器 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 下行采集层 │ │ 数据处理层 │ │ 上行通信层 │ │
│ │ │ │ │ │ │ │
│ │ • MBus总线 │ │ • 协议解析 │ │ • GPRS/4G │ │
│ │ • RS485接口 │ │ • 数据存储 │ │ • WiFi/LoRa │ │
│ │ • 脉冲采集 │ │ • 异常处理 │ │ • NB-IoT │ │
│ │ • 红外通信 │ │ • 远程升级 │ │ • 以太网 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ 核心控制单元(MCU) ││
│ │ • 实时调度 • 功耗管理 • 安全加密 • 看门狗 ││
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
二、单片机选型对比
| 方案 |
型号 |
优势 |
劣势 |
适用场景 |
| STM32L4系列 |
STM32L476RG |
超低功耗,丰富外设 |
成本中等 |
电池供电,长期运行 |
| GD32L233 |
GD32L233KC |
国产,性价比高 |
生态相对新 |
成本敏感项目 |
| HC32L136 |
HC32L136KCTA |
极低功耗,内置LCD |
资料较少 |
显示需求场景 |
| ESP32-C3 |
ESP32-C3-MINI |
集成WiFi,RISC-V |
功耗较高 |
市电供电,需无线 |
推荐方案 :STM32L476RG(主频80MHz,休眠<5μA,外设齐全)
三、硬件接口设计
1、下行采集接口
c
复制代码
/* 硬件资源配置 */
#define MBUS_PORT GPIOA
#define MBUS_TX_PIN GPIO_PIN_9 // USART1_TX
#define MBUS_RX_PIN GPIO_PIN_10 // USART1_RX
#define MBUS_DE_PIN GPIO_PIN_11 // RS485方向控制
#define PULSE_PORT GPIOB
#define PULSE_CH1_PIN GPIO_PIN_0 // 脉冲输入1
#define PULSE_CH2_PIN GPIO_PIN_1 // 脉冲输入2
#define PULSE_CH3_PIN GPIO_PIN_2 // 脉冲输入3
#define PULSE_CH4_PIN GPIO_PIN_3 // 脉冲输入4
#define IR_PORT GPIOC
#define IR_TX_PIN GPIO_PIN_6 // 红外发射
#define IR_RX_PIN GPIO_PIN_7 // 红外接收
2、上行通信接口
c
复制代码
/* 通信模块接口 */
#define GPRS_UART USART2
#define WIFI_UART USART3
#define LORA_UART UART4
#define ETH_MAC ETH
/* 状态指示 */
#define LED_POWER GPIO_PIN_12 // 电源指示
#define LED_RUN GPIO_PIN_13 // 运行指示
#define LED_COMM GPIO_PIN_14 // 通信指示
#define LED_FAULT GPIO_PIN_15 // 故障指示
四、核心数据结构
1、水表设备信息
c
复制代码
#pragma pack(push, 1)
typedef struct {
uint32_t meter_id; // 表号(8字节BCD)
uint8_t meter_type; // 表类型:01-冷水表,02-热水表
uint8_t protocol_type; // 协议类型:01-CJ/T188,02-GB/T26831
uint16_t comm_addr; // 通信地址
float last_reading; // 上次读数
float current_reading; // 当前读数
uint32_t total_pulses; // 累计脉冲数
uint8_t status; // 状态:00-正常,01-倒流,02-漏水,03-拆除
uint32_t install_time; // 安装时间
uint8_t battery_level; // 电池电量
} Meter_Info_t;
#pragma pack(pop)
2、抄表数据包
c
复制代码
typedef struct {
uint8_t cmd; // 命令字
uint16_t seq; // 序列号
uint8_t meter_count; // 表数量
Meter_Info_t meters[32]; // 表数据数组
uint16_t crc; // CRC校验
} Meter_Data_Packet_t;
五、MBus总线通信实现
1、MBus协议帧格式(CJ/T188)
c
复制代码
/* CJ/T188协议帧 */
typedef struct {
uint8_t start; // 起始符 0x68
uint8_t addr[7]; // 地址域
uint8_t ctrl; // 控制码
uint8_t length; // 数据长度
uint8_t data[252]; // 数据域
uint8_t cs; // 校验和
uint8_t end; // 结束符 0x16
} CJ188_Frame_t;
2、MBus主机驱动
c
复制代码
#include "stm32l4xx_hal.h"
#define MBUS_BAUDRATE 2400
#define MBUS_TIMEOUT_MS 3000
UART_HandleTypeDef huart1;
TIM_HandleTypeDef htim2;
/* MBus初始化 */
void MBus_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = MBUS_BAUDRATE;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart1);
/* 定时器用于超时检测 */
HTIM2_Init();
}
/* MBus发送数据 */
HAL_StatusTypeDef MBus_SendFrame(uint8_t *data, uint16_t len)
{
/* 使能发送 */
HAL_GPIO_WritePin(MBUS_PORT, MBUS_DE_PIN, GPIO_PIN_SET);
/* 发送数据 */
HAL_StatusTypeDef ret = HAL_UART_Transmit(&huart1, data, len, 1000);
/* 等待发送完成 */
while(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET);
/* 切换到接收 */
HAL_GPIO_WritePin(MBUS_PORT, MBUS_DE_PIN, GPIO_PIN_RESET);
return ret;
}
/* MBus接收数据 */
HAL_StatusTypeDef MBus_ReceiveFrame(uint8_t *buffer, uint16_t *len)
{
uint32_t start_time = HAL_GetTick();
uint16_t recv_len = 0;
while((HAL_GetTick() - start_time) < MBUS_TIMEOUT_MS) {
if(HAL_UART_Receive(&huart1, &buffer[recv_len], 1, 100) == HAL_OK) {
recv_len++;
if(buffer[recv_len-1] == 0x16 && recv_len > 10) {
*len = recv_len;
return HAL_OK;
}
}
}
*len = 0;
return HAL_TIMEOUT;
}
3、水表抄表核心函数
c
复制代码
/* 抄读单个水表 */
Meter_Info_t Read_Single_Meter(uint32_t meter_id)
{
Meter_Info_t meter_info = {0};
CJ188_Frame_t tx_frame, rx_frame;
uint8_t buffer[256];
uint16_t len;
/* 构建抄表命令帧 */
tx_frame.start = 0x68;
memcpy(tx_frame.addr, &meter_id, 4);
tx_frame.ctrl = 0x01; // 读数据命令
tx_frame.length = 0x02;
tx_frame.data[0] = 0x90; // 当前累积流量
tx_frame.data[1] = 0x1F;
tx_frame.cs = Calculate_CS(&tx_frame);
tx_frame.end = 0x16;
/* 发送并接收 */
MBus_SendFrame((uint8_t*)&tx_frame, sizeof(tx_frame));
if(MBus_ReceiveFrame(buffer, &len) == HAL_OK) {
Parse_Meter_Response(buffer, &meter_info);
}
return meter_info;
}
/* 批量抄表 */
void Batch_Read_Meters(void)
{
uint8_t meter_count = Get_Total_Meter_Count();
for(uint8_t i = 0; i < meter_count; i++) {
uint32_t meter_id = Get_Meter_ID(i);
Meter_Info_t meter_data = Read_Single_Meter(meter_id);
/* 存储到Flash */
Save_Meter_Data(&meter_data);
/* 异常检测 */
Check_Meter_Exception(&meter_data);
/* 延时避免总线冲突 */
HAL_Delay(100);
}
}
六、脉冲计数实现(备用方案)
c
复制代码
/* 脉冲计数器 */
typedef struct {
uint32_t pulse_count; // 脉冲计数
uint32_t last_time; // 上次脉冲时间
float flow_rate; // 瞬时流量
uint8_t pulse_per_liter; // 每升脉冲数
} Pulse_Counter_t;
Pulse_Counter_t pulse_counters[4];
/* 外部中断回调 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
uint32_t current_time = HAL_GetTick();
if(GPIO_Pin == PULSE_CH1_PIN) {
pulse_counters[0].pulse_count++;
/* 计算瞬时流量 */
uint32_t time_diff = current_time - pulse_counters[0].last_time;
if(time_diff > 0) {
pulse_counters[0].flow_rate =
(3600000.0f / time_diff) / pulse_counters[0].pulse_per_liter;
}
pulse_counters[0].last_time = current_time;
}
}
七、数据存储与管理
1、Flash存储结构
c
复制代码
/* Flash存储布局 */
#define FLASH_BASE_ADDR 0x08080000
#define METER_DATA_ADDR (FLASH_BASE_ADDR + 0x0000)
#define SYSTEM_CONFIG_ADDR (FLASH_BASE_ADDR + 0x4000)
#define LOG_DATA_ADDR (FLASH_BASE_ADDR + 0x6000)
/* 系统配置 */
typedef struct {
uint32_t device_id; // 设备ID
uint8_t network_mode; // 网络模式:01-GPRS,02-WiFi,03-NB-IoT
uint8_t read_interval; // 抄表间隔(小时)
uint8_t upload_interval; // 上传间隔(小时)
uint8_t alarm_enable; // 报警使能
uint16_t low_flow_threshold; // 小流量阈值(L/h)
uint16_t leak_time_threshold; // 漏水判断时间(分钟)
} System_Config_t;
2、数据压缩存储
c
复制代码
/* 日冻结数据压缩存储 */
typedef struct {
uint16_t date; // 日期:YYYYMMDD格式
uint32_t total_flow; // 当日总流量(L)
uint16_t max_flow; // 最大瞬时流量(L/h)
uint16_t avg_flow; // 平均流量(L/h)
uint8_t alarm_flags; // 报警标志
} Daily_Freeze_Data_t;
/* 存储到Flash */
void Save_Daily_Freeze(Daily_Freeze_Data_t *data)
{
uint32_t addr = LOG_DATA_ADDR +
(Get_Days_Since_Install() * sizeof(Daily_Freeze_Data_t));
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, *(uint64_t*)data);
HAL_FLASH_Lock();
}
八、通信协议实现
1、与平台通信协议
c
复制代码
/* 上报数据包 */
typedef struct {
uint8_t header[4]; // "MTR\xAA"
uint32_t device_id; // 集中器ID
uint32_t timestamp; // 时间戳
uint16_t data_len; // 数据长度
uint8_t data[1024]; // 压缩数据
uint16_t crc16; // CRC16校验
} Upload_Packet_t;
/* 数据压缩上传 */
void Upload_Meter_Data(void)
{
Upload_Packet_t packet = {0};
packet.header[0] = 'M'; packet.header[1] = 'T';
packet.header[2] = 'R'; packet.header[3] = 0xAA;
packet.device_id = system_config.device_id;
packet.timestamp = Get_RTC_Time();
/* 压缩当天数据 */
Compress_Daily_Data(packet.data, &packet.data_len);
/* 计算CRC */
packet.crc16 = CRC16_Calculate(packet.data, packet.data_len);
/* 通过GPRS上传 */
GPRS_Send_Data((uint8_t*)&packet, sizeof(packet));
}
2、远程控制指令
c
复制代码
/* 远程控制命令 */
typedef enum {
CMD_READ_METER = 0x01, // 即时抄表
CMD_SET_INTERVAL = 0x02, // 设置抄表间隔
CMD_REBOOT = 0x03, // 重启设备
CMD_FACTORY_RESET = 0x04, // 恢复出厂设置
CMD_FIRMWARE_UPDATE = 0x05, // 固件升级
CMD_CALIBRATE = 0x06 // 校准参数
} Remote_Cmd_t;
/* 命令处理 */
void Process_Remote_Command(uint8_t *cmd_data, uint16_t len)
{
Remote_Cmd_t cmd = (Remote_Cmd_t)cmd_data[0];
switch(cmd) {
case CMD_READ_METER:
Immediate_Read_All_Meters();
break;
case CMD_SET_INTERVAL:
Set_Read_Interval(cmd_data[1]);
break;
case CMD_REBOOT:
NVIC_SystemReset();
break;
case CMD_FIRMWARE_UPDATE:
Start_Firmware_Update(&cmd_data[1], len-1);
break;
default:
Send_Error_Response(0x01); // 未知命令
break;
}
}
九、低功耗管理
1、功耗模式设计
c
复制代码
/* 功耗模式 */
typedef enum {
POWER_ACTIVE = 0, // 活跃模式:全速运行
POWER_SLEEP = 1, // 睡眠模式:CPU停止,外设运行
POWER_STOP = 2, // 停止模式:大部分外设停止
POWER_STANDBY = 3 // 待机模式:最低功耗
} Power_Mode_t;
/* 动态功耗管理 */
void Power_Management(void)
{
static uint32_t last_activity = 0;
uint32_t current_time = HAL_GetTick();
if(current_time - last_activity > 300000) { // 5分钟无活动
Enter_Stop_Mode();
} else if(current_time - last_activity > 60000) { // 1分钟无活动
Enter_Sleep_Mode();
}
}
/* 进入停止模式 */
void Enter_Stop_Mode(void)
{
/* 关闭非必要外设 */
__HAL_RCC_USART1_CLK_DISABLE();
__HAL_RCC_TIM2_CLK_DISABLE();
/* 配置唤醒源 */
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 30, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
/* 进入STOP2模式 */
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
/* 唤醒后恢复 */
SystemClock_Config();
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_TIM2_CLK_ENABLE();
}
十、异常检测与报警
c
复制代码
/* 异常检测算法 */
typedef struct {
uint8_t reverse_flow_alarm; // 倒流报警
uint8_t leak_detection_alarm; // 漏水检测
uint8_t tamper_alarm; // 拆卸报警
uint8_t low_battery_alarm; // 低电量报警
uint8_t communication_alarm; // 通信异常报警
} Alarm_Status_t;
/* 智能漏水检测 */
void Leak_Detection_Algorithm(void)
{
static uint32_t leak_start_time = 0;
static float baseline_flow = 0;
float current_flow = Get_Current_Flow_Rate();
/* 建立基线流量 */
if(baseline_flow == 0) {
baseline_flow = current_flow;
}
/* 检测持续小流量 */
if(current_flow > system_config.low_flow_threshold &&
current_flow < baseline_flow * 0.5f) {
if(leak_start_time == 0) {
leak_start_time = HAL_GetTick();
} else if(HAL_GetTick() - leak_start_time >
system_config.leak_time_threshold * 60000) {
Set_Alarm_Flag(ALARM_LEAK_DETECTION);
Send_Alarm_To_Platform(ALARM_LEAK_DETECTION);
}
} else {
leak_start_time = 0; // 重置计时
}
}
十一、完整主程序框架
c
复制代码
int main(void)
{
/* 系统初始化 */
HAL_Init();
SystemClock_Config();
/* 外设初始化 */
MBus_Init();
Pulse_Init();
Storage_Init();
Communication_Init();
RTC_Init();
Watchdog_Init();
/* 加载系统配置 */
Load_System_Config();
/* 创建任务 */
osThreadNew(Read_Meter_Task, NULL, &read_meter_attr);
osThreadNew(Data_Upload_Task, NULL, &upload_task_attr);
osThreadNew(Power_Manage_Task, NULL, &power_task_attr);
osThreadNew(Alarm_Check_Task, NULL, &alarm_task_attr);
/* 启动RTOS */
osKernelStart();
while(1) {
/* 主循环 - 低功耗等待 */
__WFI();
}
}
参考代码 水表集中抄表器单片机实现 www.youwenfan.com/contentcsv/72707.html
十二、测试验证要点
| 测试项目 |
测试方法 |
合格标准 |
| MBus通信稳定性 |
连续抄表1000次 |
成功率>99.5% |
| 功耗测试 |
电池供电72小时 |
平均电流<50μA |
| 数据存储可靠性 |
断电重启测试 |
数据无丢失 |
| 网络重连能力 |
断网恢复测试 |
30秒内重连 |
| 异常检测准确性 |
模拟各种故障 |
误报率<1% |
十三、成本与BOM建议
| 器件 |
型号 |
单价 |
备注 |
| MCU |
STM32L476RG |
¥18 |
主控制器 |
| MBus收发器 |
TSS721A |
¥12 |
MBus接口 |
| 电源管理 |
TPS61099 |
¥6 |
升压/降压 |
| 存储 |
W25Q128 |
¥8 |
Flash存储 |
| 通信模块 |
EC200S |
¥45 |
4G Cat.1 |
| 外壳 |
定制 |
¥15 |
IP65防护 |