找回密码
 立即注册
查看: 823|回复: 1

STC32G12K128单片机的 moubus-rtu 从机测试工程

[复制链接]
  • TA的每日心情
    开心
    2024-3-20 10:28
  • 签到天数: 1 天

    [LV.1]初来乍到

    3

    主题

    2

    回帖

    57

    积分

    注册会员

    积分
    57
    发表于 2023-5-5 17:38:50 | 显示全部楼层 |阅读模式
    ## 简介
    [STC32G12K128](https://www.stcai.com/cp_stc32xl) 是STC 推出的一款32位的 C251 的单片机。
    最近拿到一块官方申请的 屠龙刀-STC32G开发板,就用它的提供的库函数, 移植了一个 modbus-rtu 从站的工程。
    ## modbus-rtu slave 移植注意点
    1. modbus-rtu 功能配置
    - 配置 modbus-rtu 使能主机还是从机,亦或是全部使能
    - 配置主机或者从机使用的串口、波特率、从机地址、打印调试信息
    屏幕截图 2023-05-05 164752.png

    2. 初始化 modbus-rtu 从站使用到的串口和定时器
    - modbus-rtu 没有开始和结束符,通过3.5个字符的时间间隔来断帧,所以此处初始化一个定时器4来计算3.5个字符的时间用于断帧。**注意:此处使用定时器不要和对应串口波特率产生的定时器冲突**
    - 初始化对应的串口4

    1. <p>void MODS_PeripheralInit(void)</p><p>{</p><p>    TIM_InitTypeDef<span style="white-space:pre">                </span>TIM_InitStructure = {0};</p><p>    GPIO_InitTypeDef<span style="white-space:pre">        </span>GPIO_InitStructure;<span style="white-space:pre">                </span>//结构定义</p><p>    COMx_InitDefine<span style="white-space:pre">                </span>COMx_InitStructure;<span style="white-space:pre">                                        </span>//结构定义</p><p>
    2. </p><p>    /* 硬件定时器初始化 */</p><p>    /*</p><p>    <span style="white-space:pre">        </span>3.5个字符的时间间隔,只是用在RTU模式下面,因为RTU模式没有开始符和结束符,</p><p>    <span style="white-space:pre">        </span>两个数据包之间只能靠时间间隔来区分,Modbus定义在不同的波特率下,间隔时间是不一样的,</p><p>    <span style="white-space:pre">        </span>所以就是3.5个字符的时间,波特率高,这个时间间隔就小,波特率低,这个时间间隔相应就大</p><p>
    3. </p><p>    <span style="white-space:pre">        </span>4800  = 7.297ms</p><p>    <span style="white-space:pre">        </span>9600  = 3.646ms</p><p>    <span style="white-space:pre">        </span>19200  = 1.771ms</p><p>    <span style="white-space:pre">        </span>38400  = 0.885ms</p><p>    */</p><p>    uint32_t timeout = 0;</p><p>
    4. </p><p>    //timeout = 35000000 / MODBUS_SLAVE_BAUD;<span style="white-space:pre">        </span>/* 计算超时时间,单位us 35000000*/</p><p>    /* 此处直接将定时器的初始值赋值,具体计算的公式参考注释,此处默认使用9600的波特率/主频22.1184MHZ,且定时器使用12T模式 */</p><p>    uiTimerAutoLoadVal = 63558;//(65536UL - ((12000000*3646) / MAIN_Fosc));//3646=35000000 / 9600</p><p>
    5. </p><p>    /* 使用硬件定时器4  如果有冲突,请改修此处定时器 */</p><p>    //<span style="white-space:pre">        </span>//定时器4做16位自动重装, 中断频率为50HZ,中断函数从P6.3取反输出25HZ方波信号.</p><p>    TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_12T;<span style="white-space:pre">        </span>//指定时钟源,     TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext</p><p>    TIM_InitStructure.TIM_ClkOut    = DISABLE;<span style="white-space:pre">                                        </span>//是否输出高速脉冲, ENABLE或DISABLE</p><p>    TIM_InitStructure.TIM_Value     = uiTimerAutoLoadVal;<span style="white-space:pre">                </span>//初值 因为上面12分频了  所以是 12*1000000</p><p>    TIM_InitStructure.TIM_Run       = DISABLE;<span style="white-space:pre">                                        </span>//是否初始化后启动定时器, ENABLE或DISABLE</p><p>    Timer_Inilize(Timer4, &TIM_InitStructure);<span style="white-space:pre">                                        </span>//初始化Timer4<span style="white-space:pre">        </span>  Timer0,Timer1,Timer2,Timer3,Timer4</p><p>    NVIC_Timer4_Init(ENABLE, NULL);<span style="white-space:pre">                </span>//中断使能, ENABLE/DISABLE; 无优先级</p><p>
    6. </p><p>
    7. </p><p>    /* 初始化 MODBUS 从机使用的串口 P02-RXD P03-TXD */</p><p>    GPIO_InitStructure.Pin  = GPIO_Pin_2 | GPIO_Pin_3;<span style="white-space:pre">                </span>//指定要初始化的IO, GPIO_Pin_0 ~ GPIO_Pin_7</p><p>    GPIO_InitStructure.Mode = GPIO_PullUp;<span style="white-space:pre">        </span>                //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP</p><p>    GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);<span style="white-space:pre">        </span>            //初始化</p><p>
    8. </p><p>    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;<span style="white-space:pre">                </span>//模式,   UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx</p><p>    COMx_InitStructure.UART_BRT_Use   = BRT_Timer2;<span style="white-space:pre">                        </span>//使用波特率,   BRT_Timer2, BRT_Timer4 (注意: 串口2固定使用BRT_Timer2)</p><p>    COMx_InitStructure.UART_BaudRate  = 9600ul;<span style="white-space:pre">                        </span>    //波特率,     110 ~ 115200</p><p>    COMx_InitStructure.UART_RxEnable  = ENABLE;<span style="white-space:pre">                                </span>//接收允许,   ENABLE或DISABLE</p><p>    UART_Configuration(UART4, &COMx_InitStructure);<span style="white-space:pre">                </span>    //初始化串口4 UART1,UART2,UART3,UART4</p><p>    NVIC_UART4_Init(ENABLE, Priority_1);<span style="white-space:pre">                </span>            //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3</p><p>
    9. </p><p>    UART4_SW(UART4_SW_P02_P03);<span style="white-space:pre">                </span>                        //UART4_SW_P02_P03,UART4_SW_P52_P53</p><p>
    10. </p><p>}</p>
    复制代码

    3. 在定时器的中断函数中添加 modbus-rtu 从机 3.5 个字符超时处理函数

    1. <p>//========================================================================</p><p>// 函数: Timer4_ISR_Handler</p><p>// 描述: Timer4中断函数.</p><p>// 参数: none.</p><p>// 返回: none.</p><p>// 版本: V1.0, 2020-09-23</p><p>//========================================================================</p><p>void Timer4_ISR_Handler (void) interrupt TMR4_VECTOR<span style="white-space:pre">                </span>//进中断时已经清除标志</p><p>{</p><p>#if ( MODBUS_CFG_SLAVE_EN == 1 )</p><p>    MODS_RxTimeOut();    /* Modbus从站超时处理 */</p><p>#endif</p><p>#if ( MODBUS_CFG_HOST_EN == 1 )</p><p>    MODH_RxTimeOut();    /* Modbus主站超时处理 */</p><p>#endif</p><p><span style="white-space:pre">        </span>// TODO: 在此处添加用户代码</p><p><span style="white-space:pre">        </span>//P63 = ~P63;</p><p>}</p>
    复制代码



    5. 在串口的接口中断函数中添加 modbus-rtu 从机接收字节的处理函数

    1. <p>//========================================================================</p><p>// 函数: UART4_ISR_Handler</p><p>// 描述: UART4中断函数.</p><p>// 参数: none.</p><p>// 返回: none.</p><p>// 版本: V1.0, 2020-09-23</p><p>//========================================================================</p><p>#ifdef UART4</p><p>void UART4_ISR_Handler(void) interrupt UART4_VECTOR</p><p>{</p><p>    if(S4RI)</p><p>    {</p><p>        CLR_RI4();</p><p>#if ( MODBUS_CFG_SLAVE_EN == 1 )</p><p>        MODS_ReciveNew(S4BUF);</p><p>#elif ( MODBUS_CFG_HOST_EN == 1 )</p><p>        MODH_ReciveNew(S4BUF);</p><p>#else</p><p>        if(COM4.RX_Cnt >= COM_RX4_Lenth)<span style="white-space:pre">        </span>COM4.RX_Cnt = 0;</p><p>        RX4_Buffer[COM4.RX_Cnt++] = S4BUF;</p><p>        COM4.RX_TimeOut = TimeOutSet4;</p><p>#endif</p><p>    }</p><p>
    2. </p><p>    if(S4TI)</p><p>    {</p><p>        CLR_TI4();</p><p>
    3. </p><p>#if(UART_QUEUE_MODE == 1)   //判断是否使用队列模式</p><p>        if(COM4.TX_send != COM4.TX_write)</p><p>        {</p><p>            S4BUF = TX4_Buffer[COM4.TX_send];</p><p>            if(++COM4.TX_send >= COM_TX4_Lenth)<span style="white-space:pre">                </span>COM4.TX_send = 0;</p><p>        }</p><p>        else<span style="white-space:pre">        </span>COM4.B_TX_busy = 0;</p><p>#else</p><p>        COM4.B_TX_busy = 0;     //使用阻塞方式发送直接清除繁忙标志</p><p>#endif</p><p>    }</p><p>}</p>
    复制代码


    6. 在 main() 函数的大循环之前调用 MODS_PeripheralInit() 以初始化使用到的相关硬件;然后在死循环里一直调用 MODS_Poll() 解析 modbus-rtu 从机协议。
    7. 可以通过  modbus_slave.h 文件中的宏定义对 modbus-rtu 的功能进行裁剪,可以禁用不需要使用的功能,以解决空间。
    1. <p>
    2. </p><p>//-----------------------------------------------------------------------------------------//</p><p>#define MODBUS_SLAVE_RTU_01H_FUNCTION DISABLE</p><p>#define MODBUS_SLAVE_RTU_02H_FUNCTION DISABLE</p><p>#define MODBUS_SLAVE_RTU_03H_FUNCTION ENABLE</p><p>#define MODBUS_SLAVE_RTU_04H_FUNCTION DISABLE</p><p>#define MODBUS_SLAVE_RTU_05H_FUNCTION DISABLE</p><p>#define MODBUS_SLAVE_RTU_06H_FUNCTION ENABLE</p><p>#define MODBUS_SLAVE_RTU_10H_FUNCTION ENABLE</p><p>/* 使能测试功能,实际使用可禁用 */</p><p>#define MODBUS_RTU_TEST ENABLE//DISABLE  ENABLE</p><p>
    3. </p>
    复制代码


    ## 工程源码
    STC32G_Project_modbus_rtu_slave.zip (223.8 KB, 下载次数: 76)


    # 测试
    1. 先将工程源码编译生成后的hex烧录到开发板中
    2. 再使用 USB转TTL 连接 STC32G12K28 开发板的串口4(P02-RXD P03-TXD)
    3. 电脑端使用 modbus 主机的模拟软件(此处使用 modbuspoll )
    - 设置串口和波特率
    屏幕截图 2023-05-05 172709.png


    - 根据工程源码里设置的保存寄存器的参数,设置 modbuspoll 软件里读取的地址
    1. <p>//-----------------------------------------------------------------------------------------//</p><p>#if (MODBUS_RTU_TEST == ENABLE)   //测试使用代码</p><p>/* 01H 读强制单线圈 */</p><p>/* 05H 写强制单线圈 */</p><p>#define REG_D01<span style="white-space:pre">                </span>0x0101</p><p>#define REG_D02<span style="white-space:pre">                </span>0x0102</p><p>#define REG_D03<span style="white-space:pre">                </span>0x0103</p><p>#define REG_D04<span style="white-space:pre">                </span>0x0104</p><p>#define REG_DXX <span style="white-space:pre">        </span>REG_D04</p><p>
    2. </p><p>/* 02H 读取输入状态 */</p><p>#define REG_T01<span style="white-space:pre">                </span>0x0201</p><p>#define REG_T02<span style="white-space:pre">                </span>0x0202</p><p>#define REG_T03<span style="white-space:pre">                </span>0x0203</p><p>#define REG_TXX<span style="white-space:pre">                </span>REG_T03</p><p>
    3. </p><p>/* 03H 读保持寄存器 */</p><p>/* 06H 写保持寄存器 */</p><p>/* 10H 写多个保存寄存器 */</p><p>#define SLAVE_REG_P01<span style="white-space:pre">                </span>0x0301</p><p>#define SLAVE_REG_P02<span style="white-space:pre">                </span>0x0302</p><p>
    4. </p><p>/* 04H 读取输入寄存器(模拟信号) */</p><p>#define REG_A01<span style="white-space:pre">                </span>0x0401</p><p>#define REG_AXX<span style="white-space:pre">                </span>REG_A01</p><p>typedef struct</p><p>{</p><p><span style="white-space:pre">        </span>/* 03H 06H 读写保持寄存器 */</p><p><span style="white-space:pre">        </span>uint16_t P01;</p><p><span style="white-space:pre">        </span>uint16_t P02;</p><p>
    5. </p><p><span style="white-space:pre">        </span>/* 04H 读取模拟量寄存器 */</p><p><span style="white-space:pre">        </span>uint16_t A01;</p><p>
    6. </p><p><span style="white-space:pre">        </span>/* 01H 05H 读写单个强制线圈 */</p><p><span style="white-space:pre">        </span>uint16_t D01;</p><p><span style="white-space:pre">        </span>uint16_t D02;</p><p><span style="white-space:pre">        </span>uint16_t D03;</p><p><span style="white-space:pre">        </span>uint16_t D04;</p><p>
    7. </p><p>}SLAVE_VAR_T;</p><p>#endif</p>
    复制代码

    屏幕截图 2023-05-05 172734.png

    - 单击对应保持寄存器的值,可以设置该寄存器的值
    屏幕截图 2023-05-05 172748.png




    回复 送花

    使用道具 举报

  • TA的每日心情
    慵懒
    半小时前
  • 签到天数: 155 天

    [LV.7]常住居民III

    25

    主题

    714

    回帖

    1779

    积分

    金牌会员

    积分
    1779
    发表于 2024-3-31 16:28:33 | 显示全部楼层
    谢谢楼主分享
    回复 支持 反对 送花

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-5-1 09:46 , Processed in 0.062394 second(s), 36 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

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