找回密码
 立即注册
查看: 218|回复: 0

我的小白,让豆包帮我写程序实现modbus 03/06/16功能码

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:1
  • 最近打卡:2026-01-17 13:38:48
已绑定手机

1

主题

0

回帖

5

积分

新手上路

积分
5
发表于 2026-1-17 13:38:48 | 显示全部楼层 |阅读模式
[13:36:24.185]发送→01 03 00 00 00 03 05 CB
[13:36:24.260]接收←01 03 06 FF FF FF FF 0C E8 24 04
[13:36:29.706]发送→01 06 00 01 07 D0 DB A6
[13:36:29.796]接收←01 06 00 01 07 D0 DB A6
[13:36:32.908]发送→01 03 00 00 00 03 05 CB
[13:36:32.995]接收←01 03 06 FF FF 07 D0 0C EA A5 6C
[13:36:47.223]发送→01 10 00 00 00 02 04 00 01 86 5F 80 37
[13:36:47.340]接收←45 6E 74 65 72 20 30 78 31 30 20 70 61 72 73 65
                    0D 0A 62 79 74 65 5F 6C 65 6E 3D 31 30 32 36 2C
                    20 72 65 67 5F 6E 75 6D 3D 30 2C 20 72 65 67 5F
                    61 64 64 72 3D 30 0D 0A 72 65 63 76 5F 63 72 63
                    3D 30 78 33 37 38 30 2C 20 63 61 6C 63 5F 63 72
                    63 3D 30 78 33 37 38 30 0D 0A 77 72 69 74 65 5F
                    62 75 66 5B 30 5D 3D 30 78 30 31 38 30 0D 0A 77
                    72 69 74 65 5F 62 75 66 5B 33 39 30 5D 3D 30 78
                    35 66 38 30 0D 0A 01 10 00 00 00 02 41 C8
[13:36:51.205]发送→01 03 00 00 00 03 05 CB
[13:36:51.287]接收←01 03 06 00 01 86 5F 0C E8 00 A1
[13:37:03.412]发送→01 06 00 04 00 02 49 CA
[13:37:03.498]接收←01 06 00 04 00 02 49 CA
[13:37:09.230]发送→02 03 00 00 00 03 05 F8
[13:37:09.312]接收←02 03 06 00 01 86 5F 0C E8 14 51

/**************************************************************************************
* 程序名称:水表计量主控程序 - 精简版(仅保留03/06/16功能码)
* 核心功能:1.双霍尔正向/反向计数 2.Modbus 03读/06单写/16批量写 3.EEPROM掉电存储 4.低功耗休眠 5.ADC电压采样 6.站号修改
* 硬件平台:STC8H单片机 | 波特率:9600bps 8N1 | 晶振:11.0592MHz/12MHz通用
* 通信指令:03读/06单写/16批量写 均支持标准CRC校验
**************************************************************************************/
#include <STC8H.H>        // STC8H寄存器定义头文件
//#include <intrins.h>      // _nop_()函数依赖
#include <stdio.h>      // printf函数依赖的头文件

/**************************************************************************************
* putchar重定向函数(放在头文件之后,其他函数之前,核心位置!)
**************************************************************************************/
char putchar(char c)
{
    // 和你现有的uart_send_byte函数逻辑一致,实现字符发送
    SBUF = c;
    while (!TI); // 等待发送完成
    TI = 0;      // 清除发送完成标志
    return c;
}

/**************************************************************************************
* 引脚定义区 - 硬件接线映射(请勿随意修改)
**************************************************************************************/
sbit INT0_PIN = P3^2;     // 正向霍尔信号输入引脚 -> INT0外部中断0
sbit INT1_PIN = P3^3;     // 反向霍尔信号输入引脚 -> INT1外部中断1

/**************************************************************************************
* 数据类型重定义 - 简化代码书写,提升可读性
**************************************************************************************/
typedef unsigned char u8;  // 无符号字符型,1字节,0~255
typedef unsigned int u16;  // 无符号整型,2字节,0~65535
typedef unsigned long u32; // 无符号长整型,4字节,0~4294967295

/**************************************************************************************
* 全局变量声明 - 跨函数共用变量,全局生效
**************************************************************************************/
unsigned int VDDA;         // ADC采样得到的系统电压值,单位:毫伏(mV)
unsigned int BGV;          // 单片机内部基准电压值,用于ADC校准,单位:毫伏(mV)
u8 modbus_slave_addr = 1;  // === 新增 === 动态站号变量(默认1,支持修改)

