diyeyuye 发表于 2023-5-5 17:38:29

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

本帖最后由 diyeyuye 于 2023-5-5 17:42 编辑

## 简介

(https://www.stcai.com/cp_stc32xl) 是STC 推出的一款32位的 C251 的单片机。最近拿到一块官方申请的 屠龙刀-STC32G开发板,就用它的提供的库函数,查考[安富莱](https://www.armbbs.cn/forum.php?mod=viewthread&tid=16989)提供的 modbus 例程移植了一个 modbus-rtu 从站的工程。

## modbus-rtu slave 移植注意点

1. modbus-rtu 功能配置

- 配置 modbus-rtu 使能主机还是从机,亦或是全部使能

- 配置主机或者从机使用的串口、波特率、从机地址、打印调试信息



2. 初始化 modbus-rtu 从站使用到的串口和定时器

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

- 初始化对应的串口4


void MODS_PeripheralInit(void)

{

    TIM_InitTypeDef                TIM_InitStructure = {0};

    GPIO_InitTypeDef        GPIO_InitStructure;                //结构定义

    COMx_InitDefine                COMx_InitStructure;                                        //结构定义



    /* 硬件定时器初始化 */

    /*

            3.5个字符的时间间隔,只是用在RTU模式下面,因为RTU模式没有开始符和结束符,

            两个数据包之间只能靠时间间隔来区分,Modbus定义在不同的波特率下,间隔时间是不一样的,

            所以就是3.5个字符的时间,波特率高,这个时间间隔就小,波特率低,这个时间间隔相应就大



            4800= 7.297ms

            9600= 3.646ms

            19200= 1.771ms

            38400= 0.885ms

    */

    uint32_t timeout = 0;



    //timeout = 35000000 / MODBUS_SLAVE_BAUD;        /* 计算超时时间,单位us 35000000*/

    /* 此处直接将定时器的初始值赋值,具体计算的公式参考注释,此处默认使用9600的波特率/主频22.1184MHZ,且定时器使用12T模式 */

    uiTimerAutoLoadVal = 63558;//(65536UL - ((12000000*3646) / MAIN_Fosc));//3646=35000000 / 9600



    /* 使用硬件定时器4如果有冲突,请改修此处定时器 */

    //        //定时器4做16位自动重装, 中断频率为50HZ,中断函数从P6.3取反输出25HZ方波信号.

    TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_12T;        //指定时钟源,   TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext

    TIM_InitStructure.TIM_ClkOut    = DISABLE;                                        //是否输出高速脉冲, ENABLE或DISABLE

    TIM_InitStructure.TIM_Value   = uiTimerAutoLoadVal;                //初值 因为上面12分频了所以是 12*1000000

    TIM_InitStructure.TIM_Run       = DISABLE;                                        //是否初始化后启动定时器, ENABLE或DISABLE

    Timer_Inilize(Timer4, &TIM_InitStructure);                                        //初始化Timer4          Timer0,Timer1,Timer2,Timer3,Timer4

    NVIC_Timer4_Init(ENABLE, NULL);                //中断使能, ENABLE/DISABLE; 无优先级





    /* 初始化 MODBUS 从机使用的串口 P02-RXD P03-TXD */

    GPIO_InitStructure.Pin= GPIO_Pin_2 | GPIO_Pin_3;                //指定要初始化的IO, GPIO_Pin_0 ~ GPIO_Pin_7

    GPIO_InitStructure.Mode = GPIO_PullUp;                      //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP

    GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);                    //初始化



    COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;                //模式,   UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx

    COMx_InitStructure.UART_BRT_Use   = BRT_Timer2;                        //使用波特率,   BRT_Timer2, BRT_Timer4 (注意: 串口2固定使用BRT_Timer2)

    COMx_InitStructure.UART_BaudRate= 9600ul;                          //波特率,   110 ~ 115200

    COMx_InitStructure.UART_RxEnable= ENABLE;                                //接收允许,   ENABLE或DISABLE

    UART_Configuration(UART4, &COMx_InitStructure);                  //初始化串口4 UART1,UART2,UART3,UART4

    NVIC_UART4_Init(ENABLE, Priority_1);                            //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3



    UART4_SW(UART4_SW_P02_P03);                                        //UART4_SW_P02_P03,UART4_SW_P52_P53



}



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



//========================================================================

// 函数: Timer4_ISR_Handler

// 描述: Timer4中断函数.

// 参数: none.

// 返回: none.

// 版本: V1.0, 2020-09-23

//========================================================================

void Timer4_ISR_Handler (void) interrupt TMR4_VECTOR                //进中断时已经清除标志

{

#if ( MODBUS_CFG_SLAVE_EN == 1 )

    MODS_RxTimeOut();    /* Modbus从站超时处理 */

#endif

#if ( MODBUS_CFG_HOST_EN == 1 )

    MODH_RxTimeOut();    /* Modbus主站超时处理 */

#endif

        // TODO: 在此处添加用户代码

        //P63 = ~P63;

}



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


//========================================================================

// 函数: UART4_ISR_Handler

// 描述: UART4中断函数.

// 参数: none.

// 返回: none.

// 版本: V1.0, 2020-09-23

//========================================================================

#ifdef UART4

void UART4_ISR_Handler(void) interrupt UART4_VECTOR

