diyeyuye 发表于 2023-5-12 18:29:16

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

## 简介
(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 host 移植注意点
1. modbus-rtu 功能配置
- 配置 modbus-rtu 使能主机还是从机,亦或是全部使能
- 配置主机或者从机使用的串口、波特率、打印调试信息

- 主机要读取的从机地址通过全局变量 **g_SlaveAddr** 设置。


2. 初始化 modbus-rtu 主站使用到的串口和定时器
- modbus-rtu 没有开始和结束符,通过3.5个字符的时间间隔来断帧,所以此处初始化一个定时器4来计算3.5个字符的时间用于断帧。**注意:此处使用定时器不要和对应串口波特率产生的定时器冲突**
- 初始化对应的串口4
```c
void MODH_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如果有冲突,请改修此处定时器 */
    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; 无优先级


    /* 初始化串口 */
    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 个字符超时处理函数

```c
//========================================================================
// 函数: 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 从机协议,并且可以定时通过API接口读取或者写入从机某个寄存器地址的值。
例程例程里,每隔 2s 往 0x301 寄存器写入一个自增 1 的值,然后下一秒从该寄存器中再读取,结果通过串口助手打印可以查看。


/* modbus主机测试任务,每1s执行一次该函数 */
void app_ModbusHostTask(void)
{
    static uint8_t wrflag = 0;
    static uint16_t regVal = 0;
   
    if(wrflag == 0)
    {
      MODH_WriteParam_06H(REG_P01, regVal);        //往寄存器 ERG_P01 寄存器中写入 regVal
      printf("modbus host write reg[%hu] value:%hu\n",REG_P01,regVal);
      regVal++; //写完一次后自增
    }
    else
    {
      MODH_ReadParam_03H(REG_P01,1);        //从寄存器 ERG_P01 中读取
      printf("modbus host read reg[%hu] value:%hu\n",REG_P01,g_tHostVar.P01);
    }
    wrflag = !wrflag;
}


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


//-----------------------------------------------------------------------------------------//
#define MODBUS_HOST_RTU_01H_FUNCTION DISABLE
#define MODBUS_HOST_RTU_02H_FUNCTION DISABLE
#define MODBUS_HOST_RTU_03H_FUNCTION ENABLE
#define MODBUS_HOST_RTU_04H_FUNCTION DISABLE
#define MODBUS_HOST_RTU_05H_FUNCTION DISABLE
#define MODBUS_HOST_RTU_06H_FUNCTION ENABLE
#define MODBUS_HOST_RTU_10H_FUNCTION ENABLE

# 测试
1. 先将工程源码编译生成后的hex烧录到开发板中
2. 再使用 USB转TTL 连接 STC32G12K28 开发板的串口4(P02-RXD P03-TXD)
3. 电脑端使用 modbus 从机的模拟软件(此处使用 modbusslave )
- 根据工程源码里设置的保存寄存器的参数,设置 modbusslave 软件里,从机的地址,被读取的寄存器地址和寄存器数量

- 设置串口和波特率

- 通过 modbusslave 窗口和单片机的串口打印日志观察现象


## 工程源码

68336016 发表于 2023-5-12 18:34:33

看起来文件数量比较少,比较直观,不像Freemodbus那样文件多

diyeyuye 发表于 2023-5-12 18:38:47

68336016 发表于 2023-5-12 18:34
看起来文件数量比较少,比较直观,不像Freemodbus那样文件多
(引用自2楼)

功能没有人家完善,且这仅支持RTU模式 freemodbus 支持ascii/tcp等,如果上了系统还是建议上freemodbus/usmodbus之类的,要是裸机,运行安富莱的这个例程还是很好用的。

autolog 发表于 2023-5-12 19:35:16

安富莱的modbus例程确实好用,我在STM/STC/N76E003共3家MCU上面都跑通了主机、从机例程

diyeyuye 发表于 2023-5-12 21:29:39

autolog 发表于 2023-5-12 19:35
安富莱的modbus例程确实好用,我在STM/STC/N76E003共3家MCU上面都跑通了主机、从机例程 ...
(引用自4楼)

确实好用,移植也挺方便的

国学芯用 发表于 2023-5-13 09:28:27

厉害了,感谢为STC生态建设添砖加瓦

zhuyi25762 发表于 2023-5-13 10:57:12

本帖最后由 zhuyi25762 于 2023-5-13 12:11 编辑

国学芯用 发表于 2023-5-13 09:28
厉害了,感谢为STC生态建设添砖加瓦
(引用自6楼)

老大来改份STC8H的

这个C251用的是太难受了,首先这个中断号扩展,我有一台电脑成功,一台就是不行。而且不知道是不是我这版本不对,同一工程中各文件复制,中文都成了 ??问号,,有时输入中文也输不了。。有的.c可以输入中文,有的不能输入中文
只能把C251卸载了才正常

国学芯用 发表于 2023-5-13 11:19:55

zhuyi25762 发表于 2023-5-13 10:57
老大来改份STC8H的

这个C251用的是太难受了,首先这个中断号扩展,我有一台电成功,一台就是不行。而且 ...
(引用自7楼)

兄弟你再试试,我们都这百发百中一次性成功的,没有出现不成功的情况
https://stcai.com/filedownload/609391

diyeyuye 发表于 2023-5-13 21:15:23

zhuyi25762 发表于 2023-5-13 10:57
老大来改份STC8H的

这个C251用的是太难受了,首先这个中断号扩展,我有一台电脑成功,一台就是不行。而 ...
(引用自7楼)

STC8H的库函数和STC32G的好像差不多,你直接把头文件 stc32g换成stc8h直接编译了看看。

zhuyi25762 发表于 2023-5-17 17:49:22

diyeyuye 发表于 2023-5-13 21:15
STC8H的库函数和STC32G的好像差不多,你直接把头文件 stc32g换成stc8h直接编译了看看。 ...
(引用自10楼)

你这个3.5T定时值,好像不太明白,算出来的不是3.5T, 实测好像也不是3.5T

我自己改了,计算出3.5T的定时时间,定时器初值 65536-((MAIN_Fosc/12000)*(n)+500)/1000

不过我平时好像用的比较变态,我喜欢用 ADC 中断做定时器,ADC中断几个us一次,这就足够判断1.5T,特别是对老的STC,定时器没那么多的,非常好用,有时也用PCA定时。。。。

diyeyuye 发表于 2023-5-17 19:13:54

zhuyi25762 发表于 2023-5-17 17:49
你这个3.5T定时值,好像不太明白,算出来的不是3.5T, 实测好像也不是3.5T

我自己改了,计算出3.5T的 ...
(引用自11楼)

好像计算的是有点问题{:lol:}

后面的方法好像很不错啊定时器不够用的时候是个好主意
页: [1]
查看完整版本: STC32G12K128单片机的 moubus-rtu 主机测试工程