/**************************************************************************************
* 功能宏定义区 - 核心参数配置,可按需修改
**************************************************************************************/
// ADC相关宏定义(STC8H标准,头文件内置宏映射)
#define ADC_POWER_EN    ADC_POWER       // ADC电源使能位
#define ADC_START_CONV  ADC_START       // ADC转换启动位
#define ADC_CONV_DONE   ADC_FLAG        // ADC转换完成标志位
// EEPROM配置宏(掉电存储水度值+站号)
#define IAP_ADDR        0x0000          // EEPROM水度值起始地址
#define IAP_ADDR_ADDR   0x0008          // === 新增 === EEPROM站号存储地址(和水度值分离)
#define IAP_DATA_LEN    4               // 存储数据长度:4字节存储u32水度值
#define MAIN_Fosc       11059200L       // 晶振频率,和官方代码一致
#define IAP_PAGE_ERASE  0x04            // EEPROM页擦除命令
#define IAP_READ_CMD    0x01            // EEPROM字节读命令
#define IAP_WRITE_CMD   0x02            // EEPROM字节写命令
#define IAP_EXEC_CMD    0x03            // EEPROM命令执行位
#define IAP_POWER_EN    IAPEN           // IAP模块电源使能位
#define IAP_TPS_VAL     12              // 晶振12MHz,参考代码配置值

// 水流计数时序配置
#define FLOW_TIMEOUT     5              // 霍尔时序超时阈值(5×10ms=50ms),超时放弃计数
// 低功耗休眠配置
#define SLEEP_TIMEOUT    1000           // 空闲休眠阈值(1000×10ms=10秒),无操作休眠
// Modbus核心配置(通信关键,请勿随意修改)
#define SLAVE_ADDR       modbus_slave_addr // === 修改 === 宏映射到动态站号变量
#define HOLD_REG_COUNT   0x0008         // Modbus保持寄存器总数(0000~0007)
#define MAX_READ_NUM     16             // 单次最大读取寄存器数量

/**************************************************************************************
* 系统运行状态变量 - 计数/休眠/通信状态管理
**************************************************************************************/
u8 sleep_flag;                          // 休眠标志位:0=工作,1=休眠
u8 wakeup_count;                        // 空闲计时变量,累计达阈值触发休眠
u32 xdata water_meter;                  // 核心水度计数值,1单位=0.001m³(1升),掉电保存
u8 xdata flow_dir_state;                // 水流方向状态机:0=初始,1=正向触发中,2=反向触发中
u16 xdata flow_timer;                   // 霍尔时序计时变量,超时复位状态机
u8 xdata valid_count_flag;              // 计数有效标志:0=无效,1=正向,2=反向

/**************************************************************************************
* Modbus通信缓冲区 - 保障数据收发稳定
**************************************************************************************/
u16 xdata holding_regs[HOLD_REG_COUNT]; // Modbus保持寄存器数组
u8 xdata uart_rx_buf[32];               // 串口接收缓冲区(扩容适配16功能码)
u8 xdata modbus_resp_buf[64];           // Modbus应答缓冲区
u8 rx_len;                              // 串口接收字节数计数
bit rx_complete;                        // 指令接收完成标志位:0=未完成,1=完成
u16 xdata modbus_timeout;               // Modbus接收超时计数器(防粘包)

/**************************************************************************************
* 函数声明区 - 声明所有自定义函数,避免编译报错
**************************************************************************************/
// 系统初始化函数
void init_sys(void);          // 系统总初始化
void init_ext_int(void);      // 外部中断初始化(霍尔计数)
void init_adc(void);          // ADC初始化(电压采样)
void init_timer0(void);       // 定时器0初始化(10ms中断)
void init_uart(void);         // 串口初始化(Modbus RTU通信)
// 低功耗函数
void sleep_mode(void);        // 进入掉电休眠模式
void wakeup_proc(void);       // 休眠唤醒处理
// EEPROM函数(掉电存储)
void eeprom_read(void);       // 上电读取EEPROM水度值
void eeprom_write(void) reentrant; // 水度更新后写入EEPROM
void eeprom_page_erase(u16 addr);   // EEPROM页擦除
u8 eeprom_read_byte(u16 addr);      // EEPROM单字节读取
void eeprom_write_byte(u16 addr, u8 dat); // EEPROM单字节写入
void iap_idle(void);          // IAP模块空闲复位
void eeprom_read_addr(void);  // === 新增 === 上电读取EEPROM站号
void eeprom_write_addr(void) reentrant; // === 新增 === 站号更新后写入EEPROM
// 硬件功能函数
void battery_check(void);     // 电池电压采样计算
void delay_ms(u16 ms);        // 毫秒级延时函数
u16 ADCRead(u8 channel);      // ADC采样函数(12位精度)
// Modbus通信核心函数
void uart_send_byte(u8 dat);  // 串口单字节发送
void uart_send_buf(u8 *buf, u8 len); // 串口多字节发送(RS485控制)
u16 crc16(u8 *buf, u8 len);   // Modbus标准CRC16校验
void modbus03_response(u8 addr, u16 reg_addr, u8 reg_num); // 03功能码应答
void modbus06_write(u8 addr, u16 reg_addr, u16 write_val); // 06功能码单写
void modbus10_write(u8 addr, u16 reg_addr, u8 reg_num, u16 *write_buf); //16批量写
void modbus_parse(void);      // Modbus指令解析
void update_holding_regs(void);// 更新保持寄存器数据
void limit_slave_addr(void);  // === 新增 === 站号合法性校验

