STC8A8K64D4共有4个串口,开发板上将串口4用于了RS-485电路,串口1用于了串口通信和程序下载,剩余的两个串口均通过排针引出。
开发板的串口电路如下图所示,为了适应不同的应用需求,设计了两种串口电路:USB转串口电路(UART TTL转USB)和RS232通信电路,读者可以根据需求通过跳线选择使用哪一种。
图1:串口电路
两种串口通信电路中,我们常用的是USB转串口电路,因为电脑大多是没有RS232接口的。开发板上USB转串口的主要作用有以下2个:
在上面的电路图中,USB转串口的接收和发送的管脚上均连接了LED指示灯,收发数据时指示灯会闪烁,这样,更方便我们从硬件的角度观察串口有没有在进行数据收发。
这一设计对于产品的调试和维护很方便,试想一下,当我们的产品通过串口向外发送数据,而对方没收到,这时,我们可以通过观察发送指示灯是否闪烁来快速判断数据有没有发送出去,从而方便我们定位问题。
USB转串口电路采用的USB转串口芯片是CH340,该芯片特点如下:
另外,CH340 芯片内置了 USB 上拉电阻,所以我们直接将UD+和 UD-引脚连接USB 总线上即可,而不用在芯片外部加上拉电阻。同时,CH340 芯片也内置了电源上电复位电路,不需要另外增加外部复位电路。
STC8A8K64D4共有4个UART,他们是相互独立的,可以同时使用。每个UART会有多组引脚与之对应(具体几组还取决于芯片封装引脚数),注意同一个UART只能通过相关寄存器配置其中的一组使用,比如P3.0、P3.1是串口1,而P1.6、P1.7也是串口1,在使用串口1时只能选择其中一组使用,而不能同时将P3.0、P3.1和P1.6、P1.7这2组引脚用于串口1。STC8A8K64D4单片机串口的引脚分配如下表。
表1:STC8A8K64D4单片机4个串口引脚分配
串口 |
信号名称 |
引脚 |
说明 |
UART1 |
TxD |
P3.1 |
串口1第1组引脚的发送引脚 |
RxD |
P3.0 |
串口1第1组引脚的接收引脚 |
|
TxD_2 |
P3.7 |
串口1第2组引脚的发送引脚 |
|
RxD_2 |
P3.6 |
串口1第2组引脚的接收引脚 |
|
TxD_3 |
P1.7 |
串口1第3组引脚的发送引脚 |
|
RxD_3 |
P1.6 |
串口1第3组引脚的接收引脚 |
|
TxD_4 |
P4.4 |
串口1第4组引脚的发送引脚 |
|
RxD_4 |
P4.3 |
串口1第4组引脚的接收引脚 |
|
UART2 |
TxD2 |
P1.1 |
串口2第1组引脚的发送引脚 |
RxD2 |
P1.0 |
串口2第1组引脚的接收引脚 |
|
TxD2_2 |
P4.2 |
串口2第2组引脚的发送引脚 |
|
RxD2_2 |
P4.0 |
串口2第2组引脚的接收引脚 |
|
UART3 |
TxD3 |
P0.1 |
串口3第1组引脚的发送引脚 |
RxD3 |
P0.0 |
串口3第1组引脚的接收引脚 |
|
TxD3_2 |
P5.1 |
串口3第2组引脚的发送引脚 |
|
RxD3_2 |
P5.0 |
串口3第2组引脚的接收引脚 |
|
UART4 |
TxD4 |
P0.3 |
串口4第1组引脚的发送引脚 |
RxD4 |
P0.2 |
串口4第1组引脚的接收引脚 |
|
TXD4_2 |
P5.3 |
串口4第2组引脚的发送引脚 |
|
RxD4_2 |
P5.2 |
串口4第2组引脚的接收引脚 |
STC8A8K64D4的4个UART均有多组引脚与之对应,因此,使用串口时需要配置该串口使用哪一组引脚。
串口1是通过“外设端口切换控制寄存器1(P_SW1)”中的S1_S[1:0]配置的,如下图所示。
外设端口切换控制寄存器1(P_SW1):
P_SW1寄存器中的S1_S[1:0]为串口1功能脚选择位,如下表所示。
表2:串口1功能脚选择位
S1_S[1:0] |
RxD |
TxD |
00 |
P3.0 |
P3.1 |
01 |
P3.6 |
P3.7 |
10 |
P1.6 |
P1.7 |
11 |
P4.3 |
P4.4 |
串口2、3、4是通过“外设端口切换控制寄存器2(P_SW2)”中的S2_S、S3_S和S4_S配置的,如下图所示。
外设端口切换控制寄存器2(P_SW2):
表3:串口2功能脚选择位
S2_S |
RxD |
TxD |
0 |
P1.0 |
P1.1 |
1 |
P4.0 |
P4.2 |
表4:串口3功能脚选择位
S3_S |
RxD |
TxD |
0 |
P0.0 |
P0.1 |
1 |
P5.0 |
P5.1 |
表5:串口4功能脚选择位
S4_S |
RxD |
TxD |
0 |
P0.2 |
P0.3 |
1 |
P5.2 |
P5.3 |
STC8A8K64D4单片机的4个UART均有多种工作模式,其中2种工作模式的波特率是可变的,另2种工作模式的波特率是固定的,以供不同应用场合选用。串口1有4种工作模式,串口2、串口3和串口4均只用2种工作模式,这2种工作模式的波特率都是可变的。下表列出了4个UART的工作模式。
表6:串口工作模式
串口 |
工作模式 |
描述 |
备注 |
UART1 |
模式0 |
同步移位串行方式:移位寄存器 |
不建议学习 |
模式1 |
8位UART,波特率可变 |
推荐学习 |
|
模式2 |
9位UART,波特率固定 |
不建议学习 |
|
模式3 |
9位UART,波特率可变 |
可以学习 |
|
UART2 |
模式0 |
8位UART,波特率可变 |
推荐学习 |
模式1 |
9位UART,波特率可变 |
可以学习 |
|
UART3 |
模式0 |
8位UART,波特率可变 |
推荐学习 |
模式1 |
9位UART,波特率可变 |
可以学习 |
|
UART4 |
模式0 |
8位UART,波特率可变 |
推荐学习 |
模式1 |
9位UART,波特率可变 |
可以学习 |
串口1的工作模式通过“串口1控制寄存器(SCON)”中的SM0位和SM1位配置。当PCON寄存器中的SMOD0位为0时,SM0位和SM1位的组合决定了串口1的通信工作模式,如下表所示。
表7:串口1工作模式配置
SM0 |
SM1 |
工作模式 |
功能描述 |
0 |
0 |
模式0 |
同步移位串行方式:移位寄存器 |
0 |
1 |
模式1 |
8位UART,波特率可变 |
1 |
0 |
模式2 |
9位UART,波特率固定 |
1 |
1 |
模式3 |
9位UART,波特率可变 |
串口2的工作模式通过“串口2控制寄存器(S2CON)”中的S2SM0位配置,如下表所示。
表8:串口2工作模式配置
S2SM0 |
工作模式 |
功能描述 |
0 |
模式0 |
8位UART,波特率可变 |
1 |
模式1 |
9位UART,波特率可变 |
串口3的工作模式通过“串口3控制寄存器(S3CON)”中的S3SM0位配置,如下表所示。
表9:串口3工作模式配置
S3SM0 |
工作模式 |
功能描述 |
0 |
模式0 |
8位UART,波特率可变 |
1 |
模式1 |
9位UART,波特率可变 |
串口4的工作模式通过“串口4控制寄存器(S2CON)”中的S4SM0位配置,如下表所示。
表10:串口4工作模式配置
S4SM0 |
工作模式 |
功能描述 |
0 |
模式0 |
8位UART,波特率可变 |
1 |
模式1 |
9位UART,波特率可变 |
串口的波特率是指每秒传输了多少码元(二进制)的数据,单位是bps,串口常用的波特率有4800bps、9600 bps、19200 bps和115200 bps。串口通信时,发送方和接收方的波特率必须一样,否则是无法正常通信的。
串口的几种模式中,最常用的是“8位UART,波特率可变”的模式,因为这种模式下,定时器的值在硬件上会自动重装,无需在中断里面通过软件赋值,这样就不会因为软件参与而产生误差。
本节我们以串口1的“8位UART,波特率可变”的模式为例来说明波特率的配置。波特率配置主要涉及到波特率加倍配置;选择波特率发生器使用的定时器、设置定时器速度;计算定时器重装值这三个方面。
SMOD是电源管理寄存器(PCON)的第7位,如下图所示,他是串口1波特率控制位。
电源管理寄存器(PCON):
SMOD:串口1波特率控制位
通常,我们会使用“波特率不加倍”,即SMOD设置为0。
串口1的波特率是可变的,其波特率可由定时器1或者定时器2产生,通过“辅助寄存器1(AUXR)”的位S1ST2可设置使用的定时器。
辅助寄存器1(AUXR):
S1ST2:串口1波特率发生器选择位
选择了波特率发生器使用的定时器后,还需设置定时器的模式(1T模式或12T模式),定时器1通过“辅助寄存器1(AUXR)”的位T1x12设置,定时器2通过“辅助寄存器1(AUXR)”的位T2x12设置。当定时器采用1T 模式时(12倍速),相应的波特率的速度也会相应提高12倍。
T1x12:定时器1速度控制位
T2x12:定时器2速度控制位
串口1模式1的波特率计算公式如下表所示,其中SYSclk 为系统工作频率。这里需要注意到,波特率发生器使用定时器1的话,可以选择16位重装或者8位重装,波特率发生器使用定时器2的话,只能用16位重装。
表11:串口1模式1的波特率计算公式(SYSclk 为系统工作频率)
计算过程如下,由上表可以知:
将65248转换为16进制,即0xFEE0。所以对定时器2的高8位寄存器初始装载值为0xFE,低8位寄存器初始装载值为0x E0。
对于波特率的计算,知道原理即可。在学习过程中如果每次改动波特率都需要去计算一次,无疑是很麻烦的,而且,也会降低开发效率。宏晶科技考虑到了这一点,为方便广大开发者,宏晶科技发布了波特率计算的工具,我们只需输入相关参数,即可得到波特率配置的代码以及波特率的误差,使用起来很方便。该工具具体的使用步骤如下。
图2:打开波特率计算工具
图3:计算波特率
串行通信模式发送时,需要先将待发送的数据写入到SBUF寄存器。
SBUF:串口 1 数据接收/发送缓冲区。 SBUF 实际是 2 个缓冲器, 读缓冲器和写缓冲器, 两个操作分别对应两个不同的寄存器,1个是只写寄存器(写缓冲器),1个是只读寄存器(读缓冲器)。对SBUF进行读操作,实际是读取串口接收缓冲区,对 SBUF 进行写操作则是触发串口开始发送数据。
当写 SBUF的指令执行后,待发送数据被写入到SBUF寄存器,并且串行通信的发送序列也被启动,同时,写“SBUF”信号还把“1”装入发送移位寄存器的第9位,如下图所示。
图4:串口1模式1发送数据
发送启动后,移位寄存器将数据不断右移送 TxD 引脚将其传送到物理线路,在数据的左边不断移入“0”作补充。当数据的最高位移到移位寄存器的输出位置,紧跟其后的是第 9 位“1”,在他的左边各位全为“0”,这个状态条件,使 TX 控制单元作最后一次移位输出,然后使允许发送信号“SEND”失效,完成一帧信息的发送,并置位中断请求位 TI,即 TI=1,向主机请求中断处理。
发送中断请求位 TI是“串口1控制寄存器(SCON)”的位1,如下所示。
TI:串口1发送中断请求标志位。在模式0中,当串口发送数据第8 结束时,由硬件自动将 TI 置 1,向主机请求中断,响应中断后TI必须用软件清零。在其他模式中,则在停止位开始发送时由硬件自动将 TI 置1,向CPU发请求中断,响应中断后TI必须用软件清零。
了解了串口发送的过程,接下来,我们再看一下,在处理串口发送时,常用的操作方式。串口发送操作有两种方式:查询方式和中断方式。
实际应用中,我们通常会使用查询方式发送数据,下面的代码是串口1以查询方式发送一字节数据的示例。
代码清单:串口1查询方式发送数据
串口1模式1接收数据接收的时序如下图所示。
图9:串口1模式1接收数据
当软件置位接收允许标志位REN,即REN=1时,接收器便对RxD端口的信号进行检测,当检测到RxD端口发送从“1”→“0”的下降沿跳变时就启动接收器准备接收数据,并立即复位波特率发生器的接收计数器,将1FFH装入移位寄存器。接收的数据从接收移位寄存器的右边移入,已装入的 1FFH 向左边移出,当起始位“0”移到移位寄存器的最左边时,使 RX 控制器作最后一次移位,完成一帧的接收。
若以下两个条件同时满足:
则接收到的数据有效,数据装载入SBUF,停止位进入RB8,RI标志位被置位,并请求中断,若上述两条件不能同时满足,则接收到的数据作废并丢失。无论条件满足与否,接收器重又检测 RxD 端口上的"1"→"0"的跳变,继续下一帧的接收。
接收中断请求标志是“串口1控制寄存器(SCON)”的位0,如下所示。RI标志置位后,硬件不会自动清零,必须由软件清零。
RI:串口1接收中断请求标志位。在模式0中,当串口接收第8位数据结束时,由硬件自动将RI置位,向主机请求中断,响应中断后RI必须用软件清零。在其他模式中,串行接收到停止位的中间时刻由硬件自动将RI置1,向CPU发中断申请,响应中断后RI必须由软件清零。
串口接收数据时,和发送一样,操作方式也有查询方式接收和中断方式接收。
实际应用中,我们通常会使用中断方式接收数据,很少会用查询方式去接收数据。下面的代码是串口1以中断方式接收数据的示例。
代码清单:串口1中断方式接收数据
串口应用的步骤主要包括串口初始化、数据发送和数据接收,这里面串口初始化内容比较固定,容易掌握。至于数据发送,因为STC8A8K64D4没有硬件FIFO,因此大多采样查询的方式发送,也容易理解。相对来说,最难的部分是数据接收,因为数据接收存在随机性,接收方通常不知道对方什么时候发数据过来,也不知道对方一次会发多少数据过来,因此,处理起来相对复杂一些。
串口初始化包含设置串口使用的引脚、串口的工作模式、波特率以及中断的开启(如果仅仅使用串口发送,通常使用查询方式发送,可以不使能中断;如果使用了串口接收功能,通常使用中断接收,需要使能串口中断)。
下面的代码是串口1初始化的示例,代码中对串口1进行了如下配置。
代码清单:串口1初始化
前文中,我们给出了使用查询方式发送一个字节数据的代码示例,当我们有多个数据需要发送的时候,使用循环语句调用该函数发送数据,直到数据全部发送完成即可。
大多数情况下,接收数据都存在不确定性,就比如我们下面要做的串口收发的例子,开发板无法知道串口调试助手什么时候给他发数据,也无法确定一次发多少个字节的数据。在这种情况下,接收方为了保证完整地接收搭配数据,应该怎么做?
通常,我们会做一个软件缓存,在串口中断中接收数据存入到软件缓存,并定义一个变量用于记录串口接收的字节数(接收计数器),应用程序中可以通过查询接收计数器从而判断串口是否接收到数据,如接收到数据,则从缓存中取出数据进行处理。该方式的软件流程如下图所示,具体实现的代码参见下一节中的例子。
图5:串口接收处理流程
因为在“main.c”文件中使用了“uart.c”文件中的函数,所以需要引用下面的头文件“uart.h”。
代码清单:引用头文件
串口1初始化函数代码清单如下,该函数中设置了串口1使用的引脚:RxD为P3.0,TxD为P3.1。串口1配置为8位数据位、波特率9600bps。同时,初始化函数中也初始化了串口接收软件缓存(软件缓存在下文的串口接收部分描述)。
代码清单:串口初始化
串口发送采用查询方式,待发送数据写入SBUF后,一直等待中断请求位TI置位,TI置位后,软件将其清零。串口发送函数代码清单如下。
代码清单:串口发送函数
串口接收相对麻烦一些,这里,我们做了一个软件缓存用于存储接收到的数据。该软件缓存是基于循环队列的原理实现的,具体代码实现方式如下。
定义一个数组用来存放串口接收到数据,定义一个变量用来记录缓存中写的位置,称为“写指针”,定义一个变量用来记录缓存中读的位置,称为“读指针”,同时定义一个变量用来记录数组中数据个数,称为“计数器”,代码清单如下。
代码清单:软件缓存相关变量定义
接收软件缓存在使用前需要初始化,即将用于记录的各个变量清零,初始化函数代码清单如下。
代码清单:软件缓存初始化
串口中断服务函数中,将接收到的串口数据写入缓存,同时计数器加1。这里需要注意,写入数据时需要判断写指针是否到达缓存尾,如果到达缓存尾,则翻转,重新指向缓存头。
代码清单:串口1中断服务函数
数据存入软件缓存后,我们还需提供一个读取函数用于主程序从缓存中读取数据。读取函数的代码清单如下,这里同样需要注意,读数据时需要判断读指针是否到达缓存尾,如果到达缓存尾,则翻转,重新指向缓存头。
代码清单:从软件缓存中读取一个字节数据
在程序开发调试过程中,我们习惯使用printf函数来打印数据或调试信息,但在默认情况下,使用printf函数并不能直接通过串口输出数据,因此,我们需要重定向printf函数。重定向printf函数只需要改写putchar函数即可,本例中,我们将printf函数重定向到串口1,代码清单如下
代码清单:重定向printf函数到串口1
printf函数是C语言标准库函数,定义于头文件
代码清单:引用stdio.h头文件
主函数中,调用串口1初始化“uart1_init()”完成串口1初始化,并且使能了总中断。之后在主循环中查询接收软件缓存中是否有数据,如有数据,读出数据,并调用串口1发送函数“uart1_send_byte()”将读取的数据发送,由此完成串口接收数据的回环。
代码清单:主函数
本实验使用的是USB转串口,按照下图所示短接J5的跳线帽,将串口1的P3.0和P3.1连接到USB转串口电路。
图6:跳线帽短接
图7:串口调试助手收发数据
串口数据收发中我们学习了串口如何收发数据,这一节我们来看一下如何解析简单命令。我们定义的LED指示灯控制的命令格式如下:
开发板接收到一个命令包后,对命令包进行解析,并根据解析结果点亮相应的LED指示灯。
串口解析接收数据的代码清单如下,当串口接收到字符“#”,若此时接收数据长度uart_rx_cnt为0,则认为接收到了命令包的起始字节。此后,当接收字节数达到3的时候,认为一个命令包接收完成。接着,从命令包中解析出要点亮的指示灯的编号,即将命令包中的第3个字节减去48,得到ASCII对应的十进制数值,由此得到指示灯的编号。最后,调用指示灯操作函数点亮对应的指示灯即可。
代码清单:主函数
本实验需要使用USB转串口和4个LED指示灯,串口部分的硬件连接和“实验2-6-1:串口1数据收发实验”一样。
指示灯部分按照下图用跳线帽短接复用引脚的指示灯(D1和D2),而指示灯D3和D4是独立引脚,没有和其他电路复用引脚,是没有短接跳线帽的操作的。
图8:跳线帽短接
我们也编写好了串口2、串口3和串口4的收发例子,这些例子在资料的“…\第3部分:配套例程源码”目录下,他们的实验名称如下,读者在编写的过程中可以参考一下。