找回密码
 立即注册
查看: 122|回复: 6

例程:串口1中断收发-C语言-MODBUS协议把超时检测,由定时器T0改为T3不能用?

[复制链接]
  • TA的每日心情
    奋斗
    9 小时前
  • 签到天数: 150 天

    [LV.7]常住居民III

    109

    主题

    455

    回帖

    839

    积分

    高级会员

    积分
    839
    发表于 2024-3-27 10:59:29 | 显示全部楼层 |阅读模式
    本帖最后由 380091044 于 2024-3-27 11:26 编辑

    /*********************************************************/
    #define MAIN_Fosc                11059200L        //定义主时钟

    #include        "STC8Gxxx.h"


    /*************        功能说明        **************

    请先别修改程序, 直接下载"08-串口1中断收发-C语言-MODBUS协议"里的"UART1.hex"测试, 主频选择11.0592MHZ. 测试正常后再修改移植.

    串口1按MODBUS-RTU协议通信. 本例为从机程序, 主机一般是电脑端.

    本例程只支持多寄存器读和多寄存器写, 寄存器长度为64个, 别的命令用户可以根据需要按MODBUS-RTU协议自行添加.

    本例子数据使用大端模式(与C51一致), CRC16使用小端模式(与PC一致).

    默认参数:
    串口1设置均为 1位起始位, 8位数据位, 1位停止位, 无校验.
    串口1(P3.0 P3.1): 9600bps.

    定时器0用于超时计时. 串口每收到一个字节都会重置超时计数, 当串口空闲超过35bit时间时(9600bps对应3.6ms)则接收完成.
    用户修改波特率时注意要修改这个超时时间.

    本例程只是一个应用例子, 科普MODBUS-RTU协议并不在本例子职责范围, 用户可以上网搜索相关协议文本参考.
    本例定义了64个寄存器, 访问地址为0x1000~0x103f.
    命令例子:
    写入4个寄存器(8个字节):
    10 10 1000 0004 08 1234 5678 90AB CDEF 4930
    返回:
    10 10 10 00 00 04 C64B
    读出4个寄存器:
    10 03 1000 0004 4388
    返回:
    10 03 08 12 34 56 78 90 AB CD EF D53D

    命令错误返回信息(自定义):
    0x90: 功能码错误. 收到了不支持的功能码.
    0x91: 命令长度错误.
    0x92: 写入或读出寄存器个数或字节数错误.
    0x93: 寄存器地址错误.

    注意: 收到广播地址0x00时要处理信息, 但不返回应答.

    ******************************************/

    /*************        本地常量声明        **************/
    #define        RX1_Length        128                /* 接收缓冲长度 */
    #define        TX1_Length        128                /* 发送缓冲长度 */


    /*************        本地变量声明        **************/
    u8        xdata        RX1_Buffer[RX1_Length];        //接收缓冲
    u8        xdata        TX1_Buffer[TX1_Length];        //发送缓冲

    u8        RX1_cnt;                //接收字节计数.
    u8        TX1_cnt;                //发送字节计数
    u8        TX1_number;                //要发送的字节数
    u8        RX1_TimeOut;        //接收超时计时器

    bit        B_RX1_OK;                // 接收数据标志
    bit        B_TX1_Busy;                // 发送忙标志


    /*************        本地函数声明        **************/
    void        UART1_config(u32 brt, u8 timer, u8 io);        //
    u8                Timer3_Config(u8 t, u32 reload);//第一处修改
    u16                MODBUS_CRC16(u8 *p, u8 n);
    u8                MODBUS_RTU(void);



    #define        SL_ADDR                0x10        /* 本从机站号地址 */
    #define        REG_ADDRESS        0x1000        /* 寄存器首地址   */
    #define        REG_LENGTH        64                /* 寄存器长度     */
    u16                xdata modbus_reg[REG_LENGTH];        /* 寄存器地址 */

    //========================================================================
    // 函数: void main(void)
    // 描述: 主函数
    // 参数: none.
    // 返回: none.
    // 版本: VER1.0
    // 日期: 2018-4-2
    // 备注:
    //========================================================================
    void main(void)
    {
            u8        i;
            u16        crc;
            Timer3_Config(0, MAIN_Fosc / 10000);// 第二处修改
            UART1_config(9600UL, 1, 2);        //(io == 2)        切换到 P1.6 P1.7

            EA = 1;

            while (1)
            {
                    if(B_RX1_OK && !B_TX1_Busy)        //收到数据, 进行MODBUS-RTU协议解析
                    {
                            if(MODBUS_CRC16(RX1_Buffer, RX1_cnt) == 0)        //首先判断CRC16是否正确, 不正确则忽略, 不处理也不返回信息
                            {
                                    if((RX1_Buffer[0] == 0x00) || (RX1_Buffer[0] == SL_ADDR))        //然后判断站号地址是否正确, 或者是否广播地址(不返回信息)
                                    {
                                            if(RX1_cnt > 2) RX1_cnt -= 2;        //去掉CRC16校验字节
                                            i = MODBUS_RTU();        //MODBUS-RTU协议解析
                                            if(i != 0)        //错误处理
                                            {
                                                    TX1_Buffer[0] = SL_ADDR;        //站号地址
                                                    TX1_Buffer[1] = i;                        //错误代码
                                                    crc = MODBUS_CRC16(TX1_Buffer, 2);
                                                    TX1_Buffer[2] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                                                    TX1_Buffer[3] = (u8)(crc>>8);
                                                    B_TX1_Busy = 1;                //标志发送忙
                                                    TX1_cnt    = 0;                //发送字节计数
                                                    TX1_number = 4;                //要发送的字节数
                                                    TI = 1;                                //启动发送
                                            }
                                    }
                            }
                            RX1_cnt = 0;
                            B_RX1_OK = 0;
                    }
            }
    }


    /****************************** MODBUS_CRC (shift) *************** past test 06-11-27 *********
            计算CRC,调用方式        MODBUS_CRC16(&CRC,8);        &CRC为首地址,8为字节数
            CRC-16 for MODBUS
            CRC16=X16+X15+X2+1
            TEST: ---> ABCDEFGHIJ        CRC16=0x0BEE        1627T
    */
    //========================================================================
    // 函数: u16        MODBUS_CRC16(u8 *p, u8 n)
    // 描述: 计算CRC16函数.
    // 参数: *p: 要计算的数据指针.
    //        n: 要计算的字节数.
    // 返回: CRC16值.
    // 版本: V1.0, 2022-3-18 梁工
    //========================================================================
    u16        MODBUS_CRC16(u8 *p, u8 n)
    {
            u8        i;
            u16        crc16;

            crc16 = 0xffff;        //预置16位CRC寄存器为0xffff(即全为1)
            do
            {
                    crc16 ^= (u16)*p;                //把8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器
                    for(i=0; i<8; i++)                //8位数据
                    {
                            if(crc16 & 1)        crc16 = (crc16 >> 1) ^ 0xA001;        //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位,
                                                                                                                            //再异或多项式0xA001
                            else        crc16 >>= 1;                                                        //如果最低位为0,把CRC寄存器的内容右移一位(朝低位),用0填补最高位
                    }
                    p++;
            }while(--n != 0);
            return        (crc16);
    }

    /********************* modbus协议 *************************/
    /***************************************************************************
    写多寄存器
    数据:          地址    功能码   寄存地址 寄存器个数  写入字节数   写入数据   CRC16
    偏移:     0        1        2 3      4 5          6          7~        最后2字节
    字节:   1 byte   1 byte    2 byte   2 byte      1byte       2*n byte   2 byte
             addr     0x10      xxxx     xxxx        xx         xx....xx    xxxx

      返回
    数据:          地址    功能码   寄存地址 寄存器个数   CRC16
    偏移:     0        1        2 3      4 5         6 7
    字节:   1 byte   1 byte    2 byte   2 byte      2 byte
             addr     0x10      xxxx     xxxx        xxxx


    读多寄存器
    数据:站号(地址)  功能码   寄存地址 寄存器个数  CRC16
    偏移:      0       1        2 3      4 5         6 7
    字节:    1 byte  1 byte    2 byte   2 byte     2 byte
              addr    0x03      xxxx     xxxx       xxxx

      返回
    数据:站号(地址)  功能码   读出字节数  读出数据  CRC16
    偏移:      0       1        2           3~      最后2字节
    字节:   1 byte   1 byte    1byte      2*n byte  2 byte
             addr     0x03       xx       xx....xx   xxxx

      返回错误代码
    数据:站号(地址)  错误码   CRC16
    偏移:      0       1      最后2字节
    字节:   1 byte   1 byte   2 byte
             addr     0x93     xxxx
    ***************************************************************************/
    u8        MODBUS_RTU(void)
    {
            u8        i,j,k;
            u16        reg_addr;        //寄存器地址
            u8        reg_len;        //写入寄存器个数
            u16        crc;

            if(RX1_Buffer[1] == 0x10)        //写多寄存器
            {
                    if(RX1_cnt < 9)                return 0x91;                //命令长度错误
                    if((RX1_Buffer[4] != 0) || ((RX1_Buffer[5] *2) != RX1_Buffer[6]))        return 0x92;        //写入寄存器个数与字节数错误
                    if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH))        return 0x92;        //写入寄存器个数错误

                    reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3];        //寄存器地址
                    reg_len = RX1_Buffer[5];        //写入寄存器个数
                    if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH))        return 0x93;        //寄存器地址错误
                    if(reg_addr < REG_ADDRESS)                return 0x93;        //寄存器地址错误
                    if((reg_len*2+7) != RX1_cnt)        return 0x91;        //命令长度错误

                    j = reg_addr - REG_ADDRESS;        //寄存器数据下标
                    for(k=7, i=0; i<reg_len; i++,j++)
                    {
                            modbus_reg[j] = ((u16)RX1_Buffer[k] << 8) + RX1_Buffer[k+1];        //写入数据, 大端模式
                            k += 2;
                    }

                    if(RX1_Buffer[0] != 0)        //非广播地址则应答
                    {
                            for(i=0; i<6; i++)        TX1_Buffer = RX1_Buffer;        //要返回的应答
                            crc = MODBUS_CRC16(TX1_Buffer, 6);
                            TX1_Buffer[6] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                            TX1_Buffer[7] = (u8)(crc>>8);
                            B_TX1_Busy = 1;                //标志发送忙
                            TX1_cnt    = 0;                //发送字节计数
                            TX1_number = 8;                //要发送的字节数
                            TI = 1;                                //启动发送
                    }
            }
            else if(RX1_Buffer[1] == 0x03)        //读多寄存器
            {
                    if(RX1_Buffer[0] != 0)        //非广播地址则应答
                    {
                            if(RX1_cnt != 6)                return 0x91;                //命令长度错误
                            if(RX1_Buffer[4] != 0)        return 0x92;        //读出寄存器个数错误
                            if((RX1_Buffer[5]==0) || (RX1_Buffer[5] > REG_LENGTH))        return 0x92;        //读出寄存器个数错误

                            reg_addr = ((u16)RX1_Buffer[2] << 8) + RX1_Buffer[3];        //寄存器地址
                            reg_len = RX1_Buffer[5];        //读出寄存器个数
                            if((reg_addr+(u16)RX1_Buffer[5]) > (REG_ADDRESS+REG_LENGTH))        return 0x93;        //寄存器地址错误
                            if(reg_addr < REG_ADDRESS)                return 0x93;        //寄存器地址错误

                            j = reg_addr - REG_ADDRESS;        //寄存器数据下标
                            TX1_Buffer[0] = SL_ADDR;        //站号地址
                            TX1_Buffer[1] = 0x03;                //读功能码
                            TX1_Buffer[2] = reg_len*2;        //返回字节数

                            for(k=3, i=0; i<reg_len; i++,j++)
                            {
                                    TX1_Buffer[k++] = (u8)(modbus_reg[j] >> 8);        //数据为大端模式
                                    TX1_Buffer[k++] = (u8)modbus_reg[j];
                            }
                            crc = MODBUS_CRC16(TX1_Buffer, k);
                            TX1_Buffer[k++] = (u8)crc;        //CRC是小端模式, 先发低字节,后发高字节。
                            TX1_Buffer[k++] = (u8)(crc>>8);
                            B_TX1_Busy = 1;                //标志发送忙
                            TX1_cnt    = 0;                //发送字节计数
                            TX1_number = k;                //要发送的字节数
                            TI = 1;                                //启动发送
                    }
            }
            else        return 0x90;        //功能码错误

            return 0;        //解析正确
    }


    //=============定时器T3初始化===========================


    u8        Timer3_Config(u8 t, u32 reload)        //第三处修改
    {
            if(t != 0)        reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL);        //重装的是时间(us), 计算所需要的系统时钟数.
            if(reload >= (65536UL * 12))        return 1;        //值过大, 返回错误
            if(reload < 65536UL)        T4T3M |= 0x02;//1T mode
            else//reload >= 65536UL        
            {
                   
            T4T3M &= 0xFD;//12T mode;12T模式;
            reload = reload / 12;//12T模式变为1T模式;
                   
            }
            reload = 65536UL - reload;
            T3H = (u8)(reload >> 8);//
            T3L = (u8)(reload);//
            T4T3M |= 0x08;//开启定时器T3
            IE2 |= 0x20;//允许定时器T3中断
            return 0;
    }


    //=============定时器T2中断=============================

    void timer3_ISR (void) interrupt TIMER3_VECTOR//第四处修改
    {
            if(RX1_TimeOut != 0)
            {
                    if(--RX1_TimeOut == 0)//超时
                    {
                            if(RX1_cnt != 0)        //接收有数据
                            {
                                    B_RX1_OK = 1;        //标志已收到数据块
                            }
                    }
            }
    }

    //========================================================================
    // 函数: SetTimer2Baudraye(u16 dat)
    // 描述: 设置Timer2做波特率发生器。
    // 参数: dat: Timer2的重装值.
    // 返回: none.
    // 版本: VER1.0
    // 日期: 2018-4-2
    // 备注:
    //========================================================================
    void        SetTimer2Baudraye(u16 dat)        // 选择波特率, 2: 使用Timer2做波特率, 其它值: 使用Timer1做波特率.
    {
            AUXR &= ~(1<<4);        //Timer stop
            AUXR &= ~(1<<3);        //Timer2 set As Timer
            AUXR |=  (1<<2);        //Timer2 set as 1T mode
            TH2 = (u8)(dat >> 8);
            TL2 = (u8)dat;
            IE2  &= ~(1<<2);        //禁止中断
            AUXR |=  (1<<4);        //Timer run enable
    }


    //========================================================================
    // 函数: void        UART1_config(u32 brt, u8 timer, u8 io)
    // 描述: UART1初始化函数。
    // 参数:   brt: 通信波特率.
    //       timer: 波特率使用的定时器, timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率.
    //          io: 串口1切换到的IO,  io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
    // 返回: none.
    // 版本: VER1.0
    // 日期: 2018-4-2
    // 备注:
    //========================================================================
    void        UART1_config(u32 brt, u8 timer, u8 io)        // brt: 通信波特率,  timer=2: 波特率使用定时器2, 其它值: 使用Timer1做波特率. io=0: 串口1切换到P3.0 P3.1,  =1: 切换到P3.6 P3.7, =2: 切换到P1.6 P1.7,  =3: 切换到P4.3 P4.4.
    {
            brt = 65536UL - (MAIN_Fosc / 4) / brt;
            if(timer == 2)        //波特率使用定时器2
            {
                    AUXR |= 0x01;                //S1 BRT Use Timer2;
                    SetTimer2Baudraye((u16)brt);
            }

            else                //波特率使用定时器1
            {
                    TR1 = 0;
                    AUXR &= ~0x01;                //S1 BRT Use Timer1;
                    AUXR |=  (1<<6);        //Timer1 set as 1T mode
                    TMOD &= ~(1<<6);        //Timer1 set As Timer
                    TMOD &= ~0x30;                //Timer1_16bitAutoReload;
                    TH1 = (u8)(brt >> 8);
                    TL1 = (u8)brt;
                    ET1 = 0;                        // 禁止Timer1中断
                    INT_CLKO &= ~0x02;        // Timer1不输出高速时钟
                    TR1  = 1;                        // 运行Timer1
            }

                     if(io == 1)        {S1_USE_P36P37();        P3n_standard(0xc0);}        //切换到 P3.6 P3.7
            else if(io == 2)        {S1_USE_P16P17();        P1n_standard(0xc0);}        //切换到 P1.6 P1.7
            else if(io == 3)        {S1_USE_P43P44();        P4n_standard(0x18);}        //切换到 P4.3 P4.4
            else                                {S1_USE_P30P31();        P3n_standard(0x03);}        //切换到 P3.0 P3.1

            SCON = (SCON & 0x3f) | (1<<6);        // 8位数据, 1位起始位, 1位停止位, 无校验
    //        PS  = 1;        //高优先级中断
            ES  = 1;        //允许中断
            REN = 1;        //允许接收
    }


    //========================================================================
    // 函数: void UART1_ISR (void) interrupt UART1_VECTOR
    // 描述: 串口1中断函数
    // 参数: none.
    // 返回: none.
    // 版本: VER1.0
    // 日期: 2018-4-2
    // 备注:
    //========================================================================
    void UART1_ISR (void) interrupt UART1_VECTOR
    {
            if(RI)
            {
                    RI = 0;
                    if(!B_RX1_OK)        //接收缓冲空闲
                    {
                            if(RX1_cnt >= RX1_Length)        RX1_cnt = 0;
                            RX1_Buffer[RX1_cnt++] = SBUF;
                            RX1_TimeOut = 36;        //接收超时计时器, 35个位时间
                    }
            }

            if(TI)
            {
                    TI = 0;
                    if(TX1_number != 0)        //有数据要发
                    {
                            SBUF = TX1_Buffer[TX1_cnt++];
                            TX1_number--;
                    }
                    else        B_TX1_Busy = 0;
            }
    }









    Snipaste_2024-03-27_11-24-37.png
    回复 送花

    使用道具 举报

  • TA的每日心情
    开心
    5 天前
  • 签到天数: 90 天

    [LV.6]常住居民II

    68

    主题

    564

    回帖

    1850

    积分

    超级版主

    积分
    1850
    QQ
    发表于 2024-3-27 11:13:31 | 显示全部楼层
    串口1 只能选T1 或者T2
    截图202403271113147335.jpg
    热线19952583534
    www.STCAI.com
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    奋斗
    9 小时前
  • 签到天数: 150 天

    [LV.7]常住居民III

    109

    主题

    455

    回帖

    839

    积分

    高级会员

    积分
    839
     楼主| 发表于 2024-3-27 11:22:20 | 显示全部楼层
    国学芯用 发表于 2024-3-27 11:13
    串口1 只能选T1 或者T2

    波特率使用的是定时器1,没有动,T3用于超时检测,超时时,没有数据传输了,不是波特率,
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    奋斗
    8 小时前
  • 签到天数: 127 天

    [LV.7]常住居民III

    26

    主题

    1281

    回帖

    3985

    积分

    论坛元老

    积分
    3985
    发表于 2024-3-27 13:48:01 | 显示全部楼层
    修改的部分看起来没什么问题,先确认一下你使用的芯片是什么型号的?有没有定时器3?
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    奋斗
    9 小时前
  • 签到天数: 150 天

    [LV.7]常住居民III

    109

    主题

    455

    回帖

    839

    积分

    高级会员

    积分
    839
     楼主| 发表于 2024-3-27 14:03:48 | 显示全部楼层
    乘风飞扬 发表于 2024-3-27 13:48
    修改的部分看起来没什么问题,先确认一下你使用的芯片是什么型号的?有没有定时器3? ...

    使用的是STC8G1K08    20脚的,改成T0就能正常通信,把T3改成状态机的时基,状态就不对了,T0-T2三个,是没有T3,感谢老师指点,还是没有注意到这个细节,定时器不够用了,

    点评

    定时器不够用了? 【分享】按键程序,大道至简,按键扫描 + 累计主循环次数去抖动/不占用定时器 https://www.stcaimcu.com/forum.php?mod=viewthread&tid=5936 (出处: 国芯论坛-STC全球32位8051爱好者互助交流社区)  详情 回复 发表于 2024-3-27 15:07
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    昨天 05:23
  • 签到天数: 153 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1285

    积分

    荣誉版主

    积分
    1285
    发表于 2024-3-27 15:07:28 | 显示全部楼层
    380091044 发表于 2024-3-27 14:03
    使用的是STC8G1K08    20脚的,改成T0就能正常通信,把T3改成状态机的时基,状态就不对了,T0-T2三个,是 ...

    定时器不够用了?

    【分享】按键程序,大道至简,按键扫描 + 累计主循环次数去抖动/不占用定时器
    https://www.stcaimcu.com/forum.php?mod=viewthread&tid=5936
    (出处: 国芯论坛-STC全球32位8051爱好者互助交流社区)

    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    0

    主题

    1

    回帖

    14

    积分

    新手上路

    积分
    14
    发表于 2024-3-27 16:27:17 | 显示全部楼层
    终于找到了相关的例程,理论上stc15是不是只要修改相关寄存器就可以了
    回复 支持 反对 送花

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-4-29 17:18 , Processed in 0.068951 second(s), 57 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

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