/**************************************************************************************
* 主函数 - 程序入口,执行总逻辑
**************************************************************************************/
void main(void)
{
    P_SW2 |= 0x80;           // 开启STC8H扩展寄存器访问权限(必须)
    init_sys();              // 初始化所有外设
    eeprom_read();           // 上电读取EEPROM中历史水度值
    eeprom_read_addr();      // === 新增 === 上电读取EEPROM中保存的站号
    limit_slave_addr();      // === 新增 === 站号合法性兜底校验
    printf("System init ok! Slave addr=%d\r\n", modbus_slave_addr); // === 修改 === 打印当前站号
    while(1)                 // 主循环,永不退出
    {
        if(sleep_flag)       // 休眠标志置1,进入低功耗
        {
            sleep_mode();
        }
        else                 // 工作模式,执行业务逻辑
        {
            battery_check(); // 实时采样电池电压
            update_holding_regs(); // 更新Modbus寄存器数据

            if (rx_complete) // 接收到完整Modbus指令,执行解析
            {
                modbus_parse();
            }

            // 空闲计时达阈值,触发休眠
            if(wakeup_count > SLEEP_TIMEOUT)
            {
                wakeup_count = 0;
                sleep_flag = 1;
            }
        }
    }
}

/**************************************************************************************
* 系统总初始化函数 - 统一初始化所有外设,便于管理
**************************************************************************************/
void init_sys(void)
{
    P0 = 0xFF;P1 = 0xFF;P2 = 0xFF;P3 = 0xFF; // 所有IO口置高,防止误触发
    init_ext_int();   // 初始化外部中断(霍尔)
    init_adc();       // 初始化ADC(电压采样)
    init_timer0();    // 初始化定时器0(10ms中断)
    init_uart();      // 初始化串口(Modbus通信)
    EA = 1;           // 开启总中断
}

/**************************************************************************************
* 外部中断初始化函数 - 配置INT0/INT1为下降沿触发,适配霍尔信号
**************************************************************************************/
void init_ext_int(void)
{
    P3PU |= 0x0C;     // P3^2(INT0)、P3^3(INT1)使能内部上拉电阻,抗干扰
    IT0 = 1;          // INT0配置为下降沿触发
    EX0 = 1;          // 使能INT0中断
    IT1 = 1;          // INT1配置为下降沿触发
    EX1 = 1;          // 使能INT1中断
}

/**************************************************************************************
* ADC初始化函数 - 配置12位精度,校准内部基准电压
**************************************************************************************/
void init_adc(void)
{
    // 读取内部基准电压值(CHIPID7/8为STC8H内置基准电压寄存器)
    BGV = ((unsigned int)CHIPID7 << 8) | (unsigned int)CHIPID8;
    ADCCFG = 0x2F;    // ADC配置:12位精度、右对齐、分频适配
    delay_ms(1);      // 延时稳定ADC模块
    ADC_CONTR |= ADC_POWER_EN; // 开启ADC电源
}

/**************************************************************************************
* 定时器0初始化函数 - 16位模式,11.0592MHz晶振,精准10ms中断
**************************************************************************************/
void init_timer0(void)
{
    TMOD |= 0x01;     // 定时器0配置为16位定时器模式
    TH0 = 0xDC;       // 定时器高8位初值
    TL0 = 0x00;       // 定时器低8位初值
    ET0 = 1;          // 使能定时器0中断
    TR0 = 1;          // 启动定时器0
}

/**************************************************************************************
* 串口初始化函数 - 9600bps波特率,8N1格式,适配Modbus RTU通信
**************************************************************************************/
void init_uart(void)
{
    ES = 1;           // 使能串口中断
    PCON &= 0x7F;     // 关闭串口倍速模式,保证波特率稳定
    SCON = 0x50;      // 串口配置:8位数据位、1位停止位、接收使能
    AUXR &= 0xBF;     // 定时器1分频配置
    AUXR &= 0xFE;     // 定时器1为12T模式,适配波特率
    TMOD &= 0x0F;     // 清空定时器1模式
    TMOD |= 0x20;     // 定时器1配置为8位自动重装模式
    TL1 = 0xFD;       // 9600bps波特率初值(11.0592MHz/12MHz通用)
    TH1 = 0xFD;       // 定时器1重装值
    ET1 = 0;          // 关闭定时器1中断(仅用于波特率生成)
    TR1 = 1;          // 启动定时器1
}

/**************************************************************************************
* 低功耗休眠函数 - 进入掉电模式,最低功耗,霍尔/串口可唤醒
**************************************************************************************/
void sleep_mode(void)
{
    TR0 = 0;          // 停止定时器0
    PCON |= PD;       // 开启STC8H掉电休眠模式(最低功耗)
    _nop_();_nop_();  // 空操作,保证休眠指令执行完成
    TR0 = 1;          // 唤醒后重启定时器0
}

