示波器查看串口注意地方:
(1) RS232电平的定义是:-15V~-3V代表“1”,+3V~+15V代表“0”。
(2) 低位在前,高位在后
(3) TTL:无数据时候,都是高电平,起始位为低电平,停止位为高电平;RS232反之
(4)每个字节头和尾都要加起始位(低电平)和结束位(高电平)
(5)9600波特率相当1byte/ms ,115200波特率相当10byte/ms
(6)非级联串口TX配置推挽方式;级联串口TX配置漏极开路方式
波特率9600bps, 9600/10=960Bps(包含起始位和停止位), 大约1ms一个字节时间
串口工作原理:
一般CPU会采用8倍或16倍的波特率的速率采样。采用下降沿触发,进行过采样,并对中间的几个采样值进行判断,如果大部分一致则认为正确,否则会认为出错,有NOSIE,会把寄存器某一位置1。
串口超时机制(上位机):
// 接收时,两字符间最大的时延
comTimeOut.ReadIntervalTimeout = 3;
// 读取每字节的超时
comTimeOut.ReadTotalTimeoutMultiplier = 3;
// 读串口数据的固定超时
// 总超时 = ReadTotalTimeoutMultiplier * 字节数 + ReadTotalTimeoutConstant
9位串口通信:
(1) 第9位(即最高位)--表示地址/数据信息(0:表示前8位为数据; 1:表示前8位为地址).即当串行数据第9位为“1”时,前8位数据指示的是用来和主机通讯的从机地址;当串行数据第9位为“0”时,前8位数据则用为真正的数据
(2) 对PC串口调试助手来说校验位就是第9位。---(mark校验(校验位固定是1)或者space校验(校验位固定是0)),可基于此编写9位串口调试助手
(3) 一般机制,对从设备接收来说,只有接收到与自己地址匹配的才会跳入中断,接收的第一个字节数据即是地址数据,而后再接收实际数据;对从设备发来说,首先要发自己地址数据信息,再发实际数据。
(4)从设备采用漏极开路方式级联,从设备的串口TX必须配置为漏极开路,不能是推挽方式,如果配置成推挽方式,会导致灌电流过大,低电平低不下去问题
一般主要2,3,5三个引脚连接就可以通信。(2为接收,3为发送,5为地。注意这里的接收和发送都是对外端(例如PC来说),就是PC的接收和发送,理解这一点对用交叉还是直通串口线就不会混淆了)
一般来说:板子是公头采用交叉串口线,板子是母头采用直通串口线 (PC机是公头)
一般采用查询发送(循环发送)方式:
unsigned char Send[10]; //发送量
unsigned char i; //循环量
for(i = 0; i < 10; i++)
{
SBUF = Send[i]; //发送
while(TI == 0); //等待发送完成
TI = 0; //清标志
}
采用中断发送方式:
unsigned char Send[10]; //发送量
unsigned char num; //发送数据量
unsigned char *p; //发送用指针
//发送时:
num = 10; //定义发送数据量
p = &Send ; //取首地址
SBUF = *p; //发送第一个数据,启动发送中断
//中断代码:
void ComInt() interrupt 4
{
if(RI) RI = 0; //接收中断略;
if(TI) //发送中断处理
{
TI = 0; //清标志
num--; //计数减1
p++; //指针加1
if(num > 0) SBUF = *p; //数据继续发送至全部发完
}
}
例如通信协议格式:EF + LEN + DATA1......+DATAN + SUM
LEN (DATA1.....SUM)
SUM(EF..........DATAN)
1.查询方式的应用:
bit WaitUART()
{
unsigned int xdata wait_j;
for(wait_j=0;wait_j<4000;wait_j++) //374-0.58,500-0.8ms,600-0.9,700-1.08,800-1.18,900-1.36,1100-1.6ms
{
if(RI1==1)
{
ClearRI1
return 1;
}
}
return 0;
}
void uart1_isr() interrupt 20 //UART1 INTERRUPT
{
register unsigned char Redata;
unsigned char m;
unsigned char CheckReSum=0;
EA = 0; //disable all interrupt;
if(!WaitUART()) goto Exit;
Redata = SBUF1;
if(Redata != 0xef) // check receive flag
{
goto Exit;
}
if(!WaitUART()) goto Exit;
Redata = SBUF1;
if(Redata == 0)
{
goto Exit;
}
iCmddata[0]=Redata; //data length
CheckReSum = 0xef + iCmddata[0];
for(m=1;m<iCmddata[0];m++)
{
if(!WaitUART()) goto Exit;
Redata = SBUF1;
iCmddata[m] = Redata;
CheckReSum += Redata;
}
if(!WaitUART()) goto Exit;
Redata = SBUF1;
iCmddata[m] = Redata; //sum
if(CheckReSum != iCmddata[m]) //check transdata sum
{
goto Exit;
}
ProcessOrder();
Exit:
EA = 1; //enable all interrupt
}
优点:对包响应的实时性要好
缺点: 由于采样查询方式,CPU一直等数据接收完(不像中断),占用CPU时间,对于大数据量的传输,这个缺点尤其明显。
2.中断方式(采用队列)
(1)定义的一个数组用来接收数据,数据满的时候,最前面的数据会被最新的数据替换。
(2)有两个指针,一个头指针,由用户控制,用来查找符合包格式的数据包;一个尾指针永远指向队列的末尾处
如何查找:当发现头指针和尾指针不相等时,说明有新的数据,头指针向后移动,取出数据。
(1)如果数据不符合格式,头指针回到最初的位置+1
(2)如果数据符合格式,但未接收完,当头指针和尾指针相等,头指针返回最初的位置。
优点: 不占用CPU资源,对大数据量传输比较好
缺点: 对包响应的实时性要差,依靠用户从队列取包,没有超时判断,容易包误判。
问题:
假设发送 (1)第一个包发送 EF
(2)第二个包发送 EF 02 10 01
队列的数据就为
EF EF 02 10 01
由于队列没有超时机制,只是将接收的数据依次放在队列中。当在队列取数据的时候,当取出EF后,会将后面的EF当成LEN,当取到最后值01时候,头指针会回到当初的EF头处 (认为数据没接收完),这样对第二个包就没有响应了。
解决方法:
如果第一字节出现AB 第二字节再从头一帧数据,那程序肯定无法判断,这是避免不了的。但如果发一字节AB后间隔一断时间,可以用定时器限制来判断,接收到字节时开始计时,如果超过一定时间,定时器中断对接收队列指针回0。
3.中断方式(开启定时器等待)
当串口接收到第一个数据时候(通常是包头),如果包头正确的话,开启一个定时器开始计数。当定时器规定时间(即接收串口数据最长包的时间)到达的时候,设置一个标志表示数据接收完毕。 在程序主循环不断判断这个标志,如果标志被置1,则进行相应处理,处理完后再将该标志清零。
void UsartIsr0() interrupt 4
{
EA=0;
if(SCON0&0x01)
{
SCON0&=0xFE;
Usart0Buff = SBUF0;
if(RxIndex0 >=80) RxIndex0 = 0;
RxBuff0[RxIndex0++] = Usart0Buff;
DataLength0 = RxIndex0;
Time1Flag = 0;
TR1 = 1;
}
SCON0&=0xFE;
EA=1;
}
void Timer1_ISR (void) interrupt 3
{
//EA=0 ;
Time1Flag ++;
if(Time1Flag >2)// &&(RxIndex2>4))
{
Time1Flag = 0;
usart0_RxFlag=1;
RxIndex0=0;
TR1 = 0;
}
//EA=1 ;
}
void main(void)
{
......
while(1)
{
if(usart0_RxFlag)
{
............................
usart0_RxFlag = 0;
}
}
}
优点: 不占用CPU资源,有超时判断
缺点: 定时器设置时间不好确定,导致小数据包和大数据包的响应时间都一致。