找回密码
 注册
关于网站域名变更的通知
查看: 225|回复: 2
打印 上一主题 下一主题

stc32单片机modbus-rtu通讯源程序

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2022-6-22 10:35 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
单片机源程序如下:
  • #include "stc32g.h" //头文件见下载软件
  • #define MAIN_Fosc 11059200L //定义主时钟
  • /************* 功能说明 **************
  • 请先别修改程序, 直接下载"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 4B C6
  • 读出 4 个寄存器:
  • 10 03 1000 0004 4388
  • 返回:
  • 10 03 08 12 34 56 78 90 AB CD EF 3D D5
  • 命令错误返回信息(自定义):
  • 0x90: 功能码错误. 收到了不支持的功能码.
  • 0x91: 命令长度错误.
  • 0x92: 写入或读出寄存器个数或字节数错误.
  • 0x93: 寄存器地址错误.
  • 注意: 收到广播地址 0x00 时要处理信息, 但不返回应答.
  • ******************************************/
  • typedef unsigned char u8;
  • typedef unsigned int u16;
  • typedef unsigned long u32;
  • /************* 本地常量声明 **************/
  • #define RX1_Length 128 /* 接收缓冲长度 */
  • #define TX1_Length 128 /* 发送缓冲长度 */
  • //
  • //
  • //uint gnmbl=7;//1|?ü??±?á?
  • u16  FREQ;              //
  • float temp;
  • //sbit OUTPUT1=P26;               //PWM1
  • //sbit OUTPUT2=P27;
  • /************* 本地变量声明 **************/
  • 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); // 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.
  • u8 Timer0_Config(u8 t, u32 reload); //t=0: reload 值是主时钟周期数, t=1: reload 值是时间(单位 us), 返回 0 正确, 返回 1 装载值过大错误.
  • u16 MODBUS_CRC16(u8 *p, u8 n);
  • u8 MODBUS_RTU(void);
  • #define SL_ADDR 0x01 /* 本从机站号地址 */
  • #define REG_ADDRESS 0x0000 /* 寄存器首地址 */
  • #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;
  • EAXFR = 1; //使能访问 XFR
  • WTST = 0x00; //设置程序代码等待参数,
  • //赋值为 0 可将 CPU 执行程序的速度设置为最快
  • Timer0_Config(0, MAIN_Fosc / 10000); //t=0: reload 值是主时钟周期数, (中断频率, 20000 次/秒)
  • UART1_config(9600UL, 1, 0);// 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.
  • //  init();
  • Timer3_Init();
  •         calculate_F();
  • 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>>8); //CRC 是小端模式
  •                                                                                 TX1_Buffer[3] = (u8)crc;
  •                                                                                 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
  • STC32G 系列技术手册 官方网站: STCmcuDATA
  • 深圳国芯人工智能
  • 字节: 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 0x03 xxxx
  • ***************************************************************************/
  • u8 MODBUS_RTU(void)
  • {
  • u8 i,j,k;
  • u16 reg_addr; //寄存器地址
  • u8 reg_len; //写入寄存器个数
  • u16 crc;
  • if(RX1_Buffer[1] == 0x06)//写多寄存器
  • {
  •                 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>>8); //CRC 是小端模式
  •                                 TX1_Buffer[7] = (u8)crc;
  •                                 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>>8); //CRC 是小端模式
  •                                 TX1_Buffer[k++] = (u8)crc;
  •                                 B_TX1_Busy = 1; //标志发送忙
  •                                 TX1_cnt = 0; //发送字节计数
  •                                 TX1_number = k; //要发送的字节数
  •                                 TI = 1; //启动发送
  •                 }
  •         }
  • else return 0x90; //功能码错误
  • return 0; //解析正确
  • }
  • //========================================================================
  • // 函数:u8 Timer0_Config(u8 t, u32 reload)
  • // 描述: timer0 初始化函数.
  • // 参数: t: 重装值类型, 0 表示重装的是系统时钟数, 其余值表示重装的是时间(us).
  • // reload: 重装值.
  • // 返回: 0: 初始化正确, 1: 重装值过大, 初始化错误.
  • // 版本: V1.0, 2018-3-5
  • //========================================================================
  • u8 Timer0_Config(u8 t, u32 reload) //t=0: reload 值是主时钟周期数, t=1: reload 值是时间(单位 us)
  • {
  •         TR0 = 0; //停止计数
  •         if(t != 0) reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL); //重装的是时间(us), 计算所需要的系统时钟数.
  •         if(reload >= (65536UL * 12)) return 1; //值过大, 返回错误
  •         if(reload < 65536UL) AUXR |= 0x80; //1T mode
  •         else
  •         {
  •                         AUXR &= ~0x80; //12T mode
  •                         reload = reload / 12;
  •         }
  •                 reload = 65536UL - reload;
  •                 TH0 = (u8)(reload >> 8);
  •                 TL0 = (u8)(reload);
  •                 ET0 = 1; //允许中断
  •                 TMOD &= 0xf0;
  •                 TMOD |= 0; //工作模式, 0: 16 位自动重装, 1: 16 位定时/计数, 2: 8 位自动重装, 3: 16 位自动重装, 不可屏蔽中断
  •                 TR0 = 1; //开始运行
  •                 return 0;
  • }
  • //========================================================================
  • // 函数: void timer0_ISR (void) interrupt TIMER0_VECTOR
  • // 描述: timer0 中断函数.
  • // 参数: none.
  • // 返回: none.
  • // 版本: V1.0, 2016-5-12
  • //========================================================================
  • void timer0_ISR (void) interrupt 1
  • {
  •         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
  • T2H = (u8)(dat >> 8);
  • T2L = (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 中断
  •                 TR1 = 1; // 运行 Timer1
  •         }
  •         P_SW1 &= ~0xc0; //默认切换到 P3.0 P3.1
  •         if(io == 1)
  •         {
  •                 P_SW1 |= 0x40; //切换到 P3.6 P3.7
  •                 P3M1 &= ~0xc0;
  •                 P3M0 &= ~0xc0;
  •         }
  •         else if(io == 2)
  •         {
  •                 P_SW1 |= 0x80; //切换到 P1.6 P1.7
  •                 P1M1 &= ~0xc0;
  •                 P1M0 &= ~0xc0;
  •         }
  •         else if(io == 3)
  •         {
  •                         P_SW1 |= 0xc0; //切换到 P4.3 P4.4
  •                         P4M1 &= ~0x18;
  •                         P4M0 &= ~0x18;
  •         }
  •         else
  •         {
  •                         P3M1 &= ~0x03;
  •                         P3M0 &= ~0x03;
  •         }
  •         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 4
  • {
  •         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;
  •         }
  • }/ E) w* g% O% |4 G  y6 w
: u, r2 m0 b/ g8 [  a% e

该用户从未签到

2#
发表于 2022-6-22 11:11 | 只看该作者
一晃眼差点看成了stm32!8 w2 y" r6 a( t9 N% J5 u6 p4 |
' c! T3 d# I* \, [) K/ m
正好下载,谢谢。O(∩_∩)O哈哈~

点评

O(∩_∩)O哈哈~。学习累了吧  详情 回复 发表于 2022-6-22 13:24

该用户从未签到

3#
发表于 2022-6-22 13:24 | 只看该作者
名字好听吗 发表于 2022-6-22 11:11
  `/ E) t+ m) M2 l# P0 [一晃眼差点看成了stm32!0 R/ ~9 m6 G3 K7 e, y1 X

( c. g' u7 a5 P' V0 D正好下载,谢谢。O(∩_∩)O哈哈~

' m4 n0 `) W" H* H4 ~4 KO(∩_∩)O哈哈~。学习累了吧5 L. ^# \. v( D# q
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-6-20 23:48 , Processed in 0.109375 second(s), 27 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

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