例程:串口1中断收发-C语言-MODBUS协议把超时检测,由定时器T0改为T3不能用?
本帖最后由 380091044 于 2024-3-27 11:26 编辑/*********************************************************/
#define MAIN_Fosc 11059200L //定义主时钟
#include "STC8Gxxx.h"
/************* 功能说明 **************
请先别修改程序, 直接下载"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 C64B
读出4个寄存器:
10 03 1000 0004 4388
返回:
10 03 08 12 34 56 78 90 AB CD EF D53D
命令错误返回信息(自定义):
0x90: 功能码错误. 收到了不支持的功能码.
0x91: 命令长度错误.
0x92: 写入或读出寄存器个数或字节数错误.
0x93: 寄存器地址错误.
注意: 收到广播地址0x00时要处理信息, 但不返回应答.
******************************************/
/************* 本地常量声明 **************/
#define RX1_Length 128 /* 接收缓冲长度 */
#define TX1_Length 128 /* 发送缓冲长度 */
/************* 本地变量声明 **************/
u8 xdata RX1_Buffer; //接收缓冲
u8 xdata TX1_Buffer; //发送缓冲
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); //
u8 Timer3_Config(u8 t, u32 reload);//第一处修改
u16 MODBUS_CRC16(u8 *p, u8 n);
u8 MODBUS_RTU(void);
#define SL_ADDR 0x10 /* 本从机站号地址 */
#define REG_ADDRESS 0x1000 /* 寄存器首地址 */
#define REG_LENGTH 64 /* 寄存器长度 */
u16 xdata modbus_reg; /* 寄存器地址 */
//========================================================================
// 函数: void main(void)
// 描述: 主函数
// 参数: none.
// 返回: none.
// 版本: VER1.0
// 日期: 2018-4-2
// 备注:
//========================================================================
void main(void)
{
u8 i;
u16 crc;
Timer3_Config(0, MAIN_Fosc / 10000);// 第二处修改
UART1_config(9600UL, 1, 2); //(io == 2) 切换到 P1.6 P1.7
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 == 0x00) || (RX1_Buffer == SL_ADDR)) //然后判断站号地址是否正确, 或者是否广播地址(不返回信息)
{
if(RX1_cnt > 2) RX1_cnt -= 2; //去掉CRC16校验字节
i = MODBUS_RTU(); //MODBUS-RTU协议解析
if(i != 0) //错误处理
{
TX1_Buffer = SL_ADDR; //站号地址
TX1_Buffer = i; //错误代码
crc = MODBUS_CRC16(TX1_Buffer, 2);
TX1_Buffer = (u8)crc; //CRC是小端模式, 先发低字节,后发高字节。
TX1_Buffer = (u8)(crc>>8);
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
字节: 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 byte1 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 byte2 byte
addr 0x03 xx xx....xx xxxx
返回错误代码
数据:站号(地址)错误码 CRC16
偏移: 0 1 最后2字节
字节: 1 byte 1 byte 2 byte
addr 0x93 xxxx
***************************************************************************/
u8 MODBUS_RTU(void)
{
u8 i,j,k;
u16 reg_addr; //寄存器地址
u8 reg_len; //写入寄存器个数
u16 crc;
if(RX1_Buffer == 0x10) //写多寄存器
{
if(RX1_cnt < 9) return 0x91; //命令长度错误
if((RX1_Buffer != 0) || ((RX1_Buffer *2) != RX1_Buffer)) return 0x92; //写入寄存器个数与字节数错误
if((RX1_Buffer==0) || (RX1_Buffer > REG_LENGTH)) return 0x92; //写入寄存器个数错误
reg_addr = ((u16)RX1_Buffer << 8) + RX1_Buffer; //寄存器地址
reg_len = RX1_Buffer; //写入寄存器个数
if((reg_addr+(u16)RX1_Buffer) > (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 = ((u16)RX1_Buffer << 8) + RX1_Buffer; //写入数据, 大端模式
k += 2;
}
if(RX1_Buffer != 0) //非广播地址则应答
{
for(i=0; i<6; i++) TX1_Buffer = RX1_Buffer; //要返回的应答
crc = MODBUS_CRC16(TX1_Buffer, 6);
TX1_Buffer = (u8)crc; //CRC是小端模式, 先发低字节,后发高字节。
TX1_Buffer = (u8)(crc>>8);
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = 8; //要发送的字节数
TI = 1; //启动发送
}
}
else if(RX1_Buffer == 0x03) //读多寄存器
{
if(RX1_Buffer != 0) //非广播地址则应答
{
if(RX1_cnt != 6) return 0x91; //命令长度错误
if(RX1_Buffer != 0) return 0x92; //读出寄存器个数错误
if((RX1_Buffer==0) || (RX1_Buffer > REG_LENGTH)) return 0x92; //读出寄存器个数错误
reg_addr = ((u16)RX1_Buffer << 8) + RX1_Buffer; //寄存器地址
reg_len = RX1_Buffer; //读出寄存器个数
if((reg_addr+(u16)RX1_Buffer) > (REG_ADDRESS+REG_LENGTH)) return 0x93; //寄存器地址错误
if(reg_addr < REG_ADDRESS) return 0x93; //寄存器地址错误
j = reg_addr - REG_ADDRESS; //寄存器数据下标
TX1_Buffer = SL_ADDR; //站号地址
TX1_Buffer = 0x03; //读功能码
TX1_Buffer = reg_len*2; //返回字节数
for(k=3, i=0; i<reg_len; i++,j++)
{
TX1_Buffer = (u8)(modbus_reg >> 8); //数据为大端模式
TX1_Buffer = (u8)modbus_reg;
}
crc = MODBUS_CRC16(TX1_Buffer, k);
TX1_Buffer = (u8)crc; //CRC是小端模式, 先发低字节,后发高字节。
TX1_Buffer = (u8)(crc>>8);
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = k; //要发送的字节数
TI = 1; //启动发送
}
}
else return 0x90; //功能码错误
return 0; //解析正确
}
//=============定时器T3初始化===========================
u8 Timer3_Config(u8 t, u32 reload) //第三处修改
{
if(t != 0) reload = (u32)(((float)MAIN_Fosc * (float)reload)/1000000UL); //重装的是时间(us), 计算所需要的系统时钟数.
if(reload >= (65536UL * 12)) return 1; //值过大, 返回错误
if(reload < 65536UL) T4T3M |= 0x02;//1T mode
else//reload >= 65536UL
{
T4T3M &= 0xFD;//12T mode;12T模式;
reload = reload / 12;//12T模式变为1T模式;
}
reload = 65536UL - reload;
T3H = (u8)(reload >> 8);//
T3L = (u8)(reload);//
T4T3M |= 0x08;//开启定时器T3
IE2 |= 0x20;//允许定时器T3中断
return 0;
}
//=============定时器T2中断=============================
void timer3_ISR (void) interrupt TIMER3_VECTOR//第四处修改
{
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
TH2 = (u8)(dat >> 8);
TL2 = (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中断
INT_CLKO &= ~0x02; // Timer1不输出高速时钟
TR1= 1; // 运行Timer1
}
if(io == 1) {S1_USE_P36P37(); P3n_standard(0xc0);} //切换到 P3.6 P3.7
else if(io == 2) {S1_USE_P16P17(); P1n_standard(0xc0);} //切换到 P1.6 P1.7
else if(io == 3) {S1_USE_P43P44(); P4n_standard(0x18);} //切换到 P4.3 P4.4
else {S1_USE_P30P31(); P3n_standard(0x03);} //切换到 P3.0 P3.1
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 UART1_VECTOR
{
if(RI)
{
RI = 0;
if(!B_RX1_OK) //接收缓冲空闲
{
if(RX1_cnt >= RX1_Length) RX1_cnt = 0;
RX1_Buffer = SBUF;
RX1_TimeOut = 36; //接收超时计时器, 35个位时间
}
}
if(TI)
{
TI = 0;
if(TX1_number != 0) //有数据要发
{
SBUF = TX1_Buffer;
TX1_number--;
}
else B_TX1_Busy = 0;
}
}
串口1 只能选T1 或者T2
国学芯用 发表于 2024-3-27 11:13
串口1 只能选T1 或者T2
波特率使用的是定时器1,没有动,T3用于超时检测,超时时,没有数据传输了,不是波特率, 修改的部分看起来没什么问题,先确认一下你使用的芯片是什么型号的?有没有定时器3? 乘风飞扬 发表于 2024-3-27 13:48
修改的部分看起来没什么问题,先确认一下你使用的芯片是什么型号的?有没有定时器3? ...
使用的是STC8G1K08 20脚的,改成T0就能正常通信,把T3改成状态机的时基,状态就不对了,T0-T2三个,是没有T3,感谢老师指点,还是没有注意到这个细节,定时器不够用了, 380091044 发表于 2024-3-27 14:03
使用的是STC8G1K08 20脚的,改成T0就能正常通信,把T3改成状态机的时基,状态就不对了,T0-T2三个,是 ...
定时器不够用了?
【分享】按键程序,大道至简,按键扫描 + 累计主循环次数去抖动/不占用定时器
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=5936
(出处: 国芯论坛-STC全球32位8051爱好者互助交流社区)
终于找到了相关的例程,理论上stc15是不是只要修改相关寄存器就可以了
页:
[1]