## 简介
[STC32G12K128](https://www.stcai.com/cp_stc32xl) 是STC 推出的一款32位的 C251 的单片机。
最近拿到一块官方申请的 屠龙刀-STC32G开发板,就用它的提供的库函数, 移植了一个 modbus-rtu 从站的工程。
## modbus-rtu slave 移植注意点
1. modbus-rtu 功能配置
- 配置 modbus-rtu 使能主机还是从机,亦或是全部使能
- 配置主机或者从机使用的串口、波特率、从机地址、打印调试信息
2. 初始化 modbus-rtu 从站使用到的串口和定时器
- modbus-rtu 没有开始和结束符,通过3.5个字符的时间间隔来断帧,所以此处初始化一个定时器4来计算3.5个字符的时间用于断帧。**注意:此处使用定时器不要和对应串口波特率产生的定时器冲突**
- 初始化对应的串口4
- <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>
- </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>
- </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>
- </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>
- </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>
- </p><p>
- </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>
- </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>
- </p><p> UART4_SW(UART4_SW_P02_P03);<span style="white-space:pre"> </span> //UART4_SW_P02_P03,UART4_SW_P52_P53</p><p>
- </p><p>}</p>
复制代码
3. 在定时器的中断函数中添加 modbus-rtu 从机 3.5 个字符超时处理函数
- <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 从机接收字节的处理函数
- <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>
- </p><p> if(S4TI)</p><p> {</p><p> CLR_TI4();</p><p>
- </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 的功能进行裁剪,可以禁用不需要使用的功能,以解决空间。
- <p>
- </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>
- </p>
复制代码
## 工程源码
STC32G_Project_modbus_rtu_slave.zip
(223.8 KB, 下载次数: 274)
# 测试
1. 先将工程源码编译生成后的hex烧录到开发板中
2. 再使用 USB转TTL 连接 STC32G12K28 开发板的串口4(P02-RXD P03-TXD)
3. 电脑端使用 modbus 主机的模拟软件(此处使用 modbuspoll )
- 设置串口和波特率
- 根据工程源码里设置的保存寄存器的参数,设置 modbuspoll 软件里读取的地址
- <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>
- </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>
- </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>
- </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>
- </p><p><span style="white-space:pre"> </span>/* 04H 读取模拟量寄存器 */</p><p><span style="white-space:pre"> </span>uint16_t A01;</p><p>
- </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>
- </p><p>}SLAVE_VAR_T;</p><p>#endif</p>
复制代码
- 单击对应保持寄存器的值,可以设置该寄存器的值
|