/**************************************************************************************
* 休眠唤醒处理函数 - 退出休眠,复位状态,稳定外设
**************************************************************************************/
void wakeup_proc(void)
{
    sleep_flag = 0;       // 清除休眠标志,进入工作模式
    wakeup_count = 0;     // 复位空闲计时器
    delay_ms(10);         // 延时稳定外设,避免误触发
}

/**************************************************************************************
* ADC采样函数 - 12位精度采样指定通道,返回0~4095采样值
**************************************************************************************/
u16 ADCRead(u8 channel)
{
    ADC_RES = 0;ADC_RESL = 0; // 清空采样结果寄存器
    // 配置采样通道+启动ADC转换
    ADC_CONTR = (ADC_CONTR & 0xF0) | 0x40 | (channel & 0x0F);
    _nop_();_nop_();_nop_();_nop_(); // 等待转换启动
    while((ADC_CONTR & 0x20) == 0);  // 等待转换完成
    ADC_CONTR &= ~0x20;              // 清除转换完成标志
    return ((((u16)ADC_RES << 8) | ADC_RESL) & 0x0FFF); // 返回12位采样值
}

/**************************************************************************************
* 电池电压采样函数 - 计算实际电压值(mV),每次采样重启ADC电源
**************************************************************************************/
void battery_check(void)
{
    u8  i;u16 res = 0;
    ADC_CONTR |= ADC_POWER_EN; // 每次采样前重启ADC电源,保证稳定
    ADCRead(15);ADCRead(15);   // 丢弃前两次不稳定采样值
    for(i=0; i<8; i++) res += ADCRead(15); // 8次采样取平均,滤除噪声
    res >>= 3; // 平均值计算(除以8)
    // 根据基准电压计算实际电压值:VDDA = BGV * 4096 / 采样值
    VDDA = (unsigned int)((unsigned long)BGV * 4096UL / (unsigned long)res);
}

/**************************************************************************************
* 毫秒级延时函数 - 适配11.0592MHz晶振,精准延时,用于外设稳定/RS485时序
**************************************************************************************/
void delay_ms(u16 ms)
{
    u16 i, j;
    for(i = ms; i > 0; i--)
        for(j = 110; j > 0; j--);
}

/**************************************************************************************
* 正向霍尔中断服务函数 - INT0,下降沿触发,硬件消抖+反向计数逻辑
**************************************************************************************/
void int0_isr(void) interrupt INT0_VECTOR
{
    u8 temp=0,i;
    // 硬件消抖:连续检测10次,低电平≥8次判定为有效信号
    for(i=0;i<10;i++){if(INT0_PIN==0)temp++;_nop_();}
    if(temp<8) return; // 消抖失败,直接退出

    wakeup_proc(); // 触发唤醒,退出休眠
    switch(flow_dir_state)
    {
        case 0: // 初始状态,标记为正向触发中
            flow_dir_state = 1;flow_timer=0;valid_count_flag=0;break;
        case 2: // 反向触发中,接收到正向信号 → 反向计数
            if(water_meter>0) water_meter--;
            eeprom_write(); // 水度更新,写入EEPROM
            flow_dir_state=0;flow_timer=0;valid_count_flag=2;break;
        default: // 异常状态,复位状态机
            flow_dir_state=0;flow_timer=0;valid_count_flag=0;break;
    }
}

/**************************************************************************************
* 反向霍尔中断服务函数 - INT1,下降沿触发,硬件消抖+正向计数逻辑
**************************************************************************************/
void int1_isr(void) interrupt INT1_VECTOR
{
    u8 temp=0,i;
    // 硬件消抖:连续检测10次,低电平≥8次判定为有效信号
    for(i=0;i<10;i++){if(INT1_PIN==0)temp++;_nop_();}
    if(temp<8) return; // 消抖失败,直接退出

    wakeup_proc(); // 触发唤醒,退出休眠
    switch(flow_dir_state)
    {
        case 0: // 初始状态,标记为反向触发中
            flow_dir_state = 2;flow_timer=0;valid_count_flag=0;break;
        case 1: // 正向触发中,接收到反向信号 → 正向计数
            water_meter++;
            eeprom_write(); // 水度更新,写入EEPROM
            flow_dir_state=0;flow_timer=0;valid_count_flag=1;break;
        default: // 异常状态,复位状态机
            flow_dir_state=0;flow_timer=0;valid_count_flag=0;break;
    }
}

