特别请教,大数据量通讯,波特率很低,采用“中断+缓存+超时+重发” 仍不能确保通讯畅通的解
我需要解决在低波特率(1200、2400、4800)时,通过上位机和下位机之间通讯,数量量比较大,采用“中断+缓存+超时+重发” 仍不能解决通讯畅通,但是在9600、19200以上的波特率时,通讯质量非常好,请问解决办法。
(1)、协议采用MODBUS-RTU
(2)、通讯模式,采用RS485方式。
(2)、通讯结构图如下:
|-----------------------------| |----------------------------| |---------------------------
| | | | | |
| | | 中转站 | | 总线上子模块1 |
| PC机 | | | | |
| | |---------| |-----------| | |-----------|
| |---->| USART0 |----->| USART1 |------------------> | USART1 |
| | | | | | | | |
|-----------------------------| |----------------------------| |--------------------------|
说明:
(A)PC机和“中转站”模块的USART0端口直连。
(B)“中转站”的USART1端口和总线上子模块的USART1端口直连,构成RS485。
///////////////////////////////////////////////////////////////////
(C).PC机要访问总线上的子模块1必须通过“中转站”模块,PC机访问子模块1的过程如下:
(C.1)PC机将访问”子模块1“的数据包发送到“中转站”的USART0端口。
(C.2)“中转站”平时处于“RS485接收状态”,当接收到一帧完整的数据包后,将自身置为“RS485发送状态”。
然后通过自身的USART1端口发布到总线上。发送完毕后,将自身置为“RS485接收状态”。
(C.3)总线上的“子模块1”接收到一帧完整的数据包后,将自身置为“RS485发送状态”,然后将《应答数据包》通过USART1端口
发布到总线上。发送完毕,将自身置为“RS485接收状态”。
(C.4)”中转站“的USART1端口接收到一帧完整的应答数据包后,通过自身的USART0端口,将“子模块1”的应答数据包
转发给PC机。
(D)、串口接收中断服务程序非常短,定时中断服务程序中一进入中断服务程序,就执行SEI()。
我做了如下实验:
(1)、PC机每隔5秒钟发送一帧数据包(长度=249字节),如果中转站接收到数据包,中转站的第一排数码管上就加1。
(2) 子模块1接收到“中转站”转发的数据包,子模块1的数码管上就加1。
(3)、中转站模块接收到“子模块1”的应答数据包,中转站的第二排数码管就加1。
经过反复测试,当波特率=1200时,子模块90%能够接收到PC机的数据包,
★★★ 然而,“中转站”却接收不到“子模块1”的应答消息(应答消息才8个字节) 你是不是偷懒只用了一个MAX232芯片连接了USART0和USART1?如果是换一个……
我以前遇到过这种歪货……气死俺了……
本贴被 Gorgon Meducer 编辑过,最后修改时间:2008-11-25,21:02:26. 我感觉好象是硬件的问题。
(1)、波特率=1200时,去掉子模块后,PC机直接给中转站发送数据(中转站的USART0端口接收PC机的数据),
中转站的USART1端口竟然产生接收中断。(中转站的USART1端口接收子模块的数据或者给子模块“转发”数据)
(2)、波特率=2400、4800时
不会产生上述现象。
此时挂上子模块,但是却会产生下述现象:
如果PC机发送一帧数据中,包含0xFF时(整型负数转换成两个BYTE型),产生的应答数会是随机数,如果一帧数据中无0xFF,则一切正常。
应该怀疑是RS485转换电路的原因。
to 【22楼】 ba_wang_mao
去掉数据帧的解析部分,收到上位机数据以后直接回发。看看效果。 PC机固定发送129字节是没秒发送的数据个数吗?
17楼说的很有可能
你在1200、2400波特率时减少数据发送的个数,先减少到10个字节,看看还会不会出现问题 去掉从机也一样,
当波特率高于4800时,一切正常。
当波特率=1200、2400,发现
PC机固定发送129字节时,中转站接收的数据个数竟然是随机的,不是129字节,到底是什么原因造成的呢? 建议楼主隔离测试……就是去掉从机,只保留中转站和PC用1200波特率通讯看看……
是否有丢数据的现象 你pc到中转站也出问题?怀疑你中转站波特率设置不正确 favr:
1、PC机--->中转站速度很快(中转站的中断服务程序非常短,只是判断1.5字符的静止时间,打断串口的时间基本不用考虑)
2、子模块1的定时中断服务程序也很短,而且我在一进入定时中断后,就开放了全局中断(SEI()),
3、PC机等待应答数据包的时间,我根据不同的波特率,存储了一张超时时间表,该时间充分考虑了上述时间(从PC--->中转站--->子模块)、然后从(子模块1--->中转站--->PC机),该超时时间是正常计算时间的2倍。
4、只有超时结束后,还没有收到应答数据包,PC机才会重发该帧数据。 先做一个简单的计算:
当使用1200Bit的速率时;以1个起始位,8个数据位一个停止位的格式传递一个字节,传递一个字节至少需要10个位,即每秒最多可以传递120个Byte,249Byte需要249/120=2.075(s),大概2秒左右。按照你的传输方式,pc->中转站需要2s,中转站->子模块需要2s;最少需要4s时间,加上中间转换、计算、判断、总线等待等,5s/帧原理上没问题,但如果软件没做好,例如中间的计算、总线等待等没做好就可能不行,[对C不在行,不能从你的代码判断整个接收/发送过程时间消耗情况]。
如果实际出现你描述的情况:不稳定。则十有八九通讯已经出差了,例如PC当前收到的应答是前一、二、个数据包的回应,你应该用能反映不同数据包返回信息判断是否出现这样的情况,如果2400bit速率还出现这种情况,而4800以上就没问题,则出现套帧的可能性就n大了。 两个帧解析函数在“主程序”调用
void main(void)
{
CLI();
PORT_Init();
POST_Delay();
QUEUE_Init();
TIMER2_Init();
PROC_Init();
SEI();
while (1)
{
// _StackCheck(); // 测试堆栈
USART0_Modbus_Analyze();
USART1_Modbus_Analyze();
MSCOMM_ID_Switch();//上位机身份切换子程序
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//广播设置总线波特率时要占用USART1端口,通讯模块扫描总线上的监视模块时,也要占用USART1端口,因此应该避开这种情况。防止总线//数据混乱,同时由于广播设置总线的波特率的优先级最高,因此USART1端口应该等发送完广播消息后,再发送扫描消息。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (MSCOMM_send_mark && MSCOMM_ID == enum_DCS_SOFTWARE && !BoardCast_mark)
{
MSCOMM_send_mark = FALSE;
switch (MSCOMM_scan_Func)
{
case enum_SEND_CoilRegisters:
MODBUS_Read_CoilRegisters();
break;
case enum_SEND_DiscreteRegisters:
MODBUS_Read_DiscreteRegisters();
break;
case enum_SEND_InputRegisters:
MODBUS_Read_InputRegisters();
break;
}
}
Watch_Dog();
}
} 问题终于找到了。现象如下:
在波特率=1200、2400,发现
PC机固定发送129字节时,中转站接收的数据个数竟然是随机的,不是129字节,到底是什么原因造成的呢? 这种通讯系统按道理基本不错才对……
你的两个帧解析函数在什么地方调用的? 在定时中断服务程序的开头,我加了SEI()。 不同的波特率,超时时间我计算过的,对于1200波特率来说,每传送一个字节需要11位(1个起始位+8个数据位+2位停止位)
1200 11
-------------- = ----- ===> x=1000*11/1200=9.2毫秒
1000 x
也就是说在波特率=1200时,传输1个字节至少需要9.2毫秒,而我的超时时间设置=50毫秒,应该足够了。
void USART1_Modbus_Analyze(void)
{
UCHAR i;
SHORT tempData;
USHORT crcData;
if (USART1_receCount> 2)
{
switch (USART1_mscomm_buffer)
{
case 1:
if (USART1_checkoutError == 0 && USART1_mscomm_buffer != 0)
{
tempData = (SHORT)(USART1_mscomm_buffer) + 5;
if (USART1_receCount>= tempData && USART1_receCount < MSCOMM_BUFFER_LENGTH)
{
UCSR1B &= ~BIT(7);
crcData = CRC16(USART1_mscomm_buffer,tempData - 2);
if (crcData == (USHORT)(USART1_mscomm_buffer << 8) + (USHORT)USART1_mscomm_buffer)
{
switch (MSCOMM_ID)
{
case enum_POS3000:
case enum_CONFIG_SOFTWARE:
USART0_sendCount = tempData;
for (i = 0 ; i < tempData ; i++)
USART0_send_buffer = USART1_mscomm_buffer;
USART0_Begin_Send();
break;
case enum_DCS_SOFTWARE:
MSCOMM_Cache.DO = USART1_mscomm_buffer;
MSCOMM_Cache.OnLine_Type = enum_OnLine;
MSCOMM_ack_type = enum_ACK_OK;
/* #ifdef __DEBUG_COUNT__
if (MSCOMM_scan_type == enum_ONLINE_SCAN)
CHB++;
#endif*/
break;
}
}
USART1_receCount = 0;
USART1_checkoutError = 0;
UCSR1B |= BIT(7);
}
}
break;
case 2:
if (USART1_checkoutError == 0 && USART1_mscomm_buffer != 0)
{
tempData = (SHORT)(USART1_mscomm_buffer) + 5;
if (USART1_receCount>= tempData && USART1_receCount < MSCOMM_BUFFER_LENGTH)
{
UCSR1B &= ~BIT(7);
crcData = CRC16(USART1_mscomm_buffer,tempData - 2);
if (crcData == (USHORT)(USART1_mscomm_buffer << 8) + (USHORT)USART1_mscomm_buffer)
{
switch (MSCOMM_ID)
{
case enum_POS3000:
case enum_CONFIG_SOFTWARE:
USART0_sendCount = tempData;
for (i = 0 ; i < tempData ; i++)
USART0_send_buffer = USART1_mscomm_buffer;
USART0_Begin_Send();
break;
case enum_DCS_SOFTWARE:
MSCOMM_Cache.DI = USART1_mscomm_buffer;
MSCOMM_Cache.OnLine_Type = enum_OnLine;
MSCOMM_ack_type = enum_ACK_OK;
/* #ifdef __DEBUG_COUNT__
if (MSCOMM_scan_type == enum_ONLINE_SCAN)
CHB++;
#endif*/
break;
}
}
USART1_receCount = 0;
USART1_checkoutError = 0;
UCSR1B |= BIT(7);
}
}
break;
case 3:
if (USART1_checkoutError == 0 && USART1_mscomm_buffer != 0)
{
tempData = (SHORT)(USART1_mscomm_buffer) + 5;
if (USART1_receCount>= tempData && USART1_receCount < MSCOMM_BUFFER_LENGTH)
{
UCSR1B &= ~BIT(7);
crcData = CRC16(USART1_mscomm_buffer,tempData - 2);
if (crcData == (USHORT)(USART1_mscomm_buffer << 8) + (USHORT)USART1_mscomm_buffer)
{
switch (MSCOMM_ID)
{
case enum_POS3000:
case enum_CONFIG_SOFTWARE:
USART0_sendCount = tempData;
for (i = 0 ; i < tempData ; i++)
USART0_send_buffer = USART1_mscomm_buffer;
USART0_Begin_Send();
break;
}
}
USART1_receCount = 0;
USART1_checkoutError = 0;
UCSR1B |= BIT(7);
}
}
break;
case 4:
if (USART1_checkoutError == 0 && USART1_mscomm_buffer != 0)
{
tempData = (SHORT)(USART1_mscomm_buffer) + 5;
if (USART1_receCount>= tempData && USART1_receCount < MSCOMM_BUFFER_LENGTH)
{
UCSR1B &= ~BIT(7);
crcData = CRC16(USART1_mscomm_buffer,tempData - 2);
if (crcData == (USHORT)(USART1_mscomm_buffer << 8) + (USHORT)USART1_mscomm_buffer)
{
switch (MSCOMM_ID)
{
case enum_POS3000:
case enum_CONFIG_SOFTWARE:
USART0_sendCount = tempData;
for (i = 0 ; i < tempData ; i++)
USART0_send_buffer = USART1_mscomm_buffer;
USART0_Begin_Send();
break;
case enum_DCS_SOFTWARE:
for (i = 0 ; i < 4 ; i++)
MSCOMM_Cache.Input_Buffer = (SHORT)(USART1_mscomm_buffer << 8) + (SHORT)(USART1_mscomm_buffer);
MSCOMM_Cache.OnLine_Type = enum_OnLine;
MSCOMM_ack_type = enum_ACK_OK;
/* #ifdef __DEBUG_COUNT__
if (MSCOMM_scan_type == enum_ONLINE_SCAN)
CHB++;
#endif*/
break;
}
}
USART1_receCount = 0;
USART1_checkoutError = 0;
UCSR1B |= BIT(7);
}
}
break;
case 6:
if (USART1_receCount>= 8 && USART1_checkoutError == 0 && USART1_mscomm_buffer != 0)
{
UCSR1B &= ~BIT(7);
crcData = CRC16(USART1_mscomm_buffer,6);
if (crcData == (USHORT)(USART1_mscomm_buffer << 8) + (USHORT)USART1_mscomm_buffer)
{
switch (MSCOMM_ID)
{
case enum_POS3000:
case enum_CONFIG_SOFTWARE:
USART0_sendCount = 8;
for (i = 0 ; i < 8 ; i++)
USART0_send_buffer = USART1_mscomm_buffer;
USART0_Begin_Send();
break;
}
}
USART1_receCount = 0;
USART1_checkoutError = 0;
UCSR1B |= BIT(7);
}
break;
case 16:
if (USART1_receCount>= 8 && USART1_checkoutError == 0 && USART1_mscomm_buffer != 0)
{
UCSR1B &= ~BIT(7);
crcData = CRC16(USART1_mscomm_buffer,6);
if (crcData == (USHORT)(USART1_mscomm_buffer << 8) + (USHORT)USART1_mscomm_buffer)
{
switch (MSCOMM_ID)
{
case enum_POS3000:
case enum_CONFIG_SOFTWARE:
USART0_sendCount = 8;
for (i = 0 ; i < 8 ; i++)
USART0_send_buffer = USART1_mscomm_buffer;
USART0_Begin_Send();
break;
}
  在每一个中断处理程序的开头都加SEI看看。
不过需要注意,对于接收完成中断,SEI要放在读取UDR寄存器以后…… (3)中转站的定时中断服务程序
只是检测1.5字符的静止时间,没有其它代码。 (1)、中转站的USART0部分
void USART0_Begin_Send(void)
{
USART0_RS485_SEND();
NOP(); // --------|
NOP(); // |
NOP(); // |-----------等待总线释放
NOP(); // |
NOP(); // --------|
NOP(); // --------|
NOP(); // |
NOP(); // |-----------等待总线释放
NOP(); // |
NOP(); // --------|
USART0_sendPosi = 0;
UCSR0B |= BIT(5); //加快发送速度,用空中断发送
}
#pragma interrupt_handler USART0_RI_ISR:iv_USART0_RX
void USART0_RI_ISR(void)
{
UCHAR ch;
UCHAR status;
status = UCSR0A;
ch = UDR0;
if (USART0_receCount < MSCOMM_BUFFER_LENGTH)
USART0_mscomm_buffer = ch;
else
USART0_receCount = 0;
if (status & 0x1c)
USART0_checkoutError = 2;
USART0_receTimeOut = 3;
}
#pragma interrupt_handler USART0_UDRE_ISR:iv_USART0_UDRE
void USART0_UDRE_ISR(void)
{
UDR0 = USART0_send_buffer;
if (USART0_sendPosi>= USART0_sendCount)
{
UCSR0B &= ~BIT(5);
UCSR0B |= BIT(6);
}
}
#pragma interrupt_handler USART0_TX_ISR:iv_USART0_TX
void USART0_TX_ISR(void)
{
USART0_checkoutError = 0;
USART0_receCount = 0;
USART0_RS485_RECIVE();
}
(2)、中转站的USART1部分
void USART1_Begin_Send(void)
{
USART1_RS485_SEND();
NOP(); // --------|
NOP(); // |
NOP(); // |-----------等待总线释放
NOP(); // |
NOP(); // --------|
NOP(); // --------|
NOP(); // |
NOP(); // |-----------等待总线释放
NOP(); // |
NOP(); // --------|
USART1_sendPosi = 0;
UCSR1B |= BIT(5);//加快发送速度,用空中断发送
}
#pragma interrupt_handler USART1_RI_ISR:iv_USART1_RX
void USART1_RI_ISR(void)
{
UCHAR ch;
UCHAR status;
status = UCSR1A;
ch = UDR1;
if (USART1_receCount < MSCOMM_BUFFER_LENGTH)
USART1_mscomm_buffer = ch;
else
USART1_receCount = 0;
if (status & 0x1c)
USART1_checkoutError = 2;
USART1_receTimeOut = 5;
}
#pragma interrupt_handler USART1_UDRE_ISR:iv_USART1_UDRE
void USART1_UDRE_ISR(void)
{
UDR1 = USART1_send_buffer;
if (USART1_sendPosi>= USART1_sendCount)
{
UCSR1B &= ~BIT(5);
UCSR1B |= BIT(6);
}
}
#pragma interrupt_handler USART1_TX_ISR:iv_USART1_TX
void USART1_TX_ISR(void)
{
USART1_checkoutError = 0;
USART1_receCount = 0;
USART1_RS485_RECIVE();
if (BoardCast_mark) //如果PC机通过广播修改总线上其它模块的波特率时,必须待中转站将当前帧数据发送完毕后
{ //(发送给总线上的子模块后),才能修改中转站USART1端口的波特率
BoardCast_mark = FALSE;
USART1_Init(set_buffer);
}
} 楼主你的程序问题主要体现在中转模块上了。
看一下你的代码结构? 另外这个超时时间的设置要合理,得保证中转站能及时响应PC机的请求。不然在工控应用中,中转站会缓冲区不够用的 中转站和子模块都属slave,关键是要保证对总路线访问权的串行控制。对响应需要设置一定的超时时间 还有你的中转站的发送接收状态转换部分再查查看 波特率低出问题说明还是时序上的问题。你的中转站发送完以后等待应答,多长时间算超时? (1)PC机发送249字节的请求包给子模块1(预置120个保持寄存器),传输路线如下:
|-----------------------------| |----------------------------| |---------------------------
| | | | | |
| | | 中转站 | | 总线上子模块1 |
| PC机 | | | | |
| |----------| |--------- |---------| | |---------- | | | | | | |
| | |---->| USART0 | ------> | USART1 |-------------------->| USART1 |
| | USART0 | | | | | | | |
|-----------------------------| |----------------------------| |--------------------------|
(2)子模块1接收到请求包数据,然后发送8个字节的应答包给PC机,传输路线如下:
|-----------------------------| |----------------------------| |---------------------------
| | | | | |
| | | 中转站 | | 总线上子模块1 |
| PC机 | | | | |
| |----------| |--------- |---------| | |---------- | | | | | | |
| | |<----| USART0 | <------ | USART1 |<--------------------| USART1 |
| | USART0 | | | | | | | |
|-----------------------------| |----------------------------| |--------------------------|
我的试验过程如下(波特率=1200、2400时):
(A)、中转站模块有两排数码管
第一排数码管显示接收到PC机请求包的“次数”(通过了CRC16校验)
第二排数码管显示接收到到子模块1应答数据包的“字节数”,应该是8的倍数(“应答数据包”=8字节)
(B)、子模块1有两排数码管
第一排数码管显示接收到“中转站”转发的请求包的“次数”(通过了CRC16校验)
第二排数码管显示子模块1发送“应答数据包”给“中转站”的次数。
(C)、PC机用VB程序仿真
制作一个按钮,每按一下,发送一帧“预置多个寄存器指令”的数据包,长度=249字节。
经过大量试验,得出如下结果:
(1)中转站模块每次都能够正确收到PC机发送的“预置多个寄存器指令”
(2)总线上的子模块1每次都能够正确收到“中转站”转发的“预置多个寄存器指令”数据包。
(3)子模块的USART1发送完成中断中,设置一个计数器,记录发送了多少次“应答数据包”。
实验证明,子模块1只要接收到一帧请求包,就能够将应答包发送出去
(4)中转站的USART1接收中断中,设置一个计数器,记录读取了子模块1应答数据包的字节数。(如果正确,一定是8的倍数)
第二排数码管应该显示8的倍数,实验证明,USART1接收中断中读取的字节数是随机的,每次接收的应答包的字节数
却至少是50-80字节。
相同的程序,用波特率4800,却完全正确,到底是什么原因。 1、在1200低速模式下,先精简系统为一个PC,一个中转,一个子模块
看看中转站是否能收到应答信号。
2、逐渐提高PC机发送频率从5秒一次到1秒一次……观察通讯状况。
3、反馈以上的测试情况。好让大家继续分析…… 不太懂你这个通讯方面的东西,你不妨用示波器看一下是子模块没有应答,还是应答了没有收到?用二分法找故障很快的。
你有多个子模块并联吗? 那要看相应的书籍资料,查找一下485连线的具体规定,匹配电阻什么的也要搞好。
页:
[1]