「STC8A8K64D4开发板」第2-6讲:串口通信

news2024/11/17 17:55:26

第2-6讲:串口通信

    1. 学习目的
  1. 掌握USB转串口电路的原理和设计。
  2. 学习STC8A8K64D4的串口通信,包括串口初始化、波特率计算、串口发送和接收。
  3. 编写串口收发程序,尤其是串口接收的软件缓存处理。
  4. 编写串口发送命令控制LED指示灯亮灭的程序。
    1. 硬件电路设计

STC8A8K64D4共有4个串口,开发板上将串口4用于了RS-485电路,串口1用于了串口通信和程序下载,剩余的两个串口均通过排针引出。

开发板的串口电路如下图所示,为了适应不同的应用需求,设计了两种串口电路:USB转串口电路(UART TTL转USB)和RS232通信电路,读者可以根据需求通过跳线选择使用哪一种。

 

图1:串口电路

两种串口通信电路中,我们常用的是USB转串口电路,因为电脑大多是没有RS232接口的。开发板上USB转串口的主要作用有以下2个:

  1. USB转串口(TTL电平)通信:通过USB数据线连接到计算机的USB口即可使用串口通信功能。
  2. 程序下载和仿真。这里要注意的是下载和仿真必须使用4个串口中的串口1,并且只能使用串口1的P3.0和P3.1这一组引脚(串口1有4组引脚,当然,同时只能配置其中的一组作为串口1的引脚)。
  3. 开发板供电:通过USB可以为开发板供电(计算机USB可以提供约500mA的电流)。

在上面的电路图中,USB转串口的接收和发送的管脚上均连接了LED指示灯,收发数据时指示灯会闪烁,这样,更方便我们从硬件的角度观察串口有没有在进行数据收发。

这一设计对于产品的调试和维护很方便,试想一下,当我们的产品通过串口向外发送数据,而对方没收到,这时,我们可以通过观察发送指示灯是否闪烁来快速判断数据有没有发送出去,从而方便我们定位问题。

USB转串口电路采用的USB转串口芯片是CH340,该芯片特点如下:

  1. 全速 USB 设备接口,兼容 USB V2.0。
  2. 标准 USB 打印口,用于升级原并口打印机,兼容相关的 USB 规范。
  3. 支持 IEEE-1284 规范的双向通信,支持单向和双向传输打印机。
  4. 由于是通过 USB 转换的打印口,所以只能做到应用层兼容,而无法绝对相同。
  5. 软件兼容 CH341,可以直接使用 CH341 的驱动程序。
  6. 支持 5V 电源电压和 3.3V 电源电压甚至 3V 电源电压。
  7. 采用无铅封装,兼容 RoHS,引脚兼容 CH341。

另外,CH340 芯片内置了 USB 上拉电阻,所以我们直接将UD+和 UD-引脚连接USB 总线上即可,而不用在芯片外部加上拉电阻。同时,CH340 芯片也内置了电源上电复位电路,不需要另外增加外部复位电路。

    1. STC8A8K64D4串口

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组引脚的接收引脚

      1. 串口引脚配置

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):

 

  1. S2_S:串口2功能脚选择位,如下表所示。

表3:串口2功能脚选择位

S2_S

RxD

TxD

0

P1.0

P1.1

1

P4.0

P4.2

  1. S3_S:串口3功能脚选择位,如下表所示。

表4:串口3功能脚选择位

S3_S

RxD

TxD

0

P0.0

P0.1

1

P5.0

P5.1

  1. S4_S:串口4功能脚选择位,如下表所示。

表5:串口4功能脚选择位

S4_S

RxD

TxD

0

P0.2

P0.3

1

P5.2

P5.3

      1. 串口工作模式

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工作模式

串口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,波特率可变

  1. 串口2工作模式

串口2的工作模式通过“串口2控制寄存器(S2CON)”中的S2SM0位配置,如下表所示。

表8:串口2工作模式配置

S2SM0

工作模式

功能描述

0

模式0

8位UART,波特率可变

1

模式1

9位UART,波特率可变

  1. 串口3工作模式

串口3的工作模式通过“串口3控制寄存器(S3CON)”中的S3SM0位配置,如下表所示。

表9:串口3工作模式配置

S3SM0

工作模式

功能描述

0

模式0

8位UART,波特率可变

1

模式1

9位UART,波特率可变

  1. 串口4工作模式

串口4的工作模式通过“串口4控制寄存器(S2CON)”中的S4SM0位配置,如下表所示。

表10:串口4工作模式配置

S4SM0

工作模式

功能描述

0

模式0

8位UART,波特率可变

1

模式1

9位UART,波特率可变

      1. 波特率的计算和配置

串口的波特率是指每秒传输了多少码元(二进制)的数据,单位是bps,串口常用的波特率有4800bps、9600 bps、19200 bps和115200 bps。串口通信时,发送方和接收方的波特率必须一样,否则是无法正常通信的。

        1. 计算方法

串口的几种模式中,最常用的是“8位UART,波特率可变”的模式,因为这种模式下,定时器的值在硬件上会自动重装,无需在中断里面通过软件赋值,这样就不会因为软件参与而产生误差。

