Full duplex UART software simulation based on LPC1768 processor
Abstract: In order to overcome the faults of low efficiency and running only in half duplex mode in most software simulation scheme, this article adopted a new idea to realize its scheme. The hardware resources it used were a timer and two GPIO pins of LPC1768 processor. In software, the common feature of UART sending and receiving was made full use of, which facilitates sending and receiving data frames at the same time in the timer interrupt service routine. The even parity checking function was added for accuracy and the sending and receiving buffers were designed for a user-friendly interface. Actual test shows that the software UART is reliable and highly efficient. It can also operate in full duplex mode with a common baud rate. In addition, the scheme can be easily realized in other types of MCU.
Key words: LPC1768; software simulation serial ports; full duplex UART; COM extension
引言
在嵌入式系统开发过程中,用到UART串口的几率非常高,往往某些场合用到的个数还比较多,如笔者现在正在开发的项目,由于要具备多种上、下行通信方式,并且基本上都需要基于UART串口来实现,统计后发现至少需要6个串口,而市面上具备6个以上串口的价位合适的MCU又比较少见,这时串口的扩展往往就不可避免。还有一种情况,在某些低成本应用中,所选的MCU通常只有1至2个串口,此时如果MCU内部串口已作他用,但还想用串口实现其他功能,比较好的方法也只有通过串口扩展实现。
串口扩展有硬件扩展和软件扩展两种方式,硬件扩展主要是通过串口扩展芯片实现,常见的有16C550、SC16IS752/SC16IS762等,但会增加成本和占用板面空间;软件扩展就是利用MCU的GPIO引脚通过软件编程来模拟UART的发送和接收功能,该方式不仅灵活可靠,而且节约成本。本文主要是介绍UART串口的软件扩展方式。
目前有很多软件模拟串口的方案,但大部分可以归结为两种实现方式:第一、采用软件延时来逐位输入、输出数据帧bit位,第二、利用MCU内部定时器定时输入、输出数据帧bit位。软件延时方式在发送或接收过程中会一直占用MCU,效率较低,这对于一些波特率要求不高但实时性要求较高的工控场合并不适用;定时器方式接收时在中断中接收,效率较高,但发送时往往还是采用阻塞式(持续判断定时器溢出标志位)的发送方式,总体上还是有些欠缺,而且两者都只能进行半双工通信。本文的实现方式也是采用定时器方式,但完全是在定时器中断服务中实现数据的发送和接收,经过测试发现MCU的实际占用率可以降低到10%以下[1],具有较高的效率,很适合笔者所要开发的项目,并且还可以同时进行发送和接收,实现了全双工的异步串行通信方式。
1. UART串口简述
串行通信接口简称串口,它分为异步和同步两种通信方式,我们这里用到的是异步串行通信接口,即UART。通常它的一帧数据帧是10位或11位,图1是一个11位的帧,包括1位起始位(0),8位数据位,1位奇偶校验位和1位停止位(1)。通信双方必须采用相同的波特率(波特率即每秒收发的bit位数),严格按照双方约定的帧格式进行数据传输,且空闲状态下串口输入输出线上保持高电平。
2. UART的软件模拟实现
2.1 模拟实现的基本思路
根据异步串行通信接口的工作方式,UART软件模拟实现的基本思路如下:在输入上,我们可以利用LPC1768定时器的捕获输入引脚(Capture Input)来捕捉起始位,在确定是起始位后,按照波特率设置好定时器匹配寄存器(Match Register),以一定的时间间隔来采样输入引脚上的电平,在确认校验位与停止位无误后,完成一帧数据的采样;在输出上,只要按照所需波特率设置好定时器,在每一个定时中断到来后,按照从低到高的顺序,逐位输出待发送数据帧的bit位即可,需要注意的是,空闲状态下输出引脚必须保持高电平。
基本思路是比较直接的、初步的实现方法,具体实现时,我们需要在基本思路的基础上做些改变和做更多的工作。另外需要说明的是,在输入采样时,对每一数据位一般需要多次采样并确认一致后才完成输入,但实际测试与对其他方案[2][3]的研究发现,每一数据位仅采样一次也几乎没有错误发生,所以本文的程序仅在每一数据位传输的中间时刻采样一次。
2.2 具体的软件实现
我们选择LPC1768[4]内部的定时器Timer 0、相应的捕获输入引脚P1.26和通用GPIO口P1.28作为软件实现所需的硬件资源。Timer 0工作在定时器模式(Timer Mode)下;开启P1.26引脚(CAP0.0)的下降沿捕获功能,使能其中断(在捕捉到起始位后,该引脚转换为GPIO输入模式);接收和发送共用匹配寄存器1(MR1),计数器(TC)值匹配(即溢出)后TC自动清零并中断;P1.28引脚配置为GPIO输出模式。
为了能在定时器中断服务程序中同时处理数据帧的接收和发送,我们a. 定义了两个全局变量TxEnable和RxEnable用于使能和停止发送、接收功能;b. 定时器溢出常数(写入MR1的值)设置为数据位宽度(波特率的倒数)的一半,即每半个bit周期中断一次;c. 定义两个全局变量,一个发送计数器TxCount和一个接收计数器RxCount。
在定时器中断服务程序中,我们主要处理两种类型的定时器中断:定时器捕获中断和定时器溢出中断。对前者的处理主要是开启数据接收功能,对后者的处理主要是进行数据的发送和接收。定时器中断服务程序的主流程图如图2所示。
每次发送数据时使能发送功能(TxEnable=1)、启动定时器、TxCount赋值0,定时周期到后,从定时器中断服务程序中进入发送流程,判断TxCount的值进行数据的发送,并相应的使发送计数器TxCount加1。发送程序流程图如图3所示。
接收数据由捕获中断触发开始,在定时器捕获中断服务程序中禁止捕获功能、启动定时器、使能接收、RxCount赋值0,定时周期到后,由定时器中断服务程序中进入接收流程,判断RxCount值进行数据的采样接收,并使接收计数器RxCount加1。接收程序流程图如图4所示。
图4 接收程序流程图 图5 发送缓冲区示例图
这里需要指出的是,在将方案用于实际系统时,一般需要将定时器中断设为最高的优先级,以保证发送接收过程不会因为其他中断的长时间拖延而受到影响从而导致数据丢失。
2.3 偶校验的实现
根据实际项目需要,我们在模拟串口中实现了偶校验功能。发送时直接将偶校验输出位填入数据帧中,接收时当接收到第10位时,将其与程序对前8位数据的偶校验输出进行对比,若一致则继续接收下一位,否则丢弃本次数据,重新开始下一帧的接收。偶校验程序如下:
uint32_t EvenParity(uint8_t Byte)
{
uint32_t temp=0;
while(Byte)
{
if(Byte&0x01)
temp += 1;
Byte >>= 1;
}
if(temp&0x01) return 1;
return 0;
}
2.4 发送、接收缓冲区的设计
为了给应用程序提供一个友好方便的接口,我们设计了发送和接收缓冲区,这样软件模拟串口就和硬件UART在使用上很接近了。缓冲区是两个先入先出(FIFO)的循环队列,其本质是两个无符号char型数组。我们为两个缓冲区分别定义了3个全局变量,对发送有:发送位置索引TxRdIndex,用于指示模拟串口下一个要发送的数据的位置;发送缓冲区写索引SWTxIndex,用于指示应用程序下一个要写入发送缓冲区的数据的写入位置;发送缓冲区数据个数TxByteCnt,用于指示发送缓冲区中的待发送的数据个数,示例见图5(方格左下角为数组下标)。对接收缓冲区是类似道理,不再赘述。
发送一个字节时,首先判断发送缓冲区是否满,是则阻塞等待直到有空的位置,否则写入数据。接着判断发送是否使能,否则开启发送功能、启动定时器、TxCount赋值0。然后即可退出发送字节函数,去执行其他程序,由定时器中断服务程序去执行数据的发送。
接收是在定时中断服务程序中完成的,接收完一字节就将它存入接收缓冲区,并置位相应的接收标志位,应用程序通过查询接收标志位和接收字节个数变量来读取接收到的数据。若存储数据时发现缓冲区已满,则放弃该字节的存储并置位接收缓冲区溢出标志位。
需要指出的是,实际测试发现,缓冲区的长度对系统的整体运行效率影响很大[5],所以实际应用时应根据需要定义合适长度的缓冲区。
3. 测试结果
我们编写了以下程序来对软件模拟串口代码进行测试:
int main()
{
uint8_t *str = "Hello!";
uint8_t *str1 = "OverrunError!";
SystemInit(); //系统初始化,主频96MHz
uart0Init(); //硬件UART0初始化,波特率115200
InitSWUart(); //软件UART初始化,波特率9600
SWUartSendStr(str,6);
while(1)
{
while(RxByteCnt) //软件UART接收缓冲区是否有数据?
{
if(LineStatus&OE)//接收缓冲区溢出?
{
uart0SendStr(str1,13); //往上位机发“缓冲区溢出”告警信息
LineStatus &= ~OE;
}
//硬件UART0往上位机发软件UART接收到的数据
uart0SendByte(SWUartGetByte());
}
SWUartSendByte(0x88);//通过软件UART不停往上位机发数据
}
}
测试时,在上位机打开两个串口调试助手,一个通过串口1与UART0连接,另一个通过USB转的串口8与软件UART连接,准备一系列随机的字节数据用于向软件UART发送。下位机电路板运行时,可以通过连串口8的调试助手看到不断收到数据“88”,同时通过发送区将准备的一系列数据发送出去,马上就会看到连串口1的调试助手收到刚发送出去的数据,仔细比对,没有发现错误。然后将发送区的数据连续多次发送出去,对比发送接收的数据个数,发现二者是一致的,再选取发送接收的部分数据进行对比,也并无错误发生。测试结果如图6所示。由此可以看出该软件串口在全双工通信状态下运行是稳定和可靠的。另外,经测试,该软件串口在“背靠背”传输模式(发送、接收引脚短接)下工作也是正常的。表1为在LPC1768几个工作主频下的软件UART正常稳定工作的波特率范围(定时器的PCLK = CCLK)。
表1
注:1200以下的波特率也是正常的,但实际意义不大,故不包含在内。
MCU主频
96MHz
48MHz
24MHz
12MHz
波特率(bit/s)
1200~57600
1200~57600
1200~19200
1200~9600
4. 结语
相较本文,文献[5]也实现了一种全双工UART的软件模拟方案,但该方案实现方式比较复杂、代码量较大、移植也不方便,且不允许“背靠背”模式传输(无法在较严格的全双工模式下运行)。与之相比,本文的实现方案具有较大的突破,具备较强的使用价值。另外值得一提的是,若不要求在全双工方式下通信,可以对方案稍加修改实现,最终使得软件UART达到很高的波特率要求。
参考文献
[ 1 ] 广州致远电子有限公司. IO模拟UART实现[EB/OL]. 2008[2013-1-21]. www.zlgmcu.com.
[ 2 ] 吕刚, 李强. AVR单片机软件模拟UART通信接口[J]. 单片机与嵌入式系统应用, 2003, 2: 73-76.
[ 3 ] 刘亚平, 刑济收, 刘相权. AVR单片机串行口的软件扩展技术[J]. 北京信息科技大学学报(自然科学版), 2010, 25(4): 53-56.
[ 4 ] NXP Semiconductors. LPC17xx User manual[EB/OL]. 2010[2012-12-2]. www.nxp.com.
[ 5 ] NXP Semiconductors. Application Note: Full-duplex software UART for LPC111x and LPC13xx[EB/OL]. 2010[2012-12-2]. www.nxp.com/documents/application_note/AN10955.pdf.