/**************************************************************************************
* 定时器0中断服务函数 - 10ms一次,处理:霍尔时序超时/空闲计时/Modbus超时
**************************************************************************************/
void timer0_isr(void) interrupt TMR0_VECTOR
{
    TH0=0xDC;TL0=0x00; // 重装定时器初值,保证10ms中断精度

    // 1. 霍尔时序超时处理:超时复位状态机
    if(flow_dir_state!=0)
    {
        flow_timer++;
        if(flow_timer>=FLOW_TIMEOUT)
        {flow_dir_state=0;flow_timer=0;valid_count_flag=0;}
    }

    // 2. 空闲计时:工作模式下累计,触发休眠
    if(sleep_flag == 0) wakeup_count++;

    // 3. Modbus超时:50ms无数据,判定指令接收完成(防粘包)
    if(rx_len>0)
    {
        modbus_timeout++;
        if(modbus_timeout>5) // 5×10ms=50ms,符合Modbus RTU标准帧间隙
        {rx_complete=1;modbus_timeout=0;}
    }
    else modbus_timeout=0;
}

/**************************************************************************************
* Modbus通信核心函数(03/06/16功能码支撑)
**************************************************************************************/
// 串口单字节发送函数 - 底层发送接口,保证字节发送完成
void uart_send_byte(u8 dat)
{
    SBUF = dat;
    while (!TI); // 等待发送完成
    TI = 0;      // 清除发送完成标志
}

// RS485发送函数(精简时序,9600bps最优适配)
void uart_send_buf(u8 *buf, u8 len)
{
    u8 i;
//    DE_RE = 1;           // RS485切换为发送模式
    for (i = 0; i < len; i++) uart_send_byte(buf[i]);
    delay_ms(1);         // 1ms延时,确保最后1字节发送完成
//    DE_RE = 0;           // RS485切回接收模式
}

// Modbus标准CRC16校验函数(低位在前、高位在后)
u16 crc16(u8 *buf, u8 len)
{
    u16 crc = 0xFFFF;u8 i, j;
    for (i = 0; i < len; i++)
    {
        crc ^= buf[i];
        for (j = 0; j < 8; j++)
        {
            if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001;
            else crc >>= 1;
        }
    }
    return crc;
}

// 03功能码应答函数(读保持寄存器)
void modbus03_response(u8 addr, u16 reg_addr, u8 reg_num)
{
    u16 crc;u8 i, idx = 0;
    modbus_resp_buf[idx++] = addr;    // 站号
    modbus_resp_buf[idx++] = 0x03;    // 功能码
    modbus_resp_buf[idx++] = reg_num * 2; // 返回数据字节数

    // 填充寄存器数据:高字节在前
    for (i = 0; i < reg_num; i++)
    {
        modbus_resp_buf[idx++] = (holding_regs[reg_addr + i] >> 8) & 0xFF;
        modbus_resp_buf[idx++] = holding_regs[reg_addr + i] & 0xFF;
    }

    // CRC校验:低位在前
    crc = crc16(modbus_resp_buf, idx);
    modbus_resp_buf[idx++] = (u8)(crc & 0xFF);
    modbus_resp_buf[idx++] = (u8)((crc >> 8) & 0xFF);
    uart_send_buf(modbus_resp_buf, idx);
}

// 06功能码单寄存器写函数
void modbus06_write(u8 addr, u16 reg_addr, u16 write_val)
{
    u16 crc;u8 idx = 0;
    if(addr != SLAVE_ADDR) return; // 站号不匹配,直接返回

    // 寄存器写入权限控制
    switch(reg_addr)
    {
        case 0x0000: // 水度高16位
            water_meter = (water_meter & 0x0000FFFF) | ((u32)write_val << 16);
            eeprom_write();
            break;
        case 0x0001: // 水度低16位
            water_meter = (water_meter & 0xFFFF0000) | (u32)write_val;
            eeprom_write();
            break;
        // === 新增 === 站号寄存器分支(0004H)
        case 0x0004: // 站号寄存器(可写,掉电保存)
            modbus_slave_addr = (u8)write_val; // 站号为单字节,强制转换
            limit_slave_addr(); // 合法性校验
            eeprom_write_addr(); // 写入EEPROM,掉电保存
            break;
        // === 新增结束 ===
        case 0x0002: // 电压寄存器(只读)
        case 0x0003: // 状态寄存器(只读)
            return;
        default: // 预留寄存器
            holding_regs[reg_addr] = write_val;
            break;
    }

    // 组装应答帧(与下发指令一致)
    modbus_resp_buf[idx++] = addr;
    modbus_resp_buf[idx++] = 0x06;
    modbus_resp_buf[idx++] = (reg_addr >> 8) & 0xFF;
    modbus_resp_buf[idx++] = reg_addr & 0xFF;
    modbus_resp_buf[idx++] = (write_val >> 8) & 0xFF;
    modbus_resp_buf[idx++] = write_val & 0xFF;

    // CRC校验
    crc = crc16(modbus_resp_buf, idx);
    modbus_resp_buf[idx++] = (u8)(crc & 0xFF);
    modbus_resp_buf[idx++] = (u8)((crc >> 8) & 0xFF);
    uart_send_buf(modbus_resp_buf, idx);
}

