单片机串口通信原理和控制程序
2019-04-15 18:45 发布
生成海报
我们前边学串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单的收发字符或者字符串。在实际应用中,往往串口还要和电脑上的上位机软件进行交互,实现电脑软件发送不同的指令,单片机对应执行不同操作的功能,这就要求我们组织一个比较合理的通信机制和逻辑关系,用来实现我们想要的结果。 本节所提供程序的功能是,通过电脑串口调试助手下发三个不同的命令,第一条指令:buzz on 可以让蜂鸣器响;第二条指令:buzz off 可以让蜂鸣器不响;第三条指令:showstr
,这个命令空格后边,可以添加任何字符串,让后边的字符串在 1602 液晶上显示出来,同时不管发送什么命令,单片机收到后把命令原封不动的再通过串口发送给电脑,以表示“我收到了„„你可以检查下对不对”。这样的感觉是不是更像是一个小项目了呢? 对于串口通信部分来说,单片机给电脑发字符串好说,有多大的数组,我们就发送多少个字节即可,但是单片机接收数据,接收多少个才应该是一帧完整的数据呢?数据接收起始头在哪里,结束在哪里?这些我们在接收到数据前都是无从得知的。那怎么办呢? 我们的编程思路基于这样一种通常的事实: 当需要发送一帧(多个字节)数据时,这些数据都是连续不断的发送的,即发送完一个字节后会紧接着发送下一个字节,期间没有间隔或间隔很短,而当这一帧数据都发送完毕后,就会间隔很长一段时间(相对于连续发送时的间隔来讲)不再发送数据,也就是通信总线上会空闲一段较长的时间 。于是我们就建立这样一种程序机制: 设置一个软件的总线空闲定时器,这个定时器在有数据传输时(从单片机接收角度来说就是接收到数据时)清零,而在总线空闲时(也就是没有接收到数据时)时累加,当它累加到一定时间(例程里是
30ms)后,我们就可以认定一帧完整的数据已经传输完毕了,于是告诉其它程序可以来处理数据了,本次的数据处理完后就恢复到初始状态,再准备下一次的接收。 那么这个用于判定一帧结束的空闲时间取多少合适呢?它取决于多个条件,并没有一个固定值,我们这里介绍几个需要考虑的原则:第一,这个时间必须大于波特率周期,很明显我们的单片机接收中断产生是在一个字节接收完毕后,也就是一个时刻点,而其接收过程我们的程序是无从知晓的,因此在至少一个波特率周期内你绝不能认为空闲已经时间达到了。第二,要考虑发送方的系统延时,因为不是所有的发送方都能让数据严格无间隔的发送,因为软件响应、关中断、系统临界区等等操作都会引起延时,所以还得再附加几个到十几个
ms 的时间。我们选取的 30ms 是一个折中的经验值,它能适应大部分的波特率(大于1200)和大部分的系统延时(PC 机或其它单片机系统)情况。 我先把这个程序最重要的 UART.c 文件中的程序贴出来,一点点给大家解析,这个是实际项目开发常用的用法,大家一定要认真弄明白。
#include bit flagFrame = 0 ; bit flagTxd = 0 ; unsigned char cntRxd = 0 ; unsigned char pdata bufRxd[ 64 ]; extern void UartAction ( unsigned char * buf, unsigned char len); void ConfigUART ( unsigned int baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - ( 11059200 / 12 / 32 )/ baud; TL1 = TH1; ET1 = 0 ; ES = 1 ; TR1 = 1 ; } void UartWrite ( unsigned char * buf, unsigned char len) { while ( len--) { flagTxd = 0 ; SBUF = * buf++; while (! flagTxd); } } unsigned char UartRead ( unsigned char * buf, unsigned char len) { unsigned char i; if ( len > cntRxd) { len = cntRxd; } for ( i= 0 ; i< len; i++) { * buf++ = bufRxd[ i]; } cntRxd = 0 ; return len; } void UartRxMonitor ( unsigned char ms) { static unsigned char cntbkp = 0 ; static unsigned char idletmr = 0 ; if ( cntRxd > 0 ) { if ( cntbkp != cntRxd) { cntbkp = cntRxd; idletmr = 0 ; } else { if ( idletmr < 30 ) { idletmr += ms; if ( idletmr >= 30 ) { flagFrame = 1 ; } } } } else { cntbkp = 0 ; } } void UartDriver () { unsigned char len; unsigned char pdata buf[ 40 ]; if ( flagFrame) { flagFrame = 0 ; len = UartRead ( buf, sizeof ( buf)); UartAction ( buf, len); } } void InterruptUART () interrupt 4 { if ( RI) { RI = 0 ; if ( cntRxd < sizeof ( bufRxd)) {{ bufRxd[ cntRxd++] = SBUF; } } if ( TI) { TI = 0 ; flagTxd = 1 ; } }
大家可以对照注释和前面的讲解分析下这个 Uart.c 文件,在这里指出其中的两个要点希望大家多注意下。 1、接收数据的处理,在串口中断中,将接收到的字节都存入缓冲区 bufRxd 中,同时利用另外的定时器中断通过间隔调用 UartRxMonitor 来监控一帧数据是否接收完毕,判定的原则就是我们前面介绍的空闲时间,当判定一帧数据结束完毕时,设置
flagFrame 标志,主循环中可以通过调用 UartDriver 来检测该标志,并处理接收到的数据。当要处理接收到的数据时,先通过串口读取函数 UartRead 把接收缓冲区 bufRxd 中的数据读取出来,然后再对读到的数据进行判断处理。也许你会说,既然数据都已经接收到 bufRxd 中了,那我直接在这里面用不就行了嘛,何必还得再拷贝到另一个地方去呢?我们设计这种双缓冲的机制,主要是为了提高串口接收到响应效率:首先如果你在 bufRxd 中处理数据,那么这时侯就不能再接收任何数据,因为新接收的数据会破坏原来的数据,造成其不完整和混乱;其次,这个处理过程可能会耗费较长的时间,比如说上位机现在就给你发来一个延时显示的命令,那么在这个延时的过程中你都无法去接收新的命令,在上位机看来就是你暂时失去响应了。而使用这种双缓冲机制就可以大大改善这个问题,因为数据拷贝所需的时间是相当短的,而只要拷贝出去后,bufRxd
就可以马上准备去接收新数据了。 2、串口数据写入函数 UartWrite,它把数据指针 buf 指向的数据块连续的由串口发送出去。虽然我们的串口程序启用了中断,但这里的发送功能却没有在中断中完成,而是仍然靠查询发送中断标志
flagTxd(因中断函数内必须清零 TI,否则中断会重复进入执行,所以另置了一个 flagTxd 来代替 TI)来完成,当然也可以采用先把发送数据拷贝到一个缓冲区中,然后再在中断中发缓冲区数据发送出去的方式,但这样一是要耗费额外的内存,二是使程序更复杂。这里也还是想告诉大家,简单方式可以解决的问题就不要搞得更复杂。
#include sbit BUZZ = P1^ 6 ; bit flagBuzzOn = 0 ; unsigned char T0RH = 0 ; unsigned char T0RL = 0 ; void ConfigTimer0 ( unsigned int ms); extern void UartDriver (); extern void ConfigUART ( unsigned int baud); extern void UartRxMonitor ( unsigned char ms); extern void UartWrite ( unsigned char * buf, unsigned char len); extern void InitLcd1602 (); extern void LcdShowStr ( unsigned char x, unsigned char y, unsigned char * str); extern void LcdAreaClear ( unsigned char x, unsigned char y, unsigned char len); void main () { EA = 1 ; ConfigTimer0 ( 1 ); ConfigUART ( 9600 ); InitLcd1602 (); while ( 1 ) { UartDriver (); } } bit CmpMemory ( unsigned char * ptr1, unsigned char * ptr2, unsigned char len) { while ( len--) { if (* ptr1++ != * ptr2++) { return 0 ; } } return 1 ; } void UartAction ( unsigned char * buf, unsigned char len) { unsigned char i; unsigned char code cmd0[] = "buzz on" ; unsigned char code cmd1[] = "buzz off" ; unsigned char code cmd2[] = "showstr " ; unsigned char code cmdLen[] = { sizeof ( cmd0)- 1 , sizeof ( cmd1)- 1 , sizeof ( cmd2)- 1 , } ; unsigned char code * cmdPtr[] = { & cmd0[ 0 ], & cmd1[ 0 ], & cmd2[ 0 ], } ; for ( i= 0 ; i< sizeof ( cmdLen); i++) { if ( len >= cmdLen[ i]) { if ( CmpMemory ( buf, cmdPtr[ i], cmdLen[ i])) { break ; } } } switch ( i) { case 0 : flagBuzzOn = 1 ; break ; case 1 : flagBuzzOn = 0 ; break ; case 2 : buf[ len] = '
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