1、6.4 串口通信的收与发 2 6.1.1 什么是串口通信 .2 6.1.2 串口通信的属性 .2 6.1.3 什么是单片机的TTL 电平? 6 6.1.4 关于NPN 和PNP 的三极管基础知识? 8 6.1.5 RS-232 电平与TTL 电平的转换 9 6.1.6 串口波特率的理解 .11 6.1.7 (跳过) STM32 神舟I 号独特的USB 转串口的TTL 电平模块设计 .11 6.1.8 例程 01 最简单串口打印$ 字符 12 6.1.9 例程 02 单串口打印 字符- 初级 .20 6.1.10 例程 03 单串口打印 字符- 中级 .22 6.1.11 例程 04 单串口打印
2、 字符- 高级 .24 6.1.12 例程 05 USART-COM1 串口接收与发送实验- 初级版 25 6.1.13 例程 06 USART-COM1 串口接收与发送实验- 中级版 28 6.1.14 例程 05 USART-COM1 串口接收与发送实验- 高级版 28 6.4 串口通信的收与发 6.1.1 什么是串口通信 串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。 串口是计算机上一种非常通用的设备通信协议。大多数计算机(不包括笔记本电脑)包含两个基于
3、RS-232 的串口。 串口同时也是仪器仪表设备通用的通信协议(串口通信协议也可以用于获取远程采集设备的数据)。 当年 51 单片机内置串口的时候,被认为是微控制器发展史上的重大事件,因为当时的串口是唯一一个微控制器与 PC 交互的接口。MCU 微控制器经过这么多年的发展,串口仍然是其必不可少的接口之一。 6.1.2 串口通信的属性 1. 通信存在的问题 评价一个通信是否优质,主要体现在传输的速度,数据的正确性,功耗是否低,布线成本是否低(例如 1 根线收发都能满足就比 8 根线的并行收发要节约成本);使用是否普及(就好像大家都学英语,世界很大部分的人都可以独立使用英语吗,会英语的人多,就非常
4、普及,可通信面就非常广;如果你学的鸟语,那就只能跟鸟通信,没有人能听懂)。 2. 串口到底有几个标准?(经常听说有 3 线、 5 线串口) 传统的串行接口标准有 22 根线,采用标准 25 芯 D 型插头座(DB25 ),后来使用简化为9 芯 D 型插座(DB9 ),现在应用中 25 芯插头座已很少采用。 像现在所说的几线串口,一般都是指使用了几根线,最初的 RS-232 串口是 25 针的,所有的针脚定义都有用到,后来变成了 9 针的,所谓全功能串口就是所有的针脚定义都使用上了,例如流量控制,握手信号等都有用到,一般来说国外的产品做产品比较规矩,把所有的串口信号都做上去了。但是国内的技术人员
5、发现,其实 RS-232 串口最主要使用的就是 2, 3线,另外的接口如果不使用的话,也不会出现很大的问题,所以,就在 9 针的基础上做精简,所以就有所谓的 2,3 , 4,5 ,6 , 8 线的串口出来了。. 2 线串口只有 RXD,TXD 两根基本的收发信号线; 3 线串口除了 RXD 和 TXD,还有 GND;所谓 49 线只是在 TXD 和 RXD 基础上增加了相应的控制信号线,依据实际需要进行设计。 一般来说,使用 5 线的 232 通信,是加了硬件流控的,即 RTS,CTS 信号,主要是为了保证高速通信时的可靠性,如果你的通信速度不是很高,完全可以不用理会。 3. 串口的速度与距离
6、 RS-232(串口的英文代名词)采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为 2V 至 3V 左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最长为约 15 米,最高速率为 20kb/s。 RS-232 是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为 37k 。所以 RS-232 适合本地设备之间的通信。 4. 从串口通信衍生出 422 与 485 的通信方式 RS-232、RS-422 与 RS-485 都是串行数据接口标准,最初都是由电子工业协会(EIA )制订并发布的,RS-232 在 1962 年发布,命名为 EIA-232-E
7、,作为工业标准,以保证不同厂家产品之间的兼容。 RS-422 由 RS-232 发展而来,它是为弥补 RS-232 之不足而提出的。为改进 RS-232 通信距离短、速率低的缺点, RS-422 定义了一种平衡通信接口,将传输速率提高到 10Mb/s,传输距离延长到 4000 英尺(速率低于 100kb/s 时) ,并允许在一条平衡总线上连接最多 10 个接收器。 RS-422 是一种单机发送、多机接收的单向、平衡传输规范,被命名为 TIA/EIA-422-A标准。 为扩展应用范围, EIA 又于 1983 年在 RS-422 基础上制定了 RS-485 标准,增加了多点、双向通信能力,即允许
8、多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为 TIA/EIA-485-A 标准。 由于 EIA 提出的建议标准都是以“RS ”作为前缀,所以在通讯工业领域,仍然习惯将上述标准以 RS 作前缀称谓。 RS-232、 RS-422 与 RS-485 标准只对接口的电气特性做出规定,而不涉及接插件、电缆或协议,在此基础上用户可以建立自己的高层通信协议。因此在视频界的应用,许多厂家都建立了一套高层通信协议,或公开或厂家独家使用。如录像机厂家中的 Sony 与松下对录像机的 RS-422 控制协议是有差异的,视频服务器上的控制协议则更多了,如 Lo
9、uth、Odetis协议是公开的,而 ProLINK 则是基于 Profile 上的。 5. 串口的通信方式(串口属于串行通信) ( 1)并行通信和串行通信 51 单片机 与外界通信的基本方式 有两种:并行通信和串行通信,并行通信是指利用多条数据传输线将一个数据的各位同时发送或接收。串行通信是指利用一条传输线将数据一位位地顺序发送或接收。 并行通信和串行通信的示意图如下图: 在每一条传输线传输速率相同时,并行通信的传输速度比和串行通信快。然而当传输距离变长时,并行通信的缺点就会凸显,首先是相比于串行通信而言信号易受外部干扰,信号线之间的相互干扰也增加,其次是速率提升之后不能保证每根数据线的数据
10、同时到达接收方而产生接收错误,而且距离越长布线成本越高。 所以并行通信目前主要用在短距离通信,比如处理器与外部的 flash 以及外部 RAM 以及芯片内部各个功能模块之间的通信。串行通信以其通信速率快和成本低等优点成为了远距离通信的首选。RS232C 串口,以及差分串行总线像 RS485 串口、USB 接口、CAN 接口、IEEE-1394 接口、以太网接口、SATA 接口和 PCIE 接口等都属于串行通信的范畴。 下图左侧为每根数据线的数据同时到达接收方,被正确采样的最理想情况;右侧的图为每根数据线的数据不能同时到达接收方而产生接收错误情形。 (2 )异步通信与同步通信 串行通信又分为两种
11、方式:异步通信与同步通信。 A、 异步通信及其协议 异步通信以一个字符为传输单位,通信中两个字符间的时间间隔不固定可以是任意长的,然而在同一个字符中的两个相邻位代码间的时间间隔是固定的,接收时钟和发送时钟只要相近就可以。 通信双方必须使用约定的相同的一些规则(也叫通信协议)。 常见的传送一个字符的信息格式规定有起始位、数据位、奇偶校验位、停止位等,其中各位的意义如下: 或 起始位 先发出一个逻辑”0 ”信号,表示传输字符的开始。 数据位 紧接着起始位之后。数据位的个数可以是 5、6 、7 、8 等,构成一个字符。一般采用 扩展的ASCII码,范围是0255,使用8位表示 。首先传送最低位。 奇
12、偶校验位(不是必须) 奇偶校验是 串口通信中一种简单的检错方式, 当然没有校验位也是可以的。 数据位加上这一位后,使得“1 ”的位数应为偶数( 偶校验) 或奇数(奇校验) ,以此来校验数据传送的正确性。 例如,如果数据是 01100000,那么对于偶校验,校验位为0。 停止位 它是一个字符数据的结束标志。可以是 1 位、 1.5 位、 2 位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越
13、慢。 空闲位 处于逻辑“ 1”状态,表示当前线路上没有数据传送。 B、同步通信是指数据传送是以一个帧(数据块或一组字符)为传输单位,每个帧中包含有多个字符。在通信过程中,字符与字符之间、字符内部的位与位之间都同步,每个字符间的时间间隔是相等的,而且每个字符中各相邻位代码间的时间间隔也是固定的。同步通信的数据格式如图所示 同步通信的特点可以概括为: 以数据块为单位传送信息。 在一个数据块(信息帧)内,字符与字符间无间隔。 接收时钟与发送进钟严格同步 同步串行通信方式中一次连续传输一块数据,开始前使用同步信号作为同步的依据。同步字符的插入可以是单同步字符方式或双同步字符方式,均由同步字符、数据字符
14、和校验字符 CRC 等三部分组成: 同步字符位于帧结构开头,用于确认数据字符的开始。 数据字符在同步字符之后,字符个数不受限制,由所需传输的数据块长度决定; 校验字符有 12 个,位于帧结构末尾,用于接收端对接收到的数据字符的正确性的校验。 由于连续传输一个数据块,故收发双方时钟必须相当一致,否则时钟漂移会造成接收方数据辨认错误。这种方式下往往是发送方在发送数据的同时也通过一根专门的时钟信号线同时发送时钟信息,接收方使用发送方的时钟来接由数据。同步串行通信方式传输效率高,但对硬件要求高,电路结构复杂。 所有的串行接口电路都是以并行数据形式与 CPU 接口、而以串行数据形式与外部逻辑接口。所以串
15、口对外应该是串行发送的,速度慢,但是比并行传输要稳定很多。 6. 串口是如何解决干扰以及校验的问题 什么是数据校验?通俗的说,就是为保证数据的完整性,用一种指定的算法对原始数据计算出的一个校验值。接收方用同样的算法计算一次校验值,如果和随数据提供的校验值一样,就说明数据是完整的。 为了理解数据校验,什么是最简单的校验呢?最简单的校验就是把原始数据和待比较数据直接进行比较,看是否完全一样这种方法是最安全最准确的。同时这样的比对方式也是效率最低的。只适用于简单的数据量极小的通信。 串口通信使用的是奇偶校验方法,具体实现方法是在数据存储和传输中,字节中额外增加一个比特位,用来检验错误。校验位可以通过
16、数据位异或计算出来;也就是说单片机串口通讯有一模式就是一次发送 8 位的数据通讯,增加一位第 9 位用于放校验值。 奇偶校验是一种校验代码传输正确性的方法。根据被传输的一组二进制代码的数位中“1 ”的个数是奇数或偶数来进行校验。采用奇数的称为奇校验,反之,称为偶校验。采用何种校验是事先规定好的。通常专门设置一个奇偶校验位,用它使这组代码中“1 ”的个数为奇数或偶数。若用奇校验,则当接收端收到这组代码时,校验“1 ”的个数是否为奇数,从而确定传输代码的正确性。 奇偶校验能够检测出信息传输过程中的部分误码(1 位误码能检出,2 位及 2 位以上误码不能检出),同时,它不能纠错。在发现错误后,只能要
17、求重发。但由于其实现简单,仍得到了广泛使用。 6.1.3 什么是单片机的TTL电平? 单片机是一种数字集成芯片,数字电路中只有两种电平:高电平和低电平;高电平和低电平是通过单片机的管脚进行输入和输出的,我们只要记住一句话,单片机管脚不是输入就是输出,不是高电平就是低电平。 为了让大家在初学的时候对电平特性有一个清晰的认识, 我们暂且定义单片机输出与输入为 TTL 电平,其中高电平为+5V,低电平为 OV。计算机的串口出来的为 RS-232C电平,其中高电平为-5V -12V,低电平为+5V+12V。这里要强调的是,RS-232C电平为负逻辑电平,所以高电平为负的,低电平为正的,大家千万不要认为
18、上面是我写错了,因此当计算机与单片机之间要通信时,需要加电平转换芯片,我们在神舟 51 单片机实验板上所加的电平转换芯片是 MAX3232(在串口 DB9 座附近)。初学者在学习时先掌握上面这点就够了,若有兴趣请大家再看下面的知识点常用逻辑电平。 知识点:常用逻辑电平 常用的逻辑电平有 TTL、CMOS、LVTTL、ECL、PECL、GTL、RS-232、RS-422、RS-485、LVDS 等。其中 TTL 和CMOS 的逻辑电平按典型电压可分为四类:5V 系列(5V 的TTL 和5V 的CMOS)、3.3V系列,2.5V系列和 1.8V系列。 5V的 TTL和5V的 CMOS 是通用的逻辑
19、电平。3.3V及以下的逻辑电平被称为低电压逻辑电平,常用的为 LVTTL电平。低电压逻辑电平还有 2.5V 和1.8V 两种。 那为什么 TTL 电平信号用的最多呢? 原因 1:这是因为大部分数字电路器件都用这个电平标准。就好像我们学英语,国际通用英语这门语言,那大家都用这个语言进行交流和沟通,所以后来的人都要学习英语才能彼此相互能交流。所以使得越来越多的电路器件使用这个电平标准。TTL 电平数据表示通常采用二进制,+5V 等同于逻辑 1,OV等同于逻辑 O,这被称为 TTL(晶体管一晶体管逻辑电平)信号系统,这是计算机处理器控制的设备内部各部分之问通信的标准技术。TTL 电平信号对于计算机处
20、理器控制的设备内部的数据传输是很理想的,首先计算机处理器控制的设备内部的数据传输对于电源的要求不高,热损耗也较低,另外 TTL 电平信号直接与集成电路连接而不需要价格昂贵的线路驱动器以及接收器电路。 原因 2:TTL 电平的特点适合设备内数据高速的传输。TTL 的通信大多数情况是采用并行数据传输方式,但电平最高为+5V,电压相对比较低,所以传输过程中会有电压损耗和压降,导致 TTL 的传输距离是有限的,一般只适合近距离传输;而且并行数据传输对于超过10 英尺的距离就可能会有同步偏差,传输距离太远,有可能造成数据不同步;所以 TTL 电平符合近距离(在芯片内部或者计算机内部进行高速数据交互)高速
21、的并行传输,在数字电路要求数据处理速度高的时代来说,选择 TTL这个标准是正确的,可靠的。 CMOS 电平最高可达 12V, CMOS 电路输出高电平在 3V12V之间,而输出低电平接近 0 伏。CMOS 电路中不使用的输入端不能悬空,否则会造成逻辑混乱。另外,CMOS 集成电路因为电源电压可以在较大范围内变化,因而对电源的要求不像 TTL 集成电路那样严格。 TTL 电路和 CMOS 电路的逻辑电平关系如下: 1) CMOS 是场效应管构成,TTL 为双极晶体管构成;因为 TTL和 COMS 的高低电平的值不一样,所以互相连接时需 要电平的转换。 2) TTL电路是电流控制器件,而 coms
22、 电路是电压控制器件。 3) TTL电路的速度快,传输延迟时间短(5-10ns),但是功耗大;COMS 电路的速度慢,传输延迟时间长(25-50ns),但功耗低,COMS 电路本身的功耗与输入信号的脉冲频率有关,频率越高,芯片集越热,这是正常现象。 4) CMOS 集成电路电源电压可以在较大范围内变化,因而对电源的要求不像 TTL 集成电路那样严格。所以,用 TTL 电平在条件允许下他们就可以兼容。要注意到他们的驱动能力是不一样的, CMOS 的驱动能力会大一些, 有时候 TTL 的低电平触发不了 CMOS电路, 有时 CMOS的高电平会损坏 TTL 电路,在兼容性上需注意。 5) CMOS
23、的高低电平之间相差比较大、抗干扰性强,TTL则相差小,抗干扰能力差。 6) CMOS 的工作频率较 TTL 略低。 TTL 电平临界值: 1)TTL 输出电压:逻辑电平 1 = 2.4V,逻辑电平0 = 0.4V 2)TTL 输入电压:逻辑电平 1 = 2.0V,逻辑电平0 = 0.8V CMOS 电平临界值(设电源电压为+5V) 1) CMOS 输出电压:逻辑电平 1 = 4.99V,逻辑电平 0 = 0.01V 2) CMOS 输入电压:逻辑电平 1 = 3. 5V,逻辑电平 0 = 1.5V 常用逻辑芯片的特点如下: 74LS 系列: TTL 输入:TTL 输出:TTL 74HC 系列:
24、 CMOS 输 入: CMOS 输出: CMOS 74HCT 系列: CMOS 输 入:TTL 输出: CMOS CD4000 系列: CMOS 输入: CMOS 输出: CMOS 通常情况下,单片机、ARM、DSP、FPGA 等各个器件之间引脚能否直接相连要参考以下方法进行判断:一般来说,同电压的是可以相连的,不过最好还是好好查看芯片技术手册上的 VIL(逻辑电平 0 的输入电压)、VIH(逻辑电平 1 的输入电压)、VOL(逻辑电平 0 的输出电压)、VOH(逻辑电平 1 的输出电压)的值,看是否能够匹配。有些情况在一般应用中没有问题,虽然参数上有点不够匹配,但还是在管脚的最大和最小容忍值
25、范围之内,不过有可能在某些情况下可能就不够稳定,所以我们在设计电路的时候要尽量保持匹配,这样是最佳的设计。 6.1.4 关于NPN 和 PNP的三极管基础知识? 对三极管放大作用的理解,切记一点:能量不会无缘无故的产生,所以,三极管一定不会产生能量,但三极管厉害的地方在于:它可以通过小电流控制大电流。放大的原理就在于:通过小的交流输入,控制大的静态直流。假设三极管是个大坝,这个大坝奇怪的地方是,有两个阀门,一个大阀门,一个小阀门。小阀门可以用人力打开,大阀门很重,人力是打不开的,只能通过小阀门的水力打开。所以,平常的工作流程便是,每当放水的时候,人们就打开小阀门,很小的水流涓涓流出,这涓涓细流
26、冲击大阀门的开关,大阀门随之打开,汹涌的江水滔滔流下。如果不停地改变小阀门开启的大小,那么大阀门也相应地不停改变,假若能严格地按比例改变,那么,完美的控制就完成了。 在这里,基极B-发射极E就是小水流,集电极C-发射极E就是大水流。当然,如果把水流比为电流的话,会更确切,因为三极管毕竟是一个电流控制元件。 如果某一天,天气很旱,江水没有了,也就是大的水流那边是空的。管理员这时候打开了小阀门,尽管小阀门还是一如既往地冲击大阀门,并使之开启,但因为没有水流的存在, 所以,并没有水流出来。这就是三极管中的截止区。 饱和区是一样的,因为此时江水达到了很大很大的程度,管理员开的阀门大小已经没用了。如果不
27、开阀门江水就自己冲开了,这就是二极管的击穿。 在模拟电路中,一般阀门是半开的,通过控制其开启大小来决定输出水流的大小。没有信号的时候,水流也会流,所以,不工作的时候,也会有功耗。 而在数字电路中, 阀门则处于开或是关两个状态。当不工作的时候,阀门是完全关闭的,没有功耗。 那么NPN与PNP的三极管到底有些什么区别呢? NPN和PNP主要就是电流方向和电压正负不同,说得“专业”一点,就是“极性”问题。 NPN 是用 BE 的电流(小水流)控制 CE 的电流(大水流),E极电位最低,且正常放大时通常C极电位最高,即 VCVBVE。 PNP 是用 EB 的电流(小水流)控制 EC 的电流(大水流),
28、E极电位最高,且正常放大时通常C极电位最低,即VCCH340PL2303 ,PL2303 用的最多,因为最便宜,国内很多开发板板子上,包括 USB 转串口线用的都是这种芯片,几元钱一片,电路也简单,做简单的串口应用可以,但是做嵌入式开发如使用超级终端波特率在 115200 时就有可能出现延迟等现象。CH340 是南京沁恒的芯片,做的还不错,对于普通应用完全能够满足。最好的是FT232 稳定、可靠,在很多 USB 转串口的下载线、编程器中使用的都是这一种,神舟开发板上目前使用的是 PL2303HX 芯片。 USB 转串口芯片转出来串口电平就是 TTL 电平,高电平一般是 3.3V,如果转出来的电
29、平再经过 MAX232 或 MAX485 芯片再转一下就会输出 RS232 电平或者 485 电平。其实市面上的 USB 转串口线一般都是这样接的。 6.1.8 例程01 最简单串口打印 $字符 1. 例程简介: STM32 的 GPIOA 端口的 PA9 和 PA10 位,即串口 1;设置 PA9 为 TX 输出模式,复用功能推挽输出模式;设置 PA10 为 RX 输入模式,模拟输入模式;对超级终端打印输出字符”$”符号。 拿串口线将 STM32 神舟 III 号开发板上的串口 1 和电脑的串口连起来,具体硬件电路这里不细说。 2. 调试说明: 1) 打开开始菜单- 程序- 附件- 通讯-
30、超级终端 2) 输入“STM32 神舟系列开发板” 3) 紧接着,将神舟 III 号开发板上的串口 1 和电脑的串口用串口线连起来。(我这里用的是台式机,台式机预留的串口一般为串口 1) 4) 波特率例程代码中设置的是 115200,数据流控制是无,选择完毕,点确定按钮 5) 最后把例程程序下载到开发板里,然后按一下开发板复位或者重新上电,就会打印出“$ ”的字符,一会打印一个,恭喜发财哦! 3. 关键代码: int main(void) /main 是程序入口 RCC_init(); /时钟频率的配置 LED_init(); /LED 初始化配置 uart_init(); /串口接口初始化,
31、这个部分是按 STM32 芯片手册的要求来做的,比较枯燥,细节感兴趣的朋友可以去研究下 while (1) USART1-DR = 0x24; / 打印符号$ , 0x24 是 ASCII 码 LEDON; /点亮 LED 灯 Delay(0xFFFFFF); / 延时 LEDOF; / 熄灭 LED 灯 Delay(0xFFFFFF); / 延时 void uart_init() float USARTDIV; /* 因为 32 位的 USART_BRR 波特率设置寄存器只有低 16 位有效,所以这里我们定义16 位寄存器就足够了 */ u16 USARTDIV_zhengshu; /这里相
32、当于 u16,无符号 16 位 u16 USARTDIV_xiaoshu; /这里相当于 u16,无符号 16 位 RCC-APB2ENR|=1APB2ENR|=1CRH GPIOA-CRH|=0X000008B0; /IO 状态设置 USARTDIV = (float)(72*1000000)/(115200*16); USARTDIV_zhengshu = USARTDIV; USARTDIV_xiaoshu = (USARTDIV - USARTDIV_zhengshu)* 16; USARTDIV_zhengshu APB2RSTR|=1APB2RSTR USART1-CR1|=0X2
33、00C; /1 位停止, 无校验位. 代码详细分析: (1 ) RCC_init();这个函数是负责时钟频率的配置,这里默认配置为 72MHZ,前面章节有详细分析,这里简化,有疑问的可以翻看前面的章节细节。 (2 )LED_init() 这个函数是初始化 LED 的配置,这个是附带的,如果串口无法正常打印,只要程序能运行,那 LED 灯就会进行闪烁。 (3 )uart_init() 这个函数负责串口的初始化,这个部分是按 STM32 芯片手册的要求来做的,比较枯燥,细节感兴趣的朋友可以继续往下看,不感兴趣的可以跳过这一节,这个函数要把波特率初始化为 115200,下面我们仔细分析一下代码: (
34、3-1 )初始化一下波特率的值,一个是波特率的整数部分,一个是波特率的小数部分 u16 USARTDIV_zhengshu; /这里相当于 u16,无符号 16 位,波特率的整数部分 u16 USARTDIV_xiaoshu; /这里相当于 u16,无符号 16 位,波特率的小数部分 (3-2 )初始化串口的时钟,再初始化 PA9 和 PA10 两个管脚的 GPIO 端口 A 的时钟。从原理图可以看到,USART1 的 TX 和 RX 就是 PA9 和 PA10,要使用串口不仅仅要初始化GPIO 端口 A 的时钟,还要初始化串口的时钟,这里是需要注意的,如果点灯程序或者只做为普通的 GPIO
35、管脚使用,就不需要初始化串口的时钟。 串口时钟使能。串口作为 STM32 的一个外设,其时钟由外设始终使能寄存器控制,这里我们使用的串口 1 是在 APB2ENR 寄存器的第 14 位。这里需要注意的一点是,除了串口1 的时钟使能在 APB2ENR 寄存器,其他串口的时钟使能位都在 APB1ENR。 RCC-APB2ENR|=1APB2ENR|=1CRH GPIOA-CRH|=0X000008B0; /IO 状态设置 (3-4 )设置波特率,在 CPU 是 72MHZ 的频率下,设置波特率为 115200; STM32 中波特率是如何计算的,首先看下文档: (x=1、 2)是给外设的时钟(PC
36、LK1 用于串口 2、 3、 4、 5, PCLK2 用于串口1), USARTDIV 是一个无符号的定点数,它的值可以有串口的 USART_BRR 寄存器值得到。而我们更关心的是如何从 USARTDIV 的值得到 USART_BRR 的值,因为一般我们知道的是波特率,和 PCLKx 的时钟,要求的就是 USART_BRR 的值。 可以看到上图波特比率寄存器 USART_BRR 是低 16 位有效,高 16 位是闲置的,最低 4位用来存放整数部分 DIV_Fraction, 15:4这 12 位用来存放小数部分 DIV_Mantissa。高 16位未使用。这里波特率的计算通过如下公式计算: 假
37、设我们的串口 1 要设置为 115200 的波特率,而 PCLK2 的时钟为 72M。这样,我们根据上面的公式有: USARTDIV = / 波特率*16 = (72*1000000)/(115200*16) = 39.0625 我们查看STM32F10XX 参考手册中的第 525 页的一个表: USARTDIV 的值被设置为 39.0625,也就是 USART_BRR 寄存器 那么得到: DIV_ Mantissa = 39 = 0x27; DIV_ Fraction = 16*0.0625 = 1= 0x1; 这样,我们就得到了 USART1-BRR 的值为 0x271。只要设置串口 1
38、的 BRR 寄存器值为 0x271 就可以得到 115200 的波特率。 USARTDIV = (float)(72*1000000)/(115200*16);/算出 USARTDIV 的值 USARTDIV_zhengshu = USARTDIV; /* 因为波特率设置寄存器是 USARTDIV 整数在 03 位,小数在 415 位乘以 16 是因为小数点后面是 4 位,将它右移过来取成整数 */ USARTDIV_xiaoshu = (USARTDIV - USARTDIV_zhengshu)* 16; USARTDIV_zhengshu APB2RSTR|=1APB2RSTR USART
39、1-CR1|=0X200C; /1 位停止, 无校验位. (3-7 )数据发送与接收。STM32 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR) ,一个给接收用(RDR) ,该寄存器兼具读和写的功能,寄存器描述如下: DR8:0为串口数据,可以看出,虽然是一个32位寄存器,但是只用了低9 位( DR8:0),其他都是保留。 代码 USART1-DR = 0x24 是被用来打印符号$ ,0x24 是 ASCII 码请看下图,通过把这个 0x24 输送给 USART_DR 寄存器后,就可以打印出
40、$ 字符。 (4 )进入 while(1)死循环,不停的让 LED 灯亮和灭,然后每次 LED 亮灭一次,就打印一个$ 字符,中间有一些延时,具体代码很简单。 6.1.9 例程02 单串口打印 字符- 初级 1. 例程简介: 例程同上个一样,只是打印出 这么多字符来,增加了一些ASCII码。 2. 调试说明: 其他都与上个例程一样,下载进去后,打印出来的是如下图所示 3. 关键代码: int main(void) /main 是程序入口 RCC_init(); /时钟频率的配置 LED_init(); /LED 初始化配置 uart_init(); /串口接口初始化,这个部分是按 STM32
41、芯片手册的要求来做的,比较枯燥,细节感兴趣的朋友可以去研究下 while (1) /* 通过查 ASCII 码表打印“ ”的 LOGO 每打印一个字符,都需要延时一下 * 如果不加延时程序,就无法正确打印出,因为串口的缓冲数据需要一点时间才送出去,所以需要等待一下 */ USART1-DR = 0x77; /w Delay(0xFFF); / 延时 USART1-DR = 0x77; /w Delay(0xFFF); / 延时 USART1-DR = 0x77; /w Delay(0xFFF); / 延时 USART1-DR = 0x2e; /. Delay(0xFFF); / 延时 USAR
42、T1-DR = 0x61; /a Delay(0xFFF); / 延时 USART1-DR = 0x72; /r Delay(0xFFF); / 延时 USART1-DR = 0x6d; /m Delay(0xFFF); / 延时 USART1-DR = 0x6a; /j Delay(0xFFF); / 延时 USART1-DR = 0x69; /i Delay(0xFFF); / 延时 USART1-DR = 0x73; /s Delay(0xFFF); / 延时 USART1-DR = 0x68; /h Delay(0xFFF); / 延时 USART1-DR = 0x75; /u Del
43、ay(0xFFF); / 延时 USART1-DR = 0x2e; /. Delay(0xFFF); / 延时 USART1-DR = 0x63; /c Delay(0xFFF); / 延时 USART1-DR = 0x6f; /o Delay(0xFFF); / 延时 USART1-DR = 0x6d; / m Delay(0xFFF); / 延时 USART1-DR = 0x20; / 空格 LEDON; /点亮 LED 灯 Delay(0xFFFFFF); / 延时 LEDOF; / 熄灭 LED 灯 Delay(0xFFFFFF); / 延时 代码分析: 可以看到,其实就是逐个打印出
44、的ASCII 码而已。 6.1.10 例程03 单串口打印 字符- 中级 1. 例程简介: 例程同上个一样,只是打印出 这么多字符来,增加了一些ASCII 码,主要体现代码撰写表达有区别。 2. 调试说明: 其他都与上个例程一样,下载进去后,打印出来的是如下图所示 3. 关键代码: int main(void) /main 是程序入口 RCC_init(); /时钟频率的配置 LED_init(); /LED 初始化配置 uart_init(); while (1) USART1_Printf(“ “); LEDON; /点亮 LED 灯 Delay(0xFFFFFF); / 延时 LEDO
45、F; / 熄灭 LED 灯 Delay(0xFFFFFF); / 延时 void USART1_Printf(char *pch) while(*pch != 0) USART_SendData(USART1,pch); pch+; void USART_SendData(USART_TypeDef* USARTx, char *Data) USARTx-DR = *Data; Delay(0xFFF); 代码分析: (1 ) 可以看到,用 USART1_Printf(“ “)这个函数逐个打印出 的字符。 (2 ) USART1_Printf()函数内部具体实现,就是把*pch指向这个 其
46、中一个单个字符,然后调用USART_SendData(USART1,pch) 将这个字符往串口 1 发送输出,再用while(*pch != 0) 判断下一个字符是不是结束符,不是的话继续打印下一个字符。 (3 )最后调用 USART_SendData(USART_TypeDef* USARTx, char *Data)这个函数,将字符数据的 USARTx-DR = *Data;给到 USART1_DR 寄存器,这里的 USART_SendData()函数中的*Data 等同于上个函数的 USART1_Printf ()函数中的*pch ,而 *pch 等同于USART1_Printf()函数
47、中的一个字符。 关于USART1_Printf(“ “) ,这个 在USART1_Printf()函数中创造了一个内存空间,这个内存空间是在 main函数开始调用 USART1_Printf()函数时分配的,而传入到USART1_Printf ()中的pch 是为 所分配的这个内存区域的一个指针的地址,然后继续把这个指针的地址传给 USART_SendData()中的Data 变量;换句话说,Data 等于 pch等于 字符串内存区域的首地址。 6.1.11 例程04 单串口打印 字符- 高级 1. 例程简介: 例程同上个一样,只是打印出 这么多字符来,主要体现代增加了一些串口传送校验。 2
48、. 调试说明: 其他都与上个例程一样,下载进去后,打印出来的是如下图所示 3. 关键代码: void USART1_Printf(char *pch) while(*pch != 0) USART_SendData(USART1,pch); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) = RESET); USART_ClearFlag(USART1, USART_FLAG_TXE); pch+; void USART_SendData(USART_TypeDef* USARTx, char *Data) USARTx-DR = (*Data / 这里的 *Data 就是一个字符 while(USARTx-SR 代码分析: (1 ) USARTx-DR =