串口通信

单片机设计中的串口通信有问题,怎么办

demi的头像

有时单片机 (MCU)设计中,可能会在UART、SPI、I2C、软件UART等中遇到串口通信问题。通常,这些问题大多与软件设计或实现中的意外有关,而不是芯片本身的问题。 按照以下提示操作,可帮助您验证有关问题。

一、DMA功能简介

首先唠叨一下DMA的基本概念,DMA的出现大大减轻了CPU的工作量。在硬件系统中,主要由CPU(内核)、外设、内存(SRAM)、总线等结构组成,数据经常要在内存和外设之间,外设和外设之间转移。例如:CPU需要处理从外设采集回来的数据,CPU需要先将数据从ADC外设的寄存器读取到内存中(变量)去,然后进行运算处理,这是一般的解决方法。CPU的资源是非常宝贵的,我们可以设法把转移的工作交给其他部件来完成,CPU把更多的资源用于数据运算和中断响应上,如此DMA便登场了。DMA正是为CPU分担数据转移工作,因为DMA的存在,CPU才被解放出来,它可以在数据转移的同时进行数据运算,相应中断,大大提高了效率。

二、DMA的主要特性
stm32之串口通信DMA传输完成中断

三、DMA中断特性

stm32之串口通信DMA传输完成中断

四、DMA之串口通信