本节我们以串口1的“8位UART,波特率可变”的模式为例来说明波特率的配置。波特率配置主要涉及到波特率加倍配置;选择波特率发生器使用的定时器、设置定时器速度;计算定时器重装值这三个方面。

  1. 波特率加倍配置

SMOD是电源管理寄存器(PCON)的第7位,如下图所示,他是串口1波特率控制位。

电源管理寄存器(PCON):

 

SMOD:串口1波特率控制位

  1. 0:串口1的各个模式的波特率都不加倍。
  2. 1:串口1模式1(使用模式2的定时器1作为波特率发生器时有效)、模式2、模式3(使用模式 2的定时器1作为波特率发生器时有效)的波特率加倍。

通常,我们会使用“波特率不加倍”,即SMOD设置为0。

  1. 选择波特率发生器使用的定时器、设置定时器速度

串口1的波特率是可变的,其波特率可由定时器1或者定时器2产生,通过“辅助寄存器1(AUXR)”的位S1ST2可设置使用的定时器。

辅助寄存器1(AUXR):

 

S1ST2:串口1波特率发生器选择位

  1. 0:选择定时器 1 作为波特率发生器。
  2. 1:选择定时器 2 作为波特率发生器。

选择了波特率发生器使用的定时器后,还需设置定时器的模式(1T模式或12T模式),定时器1通过“辅助寄存器1(AUXR)”的位T1x12设置,定时器2通过“辅助寄存器1(AUXR)”的位T2x12设置。当定时器采用1T 模式时(12倍速),相应的波特率的速度也会相应提高12倍。

T1x12:定时器1速度控制位

  1. 0:12T 模式,即CPU时钟12分频(FOSC/12)。
  2. 1:1T 模式,即CPU 时钟不分频(FOSC/1)。

T2x12:定时器2速度控制位

  1. 0:12T 模式,即CPU时钟12分频(FOSC/12)。
  2. 1:1T 模式,即CPU 时钟不分频(FOSC/1)。

  1. 计算定时器重装值

串口1模式1的波特率计算公式如下表所示,其中SYSclk 为系统工作频率。这里需要注意到,波特率发生器使用定时器1的话,可以选择16位重装或者8位重装,波特率发生器使用定时器2的话,只能用16位重装。

表11:串口1模式1的波特率计算公式(SYSclk 为系统工作频率)

 

  1. 计算举例:系统时钟频率为11.0592MHz,波特率发生器使用定时器2(1T),波特率不加倍,计算串口1使用模式1,波特率为9600bps时的定时器重载值。

计算过程如下,由上表可以知:

 

 

将65248转换为16进制,即0xFEE0。所以对定时器2的高8位寄存器初始装载值为0xFE,低8位寄存器初始装载值为0x E0。

        1. 使用工具软件计算

对于波特率的计算,知道原理即可。在学习过程中如果每次改动波特率都需要去计算一次,无疑是很麻烦的,而且,也会降低开发效率。宏晶科技考虑到了这一点,为方便广大开发者,宏晶科技发布了波特率计算的工具,我们只需输入相关参数,即可得到波特率配置的代码以及波特率的误差,使用起来很方便。该工具具体的使用步骤如下。

  1. 打开STC-ISP软件后,依次点击“工具→独立使用波特率计算工具(B)”,打开波特率计算工具。

 

 

图2:打开波特率计算工具

  1. 选择相关参数后,点击“生成C代码”即可获取串口初始化代码,里面包含了波特率配置,同时也可以看到该配置下波特率的误差。

 

图3:计算波特率

      1. 串口发送