{

    if(S4RI)

    {

      CLR_RI4();

#if ( MODBUS_CFG_SLAVE_EN == 1 )

      MODS_ReciveNew(S4BUF);

#elif ( MODBUS_CFG_HOST_EN == 1 )

      MODH_ReciveNew(S4BUF);

#else

      if(COM4.RX_Cnt >= COM_RX4_Lenth)        COM4.RX_Cnt = 0;

      RX4_Buffer = S4BUF;

      COM4.RX_TimeOut = TimeOutSet4;

#endif

    }



    if(S4TI)

    {

      CLR_TI4();



#if(UART_QUEUE_MODE == 1)   //判断是否使用队列模式

      if(COM4.TX_send != COM4.TX_write)

      {

            S4BUF = TX4_Buffer;

            if(++COM4.TX_send >= COM_TX4_Lenth)                COM4.TX_send = 0;

      }

      else        COM4.B_TX_busy = 0;

#else

      COM4.B_TX_busy = 0;   //使用阻塞方式发送直接清除繁忙标志

#endif

    }

}


6. 在 main() 函数的大循环之前调用 MODS_PeripheralInit() 以初始化使用到的相关硬件;然后在死循环里一直调用 MODS_Poll() 解析 modbus-rtu 从机协议。

7. 可以通过modbus_slave.h 文件中的宏定义对 modbus-rtu 的功能进行裁剪,可以禁用不需要使用的功能,以解决空间。


//-----------------------------------------------------------------------------------------//

#define MODBUS_SLAVE_RTU_01H_FUNCTION DISABLE

#define MODBUS_SLAVE_RTU_02H_FUNCTION DISABLE

#define MODBUS_SLAVE_RTU_03H_FUNCTION ENABLE

#define MODBUS_SLAVE_RTU_04H_FUNCTION DISABLE

#define MODBUS_SLAVE_RTU_05H_FUNCTION DISABLE

#define MODBUS_SLAVE_RTU_06H_FUNCTION ENABLE

#define MODBUS_SLAVE_RTU_10H_FUNCTION ENABLE

/* 使能测试功能,实际使用可禁用 */

#define MODBUS_RTU_TEST ENABLE//DISABLEENABLE



## 工程源码





# 测试

1. 先将工程源码编译生成后的hex烧录到开发板中
2. 再使用 USB转TTL 连接 STC32G12K28 开发板的串口4(P02-RXD P03-TXD)
3. 电脑端使用 modbus 主机的模拟软件(此处使用 modbuspoll )

- 设置串口和波特率


- 根据工程源码里设置的保存寄存器的参数,设置 modbuspoll 软件里读取的地址

//-----------------------------------------------------------------------------------------//

#if (MODBUS_RTU_TEST == ENABLE)   //测试使用代码

/* 01H 读强制单线圈 */

/* 05H 写强制单线圈 */

#define REG_D01                0x0101

#define REG_D02                0x0102

#define REG_D03                0x0103

#define REG_D04                0x0104

#define REG_DXX         REG_D04



/* 02H 读取输入状态 */

#define REG_T01                0x0201

#define REG_T02                0x0202

#define REG_T03                0x0203

#define REG_TXX                REG_T03



/* 03H 读保持寄存器 */

/* 06H 写保持寄存器 */

/* 10H 写多个保存寄存器 */

#define SLAVE_REG_P01                0x0301

#define SLAVE_REG_P02                0x0302



/* 04H 读取输入寄存器(模拟信号) */

#define REG_A01                0x0401

#define REG_AXX                REG_A01

typedef struct

{

        /* 03H 06H 读写保持寄存器 */

        uint16_t P01;

        uint16_t P02;



        /* 04H 读取模拟量寄存器 */

        uint16_t A01;



        /* 01H 05H 读写单个强制线圈 */

        uint16_t D01;

        uint16_t D02;

        uint16_t D03;

        uint16_t D04;



}SLAVE_VAR_T;

#endif



- 单击对应保持寄存器的值,可以设置该寄存器的值



liao-ljj 发表于 2023-5-5 23:34:13

漂亮.....支持....STC厉害了!

zhuyi25762 发表于 2023-5-11 20:11:03

一直想移植安富莱的到STC上,能力不够,一直弄好,这下可以好好参考一下了

问一下哈,STC 双串口,能不能一个口做主机,一个口做从机,会不会有冲突

diyeyuye 发表于 2023-5-12 08:52:48

zhuyi25762 发表于 2023-5-11 20:11
一直想移植安富莱的到STC上,能力不够,一直弄好,这下可以好好参考一下了

问一下哈,STC 双串口,能不能 ...
(引用自3楼)

可以的 不冲突。
今天应该可以把主机的测试例程发出来

zhuyi25762 发表于 2023-5-12 18:05:47

我STM32单片机,直接复制的这个例程,从机,主机都挺好用的

STC8H上没移植好,这二天看你这个程序,从机算是弄好了,增加了 485控制脚,还是奇偶校验。 从机跑到一万多次,竟然死机,电脑都卡死了{:sweat:}
主机部分弄了一下,没调好。心累。 还是用自己的程序顺手。。

erxun 发表于 2023-5-12 18:18:48

厉害了,标记一下

diyeyuye 发表于 2023-5-12 18:31:41

zhuyi25762 发表于 2023-5-12 18:05
我STM32单片机,直接复制的这个例程,从机,主机都挺好用的

STC8H上没移植好,这二天看你这个程序,从机算 ...
(引用自5楼)

https://www.amobbs.com/thread-5779374-1-1.html

主机的例程,你可以参考一下
页: [1]
查看完整版本: STC32G12K128单片机的 moubus-rtu 从机测试工程