// 16功能码批量写寄存器函数(核心校零功能)
void modbus10_write(u8 addr, u16 reg_addr, u8 reg_num, u16 *write_buf)
{
    u16 crc;u8 idx = 0,i;
    if(addr != SLAVE_ADDR) return;

    // 批量写入寄存器
    for(i=0;i<reg_num;i++)
    {
        if((reg_addr + i) == 0x0000)
        {
            water_meter = (water_meter & 0x0000FFFF) | ((u32)write_buf[i] << 16);


            eeprom_write();
        }
        else if((reg_addr + i) == 0x0001)
        {
            water_meter = (water_meter & 0xFFFF0000) | (u32)write_buf[i];
            eeprom_write();
        }
        // === 新增 === 支持16功能码批量写站号
        else if((reg_addr + i) == 0x0004)
        {
            modbus_slave_addr = (u8)write_buf[i];
            limit_slave_addr();
            eeprom_write_addr();
        }
        // === 新增结束 ===
        else
        {
            holding_regs[reg_addr + i] = write_buf[i];
        }
    }

    // 组装应答帧(站号+功能码+起始地址+寄存器数量+CRC)
    modbus_resp_buf[idx++] = addr;
    modbus_resp_buf[idx++] = 0x10;
    modbus_resp_buf[idx++] = (reg_addr >> 8) & 0xFF;
    modbus_resp_buf[idx++] = reg_addr & 0xFF;
    modbus_resp_buf[idx++] = (reg_num >> 8) & 0xFF;
    modbus_resp_buf[idx++] = reg_num & 0xFF;

    crc = crc16(modbus_resp_buf, idx);
    modbus_resp_buf[idx++] = (u8)(crc & 0xFF);
    modbus_resp_buf[idx++] = (u8)((crc >> 8) & 0xFF);
    uart_send_buf(modbus_resp_buf, idx);
}

// 更新保持寄存器数据
void update_holding_regs(void)
{
    holding_regs[0] = (u16)(water_meter >> 16); // 水度高16位
    holding_regs[1] = (u16)(water_meter & 0xFFFF); // 水度低16位
    holding_regs[2] = VDDA; // 电池电压(mV)
    holding_regs[3] = valid_count_flag; // 水流状态
    holding_regs[4] = modbus_slave_addr; // === 新增 === 0004H寄存器映射为当前站号
    holding_regs[5] = 0x0000; // 预留
    holding_regs[6] = 0x0000;
    holding_regs[7] = 0x0000;
}