我们实现一个简单的功能,在DMA中处理串口通信,把数据转移的工作交给DMA,DMA把数据从内存(数组)到外设(串口)的转移,在main函数中不断进行闪灯操作,这样我们可以看到DMA在工作的时候CPU也在工作。非常有必要复习一下DMA的对应关系,我们知道stm32总共有2个DMA控制器(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自一个或多个外设对存储器访问的请求,还有一个仲裁器来协调DMA请求的优先级(优先级分:很高、高、中等、低),这可不是随便对应的。
stm32之串口通信DMA传输完成中断
1、LED初始化程序如下:
stm32之串口通信DMA传输完成中断
这个地方地方没什么要注意的,唯一要注意的就是输入输出模式,我们按需求这样配就好了。

2、串口初始化
stm32之串口通信DMA传输完成中断

3、DMA初始化
stm32之串口通信DMA传输完成中断
在这里我们要注意以下几点:

(1)DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;这里对应USART数据寄存器地址,这个地址我们是这样定义的:#define USART3_DR_Base 0x40004804,这个值是怎么算出来的呢?我们可以查看stm32存储器映射表:
stm32之串口通信DMA传输完成中断
USART3的起始地址是0x40004800,我们查看stm32串口数据寄存器偏移地址为0x04
stm32之串口通信DMA传输完成中断
因此我们可以计算到USART3数据寄存器地址为0x40004804

(2)我们数据传输方向内存(变量)到外设(串口),所以DMA方向为内存到外设

(3)DMA传输模式有两种:DMA_Mode_Normal(普通模式),DMA只传输一次;DMA_Mode_Circular(循环模式),DMA循环传输,比如在AD采集时要配置成循环模式。

4、主函数
stm32之串口通信DMA传输完成中断
这个函数很简单,我们很容易就可以实现,达到效果,这里就不贴图片了。

五、串口通信DMA传输完成中断

我们知道DMA可以在传输过半,传输完成,传输错误时产生中断。我们实现的功能是,DMA工作在普通模式下即只传输一次,LED灯初始化是关闭的,DMA传输完成后产生一个中断,在中断中我们做点灯操作。这个程序调了一天才调了出来,并不是因为它很难,而是有一些要注意的地方没有注意到,从而到时耽误了好长时间才调出来。不过有错误就会有进步嘛。

我先贴出正确的代码,然后在讨论我犯的错误,由于和上一个程序好多都是一样的,这里我们只贴出不同的地方。

(1)DMA初始化程序:
stm32之串口通信DMA传输完成中断
注意我们在这里打开了DMA传输完成中断。

(2)NVIC初始化
stm32之串口通信DMA传输完成中断
(3)中断处理程序

我们在stm32f10x_it.c中编写我们的中断处理程序:
stm32之串口通信DMA传输完成中断
我们也可以这样写中断处理程序:
stm32之串口通信DMA传输完成中断
这两种写法都行,我们在库开发文档可以查看。都代表DMA的通道2传输完成中断。  

(4)主函数
stm32之串口通信DMA传输完成中断
这样我们实验便可以看到,LED灯初始化是关闭的,当串口发送完40000字节的‘A’后,LED等亮。

(5)补充

原意是测试DMA发送完成中断指的是每次指定字节发送完成后便产生一个中断还是最终都传输完成触发一次中断,刚开始中断处理函数写的程序如下:
stm32之串口通信DMA传输完成中断
通过测试,我发现LED灯并没有像试想的那样每次发送完成后便触发一次中断,然后灯会间隔闪烁,而实际是第一次传输完成后灯点亮,之后就一直保持亮的状态。刚开始我还以为DMA只会触发第一次中断,后来仔细分析后才发现了问题。正确的代码应该如下。
stm32之串口通信DMA传输完成中断
在这里n是一个局部变量,如果不定义成静态变量,每次出中断时后n所占的内存(栈)便会释放,这样再次进入后n还是会初始化为0.与我们要达到的效果不符。因此,在这里我们把它指定为静态变量,那么内存就不会释放,它会保持上一次的的值,修改之后达到了效果,每次传输完成3000个字节后灯的状态就会改变一次。  

在这里我们整理一下变量:

全局动态变量:作用范围为整个工程,不释放内存,会保持上一次的值。

全局静态变量:作用范围为当前文件,不释放内存,会保持上一次的值。

局部动态变量:作用范围为当前函数,每次函数执行结束释放内存,不会保持上一次的值。

局部静态变量:作用范围为当前函数,不释放内存,会保持上一次的值。

本文转载自: xtusir

围观 818

串口通信是非常重要的,首先了解下基础

计算机串口通信基础

• 随着多微机系统的广泛应用和计算机网络技术的普及,计算机的通信功能越越显得重要。计算机通信是指计算机与外部设备或计算机与计算机之间的信息交换。

• 通信有并行通信和串行通信两种方式。在多微机系统以及现代化测控系统中信息的交换多采用串行通信方式。

• 计算机通信是将计算机技术和通信技术的相结合,完成计算机与外部设备或计算机与计算机之间的信息交换。可以分为两大类:并行通信与串行通信。

• 并行通信通常是将数据字节的各位用多条数据线同时进行传送。(如下图)

单片机串口通信基础概述

由上图可知一下可以传8位,跟并行的A/D D/A 差不多,询问和应答 是发送 和接受 来询问是否准备好了没有。

• 并行通信控制简单,传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接受存在困难。

• 串行通信是将数据字节分成一位一位的形式在一条传输线上逐个地传送。(如下图)

单片机串口通信基础概述

注意:先发的是低位

• 串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,丹数据的传送控制比并行通信复杂。

• 串行通信的基本概念

一、异步通信与同步通信

1、异步通信

异步通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。为使双方的收发协调,要求发送和接收设备的时钟尽可能一致。

单片机串口通信基础概述

异步通信是以字符(构成的帧)为单位进行传输,字符与字符之间的间隙(时间间隔)是任意的,但每个字符中的各位是以固定的时间传送的,即字符之间不一定有位间隔的整数倍的关系,但同一个字符内的各位之间的距离均为“位间隔”的整数倍。

异步通信的数据格式:

单片机串口通信基础概述

{先发起始位:低电平 表示起始位 再发数据位 LSB 是低端 (LSB: Least Significant Bit)最低有效位,先发低位数据共7位数据 + 1位校验位数据 (判断发送是否正确,如果不要校验位那么8位都是数据位) 最后发一个停止位(高电平结束就是一个位宽的高电平表示停止位) 共10位 一帧 。}

异步通讯的特点:不要求收发双方时钟的严格一致,实现容易,设备开销较小,但每个字符要附加2~3位用于起始位,各帧之间有间隔,因此传输效率不高。

2、同步通信

同步通信时要建立发送方时钟对接受方时钟的直接控制,使双方达到完全同步。此时,传输数据的位之间的距离均为“位间隔”的整数倍,同时传送的字符间不留间隙,即保持位同步关系,也保持字符同步关系。发送方对接受方的同步可以通过两种方法实现。

单片机串口通信基础概述

面向字符的同步格式:
单片机串口通信基础概述

此时,传送的数据和控制信息都必须由规定的字符集(如ASCII码)中的字符所组成。图中帧头为1个或2个同步字符SYN(ASCII码为16H),SOH为序始字符(ASCII码为01H),表示标题的开始,标题中包含源地址,目标地址和路由指示等信息。STX为文始字符(ASCII码为02H),表示传送的数据块开始。数据块是传送正文内容,由多个字符组成。数据块后面是组终字符ETB(ASCII码为17H)或文终字符ETX(ASCII码为03H)。然后是校验码。典型的面向字符的同步规程如IBM的二进制同步规程BSC。

面向位的同步格式:

单片机串口通信基础概述

此时,将数据块看做数据流,并用序列01111110作为开始和结束标志。为了避免在数据流中出现序列01111110时引起的混乱,发送方总是在其发送的数据流中没出现5个连续的1就插入一个附加的0;接收方则每检测到5个连续的1并且其后有一个0时,就删除该0.

典型的面向位的同步协议如ISO的高级数据链路控制规程HDLC和IBM的同步数据链路控制规程SDLC.

同步通信的特点::是以特定的位组合“01111110”作为帧的开始和结束标志,所传输的一帧数据可以是任意位。所以传输效率高,但实现的硬件设备比异步通信发杂。(同步通信可以作为了解我这里只是想表达下我的理解,其实现实中我们用的大多数是异步通信)。

二、串行通信的传输方向

1、单工

单工是指数据传输仅能沿着一个方向,不能实现反向传输。

2、半双工

半双工是指数据传输可以沿两个方向,不能实现反向传输。

3、全双工

全双工是指数据可以同时进行双向传输。

单片机串口通信基础概述

三、信号的调制与解调

利用调制器(MODULATOR)把数字信号转换成模拟信号,然后送到通信线路上去,再由解调器(Demodulator)把从通信线路上收到的模拟信号转换成数字信号。由于通信是双向的,调制器和解调器合并在一个装置中,这就是调制解调器MODEM。

单片机串口通信基础概述

从上图可以看出早期的计算机网络通信就是利用串口RS-232C(是计算机串口电平)经过MODEM 实现双向通信的,当然传输速率相当低。

四、串行通信的错误校验

1、奇偶校验

在发送数据时,数据位尾随的1位为奇偶校验位(1或0)。奇校验时,数据中“1”的个数与校验位“1”的个数之和应为奇数;偶校验时,数据中“1”的个数与校验位“1”的个数之和应为偶数。接受字符时,对“1”的个数进行校验,若发现不一致,则说明传输数据过程中出现了差错。(注意:为了简单等会我写的程序不加校验了)。

2、代码和校验

代码和校验是发送方将所发数据块求和(或各字节异或),产生一个字节的校验字符(校验和)附加到数据块末尾。接受方接受数据同时对数据块(除校验字节外)求和(或个字节异或),将所得结果与发送方的“校验和”进行比较,相符则无差错,否则即认为传送过程中出现了差错。

3、循环冗余校验

这种校验是通过某种数学运算实现有效信息与校验位之间的循环校验,常用于对磁盘信息的传输,存储区的完整性校验等。这种校验方法纠错能力强,广泛应用与同步通信中。(异步很少用,应用于同步通信,作为了解,在读研究生时候学信息论能学到,这里我也就是一知半解,嘿嘿毕竟哥没读过研究生)。

五、传输速率与传输距离

1、传输速率

比特率是每秒钟传输二进制代码的位数,单位是:位/秒(bps).如每秒钟传送240个字符,而每个字符格式包含10位(1起始位,1停止位,8个数据位),这时的比特率为:
10*240个/秒=2400 bps

2、传输距离与传输速率的关系

串行接口或终端直接传送串行信息位流的最大距离与传输速率及传输线的电气特性有关。当传输线使用每0.3m(约1英尺)有50PF电容的非平衡屏蔽双绞线时,传输距离随传输速率的增加而减小,当比特率超过1000BPS,最大传输距离迅速下降,如9600bps时最大距离下降到只有76m(约250英尺)。

串行通信接口标准

一、RS-232C接口

RS232C是EIA(美国电子工业协会)1969年修订RS-232C标准。RS-232C定义了数据终端设备(DTE)与数据通信设备(DCE)之间的物理接口标准。

1、机械特性

RS-232C接口规定使用25针连接器,连接器的尺寸及每个插针的排列位置都有明确的定义。(阳头)

单片机串口通信基础概述

以前是25针的这个可不是电脑的并行口内部是不一样的,因为电脑的并行口里面是有一组数据线,有询问应答等 , 现在都用9针的了(右边)。记住里面有针的是公头,另外一个带孔的是母头。现在外面卖的的串口线内部是已经对调好的了就是发送接收是对调的,这样我们直接使用就可以了。
单片机串口通信基础概述

上图是25针的串口对应现在9针的管脚定义。RTS DSR在连接MODEM 需接上 一般我们只是接2/3/5 脚就行了。

4、过程特性

过程特性规定了信号之间的时序关系,以便正确地接收和发送数据

单片机串口通信基础概述

单片机串口通信基础概述

5、RS-232C电平与TTL电平转换驱动电路

单片机串口通信基础概述

现在我们已经集合了,就用MAX232直接能够实现TTL ->RS232 及 RS-232 ->TTL 都有了。

6、采用RS-232C接口存在的问题

• 传输距离短,传输速率低

RS-232C总线标准受电容允许值的约束,使用时传输距离一般不要超过15米(线路条件好时也不能超过几十米)。最高传送速率为20Kbps.

• 有电平偏移

RS-232C总线标准要求收发双方共地。通信距离较大时,收发双方的地电平差别较大,在信号地上将有较大的地电流并产生压降。

• 抗干扰能力差

RS-232C在电平转换时采用单端输入输出,在传输过程中当干扰和噪声混在正常信号中。为了提高信噪比,RS-232C总线标准不得不采用比较大的电压摆幅。

二、RS-422A接口(为了改进 RS-232C 就是在RS232C 输出后再进行改进)

单片机串口通信基础概述

RS-422A输出驱动器为双端平衡驱动器。如果其中一条线为逻辑“1”状态,另一条线就为逻辑“0”,比采用单端不平衡驱动对电压的放大倍数大一倍。差分电路能从地线干扰中拾取有效信号,差分接收器可以分辨200mV以上电位差。若传输过程中混入了干扰和噪声,由于差分放大器的作用,可使干扰和噪声相互抵消。因此可以避免或大大减弱地线干扰和电磁干扰的影响。RS-422A传输速率(90Kbps)时,传输距离可达1200米。(作为了解下)

三、RS-485接口

单片机串口通信基础概述

RS-485是RS-422A的变型:RS-422A用于全双工,而RS-485则用于半双工。RS-485是一种多发送器标准,在通信线路上最多可以使用32 对差分驱动器/接收器。如果在一个网络中连接的设备超过32个,还可以使用中继器。

RS-485的信号传输采用两线间的电压来表示逻辑1和逻辑0。由于发送方需要两根传输线,接收方也需要两根传输线。传输线采用差动信道,所以它的干扰抑制性极好,又因为它的阻抗低,无接地问题,所以传输距离可达1200米,传输速率可达1Mbps。

RS-485是一点对多点的通信接口,一般采用双绞线的结构。普通的PC机一般不带RS485接口,因此要使用RS-232C/RS-485转换器。对于单片机可以通过芯片MAX485来完成TTL/RS-485的电平转换。在计算机和单片机组成的RS-485通信系统中,下位机由单片机系统组成,上位机为普通的PC机,负责监视下位机的运行状态,并对其状态信息进行集中处理,以图文方式显示下位机的工作状态以及工业现场被控设备的工作状况。系统中各节点(包括上位机)的识别是通过设置不同的站地址来实现的。

以上为串口通讯的基础部分。由于本人学识有限难免有错误之处。本文转自网络 ,侵删。
原文链接:
http://www.cnblogs.com/sumsung007/articles/8191296.html

围观 343

串口通信的基本认识

通信分为并行通信和串行通信,并行通信时的数据各个位同时传送,可以实现字节为单位通信,但通信线多占用资源,成本高。以前用到的的P1=0x55,一次给P1口的8个管脚分别赋值,同时进行信号输出,类似于8个车道可以过去8辆车,这样的形式是并行的,一般称P0,P1,P2,P3为51单片机的4组并行总线。

串行通信,就是一个车道,一个只能通过一辆车,如果一个0x55这样一个字节的数据要传输过去的话,假如低位在前,高位在后的话,那发送方式是:0-1-0-1-0-1-0-1,一位一位的进行传输,要发送8次才能发送完一个字节
STC89C52有两个引脚是专门用来做串口通信的,一个是P3.0(RXD),一个是P3.1(TXD),他们组成的通信接口就是串行接口,简称串口。用于两个单片机进行UART通信。两单片机通信接口连接方式:RXD——TXD,TXD——RXD。

单片机1的TXD发送通道接到单片机2的RXD接收通道,单片机的1的RXD接收通道接到单片机2的TXD发送通道,从而实现相互通信。

当单片机1想给单片机2发送数据,比如发送了0xCE,用二进制表示就是11001110,在串口通信过程中,是低位先发,高位后发的原则,那么就是让TXD首先拉低电平,持续一段时间,发送一位0,然后拉高电平,持续一段时间,发送一位1,继续拉高,在持续一段时间,发送一位1,一直把8位二进制数11001110全部发送完毕,这里涉及到一个问题,就是持续的一个时间段时间“到底是多少”。因而便引入通信中非常重要的一个概念波特率,也叫做比特率。

波特率

波特率就是发送二进制数据位的速率,习惯用baud表示,即我们发送一位二进制数据持续的时间=1/baud。在通信之前,单片机1和单片机2首先都要明确约定好他们之间的通信波特率,必须保持一致,收发双方才能正常通信。
约定好速度之后,我们还要考虑第二个问题,数据什么时候是起始,什么时候是结束?提前和延迟结束都会接收错误。在uart通信的时候,一个字节是8位,规定当没有通信信号发生时,通信线路保持高电平,当数据发送前,先发一位0表示起始位,然后发送8位数据位,数据位是先低再高,数位位发送完后才呢个后再发送一位1表示停止位,这样我们要发送的8位数据,实际上我们发送了10位,多出来两位其中一个是起始位,一个是停止位。而接受方一直保持的高电平,一旦检测到一位低电平,准备开始接受数据,接受8位数据后,然后检测停止位,再准备下一个数据接收。

串口数据发送示意图,实际上是一个时域示意图,就是信号随着时间变化的对应关系。比如在单片机的发送引脚上,左边的是先发生的,右边的是后发生的,数据位的切换时间就是波特率分之一秒,如果能够理解时域的概念,后边很多通信的时序图就很容易理解了。

RS232

在我们电脑上,一般都会有一个9针的串行接口,这个串行接口叫做RS232接口,它和UART通信有关联,但是由于现在笔记本电脑不带9针串口,所以和单片机通信越来越趋于使用USB虚拟串口。

九针串口分工头和母头

公头上5下4,上5从左到右为1.2.3.4.5;下4从左到右为6.7.8.9;
母头上5下4,上5从左到右为5.4.3.2.1;下4从左到右为9.8.7.6;

RS232接口一共有9个引脚,分别定义是:
1、载波检测DCD;
2、接收数据RXD;
3、发送数据TXD;
4、数据终端准备好DTR;
5、信号地线SG;
6、数据准备好DSR;
7、请求发送RTS;
8、清除发送CTS;
9、振铃提示RI。
我们要让这个串口和我们单片机进行通信,我们只需要关心其中的2脚RXD、3脚TXD和5脚GND即可。

虽然这三个引脚的名字和我们单片机上的串口名字一样,但是却不能直接和单片机对连通信,这是为什么呢?随着我们了解的内容越来越多,我们得慢慢知道,不是所有的电路都是5V代表高电平而0V代表低电平的。对于RS232标准来说,它是个反逻辑,也叫做负逻辑。为何叫负逻辑?它的TXD和RXD的电压,-3V~-15V电压代表是1,+3~+15V电压代表是0。低电平代表的是1,而高电平代表的是0,所以称之为负逻辑。因此电脑的9针RS232串口是不能和单片机直接连接的,需要用一个电平转换芯片MAX232来完成。

这个芯片就可以实现把标准RS232串口电平转换成我们单片机能够识别和承受的UART 0V/5V电平。从这里大家似乎慢慢有点明白了,其实RS232串口和UART串口,它们的协议类型是一样的,只是电平标准不同而已,而MAX232这个芯片起到的就是中间人的作用,它把UART电平转换成RS232电平,也把RS232电平转换成UART电平,从而实现标准RS232接口和单片机UART之间的通信连接。

USB转串口通信

随着技术的发展,工业上还有RS232串口通信的大量使用,但是商业技术的应用上,已经慢慢的使用USB转UART技术取代了RS232串口,绝大多数笔记本电脑已经没有串口这个东西了,那我们要实现单片机和电脑之间的通信该怎么办呢?

我们只需要在电路上添加一个USB转串口芯片,就可以成功实现USB通信协议和标准UART串行通信协议的转换,在我们的开发板上,我们使用的是CH340T这个芯片。

我们需要用跳线帽把中间和下边的针短接在一起。右侧的CH340T这个电路很简单,把电源、晶振接好后,6脚和7脚的DP和DM分别接USB口的2个数据引脚上去,3脚和4脚通过跳线接到了我们单片机的TXD和RXD上去。

CH340T的电路里3脚位置加了个4148的二极管,是一个小技巧。因为STC89C52这个单片机下载程序时需要冷启动,就是先点下载后上电,上电瞬间单片机会先检测需要不需要下载程序。虽然单片机的VCC是由开关来控制,但是由于CH340T的3脚是输出引脚,如果没有此二极管,开关后级单片机在断电的情况下,CH340T的3脚和单片机的P3.0(即RXD)引脚连在一起,有电流会通过这个引脚流入后级电路并且给后级的电容充电,造成后级有一定幅度的电压,这个电压值虽然只有两三伏左右,但是可能会影响到正常的冷启动。加了二极管后,一方面不影响通信,另外一个方面还可以消除这种不良影响。这个地方可以暂时作为了解,大家如果自己做这类电路,可以参考一下。

IO口模拟UART串口通信

UART串口波特率,常用的值是300、600、1200、2400、4800、9600、14400、19200、28800、38400、57600、115200等速率。IO口模拟UART串行通信程序是一个简单的演示程序,我们使用串口调试助手下发一个数据,数据加1后,再自动返回。

串口调试助手,这里我们直接使用STC-ISP软件自带的串口调试助手,先把串口调试助手的使用给大家说一下,如图11-6所示。第一步要选择串口助手菜单,第二步选择十六进制显示,第三步选择十六进制发送,第四步选择COM口,这个COM口要和自己电脑设备管理器里的那个COM口一致,波特率按我们程序设定好的选择,我们程序中让一个数据位持续时间是1/9600秒,那这个地方选择波特率就是选9600,校验位选N,数据位8,停止位1。

串口调试助手的实质就是利用电脑上的UART通信接口,发送数据给我们的单片机,也可以把我们的单片机发送的数据接收到这个调试助手界面上。

因为初次接触通信方面的技术,所以我把后面的IO模拟串口通信程序进行一下解释,大家可以边看我的解释边看程序,把底层原理先彻底弄懂。

变量定义部分就不用说了,直接看main主函数。首先是对通信的波特率的设定,在这里我们配置的波特率是9600,那么串口调试助手也得是9600。配置波特率的时候,我们用的是定时器T0的模式2。模式2中,不再是TH0代表高8位,TL0代表低8位了,而只有TL0在进行计数,当TL0溢出后,不仅仅会让TF0变1,而且还会将TH0中的内容重新自动装到TL0中。这样有一个好处,就是我们可以把想要的定时器初值提前存在TH0中,当TL0溢出后,TH0自动把初值就重新送入TL0了,全自动的,不需要程序中再给TL0重新赋值了,配置方式很简单,大家可以自己看下程序并且计算一下初值。

波特率设置好以后,打开中断,然后等待接收串口调试助手下发的数据。接收数据的时候,首先要进行低电平检测while (PIN_RXD),若没有低电平则说明没有数据,一旦检测到低电平,就进入启动接收函数StartRXD()。接收函数最开始启动半个波特率周期,初学可能这里不是很明白。大家回头看一下我们的图11-2里边的串口数据示意图,如果在数据位电平变化的时候去读取,因为时序上的误差以及信号稳定性的问题很容易读错数据,所以我们希望在信号最稳定的时候去读数据。除了信号变化的那个沿的位置外,其它位置都很稳定,那么我们现在就约定在信号中间位置去读取电平状态,这样能够保证我们读的一定是正确的。

一旦读到了起始信号,我们就把当前状态设定成接收状态,并且打开定时器中断,第一次是半个周期进入中断后,对起始位进行二次判断一下,确认一下起始位是低电平,而不是一个干扰信号。以后每经过1/9600秒进入一次中断,并且把这个引脚的状态读到RxdBuf里边。等待接收完毕之后,我们再把这个RxdBuf加1,再通过TXD引脚发送出去,同样需要先发一位起始位,然后发8个数据位,再发结束位,发送完毕后,程序运行到while (PIN_RXD),等待第二轮信号接收的开始。

串口通信基本应用

通信的三种基本类型

常见的通信传输方式可以分为单工通信、半双工通信、全双工通信。

单工通信就是只允许一个方向向另外一个方向传送信息,而另外一方不能回传消息。比如:电视遥控器、收音基等

半双工通信是指数据可以在双方之间相互传播,但是同一时刻只能呢个其中一方发给另一方,比如:对讲机

全双工通信是指发送数据同时也能接收数据,两者同步进行,就如同我们的电话一样,我们说的同时也可以听到对方的声音。

uart模块介绍

IO口模拟串口通信,让大家了解了串口通信的本质,但是我们的单片机程序却需要不停的检测扫描单片机IO口收到的数据,大量占用了单片机的运行时间。这时候就会有聪明人想了,其实我们并不是很关心通信的过程,我们只需要一个通信的结果,最终得到接收到的数据就行了。这样我们可以在单片机内部做一个硬件模块,让它自动接收数据,接收完了,通知我们一下就可以了,我们的51单片机内部就存在这样一个UART模块,要正确使用它,当然还得先把对应的特殊功能寄存器配置好。

51单片机的UART串口的结构由串行口控制寄存器SCON、发送和接收电路三部分构成,先来了解一下串口控制寄存器SCON。

SCON串行控制器的位分配(地址:0x98)

位:符号:复位值: 0:RI:0;1:TI:0;2:RB8:0;3:TB8:0;4:REN:0;5:SM2:0;6:SM1:0;7:SM0:0;
0位RI:接收中断标志位,当接收电路接收到停止位的中间位置时,RI由硬件置1,必须通过软件清零
1位TI:发送中断标志位,当发送电路发送到停止位的中间位置时,TI由硬件置1,必须通过软件清零。
2位RB8:模式2和3中接收到的第9位数据(很少用),模式1用来接收停止位。
3位TB8:模式2和3中要发送的第9位数据(很少用)。
4位REN:使能串行接收。由软件置位使能接收,软件清零则禁止接收。
5位SM2:多机通信控制位(极少用),模式1直接清零。
6位SM1和7位SM0:

这两位共同决定了串口通信的模式0~模式3共4种模式。我们最常用的就是模式1,也就是SM0=0,SM1=1,下边我们重点就讲模式1,其它模式从略。

对于串口的四种模式,模式1是最常用的,就是我们前边提到的1位起始位,8位数据位和1位停止位。下面我们就详细介绍模式1的工作细节和使用方法,至于其它3种模式与此也是大同小异,真正遇到需要使用的时候大家再去查阅相关资料就行了。

在我们使用IO口模拟串口通信的时候,串口的波特率是使用定时器T0的中断体现出来的。在硬件串口模块中,有一个专门的波特率发生器用来控制发送和接收数据的速度。对于STC89C52单片机来讲,这个波特率发生器只能由定时器T1或定时器T2产生,而不能由定时器T0产生,这和我们模拟的通信是完全不同的概念。

如果用定时器2,需要配置额外的寄存器,默认是使用定时器1的,我们本章内容主要就使用定时器T1作为波特率发生器来讲解,方式1下的波特率发生器必须使用定时器T1的模式2,也就是自动重装载模式,定时器的重载值计算公式为:
TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率
和波特率有关的还有一个寄存器,是一个电源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果写PCON |= 0x80以后,计算公式就成了:
TH1 = TL1 = 256 - 晶振值/12 /16 /波特率
公式中数字的含义这里解释一下,256是8位定时器的溢出值,也就是TL1的溢出值,晶振值在我们的开发板上就是11059200,12是说1个机器周期等于12个时钟周期,值得关注的是这个16,我们来重点说明。在IO口模拟串口通信接收数据的时候,采集的是这一位数据的中间位置,而实际上串口模块比我们模拟的要复杂和精确一些。他采取的方式是把一位信号采集16次,其中第7、8、9次取出来,这三次中其中两次如果是高电平,那么就认定这一位数据是1,如果两次是低电平,那么就认定这一位是0,这样一旦受到意外干扰读错一次数据,也依然可以保证最终数据的正确性。

串口通信的发送和接收电路在物理上有2个名字相同的SBUF寄存器,它们的地址也都是0x99,但是一个用来做发送缓冲,一个用来做接收缓冲。意思就是说,有2个房间,两个房间的门牌号是一样的,其中一个只出人不进人,另外一个只进人不出人,这样的话,我们就可以实现UART的全双工通信,相互之间不会产生干扰。但是在逻辑上呢,我们每次只操作SBUF,单片机会自动根据对它执行的是“读”还是“写”操作来选择是接收SBUF还是发送SBUF,后边通过程序,我们就会彻底了解这个问题。

##UART串口程序

一般情况下,我们编写串口通信程序的基本步骤如下所示:
1、配置串口为模式1。
2、配置定时器T1为模式2,即自动重装模式。
3、根据波特率计算TH1和TL1的初值,如果有需要可以使用PCON进行波特率加倍。
4、打开定时器控制寄存器TR1,让定时器跑起来。

这里还要特别注意一下,就是在使用T1做波特率发生器的时候,千万不要再使能T1的中断了。

我们先来看一下由IO口模拟串口通信直接改为使用硬件UART模块时的程序代码,看看程序是不是简单了很多,因为大部分的工作硬件模块都替我们做了。程序功能和IO口模拟的是完全一样的。

通信实例与ASCLL码

先抛开我们使用的汉字不谈,那么我们常用的字符就包含了0~9的数字、A~Z/a~z的字母、还有各种标点符号等。那么在单片机系统里面我们怎么来表示它们呢?ASCII码(American Standard Code for Information Interchange,即美国信息互换标准代码)可以完成这个使命:我们知道,在单片机中一个字节的数据可以有0~255共256个值,我们取其中的0~127共128个值赋予了它另外一层涵义

我们用字符格式发送一个小写的a,返回一个十六进制的0x61,数码管上显示的也是61,ASCII码表里字符a对应十进制是97,等于十六进制的0x61;我们再用字符格式发送一个数字1,返回一个十六进制的0x31,数码管上显示的也是31,ASCII表里字符1对应的十进制是49,等于十六进制的0x31。这下大家就该清楚了:所谓的十六进制发送和十六进制接收,都是按字节数据的真实值进行的;而字符格式发送和字符格式接收,是按ASCII码表中字符形式进行的,但它实际上最终传输的还是一个字节数据。这个表格,当然不需要大家去记住,理解它,用的时候过来查就行了。

转自: 单片机精讲吴鉴鹰

围观 614

在嵌入式系统中,异步串口(UART)使用非常频繁,可以用于与各种外部系统(帧括PC)之间的通信。在硬件上UART通过在每个字节的传输中插入开始位和停止位,保证接收端可以正确地找到字节的开始和结束,同时也可以通过插入奇偶校验位,让接收端检验收到的字节是否正确。而且,由于有开始位和停止位的存在,使得字节之间可以插入任意的空闲位(与停止位同为高电平),而不影响下一个字节的正常传输。因此,UART硬件保证了每个字节的正确传输,并可以有效检出字节传输的错误。但并不保证一串字节的正确传输,这需要软件来完成。

从软件的角度来看,所有的通信都是一串字节(叫做数据帧)的连续传输。软件需要采用适当的机制来保证接收端能够正确识别出一个完整的数据帧、能够检查接收到的数据帧是正确的、在传输发生错误时有合适的恢复机制。为此就需要定义一个合适的数据帧格式。
  
数据帧的提取
  
为了识别出一个完整的数据帧,基本上有两个机制:一是在软件上规定字节之间的间隔最大值,一旦两个字节间的间隔超过某个阈值,就认为一个数据帧结束;另一种机制不对字节间的间隔作规定,而是用特殊的字节来定义数据报的开始和结束,当收到该特殊字节时,就认为一个数据帧的传输已完成。
  
采用第一种机制的,比如Modbus-RTU。就是规定了同一个数据帧的字节间隔不能大于1.5个字节的传输时间,一旦大于该间隔,则认为前一个帧的传输已经结束,或者出错。同时为了保证不同数据帧之间有足够的间隔,还规定了两个数据帧之间最少插入3.5个字节的空闲位。下图摘自Modbus协议,表示了一个正确的数据帧、错误的数据帧以及数据帧之间的间隔。同时在数据报中引入了CRC,用于检出数据帧层面的传输错误。
  

嵌入式系统上的异步串口通信的实现嵌入式系统上的异步串口通信的实现

 
第二种机制需要选用一个特殊字节作为帧头帧尾(也可以给帧头帧尾选用不同的字节),比如说,选用0xFF作为帧头帧尾字节,也即发送端在每个数据帧的头尾都插入至少一个0xFF,接收端收到该字节就结束上一个帧的接收,同时从下一个非0xFF字节开始一个新的数据帧的接收。但该机制有一个问题,那就是所选的特殊字节(此处是0xFF)也可能是数据帧中的一个有效字节,如果不作特殊处理,接收端就可能把这个数据帧中的字节误认为是数据帧的结束标识,从而导致接收错误。因此,此处采取字节替换的特殊处理,以消除在数据帧的中间出现的特殊字节,以下是一个例子。
  
嵌入式系统上的异步串口通信的实现

  
在此处,选0xFE作为字节替换的标识字节,将0xFF替换成0xFE 0x0F。由于0xFE是字节替换的标志,也成了一个特殊字符,同样需要进行替换。在上图中,将0xFE替换成0xFE 0x0E。
  
采用该机制时,发送端对数据帧中的所有0xFF,0xFE进行替换。在接收端则进行反向替换。如果在接收端的0xFE后面的字节收到非0x0F,0x0E的字符,说明发生了传输错误,丢弃已经接收的内容,重新搜索帧头字符开始一个新数据帧的接收。
  
数据帧的校验
  
考虑到串口通信有一定的误码率,无论是采用那种传输方式,必须有错误帧检出机制。通常选用的是CRC。一般用2个字节或4个字节。
  
串口驱动的实现
  
在嵌入式系统中,串口驱动的实现一般有两个途径:一个是通过串口的收发中断与串口控制器的Buffer直接交换数据;另一种是通过DMA,利用DMA与串口Buffer交互自动收发数据。
  
与串口控制器直接交互的方式,实现简单,既可以实时进行字节替换以节省内存,也可以方便设置字节间的定时器。但频繁打断CPU,对有一定BUFFER大小的串口,还可以利用BUFFER进行性能优化,而对一些只有一个字节或两个字节BUFFER的串口,就需要一个字节起一次中断,如果CPU负荷较重,很容易由于中断处理不及时而导致字节丢失,特别是在高波特率的情况。
  
利用DMA与串口控制器交互的方式,由于不能实时进行字节替换,需要在放入DMA之前完成字节替换,导致占用较大内存空间。另外对于字节间间隔敏感的传输策略,需要用另外的机制,比如PRS,去操作定时器。用DMA的另一个问题是在接收时无法实时确定帧尾(采用帧头帧尾模式时),一般只能采用定时器与DMA配合,用字节流的方式处理接收。

转自: 电子产品世界

围观 490

RS485通信想必大家都知道,在学习RS232时,都会拿485(RS485下文就用485代替)和其作对比。485优缺点不说,网上有。

我用的是STM32库函数学的485通信,所以接下来就讲讲STM32串口实现485双机通信的原理:

485和232都是基于串口的通讯接口,在数据的收发操作上都是一致的。但是他两的通讯模式却大不相同~!232是全双工(例:A->B的同时B->A,瞬时同步)工作模式,而485是半双工(发时不能收,收时不能发)工作模式。在232通信中,主机在发送数据的同时可以收到从机发过来的数据;但在485通信中,收发要经过模式位的切换来进行,譬如,发送数据时,会把模式为置‘1’,表示为发送模式,此时不能接收;当接收数据时,会把模式位置‘0’,表示为接收模式,此时不能发送。

在讲STM32串口实现485双机通信的原理之前,先来复习一下串口中的中断知识点:

串口的中断类型有很多种。这里主要讲两种:接收到数据中断和发送数据完成中断。这两个中断跟两个标志位有密切关系:RXNE(读数据寄存器非空)和TC(发送完成)。譬如在接收到数据的时候(RXNE,读数据寄存器非空),我们要产生中断。在发送数据结束的时候(TC,发送完成)要产生中断。这两种中断的产生方法都是在开启串口中断函数(USART_ITConfig();)中配置。并在获取中断状态函数(USART_ITStatus();)中判断是发送中断还是接受中断。

注意:1、有人问当产生接收中断时,没看到程序中清除中断标志啊,那不就一直中断下去了吗?这里官方规定有两种方法清除中断标志(书里面也有):
(1)尽快读取USART_DR(数据寄存器),通过读USART_DR可以将该位清零;
(2)可以直接向该位写‘0’,直接清零。

2、两块板子的串口波特率一定要相同,否则将得不到想要的数据。

STM32串口实现485双机通信的原理(库函数):

1、在主函数中,扫描按键。一旦key0按下,首先将5个字节的数据存入rs485buf[]数组中,然后调用RS485_Send_Data(rs485buf,5);函数将5个字节的数据发送到串口。

当STM32遇到串口RS485双机通信,这样处理最便捷

2、进入RS485_Send_Data();函数中,先通过标志位将485设置为发送模式(RS485_TX_EN=1),然后循环5次,将这5个字节数据,通过库函数:USART_Send_Data();发送到USART_DR寄存器(自动发送),最后通过标志位将485设置为接收模式(RS485_TX_EN=0)。

当STM32遇到串口RS485双机通信,这样处理最便捷

 
3、前两个是发送。现在是接收了。主函数中通过RS485_Receive_Data();不停的接收。

4、进入RS485_Receive_Data();函数中,

如果一直没有按按键,则不会发送数据,也就不会产生接收中断,也就不会往我们定义的接收缓冲区(RS485_RX_BUF[64])里写数据。RS485_RX_CNT计数器的值也就是‘0’,对应在RS485_Receive_Data();函数中就不会进入里面的if语句(下图中红框)

当STM32遇到串口RS485双机通信,这样处理最便捷

如果按键按下了,就会发送数据,就会产生接收中断,就会往我们定义的接收缓冲区(RS485_RX_BUF[64])里写数据,RS485_RX_CNT计数器的值也就开始自增,对应在RS485_Receive_Data();函数中就会进入里面的if语句,从之前定义的接收缓冲区(RS485_RX_BUF[64])里面取数据。

5、最后就将取出来的数据(接收到的数据)显示出来。

转自: 与非网

围观 457

名称:IIC协议 EEPROM24c02 通过串口通信存数读取数据

内容:此程序用于检测EEPROM性能,测试方法如下:写入24c02一个数据,然后在内存中改变这些数据, 掉电后主内存将失去这些信息,然后从24c02中调入这些数据。看是否与写入的相同。

电脑通过串口发送一个十六进制的数据到单片机,存储进24c02,要求断电重启后在数码管上显示上一次发送的数据。

(本例是1us机器周期,即晶振频率要小于12MHZ)

[cpp] view plain copy
#include //头文件的包含
#include

#define _Nop() _nop_() //定义空指令
#define DataPort P0
sbit WEI=P2^7;
sbit DUAN=P2^6;
// 常,变量定义区
unsigned char code dofly_DuanMa[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,
0x77,0x7c,0x39,0x5e,0x79,0x71,0x40,0x00};// 显示段码值0~F,-,全空
unsigned char code dofly_WeiMa[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//分别对应相应的数码管点亮,即位码

unsigned char TempData[8];

sbit SDA=P2^1; //模拟I2C数据传送位
sbit SCL=P2^0; //模拟I2C时钟控制位

bit ack; //应答标志位

unsigned char res;
void DelayUs2x(unsigned char t);//函数声明
void DelayMs(unsigned char t);

void Delay(unsigned int t)
{
while(t--);
}

void InitUART(void)
{
SCON=0x50;
TMOD|=0x20;
TH1=0xFD;
TR1=1;
EA=1;
}

void DelayUs2x(unsigned char t)
{
while(--t);
}

void DelayMs(unsigned char t)
{

while(t--)
{
//大致延时1mS
DelayUs2x(245);
DelayUs2x(245);
}
}

void Display(unsigned char FirstBit,unsigned char Num)
{
unsigned char i;
for(i=0;i {
DataPort=0; //清空数据,防止有交替重影
DUAN=1; //段锁存
DUAN=0;

DataPort=dofly_WeiMa[i+FirstBit]; //取位码
WEI=1; //位锁存
WEI=0;

DataPort=TempData[i]; //取显示数据,段码
DUAN=1; //段锁存
DUAN=0;

Delay(200); // 扫描间隙延时,时间太长会闪烁,
//太短会造成重影

}
}

/*------------------------------------------------
启动总线
------------------------------------------------*/
void Start_I2c()
{
SDA=1; //发送起始条件的数据信号
_Nop();
SCL=1;
_Nop(); //起始条件建立时间大于4.7us,延时
_Nop();
_Nop();
_Nop();
_Nop();
SDA=0; //发送起始信号
_Nop(); //起始条件锁定时间大于4μ
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; //钳住I2C总线,准备发送或接收数据
_Nop();
_Nop();
}
/*------------------------------------------------
结束总线
------------------------------------------------*/
void Stop_I2c()
{
SDA=0; //发送结束条件的数据信号
_Nop(); //发送结束条件的时钟信号
SCL=1; //结束条件建立时间大于4μ
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; //发送I2C总线结束信号
_Nop();
_Nop();
_Nop();
_Nop();
}

void SendByte(unsigned char c)
{
unsigned char BitCnt;

for(BitCnt=0;BitCnt<8;BitCnt++) //要传送的数据长度为8位
{
if((c< else SDA=0;
_Nop();
SCL=1; //置时钟线为高,通知被控器开始接收数据位
_Nop();
_Nop(); //保证时钟高电平周期大于4μ
_Nop();
_Nop();
_Nop();
SCL=0;
}

_Nop();
_Nop();
SDA=1; //8位发送完后释放数据线,准备接收应答位
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop();
_Nop();
if(SDA==1)ack=0;
else ack=1; //判断是否接收到应答信号
SCL=0;
_Nop();
_Nop();
}

unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt;

retc=0;
SDA=1; //置数据线为输入方式
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCL=0; //置时钟线为低,准备接收数据位
_Nop();
_Nop(); //时钟低电平周期大于4.7us
_Nop();
_Nop();
_Nop();
SCL=1; //置时钟线为高使数据线上数据有效
_Nop();
_Nop();
retc=retc<<1;
if(SDA==1)retc=retc+1; //读数据位,接收的数据位放入retc中
_Nop();
_Nop();
}
SCL=0;
_Nop();
_Nop();
return(retc);
}

/*----------------------------------------------------------------
应答子函数
原型: void Ack_I2c(void);

----------------------------------------------------------------*/
void Ack_I2c(void)
{

SDA=0;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); //时钟低电平周期大于4μ
_Nop();
_Nop();
_Nop();
SCL=0; //清时钟线,钳住I2C总线以便继续接收
_Nop();
_Nop();
}
/*----------------------------------------------------------------
非应答子函数
原型: void NoAck_I2c(void);

----------------------------------------------------------------*/
void NoAck_I2c(void)
{

SDA=1;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); //时钟低电平周期大于4μ
_Nop();
_Nop();
_Nop();
SCL=0; //清时钟线,钳住I2C总线以便继续接收
_Nop();
_Nop();
}

/*----------------------------------------------------------------
向无子地址器件发送字节数据函数
函数原型: bit ISendByte(unsigned char sla,ucahr c);
功能: 从启动总线到发送地址,数据,结束总线的全过程,从器件地址sla.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
/*bit ISendByte(unsigned char sla,unsigned char c)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
*/

/*----------------------------------------------------------------
向有子地址器件发送多字节数据函数
函数原型: bit ISendStr(unsigned char sla,unsigned char suba,ucahr *s,unsigned char no);
功能: 从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件
地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
bit ISendStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i;

Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(suba); //发送器件子地址
if(ack==0)return(0);

for(i=0;i {
SendByte(*s); //发送数据
if(ack==0)return(0);
s++;
}
Stop_I2c(); //结束总线
return(1);
}

/*----------------------------------------------------------------
向无子地址器件读字节数据函数
函数原型: bit IRcvByte(unsigned char sla,ucahr *c);
功能: 从启动总线到发送地址,读数据,结束总线的全过程,从器件地
址sla,返回值在c.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
/*bit IRcvByte(unsigned char sla,unsigned char *c)
{
Start_I2c(); //启动总线
SendByte(sla+1); //发送器件地址
if(ack==0)return(0);
*c=RcvByte(); //读取数据
NoAck_I2c(); //发送非就答位
Stop_I2c(); //结束总线
return(1);
}

*/
/*----------------------------------------------------------------
向有子地址器件读取多字节数据函数
函数原型: bit ISendStr(unsigned char sla,unsigned char suba,ucahr *s,unsigned char no);
功能: 从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件
地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
----------------------------------------------------------------*/
bit IRcvStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i;

Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(suba); //发送器件子地址
if(ack==0)return(0);

Start_I2c();
SendByte(sla+1);
if(ack==0)return(0);

for(i=0;i {
*s=RcvByte(); //发送数据
Ack_I2c(); //发送就答位
s++;
}
*s=RcvByte();
NoAck_I2c(); //发送非应位
Stop_I2c(); //结束总线
return(1);
}
/*------------------------------------------------
主函数
------------------------------------------------*/
void main()
{
unsigned char doflye; // 定义临时变量
unsigned char i;

IRcvStr(0xae,4,&doflye,1); //调用存储数据
TempData[0]=dofly_DuanMa[doflye/16];
TempData[1]=dofly_DuanMa[doflye%16];

InitUART();
ES=1;

while(1)
{
Display(0,2);
doflye=res;
ISendStr(0xae,4,&doflye,1); //写入24c02

}
}

void UART_SER(void) interrupt 4
{
unsigned char Temp;
// unsigned char i;
if(RI)
{
RI=0;
Temp=SBUF;
res=Temp;
TempData[0]=dofly_DuanMa[Temp/16];
TempData[1]=dofly_DuanMa[Temp%16];
}
if(TI)
TI=0;
}

文章来源:NK_test的博客 

围观 334

页面

订阅 RSS - 串口通信