串行通信模式发送时,需要先将待发送的数据写入到SBUF寄存器。

  1. 串口 1 数据寄存器( 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,如下所示。

  1. 串口1控制寄存器(SCON):

 

TI:串口1发送中断请求标志位。在模式0中,当串口发送数据第8 结束时,由硬件自动将 TI 置 1,向主机请求中断,响应中断后TI必须用软件清零。在其他模式中,则在停止位开始发送时由硬件自动将 TI 置1,向CPU发请求中断,响应中断后TI必须用软件清零。

了解了串口发送的过程,接下来,我们再看一下,在处理串口发送时,常用的操作方式。串口发送操作有两种方式:查询方式和中断方式。

  1. 查询方式:待发送数据写入“SBUF”后,通过查询中断请求位TI是否置位(TI=1)来判断数据是否发送完成,若TI置位,则表示当前数据发送完成,可以发送下一个数据。查询模式下是无需使能串口中断的。
  2. 中断方式:使能串口中断和系统中断,待发送数据写入“SBUF”后,无需查询中断请求位TI,数据发送完成后会触发串口中断,应该程序由此获知数据发送完成。

实际应用中,我们通常会使用查询方式发送数据,下面的代码是串口1以查询方式发送一字节数据的示例。

代码清单:串口1查询方式发送数据

  1. /************************************************************************************ 
  2. 功能描述:向串口1发送1字节数据,并等待发送完成 
  3. 参    数:dat[in]:要发送的数据 
  4. 返 回 值:无 
  5. ***********************************************************************************/  
  6. void uart1_send_byte(u8 dat)  
  7. {  
  8.    SBUF = dat;          //待发送数据写入串口1数据寄存器SBUF  
  9.    while(TI == 0);      //查询中断请求位是否置位,由此判断数据是否发送完成  
  10.    TI = 0;              //清零TI位(该位必须软件清零)  
  11. }  

      1. 串口接收

串口1模式1接收数据接收的时序如下图所示。

 

图9:串口1模式1接收数据

当软件置位接收允许标志位REN,即REN=1时,接收器便对RxD端口的信号进行检测,当检测到RxD端口发送从“1”→“0”的下降沿跳变时就启动接收器准备接收数据,并立即复位波特率发生器的接收计数器,将1FFH装入移位寄存器。接收的数据从接收移位寄存器的右边移入,已装入的 1FFH 向左边移出,当起始位“0”移到移位寄存器的最左边时,使 RX 控制器作最后一次移位,完成一帧的接收。

若以下两个条件同时满足:

  1. RI=0;
  2. SM2=0 或接收到的停止位为 1。(SM2是多机通信控制位,串口工作于模式1时,通常将SM2 设置为"0")。

则接收到的数据有效,数据装载入SBUF,停止位进入RB8,RI标志位被置位,并请求中断,若上述两条件不能同时满足,则接收到的数据作废并丢失。无论条件满足与否,接收器重又检测 RxD 端口上的"1"→"0"的跳变,继续下一帧的接收。

接收中断请求标志是“串口1控制寄存器(SCON)”的位0,如下所示。RI标志置位后,硬件不会自动清零,必须由软件清零。

  1. 串口1控制寄存器(SCON):

 

RI:串口1接收中断请求标志位。在模式0中,当串口接收第8位数据结束时,由硬件自动将RI置位,向主机请求中断,响应中断后RI必须用软件清零。在其他模式中,串行接收到停止位的中间时刻由硬件自动将RI置1,向CPU发中断申请,响应中断后RI必须由软件清零。

串口接收数据时,和发送一样,操作方式也有查询方式接收和中断方式接收。

  1. 查询方式:通过查询接收中断请求标志位RI是否置位(RI=1)来判断串口是否接收到数据。
  2. 中断方式:使能串口中断和系统中断,串口接收到数据后会触发中断,在中断服务函数中完成数据接收。

实际应用中,我们通常会使用中断方式接收数据,很少会用查询方式去接收数据。下面的代码是串口1以中断方式接收数据的示例。

代码清单:串口1中断方式接收数据

  1. /********************************************************************************** 
  2.  * 描  述 : 串口1中断服务函数 
  3.  * 入  参 : 无 
  4.  * 返回值 : 无 
  5.  **********************************************************************************/  
  6. void Uart1() interrupt 4 using 1  
  7. {  
  8.    ES = 0;                    //串口1中断关闭  
  9.   
  10.    if (RI)                    //是接收中断(接收中断请求标志位为1)  
  11.    {  
  12.       RI = 0;                 //清零RI位(该位必须软件清零)         
  13.       ......  
  14.       uart_rx[i++] = SBUF;    //读取串口接收的数据  
  15.       ......  
  16.     }
  17.     ES =  1;                  //串口1中断打开  
  18. }  
    1. 软件设计
      1. 串口应用步骤

串口应用的步骤主要包括串口初始化、数据发送和数据接收,这里面串口初始化内容比较固定,容易掌握。至于数据发送,因为STC8A8K64D4没有硬件FIFO,因此大多采样查询的方式发送,也容易理解。相对来说,最难的部分是数据接收,因为数据接收存在随机性,接收方通常不知道对方什么时候发数据过来,也不知道对方一次会发多少数据过来,因此,处理起来相对复杂一些。

  1. 串口初始化

串口初始化包含设置串口使用的引脚、串口的工作模式、波特率以及中断的开启(如果仅仅使用串口发送,通常使用查询方式发送,可以不使能中断;如果使用了串口接收功能,通常使用中断接收,需要使能串口中断)。

下面的代码是串口1初始化的示例,代码中对串口1进行了如下配置。

  1. 串口1使用的引脚为:RxD:P3.0;TxD:P3.1。
  2. 串口1的工作模式为:模式1,此模式为8位UART格式,一帧信息为10位:1位起始位、 8 位数据位(低位在先)和1位停止位。
  3. 波特率:9600bps,波特率发生器使用定时器1,16位重装。
  4. 串口中断:使能,因为我们需要使用串口接收功能(中断方式接收)。

代码清单:串口1初始化

  1. /********************************************************************************** 
  2. 功能描述:初始化串口1,设置为8位数据位、波特率9600bps (系统时钟使用24MHz) 
  3. 参    数:无 
  4. 返 回 值:无 
  5. ***********************************************************************************/  
  6. void uart1_init(void)  
  7. {  
  8.    ES = 0;           //初始化前关闭UART1中断  
  9.    P_SW1 &= 0xFC;     //设置串口1使用的引脚为:RxD--P3.0;TxD--P3.1        
  10.    PCON &= 0x3f;       //波特率不倍速,串行口工作方式由SM0、SM1决定  
  11.    SCON = 0x50;        //8位数据,可变波特率(SM0=0,SM1=1)  
  12.    AUXR |= 0x40;       //定时器时钟1T模式  
  13.    AUXR &= 0xFE;       //串口1选择定时器1为波特率发生器  
  14.       
  15.    TMOD &= 0x0F;       //设置定时器1模式:16位自动重装方式  
  16.    TL1 = 0x8F;       //设置定时初始值  
  17.    TH1 = 0xFD;       //设置定时初始值  
  18.       
  19.    ET1 = 0;            //禁止定时器1中断  
  20.    TR1 = 1;            //启动定时器1  
  21.    ES = 1;             //串口1中断打开  
  22. }  

  1. 数据发送

前文中,我们给出了使用查询方式发送一个字节数据的代码示例,当我们有多个数据需要发送的时候,使用循环语句调用该函数发送数据,直到数据全部发送完成即可。

  1. 数据接收

大多数情况下,接收数据都存在不确定性,就比如我们下面要做的串口收发的例子,开发板无法知道串口调试助手什么时候给他发数据,也无法确定一次发多少个字节的数据。在这种情况下,接收方为了保证完整地接收搭配数据,应该怎么做?

通常,我们会做一个软件缓存,在串口中断中接收数据存入到软件缓存,并定义一个变量用于记录串口接收的字节数(接收计数器),应用程序中可以通过查询接收计数器从而判断串口是否接收到数据,如接收到数据,则从缓存中取出数据进行处理。该方式的软件流程如下图所示,具体实现的代码参见下一节中的例子。

 

图5:串口接收处理流程

      1. 串口数据收发实验
  • 注:本节的实验是在“实验2-3-2:触摸按键检测”的基础上修改,本节对应的实验源码是:“实验2-6-1:串口1数据收发实验”。
        1. 实验内容
  1. 配置串口1使用的引脚为:RxD为P3.0,TxD为P3.1。8位数据位,波特率9600bps。
  2. 串口1接收电脑端串口调试助手发过来的数据,并将接收到的数据原样返回。
        1. 代码编写
  1. 新建一个名称为“uart.c”的文件及其头文件“uart.h”并保存到工程的“Source”文件夹,并将“uart.c”加入到Keil工程中的“SOURCE”组。
  2. 引用头文件

因为在“main.c”文件中使用了“uart.c”文件中的函数,所以需要引用下面的头文件“uart.h”。

代码清单:引用头文件

  1. //引用串口的头文件  
  2. #include    "uart.h"  

  1. 串口初始化

串口1初始化函数代码清单如下,该函数中设置了串口1使用的引脚:RxD为P3.0,TxD为P3.1。串口1配置为8位数据位、波特率9600bps。同时,初始化函数中也初始化了串口接收软件缓存(软件缓存在下文的串口接收部分描述)。

代码清单:串口初始化

  1. /********************************************************************************** 
  2. 功能描述:初始化串口1,设置为8位数据位、波特率9600bps (系统时钟使用24MHz) 
  3. 参    数:无 
  4. 返 回 值:无 
  5. ***********************************************************************************/  
  6. void uart1_init(void)  
  7. {  
  8.    ES = 0;           //初始化前关闭UART1中断  
  9.       
  10.    P_SW1 &= 0xFC;     //设置串口1使用的引脚为:RxD--P3.0;TxD--P3.1  
  11.    uart1_init_fifo(); //初始化软件缓存  
  12.       
  13.    PCON &= 0x3f;       //波特率不倍速,串行口工作方式由SM0、SM1决定 
  14.    SCON = 0x50;        //8位数据,可变波特率(SM0=0,SM1=1)  
  15.    AUXR |= 0x40;       //定时器时钟1T模式  
  16.    AUXR &= 0xFE;       //串口1选择定时器1为波特率发生器  
  17.    TMOD &= 0x0F;       //设置定时器1模式:16位自动重装方式  
  18.    TL1 = 0x8F;         //设置定时初始值  
  19.    TH1 = 0xFD;        //设置定时初始值  
  20.       
  21.    ET1 = 0;            //禁止定时器1中断  
  22.    TR1 = 1;            //启动定时器1  
  23.    ES = 1;              //串口1中断打开  
  24. }  

  1. 串口数据发送

串口发送采用查询方式,待发送数据写入SBUF后,一直等待中断请求位TI置位,TI置位后,软件将其清零。串口发送函数代码清单如下。

代码清单:串口发送函数

  1. /********************************************************************************** 
  2. 功能描述:向串口1发送1字节数据,并等待发送完成 
  3. 参    数:dat[in]:要发送的数据 
  4. 返 回 值:无 
  5. ***********************************************************************************/  
  6. void uart1_send_byte(u8 dat)  
  7. {
  8.    SBUF = dat;          //待发送数据写入串口1数据寄存器SBUF  
  9.    while(TI == 0);      //查询中断请求位是否置位,由此判断数据是否发送完成  
  10.    TI = 0;              //清零TI位(该位必须软件清零)
  1. 串口数据接收

串口接收相对麻烦一些,这里,我们做了一个软件缓存用于存储接收到的数据。该软件缓存是基于循环队列的原理实现的,具体代码实现方式如下。

定义一个数组用来存放串口接收到数据,定义一个变量用来记录缓存中写的位置,称为“写指针”,定义一个变量用来记录缓存中读的位置,称为“读指针”,同时定义一个变量用来记录数组中数据个数,称为“计数器”,代码清单如下。

代码清单:软件缓存相关变量定义

  1. #define UART_BUF_LEN   32              //串口接收软件缓存大小,如果接收的数据较多,可以修改缓存大小
  2.   
  3. static u8 idata uart_rx[UART_BUF_LEN]; //定义串口接收缓存数据  
  4. static u8 uart_rx_wp, uart_rx_rp;      //分别用来记录在软件缓存中写数据和读数据的位置  
  5. static u8 uart_rx_cnt;                 //缓存数据计数器  

接收软件缓存在使用前需要初始化,即将用于记录的各个变量清零,初始化函数代码清单如下。

代码清单:软件缓存初始化

  1. /********************************************************************************** 
  2. 功能描述:初始化软件缓存 
  3. 参    数:无 
  4. 返 回 值:无 
  5. ***********************************************************************************/  
  6. void uart1_init_fifo(void)  
  7. {  
  8.    uart_rx_wp = 0;  
  9.    uart_rx_rp = 0;  
  10.    uart_rx_cnt = 0;  
  11. }  

串口中断服务函数中,将接收到的串口数据写入缓存,同时计数器加1。这里需要注意,写入数据时需要判断写指针是否到达缓存尾,如果到达缓存尾,则翻转,重新指向缓存头。

代码清单:串口1中断服务函数

  1. /********************************************************************************** 
  2.  * 描  述 : 串口1中断服务函数 
  3.  * 入  参 : 无 
  4.  * 返回值 : 无 
  5.  **********************************************************************************/  
  6. void Uart1() interrupt 4 using 1  
  7. {  
  8.    ES = 0;                       //串口1中断关闭  
  9.   
  10.    if (RI)                       //是接收中断(接收中断请求标志位为1)  
  11.    {  
  12.       RI = 0;                    //清零RI位(该位必须软件清零)
  13.             
  14.       if (uart_rx_cnt < UART_BUF_LEN)  //如果缓存中有剩余空间,接收的数据写入缓存
  15.       {  
  16.          uart_rx[uart_rx_wp] = SBUF;   //数据写入缓存
  17.          uart_rx_wp = (uart_rx_wp + 1) % UART_BUF_LEN;//写指针加1,若到达缓存尾,则从头开始
  18.          uart_rx_cnt++;  //计数器加1
  19.        }  
  20.     }  
  21.     ES =  1;                   //串口1中断打开  
  22. }  

数据存入软件缓存后,我们还需提供一个读取函数用于主程序从缓存中读取数据。读取函数的代码清单如下,这里同样需要注意,读数据时需要判断读指针是否到达缓存尾,如果到达缓存尾,则翻转,重新指向缓存头。

代码清单:从软件缓存中读取一个字节数据

  1. /********************************************************************************** 
  2. 功能描述:从软件缓存中读取一个字节数据 
  3. 参    数:无 
  4. 返 回 值:读取的数据 
  5. ***********************************************************************************/  
  6. u8 uart1_fifo_getbyte(void)  
  7. {  
  8.   u8 ch;  
  9.   
  10.   ES = 0; //读取时关闭串口中断  
  11.   ch = uart_rx[uart_rx_rp]; //读取一个字节数据  
  12.   uart_rx_rp = (uart_rx_rp + 1) % UART_BUF_LEN;//读指针加1,若到达缓存尾,则从头开始  
  13.   uart_rx_cnt--;//数据计数器减一  
  14.   ES = 1;       //读取完成后使能中断  
  15.   return ch;   
  16. }  
  1. 串口重定向printf

在程序开发调试过程中,我们习惯使用printf函数来打印数据或调试信息,但在默认情况下,使用printf函数并不能直接通过串口输出数据,因此,我们需要重定向printf函数重定向printf函数只需要改写putchar函数即可,本例中,我们将printf函数重定向到串口1,代码清单如下

代码清单:重定向printf函数到串口1

  1. /*************************************************************************** 
  2.  * 描  述 : 重定向c库函数printf到串口1 
  3.  * 入  参 : char dat 
  4.  * 返回值 : char  
  5.  **************************************************************************/  
  6. char putchar(char dat)  
  7. {  
  8.     uart1_send_byte(dat); 
  9.     return dat; 
  10. }  

printf函数是C语言标准库函数,定义于头文件 <stdio.h>,因此,在使用printf函数的文件中需要引用该头文件,如下所示。

代码清单:引用stdio.h头文件

  1. #include  <stdio.h>  

  1. 主函数

主函数中,调用串口1初始化“uart1_init()”完成串口1初始化,并且使能了总中断。之后在主循环中查询接收软件缓存中是否有数据,如有数据,读出数据,并调用串口1发送函数“uart1_send_byte()”将读取的数据发送,由此完成串口接收数据的回环。

代码清单:主函数

  1. /************************************************************************** 
  2. 功能描述:主函数 
  3. 入口参数:无 
  4. 返回值:int类型 
  5.  *************************************************************************/  
  6. int main(void)  
  7. {  
  8.    u8 uart1_rece_length;  
  9.       
  10.    P3M1 &= 0xFE;   P3M0 &= 0xFE;     //设置P3.0为准双向口(串口1的RxD)
  11.    P3M1 &= 0xFD;   P3M0 |= 0x02;     //设置P3.1为推挽输出(串口1的TxD)  
  12.       
  13.    uart1_init();                 //串口1初始化  
  14.    EA = 1;                       //使能总中断  
  15.    while(1)  
  16.    {  
  17.       uart1_rece_length = uart1_get_fifo_datalen(); //查询串口接收软件缓存中是否有数据 
  18.       if(uart1_rece_length > 0)                       //如有数据
  19.       {  
  20.         uart1_send_byte(uart1_fifo_getbyte());      //从串口接收软件缓存中读取数据并发送
  21.       }  
  22.    }  
  23. }  
        1. 硬件连接

本实验使用的是USB转串口,按照下图所示短接J5的跳线帽,将串口1的P3.0和P3.1连接到USB转串口电路。

 

图6:跳线帽短接

        1. 实验步骤
  1. 解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-6-1:串口1数据收发实验”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
  2. 双击“…\uart1_echo\project”目录下的工程文件“uart1_echo.uvproj”。
  3. 点击编译按钮编译工程,编译成功后生成的HEX文件“uart1_echo.hex”位于工程的“…\uart1_echo\Project\Object”目录下。
  4. 打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
  5. 电脑上打开串口调试助手,选择开发板对应的串口号,将波特率设置为9600bps,之后输入发送的数据,点击发送按钮发送数据。

 

图7:串口调试助手收发数据

  1. 观察串口接收的数据,应和发送的数据一样。
      1. 串口控制LED实验
  • 注:本节的实验是在“实验2-6-1:串口1数据收发实验”的基础上修改,本节对应的实验源码是:“实验2-6-2:串口控制LED实验”。
        1. 实验内容

串口数据收发中我们学习了串口如何收发数据,这一节我们来看一下如何解析简单命令。我们定义的LED指示灯控制的命令格式如下:

  1. 指示灯控制命令格式:‘#’+‘D’+ 指示灯标号(1、2、3、4)。
  2. 命令示例:控制指示灯D1点亮“#D1”。

开发板接收到一个命令包后,对命令包进行解析,并根据解析结果点亮相应的LED指示灯。

        1. 代码编写

串口解析接收数据的代码清单如下,当串口接收到字符“#”,若此时接收数据长度uart_rx_cnt为0,则认为接收到了命令包的起始字节。此后,当接收字节数达到3的时候,认为一个命令包接收完成。接着,从命令包中解析出要点亮的指示灯的编号,即将命令包中的第3个字节减去48,得到ASCII对应的十进制数值,由此得到指示灯的编号。最后,调用指示灯操作函数点亮对应的指示灯即可。

代码清单:主函数

  1. /************************************************************************************** 
  2. 功能描述:主函数 
  3. 入口参数:无 
  4. 返回值:int类型 
  5.  *************************************************************************************/ 
  6. int main(void)  
  7. {  
  8.    u8 uart1_rece_length,i,cr;  
  9.       
  10.    P2M1 &= 0x3F;   P2M0 &= 0x3F;     //设置P2.6、P2.7为准双向口(指示灯D1和D2)
  11.    P7M1 &= 0xF9;   P7M0 &= 0xF9;     //设置7.1、P7.2为准双向口(指示灯D4和D3
  12.    P3M1 &= 0xFE;   P3M0 &= 0xFE;     //设置P3.0为准双向口(串口1的RxD)  
  13.    P3M1 &= 0xFD;   P3M0 |= 0x02;     //设置P3.1为推挽输出(串口1的TxD)  
  14.       
  15.    uart1_init();                 //串口1初始化  
  16.    EA = 1;                       //使能总中断  
  17.    while(1)  
  18.    {  
  19.       uart1_rece_length = uart1_get_fifo_datalen(); //查询串口接收软件缓存中是否有数据  
  20.       for(i=0; i<uart1_rece_length;i++)             //处理数据  
  21.       {  
  22.         cr = uart1_fifo_getbyte();//从串口接收软件缓存中读取数据  
  23.         if(start_flag == 0)  //数据包开始标志为false
  24.         {  
  25.            if(cr == '#')//如果接收的数据是'#',表示接收到数据包起始字节  
  26.            {  
  27.               if(uart_rx_cnt == 0)//接收到起始字节后,如果uart_rx_cnt也等于0,表示当前流程是对的  
  28.               {  
  29.                  uart_rece_buf[uart_rx_cnt++] = cr;  
  30.                  start_flag = 1;  
  31.                }  
  32.                else//清零接收计数  
  33.                {  
  34.                   uart_rx_cnt = 0;  
  35.                 }               
  36.               }  
  37.             }  
  38.             else//数据包开始标志为真  
  39.             {  
  40.                uart_rece_buf[uart_rx_cnt++] = cr;  
  41.                if(uart_rx_cnt == UART_RECE_PACK_LEN)//接收命令完成  
  42.                {  
  43.                   //检查数据是否合法  
  44.                   if((uart_rece_buf[1] == 'D') || (uart_rece_buf[1] == 'd'))  
  45.                   {  
  46.                      switch(uart_rece_buf[2]-48)  //减去48,得到ASCII对应的十进制数值 
  47.                      {  
  48.                         case 1:  
  49.                           leds_off();       //熄灭所有LED指示灯  
  50.                           led_on(LED_1); //点亮D1指示灯  
  51.                           break;  
  52.                          case 2:  
  53.                            leds_off();       //熄灭所有LED指示灯  
  54.                            led_on(LED_2); //点亮D2指示灯  
  55.                            break;  
  56.                           case 3:  
  57.                             leds_off();      //熄灭所有LED指示灯  
  58.                             led_on(LED_3);   //点亮D3指示灯  
  59.                             break;  
  60.                           case 4:  
  61.                             leds_off();      //熄灭所有LED指示灯  
  62.                             led_on(LED_4); //点亮D4指示灯  
  63.                             break;  
  64.                            default:  
  65.                              break;  
  66.                           }  
  67.                     } 
  68.                     uart_rx_cnt = 0;     //清零接收计数  
  69.                     start_flag = 0;  //清零数据包开始标志  
  70.               }  
  71.          }
  72.       }  
  73.    }  
        1. 硬件连接

本实验需要使用USB转串口和4个LED指示灯,串口部分的硬件连接和“实验2-6-1:串口1数据收发实验”一样。

指示灯部分按照下图用跳线帽短接复用引脚的指示灯(D1和D2),而指示灯D3和D4是独立引脚,没有和其他电路复用引脚,是没有短接跳线帽的操作的。

 

图8:跳线帽短接

        1. 实验步骤
  1. 解压“…\第3部分:配套例程源码”目录下的压缩文件“实验2-6-2:串口控制LED实验”,将解压后得到的文件夹拷贝到合适的目录,如“D\STC8”(这样做的目的是为了防止中文路径或者工程存放的路径过深导致打开工程出现问题)。
  2. 双击“…\uart1_led\project”目录下的工程文件“uart1_led.uvproj”。
  3. 点击编译按钮编译工程,编译成功后生成的HEX文件“uart1_led.hex”位于工程的“…\uart1_led\Project\Object”目录下。
  4. 打开STC-ISP软件下载程序,下载使用内部IRC时钟,IRC频率选择:24MHz。
  5. 电脑上打开串口调试助手,选择开发板对应的串口号,将波特率设置为9600bps。
  6. 程序运行后,串口调试助手发送栏输入“#D1”、“#D2”、“#D3”或“#D4”,点击发送按钮发送数据,观察开发板上的指示灯的亮灭,应和发送的指示灯编号一致。

  • 说明:串口2、串口3和串口4,他们的操作和串口1类似,读者可以尝试在学习了串口1的基础上编写一下他们的收发程序。

我们也编写好了串口2、串口3和串口4的收发例子,这些例子在资料的“…\第3部分:配套例程源码”目录下,他们的实验名称如下,读者在编写的过程中可以参考一下。

  1. 实验2-6-3:串口2数据收发实验。
  2. 实验2-6-4:串口3数据收发实验。
  3. 实验2-6-5:串口4数据收发实验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/707224.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【电商API接口系列】店铺所有商品数据的采集

API接口允许不同应用程序之间共享数据&#xff0c;在系统之间传输、读取和更新数据。例如&#xff0c;一个电商网站可以通过API接口获取支付系统的支付状态。API接口允许开发人员使用他人开发的功能来扩展自己的应用程序。通过调用第三方API接口&#xff0c;开发人员无需重新实…

二进制部署Kubernetes

二进制部署Kubernetes v1.20 k8s集群master01&#xff1a;192.168.142.10 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群master02&#xff1a;192.168.142.20 k8s集群node01&#xff1a;192.168.142.30 kubelet kube-proxy docker k8s集群node…

基于Java汽车售票网站设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

VUE_网页自定义右键菜单组件

可以在uni-app或vue脚手架项目使用 引入组件会接管页面右键事件&#xff0c;所有options为空数组时&#xff0c;在页面右键将没有反应 rightMenu.vue <template><view><view v-if"show" class"contextMenu" :style"lay_style"…

Kafka:Kafka资料整理

一、官网 二、博主文章 1、kafka是什么 • Worktile社区 三、源码解读

一文了解云计算

目录 &#x1f34e;云服务 &#x1f34e;云计算类型 &#x1f352;公有云 &#x1f352;私有云 &#x1f352;混合云 &#x1f34e;云计算服务模式 &#x1f352;IaaS基础设施即服务 &#x1f352;PaaS平台即服务 &#x1f352;SaaS软件即服务 &#x1f352;三者之间区别 &…

4.springboot原理篇

原理篇 spring与springboot区别 spring是承载容器 springboot做的主要工作&#xff1a; ①简化配置&#xff08;省去了spring中配置xml&#xff0c;引入application.yml文件&#xff09; ②为我们提供了 spring-boot-starter-web 依赖&#xff0c;这个依赖包含了Tomcat和sprin…

二进制搭建Kubernetes集群(二)——部署Worker Node 组件

四.部署node节点 4.1 所有node节点部署 docker引擎 #所有 node 节点部署docker引擎#安装依赖包yum install -y yum-utils device-mapper-persistent-data lvm2#设置阿里云镜像源yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker- ce.…

Nuget更新全局包、缓存和临时文件夹路径位置

Nuget更新缓存 1、查看默认的Nuget路径2、更改全局包路径2.1 通过环境变量来进行修改2.2通过Nuget.Config配置文件来进行修改 3、更改http-cache路径4、更改temp文件路径5、更改plugins-cache文件路径 NuGet是一个流行的软件包管理器&#xff0c;可以帮助.NET开发人员轻松地添加…

【Python】 【Pandas 】【read_csv()】Pandas库的read_csv()方法的使用,处理:None,NULL

近期&#xff0c;使用read_csv的时候&#xff0c;遇到一个问题&#xff0c;就是本地读取的csv文件中的数据有None和NaN 两种&#xff0c;如&#xff1a; 直接使用 pd.read_csv(rF:\我爱Python\预测\历史样本.csv,encodingutf-8)发现读取的数据是将None 和 NULL 直接处理成 NaN…

SpingData-JDBC(看这篇文章就够了,新手入门指引)

JdbcTemplate 的基本使用 写在前面&#xff1a; 当DDL操作时&#xff0c;一般是用execute方法&#xff0c;这也是一种规范吧&#xff0c;这个也可以运行DML但是通常来说我DML操作是需要返回值的&#xff0c;一般就是返回影响的行数。然后这篇文章主要介绍增删改查&#xff0c…

软考A计划-系统集成项目管理工程师-项目范围管理(四)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

Linux服务器网卡流量过高排查

第一种方式&#xff1a;nethogs 1.安装 yum -y install nethogs #nethogs em1 -d 3 监控eth0 并每3s刷新一次 手动安装 wget https://github.com/raboof/nethogs/archive/v0.8.5.tar.gz 依赖包&#xff1a; yum install -y libpcap libpcap-devel 编译&#xff1a; mak…

④数据封装对象(Vo、Bo、Po..)+MySQL视图

1.数据封装对象 VO&#xff08;View Object&#xff09;&#xff1a;视图对象&#xff0c;用于展示层&#xff0c;它的作用是把某个指定页面&#xff08;或组件&#xff09;的所有数据封装起来。 DTO&#xff08;Data Transfer Object&#xff09;&#xff1a;数据传输对象&a…

小黑特种兵重庆行走一天,体验了当地风土人情的leetcode之旅:剑指 Offer II 014. 字符串中的变位词

小黑代码 class Solution:def checkInclusion(self, s1: str, s2: str) -> bool:# 字符串长度n_s1 len(s1)n_s2 len(s2)if n_s1 > n_s2:return False# s1的字符计数字典count_s1 [0] * 26# 窗口计数字典count_window [0] * 26# 寻找初始窗口for i in range(n_s1):co…

SpringBoot(四)SpringBoot搭建简单服务端

通过之前的几篇文章相信大家已经对SpringBoot项目开发有了一个基本的了解。本篇&#xff0c;介绍下如何使用SpringBoot搭建一个简单的服务端&#xff0c;实现一个新用户注册的场景&#xff0c;供前端和移动端去使用。本篇需要你对SpringBoot的starter&#xff0c;mysql&#xf…

Redis概述及安装、使用和管理

文章目录 一、NoSQL非关系型数据库1.NoSQL概述2.关系型数据库和非关系型数据库区别&#xff08;1&#xff09;数据存储方式不同&#xff08;2&#xff09;扩展方式不同&#xff08;3&#xff09;对事务性的支持不同 3.非关系型数据库使用场景 二、Redis概述1.简介2.优点3.Redis…

Learn Mongodb了解DB数据库 ④

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; PHP MYSQL &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

SIMATIC WINCC中实现弹窗跟随鼠标功能(C语言脚本)的具体方法示例

SIMATIC WINCC中实现弹窗跟随鼠标功能(C语言脚本)的具体方法示例 具体C语言脚本可参考以下代码: #include "apdefap.h" //添加的头文件//定义的函数 void OnLButtonDown(char* lpszPictureName, char* lpszObjectName, char

七.错误处理

目录 1、错误处理 1、error一般是处理一些比较低级的错误&#xff0c;不会造成程序中断或者宕机。 2、panic一般是发生了致命的错误时才会被调用&#xff0c;例如数组越界&#xff0c;空指针等等&#xff0c; 2.1 手动调用panic 2.2 数组越界造成panic 2、recover函数 1、…