/**************************************************************************************
* Modbus指令解析函数(仅解析03/06/16功能码)
**************************************************************************************/
void modbus_parse(void)
{
    u16 recv_crc, calc_crc;
    u16 reg_addr, write_val;
    u8 reg_num, byte_len, i;
    u16 write_buf[8]; // 批量写数据缓冲区

    // 指令长度校验:03/06固定8字节,16功能码长度可变
        if(uart_rx_buf[1] == 0x03 || uart_rx_buf[1] == 0x06)
        {
                if(rx_len != 8) goto parse_end; // 03/06固定8字节
        }
        else if(uart_rx_buf[1] == 0x10)
        {
                if(rx_len < 8) goto parse_end;  // 16功能码长度≥8,具体由数据长度决定
        }
        else
        {
                goto parse_end; // 其他功能码直接丢弃
        }


    // 站号校验
    if(uart_rx_buf[0] != SLAVE_ADDR && uart_rx_buf[0] != 0x00)
        goto parse_end;

    switch(uart_rx_buf[1])
    {
        case 0x03: // 读保持寄存器
            recv_crc = uart_rx_buf[6] | (uart_rx_buf[7] << 8);
            calc_crc = crc16(uart_rx_buf, 6);
            if(recv_crc != calc_crc) goto parse_end;

            reg_addr = (uart_rx_buf[2] << 8) | uart_rx_buf[3];
            reg_num = (uart_rx_buf[4] << 8) | uart_rx_buf[5];

            // 寄存器地址合法性校验
            if(reg_addr >= HOLD_REG_COUNT || (reg_addr + reg_num) > HOLD_REG_COUNT || reg_num == 0 || reg_num > MAX_READ_NUM)
            {
                // 返回地址越界错误帧
                modbus_resp_buf[0] = SLAVE_ADDR;
                modbus_resp_buf[1] = 0x83;
                modbus_resp_buf[2] = 0x02;
                recv_crc = crc16(modbus_resp_buf, 3);
                modbus_resp_buf[3] = recv_crc & 0xFF;
                modbus_resp_buf[4] = (recv_crc >> 8) & 0xFF;
                uart_send_buf(modbus_resp_buf, 5);
                goto parse_end;
            }
            modbus03_response(SLAVE_ADDR, reg_addr, reg_num);
            break;

        case 0x06: // 单寄存器写
            recv_crc = uart_rx_buf[6] | (uart_rx_buf[7] << 8);
            calc_crc = crc16(uart_rx_buf, 6);
            if(recv_crc != calc_crc) goto parse_end;

            reg_addr = (uart_rx_buf[2] << 8) | uart_rx_buf[3];
            write_val = (uart_rx_buf[4] << 8) | uart_rx_buf[5];
            modbus06_write(SLAVE_ADDR, reg_addr, write_val);
            break;

                case 0x10: // 批量写寄存器
                        printf("Enter 0x10 parse\r\n"); // 调试打印:确认进入16功能码分支
                       
                        // 第一步:先提取正确的基础参数(顺序不能错!)
                        reg_addr = (uart_rx_buf[2] << 8) | uart_rx_buf[3];  // 起始地址:字节2=高,字节3=低
                        reg_num = (uart_rx_buf[4] << 8) | uart_rx_buf[5];   // 寄存器数量:字节4=高,字节5=低
                        byte_len = uart_rx_buf[6];                          // 数据字节数:字节6(关键!顺序不能反)
                       
                        // 打印提取的参数,验证正确性
                        printf("byte_len=%d, reg_num=%d, reg_addr=%d\r\n", byte_len, reg_num, reg_addr);
                       
                        // 第二步:校验参数合法性(新增,防止越界)
                        if(byte_len != reg_num * 2 || reg_num == 0 || (reg_addr + reg_num) > HOLD_REG_COUNT)
                        {
                                printf("Param error: byte_len=%d, reg_num=%d\r\n", byte_len, reg_num);
                                goto parse_end;
                        }
                       
                        // 第三步:正确提取CRC值(范围=站号~最后1字节数据)
                        // CRC位置 = 7 + byte_len (数据起始位7) → 7+4=11 → uart_rx_buf[11] = 80, uart_rx_buf[12] = 37
                        recv_crc = uart_rx_buf[7 + byte_len] | (uart_rx_buf[8 + byte_len] << 8);
                        // CRC计算范围 = 0 ~ 6 + byte_len (站号~最后1字节数据) → 0~10
                        calc_crc = crc16(uart_rx_buf, 7 + byte_len); // 7+byte_len = 11(0~10共11字节)
                       
                        // 打印CRC值,验证正确性
                        printf("recv_crc=0x%04x, calc_crc=0x%04x\r\n", recv_crc, calc_crc);
                        if(recv_crc != calc_crc)
                        {
                                printf("CRC check fail!\r\n"); // 打印CRC失败提示
                                goto parse_end;
                        }
                       
                        // 第四步:正确提取批量写入数据
                        for(i=0;i<reg_num;i++)
                        {
                                // 数据起始位=7 → 7+i*2=高字节,8+i*2=低字节
                                write_buf[i] = (uart_rx_buf[7 + i*2] << 8) | uart_rx_buf[8 + i*2];
                                printf("write_buf[%d]=0x%04x\r\n", i, write_buf[i]); // 打印写入值
                        }
                       
                        // 执行批量写操作
                        modbus10_write(SLAVE_ADDR, reg_addr, reg_num, write_buf);
                        break;

        default: // 非法功能码
            modbus_resp_buf[0] = SLAVE_ADDR;
            modbus_resp_buf[1] = uart_rx_buf[1] | 0x80;
            modbus_resp_buf[2] = 0x01; // 错误码:非法功能码
            recv_crc = crc16(modbus_resp_buf, 3);
            modbus_resp_buf[3] = recv_crc & 0xFF;
            modbus_resp_buf[4] = (recv_crc >> 8) & 0xFF;
            uart_send_buf(modbus_resp_buf, 5);
            break;
    }

parse_end:
    rx_len = 0;
    rx_complete = 0;
}

/**************************************************************************************
* 串口中断服务函数 - 接收Modbus指令,触发唤醒
**************************************************************************************/
void uart_isr(void) interrupt 4
{
    if(RI) // 检测接收中断
    {
        RI = 0;
        if(rx_len < sizeof(uart_rx_buf))
        {
            uart_rx_buf[rx_len++] = SBUF;
            modbus_timeout = 0;
            wakeup_proc(); // 接收到数据,唤醒设备
        }
    }
}

/**************************************************************************************
* EEPROM相关函数 - 基于参考代码重构(STC8H 12MHz晶振适配)
**************************************************************************************/
// IAP空闲函数(完全复用官方代码)
void iap_idle(void)
{
    IAP_CONTR = 0;                              // 关闭IAP功能
    IAP_CMD = 0;                                // 清除命令寄存器
    IAP_TRIG = 0;                               // 清除触发寄存器
    IAP_ADDRH = 0x80;                           // 将地址设置到非IAP区域,防止误操作
    IAP_ADDRL = 0;
}

// 读取EEPROM一个字节(参考官方IapRead函数)
u8 eeprom_read_byte(u16 addr)
{
    u8 dat;
    EA = 0;                                     // 关闭总中断,防止操作被打断
    IAP_CONTR = 0x80;                           // 使能IAP(官方代码固定值)
    IAP_CMD = 1;                                // 设置IAP读命令(官方代码0x01)
    IAP_ADDRL = addr;                           // 设置IAP低地址
    IAP_ADDRH = addr >> 8;                      // 设置IAP高地址
    IAP_TRIG = 0x5a;                            // 官方代码:写触发命令1
    IAP_TRIG = 0xa5;                            // 官方代码:写触发命令2
    _nop_();                                    // 等待操作完成
    dat = IAP_DATA;                             // 读取数据
    iap_idle();                                 // 关闭IAP功能
    EA = 1;                                     // 恢复总中断
    return dat;
}

// 写入一个字节到EEPROM(参考官方IapProgram函数)
void eeprom_write_byte(u16 addr, u8 dat)
{
    EA = 0;                                     // 关闭总中断
    IAP_CONTR = 0x80;                           // 使能IAP
    IAP_CMD = 2;                                // 设置IAP写命令(官方代码0x02)
    IAP_ADDRL = addr;                           // 设置IAP低地址
    IAP_ADDRH = addr >> 8;                      // 设置IAP高地址
    IAP_DATA = dat;                             // 写入数据
    IAP_TRIG = 0x5a;                            // 触发指令1
    IAP_TRIG = 0xa5;                            // 触发指令2
    _nop_();                                    // 等待写入完成
    iap_idle();                                 // 关闭IAP
    EA = 1;                                     // 恢复总中断
}

// 擦除EEPROM扇区(参考官方IapErase函数)
void eeprom_page_erase(u16 addr)
{
    EA = 0;                                     // 关闭总中断
    IAP_CONTR = 0x80;                           // 使能IAP
    IAP_CMD = 3;                                // 设置IAP擦除命令(官方代码0x03,关键修正!)
    IAP_ADDRL = addr;                           // 设置IAP低地址
    IAP_ADDRH = addr >> 8;                      // 设置IAP高地址
    IAP_TRIG = 0x5a;                            // 触发指令1
    IAP_TRIG = 0xa5;                            // 触发指令2
    _nop_();                                    // 等待擦除完成
    iap_idle();                                 // 关闭IAP
    EA = 1;                                     // 恢复总中断
}

// 上电读取EEPROM中的水度值
void eeprom_read(void)
{
    u8 i;
    u8 data_buf[IAP_DATA_LEN];
    // 初始化IAP等待参数(官方代码核心步骤,之前缺失!)
    IAP_TPS = 11;       // 适配11.0592MHz晶振的等待参数,仅需初始化一次

    // 从IAP_ADDR读取4字节水度值
    for(i=0; i<IAP_DATA_LEN; i++)
    {
        data_buf[i] = eeprom_read_byte(IAP_ADDR + i);
    }
    // 拼接为32位水度值
    water_meter = ((u32)data_buf[0] << 24) |
                  ((u32)data_buf[1] << 16) |
                  ((u16)data_buf[2] << 8) |
                  data_buf[3];
}

// 水度值更新后写入EEPROM(参考官方擦除+写入流程)
void eeprom_write(void) reentrant
{
    u8 i;
    u8 data_buf[IAP_DATA_LEN];
    // 初始化IAP等待参数
    IAP_TPS = 11;

    // 拆分32位水度值为4个字节
    data_buf[0] = (u8)(water_meter >> 24) & 0xFF;
    data_buf[1] = (u8)(water_meter >> 16) & 0xFF;
    data_buf[2] = (u8)(water_meter >> 8) & 0xFF;
    data_buf[3] = (u8)water_meter & 0xFF;

    // 先擦除扇区(官方代码流程:擦除→写入)
    eeprom_page_erase(IAP_ADDR);
    // 逐个写入4字节数据
    for(i=0; i<IAP_DATA_LEN; i++)
    {
        eeprom_write_byte(IAP_ADDR + i, data_buf[i]);
    }
}

// === 新增 === 上电读取EEPROM中保存的站号
void eeprom_read_addr(void)
{
    IAP_TPS = 11; // 初始化IAP等待参数
    modbus_slave_addr = eeprom_read_byte(IAP_ADDR_ADDR);
}

// === 新增 === 站号更新后写入EEPROM
void eeprom_write_addr(void) reentrant
{
    IAP_TPS = 11; // 初始化IAP等待参数
    eeprom_page_erase(IAP_ADDR_ADDR); // 先擦除扇区
    eeprom_write_byte(IAP_ADDR_ADDR, modbus_slave_addr); // 写入站号
}

// === 新增 === 站号合法性校验(兜底保障)
void limit_slave_addr(void)
{
    // Modbus标准:站号范围1~247,非法值重置为1
    if(modbus_slave_addr < 1 || modbus_slave_addr > 247)
    {
        modbus_slave_addr = 1;
    }
}




回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-4-4 17:43 , Processed in 0.125018 second(s), 42 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表