GD32开发实战指南(基础篇) 第10章 串口通信

发布者:温暖梦想最新更新时间:2024-11-08 来源: elecfans关键字:GD32  开发实战  串口通信 手机看文章 扫描二维码
随时随地手机看文章

开发环境:

MDK:Keil 5.30

开发板:GD32F207I-EVAL

MCU:GD32F207IK


1 串口简介

USART(Universal Synchronous Asynchronous Receiver and Transmitter,通用同步-异步接收发射器)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。使用多缓冲器配置的DMA方式,可以实现高速数据通信

虽然USART既可以同步又可以异步,但是常见的最常用的就是使用功能的异步功能,如果作为异步通信就是UART(Universal Asynchronous Receiver and Transmitter),可以说,UART是USART的子集,但是同步通信相比异步通信多了一根时钟同步信号线。

下面简单介绍下同步和异步。

在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据,见下图。通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。

1683895220147axge4u4qtc

在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据,见下图,某些通讯中还需要双方约定数据的传输速率,以便更好地同步。

1683895220804pmuvbhvx11

在同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小,而异步通讯双方的时钟允许误差较大。

从上面的介绍可以看出,USART以同步方式通信需要时钟同步信号,但不需要额外的起始、停止位,可以实现更快的传输速度。但USART控制起来更复杂,因此本文主要讲解以异步通信。

异步串行通信以字符为单位,即一个字符一个字符地传送 。

16838952212104nl83id006

串口外设的架构图看起来十分复杂,实际上对于软件开发人员来说,我们只需要大概了解串口发送的过程即可。从下至上,我们看到串口外设主要由三个部分组成,分别是波特率控制、收发控制和数据存储转移。

  • 波特率控制

波特率,即每秒传输的二进制位数,用b/s(bps)表示,通过对时钟的控制可以改变波特率。在配置波特率时,我们向波特比率寄存器 USART_BAUD写入参数,修改了串口时钟的分频值USARTDIV。USART_BAUD寄存器包括两部分,分别是INTDIV(USARTDIV 的整数部分)和FRADIV(USARTDIV 的小数)部分,最终,计算公式为 USARTDIV= INTDIV+(FRADIV/16)。

USARTDIV 是对串口外设的时钟源进行分频的,USART0/5的系统时钟为PCLK2, USART1/2和UART3/4/6/7的系统时钟为PCLK1,串口的时钟源经过 USARTDIV 分频后分别输出作为发送器时钟及接收器时钟,控制发送和接收的时序。在使能USART之前,必须在时钟控制单元使能系统时钟。

1683895221580zntbwdjtxe

  • 收发控制

围绕着发送器和接收器控制部分,有好多个寄存器 :STAT0、USART_CTL0、USART_CTL1、USART_CTL2和 STAT1,即USART 的三个控制寄存器(Control Register)及一个状态寄存器(Status Register)。通过向寄存器写入 各种控制参数来控制发送和接收,如奇偶校验位、停止位等,还包括对USART 中断的控制;串口的状态在任何时候都可以从状态寄存器中查询得到。其中停止位的配置如下图所示。

1683895221949vzwsmuzlua

  • 发送配置步骤:

1.在USART_CTL0寄存器中置位UEN位,使能USART;

2.通过USART_CTL0寄存器的WL设置字长;

3.在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;

4.如果选择了多级缓存通信方式,应该在USART_CTL2寄存器中使能DMA (DENT位);

5.在USART_BAUD寄存器中设置波特率;

6.在USART_CTL0寄存器中设置TEN位;

7.等待TBE置位;

8.向USART_DATA寄存器写数据;

9.若DMA未使能,每发送一个字节都需重复步骤7-8;

10.等待TC=1,发送完成。

1683895222355c03jht49yj

在禁用USART或进入低功耗状态之前,必须等待TC置位。先读USART_STAT0然后再写USART_DATA可将TC位清0。在多级缓存通信方式(DENT=1)下,直接向TC写0,也能清TC。

  • 接收配置步骤:

1.写USART_CTL0寄存器的WL位去设置字长;

2.在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;

3.如果选择了多级缓存通信方式,应该在USART_CTL2寄存器中使能DMA(DENR位);

4.在USART_BAUD寄存器中设置波特率;

5.在USART_CTL0寄存器中置位UEN位,使能USART;

6.在USART_CTL0中设置REN位。

接收器在使能后若检测到一个有效的起始脉冲便开始接收码流。在接收一个数据帧的过程中会检测噪声错误,奇偶校验错误,帧错误和过载错误。

当接收到一个数据帧, USART_STAT0寄存器中的RBNE置位,如果设置了USART_CTL0寄存器中相应的中断使能位RBNEIE,将会产生中断。在USART_STAT0寄存器中可以观察接收状态标志。

软件可以通过读USART_DATA寄存器或者DMA方式获取接收到的数据。不管是直接读寄存器还是通过DMA,只要是对USART_DATA寄存器的一个读操作都可以清除RBNE位。

在接收过程中,需使能REN位,不然当前的数据帧将会丢失。

以上对串口通信进行了简单介绍,为了方便各位读者朋友更好的理解,在这里笔者将引入一个新的思想--系统分层思想。既然各位对着有意于嵌入式,那么必须得有对整个系统的架构要有一定的认知。对GD32裸机开发,我们可以将分为三层:物理层、协议层和应用层。前文讲了这么多也是对串口协议进行分析,常用的物理层的串口通信标准有232和485。

【注】UART和USART的区别

USART(universal synchronous asynchronous receiver and transmitte): 通用同步异步收发器,USART是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。

UART(universal asynchronous receiver and transmitter): 通用异步收发器,异步串行通信口(UART)就是我们在嵌入式中常说的串口,它还是一种通用的数据通信议。从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型。

当我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:大家都知道同步通信需要时钟来触发数据传输,也就是说USART相对UART的区别之一就是能提供主动时钟。如GD32的USART可以提供时钟支持ISO7816的智能卡接口。

USART是指单片机的一个端口模块,可以根据需要配置成同步模式(SPI,I2C),也可以将其配置为异步模式,后者就是UART。所以说UART姑且可以称之为一个与SPI,I2C对等的“协议”,而USART则不是一个协议,而是更应该理解为一个实体。相比于同步通讯,UART不需要统一的时钟线,接线更加方便。但是,为了正常的对信号进行解码,使用UART通讯的双方必须事先约定好波特率,即每个码元的长度。

关于串口的深入理解,请参看笔者文章:

https://blog.bruceou.cn/2021/01/detailed-explanation-of-stm32-serial-communication/555/

2 串口通信的寄存器描述

串口常用的寄存器有状态寄存器(USART_STATx)、数据寄存器(USART_DATA)、波特比率寄存器(USART_BAUD)、控制寄存器 (USART_CTLx)。

1683895222737bd67lzcxas

1683895223168dhpfo23z8c

1683895223522iwiaspele1

1683895223895iajpcvmzo3

3 串口硬件

串口的接口通过三个引脚与其他设备连接在一起。任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。

  • RX:接收数据串行输入。通过采样技术来区别数据和噪音,从而恢复数据。

  • TX :发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O 口被同时用于数据的发送和接收。

1683895224390602dmuuzev

板子使用串口0,接口用的232,但对于软件来说,都是一样的。


4 串口发送(重定向printf)

4.1 串口发送实现

下面笔者就用标准库来操作串口0。


1.串口配置


串口0时钟使能

串口1是挂载在 APB2 下面的外设,所以使能函数为:


rcu_periph_clock_enable(RCU_USART0);

值得注意的是,不仅要打开串口的时钟,还需要打开相应GPIO的时钟,最终的代码如下:


rcu_periph_clock_enable(RCU_GPIOA);

配置串口GPIO

这个比较简单,前面的章节已经讲过了,只需要注意的是,这里的GPIO不再是普通GPIO,要配置成复用功能,因此TX和RX分别配置成GPIO_MODE_AF_PP和GPIO_MODE_IN_FLOATING。


串口复位

当外设出现异常的时候可以通过复位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。复位的是在函数usart_deinit()中完成:


void usart_deinit(uint32_t usart_periph);

比如我们要复位串口0,方法为:


usart_deinit(USART0);

串口参数初始化

串口初始化是以下函数设置:


void usart_baudrate_set(uint32_t usart_periph, uint32_t baudval); //设置波特率

void usart_word_length_set(uint32_t usart_periph, uint32_t wlen); //设置传输字长

void usart_stop_bit_set(uint32_t usart_periph, uint32_t stblen); //设置停止位

void usart_parity_config(uint32_t usart_periph, uint32_t paritycfg); //设置校验位

void usart_hardware_flow_rts_config(uint32_t usart_periph, uint32_t rtsconfig); //设置RTS流控

void usart_hardware_flow_cts_config(uint32_t usart_periph, uint32_t ctsconfig); //设置CTS流控

void usart_receive_config(uint32_t usart_periph, uint32_t rxconfig); //设置接收使能

void usart_transmit_config(uint32_t usart_periph, uint32_t txconfig); //设置发送使能

从上面的初始化格式可以看出初始化需要设置的参数为:波特率,字长,停止位,奇偶校验位,硬件数据流控制,模式(收,发)。 我们可以根据需要设置这些参数。


串口使能

串口使能是通过函数usart_enable()来实现的,这个很容易理解,使用方法是:


usart_enable(USART0);

到此,串口初始化的基本配置就算完成了,完整初始化代码如下:


/*

    brief      configure COM port

    param[in]  com_typedef_enum com_id, uint32_t baudval

    param[out] none

    retval     none

*/

void com_init(com_typedef_enum com_id, uint32_t baudval)

{

    /* enable GPIO clock */

    rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);


    /* enable USART clock */

    rcu_periph_clock_enable(COM_CLK[com_id]);


    /* connect port to USARTx_Tx */

    gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);


    /* connect port to USARTx_Rx */

    gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);


    /* USART configure */

    usart_deinit(COM_USART[com_id]);

    usart_baudrate_set(COM_USART[com_id], baudval);

    usart_word_length_set(COM_USART[com_id], USART_WL_8BIT);

    usart_stop_bit_set(COM_USART[com_id], USART_STB_1BIT);

    usart_parity_config(COM_USART[com_id], USART_PM_NONE);

    usart_hardware_flow_rts_config(COM_USART[com_id], USART_RTS_DISABLE);

    usart_hardware_flow_cts_config(COM_USART[com_id], USART_CTS_DISABLE);

    usart_receive_config(COM_USART[com_id], USART_RECEIVE_ENABLE);

    usart_transmit_config(COM_USART[com_id], USART_TRANSMIT_ENABLE);

    usart_enable(COM_USART[com_id]);

}

2.数据发送与接收


GD32 的发送与接收是通过数据寄存器USART_DATA来实现的,这是一个双寄存器。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。


GD32库函数操作USART_DATA寄存器发送数据的函数是:


void usart_data_transmit(uint32_t usart_periph, uint16_t data);

通过该函数向串口寄存器 USART_DR 写入一个数据。


GD32库函数操作USART_DATA寄存器读取串口接收到的数据的函数是:


uint16_t usart_data_receive(uint32_t usart_periph);

通过该函数可以读取串口接受到的数据。


3.串口状态


串口的状态可以通过状态寄存器USART_STAT0读取。


状态寄存器的其他位我们这里就不做过多讲解,大家需要可以查看中文参考手册。


在我们固件库函数里面,读取串口状态的函数是:


FlagStatus usart_flag_get(uint32_t usart_periph, usart_flag_enum flag);

这个函数的第二个入口参数非常关键, 它是标示我们要查看串口的哪种状态, 比如上面讲解的TBE(读数据寄存器非空)以及 TC(发送完成)。例如我们要判断读寄存器是否非空(TBE), 操作库函数的方法是:


usart_flag_get (USART0, USART_FLAG_TBE);

我们要判断发送是否完成(TC),操作库函数的方法是:


usart_flag_get (USART0, USART_FLAG_TC);

这些标识号是通过枚举类型定义的:


/* USART flags */

typedef enum {

    /* flags in STAT0 register */

    USART_FLAG_CTSF = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 9U),      /*!< CTS change flag */

    USART_FLAG_LBDF = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 8U),      /*!< LIN break detected flag */

    USART_FLAG_TBE = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 7U),       /*!< transmit data buffer empty */

    USART_FLAG_TC = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 6U),        /*!< transmission complete */

    USART_FLAG_RBNE = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 5U),      /*!< read data buffer not empty */

    USART_FLAG_IDLEF = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 4U),     /*!< IDLE frame detected flag */

    USART_FLAG_ORERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 3U),     /*!< overrun error */

    USART_FLAG_NERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 2U),      /*!< noise error flag */

    USART_FLAG_FERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 1U),      /*!< frame error flag */

    USART_FLAG_PERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 0U),      /*!< parity error flag */

    /* flags in STAT1 register */

    USART_FLAG_BSY = USART_REGIDX_BIT(USART_STAT1_REG_OFFSET, 16U),      /*!< busy flag */

    USART_FLAG_EB = USART_REGIDX_BIT(USART_STAT1_REG_OFFSET, 12U),       /*!< end of block flag */

    USART_FLAG_RT = USART_REGIDX_BIT(USART_STAT1_REG_OFFSET, 11U)        /*!< receiver timeout flag */

} usart_flag_enum;


另外,笔者在此给出输出格式的说明,请读者朋友参考。

格式说明
%d按照十进制整型数打印
%6d按照十进制整型数打印,至少6个字符宽
%f按照浮点数打印
%6f按照浮点数打印,至少6个字符宽
%.2f按照浮点数打印,小数点后有2位小数
%6.2f按照浮点数打印,至少6个字符宽,小数点后有2位小数
%x按照十六进制打印
%c打印字符
%s打印字符串


接下来就可以实现串口的发送了,这里对发送函数进行封装。


/**

  * @brief  串口发送一个字节数据 

  * @param  ch:待发送字符

  * @retval None

  */

void usart_send_byte(uint8_t ch)

{

    /* 发送一个字节数据到USART */

    usart_data_transmit(USART0,ch);


    /* 等待发送完毕 */

    while (usart_flag_get(USART0, USART_FLAG_TBE) == RESET);

}


/**

  * @brief  串口发送指定长度的字符串

  * @param  str:待发送字符串缓冲器

  *         strlen:指定字符串长度

  * @retval None

  */

void usart_sendStr_length(uint8_t *str,uint32_t strlen)

{

    unsigned int k=0;

    do 

    {

        usart_send_byte(*(str + k));

        k++;

    } while(k < strlen);

}


/**

  * @brief  串口发送字符串,直到遇到字符串结束符

  * @param  str:待发送字符串缓冲器

  * @retval None

  */

void usart_send_string(uint8_t *str)

{

  unsigned int k=0;

    do 

[1] [2]
关键字:GD32  开发实战  串口通信 引用地址:GD32开发实战指南(基础篇) 第10章 串口通信

上一篇:GD32开发实战指南(基础篇) 第9章 呼吸灯
下一篇:【技术分享】星空派GD32开发板LVGL移植经验分享

推荐阅读最新更新时间:2024-11-12 00:42

STM8单片机实现蓝牙串口通信系统的设计
最近在淘宝逛的时候发现了一款单片机,STM8。相比之前一直使用的也是8位的AVR相比,感觉STM8更为强大,芯片特点如下: 内核:具有3级流水线的哈佛结构、扩展指令集 程序存储器:8K字节Flash;RAM:1K字节 数据存储器:640字节真正的数据EEPROM;可达30万次擦写 更重要的一点就是STM8系列若使用库编程的话,可以方便的不同芯片的程序移植。甚至可以方便的移植到STM32上面,大大减轻了更新硬件的重写程序的工作量。 ADC0832为8位分辨率A/D转换芯片,其最高分辨可达256级,可以适应一般的模拟量转换要求。其内部电源输入与参考电压的复用,使得芯片的模拟电压输入在0~5V之间。芯片转换时间仅为32μS,据有双数
[单片机]
STM8单片机实现蓝牙<font color='red'>串口通信</font>系统的设计
51单片机IO口模拟UART串口通信
#include reg52.h #include main.h #include smartcard.h #include stdio.h typedef enum { false, true }bool; #if 0 sbit PIN_RXD = P1^0; //接收发送同一个引脚定义 sbit PIN_TXD = P1^0; //接收发送同一个发送引脚定义 sbit PIN_CLK = P3^1; //智能卡时钟引脚定义 sbit PIN_3v5v = P3^2; //智能卡3v_5v引脚定义 sbit PIN_RST = P3^3; //智能卡复位引脚定义 sbit PIN_CMDVCC = P3^4; //智能卡CMD
[单片机]
51单片机 串口通信 中断
从一段程序开始 实现电脑向单片机发送一些数据,单片机返回Iget +数据 #include #define uchar unsigned char #define uint unsigned int unsigned char flag,a,i;//声明标志位 flag uchar code table = I get void init() { TMOD=0x20;//设置T1定时器工作方式为2 8位初值自动重装的8位定时器 这里设置了两个计数器的工作方式 TH1=0xfd; / /T1定时器装初值(高八位) 控制串口通信的波特率(由定时器1的溢出率控制) TL1=0xfd; //T1定时器装入初值(低八位)
[单片机]
STM32F103学习笔记——串口通信
在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。 下面就说一下使用printf需要做哪些配置。 有两种配置方法: 一、对工程属性进行配置,详细步骤如下 1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。 2、在main文件中重定义函数。如下: int fputc(int ch, FILE *f) // 发送数据 { USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USA
[单片机]
51单片机之串口通信 ---- 自学笔记
一、串口通信 1.1、计算机通信的基础 计算机通信:是将计算机技术与通信技术相结合,完成计算机与外部设备或计算机与计算机之间的信息交换,可分为两大类:并行通信与串行通信。 多微机系统的广泛应用与计算机网络技术的普及。 计算机通信是指计算机与外部设备或计算机与计算机之间的信息交换。 通信有两种方式:并行通信、串行通信。 在多微机系统以及现代测控系统中信息的交换多采用串行通信。 1.2、并行通信 将数据字节的各位用多条数据线同时进行传送。同时传送8个字节(下图 ) 串行通信控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收困难。 1.3、串行通信 将数据字节分成一位一位的形式存,在一条传输
[单片机]
51单片机之<font color='red'>串口通信</font> ---- 自学笔记
Java软件与单片机串口通信
从菜鸟级的电子爱好者到骨灰级的电子工程师,相信大多数人都经历过制作万年历,毕竟它比较全面的考察对单片机基础知识的掌握,并且体现了C语言编程模块化的思想。 而本文旨在介绍在完成制作万年历后实现软件通过RS-232串口对万年历进行控制。软件的编写采用的Java语言,当然,使用C++、VB等语言也可以编写出与单片机串口通信的软件,使用VB的MSCOMM控件可以更方便的实现串口通信。 Java软件方面 需要用到Comm包,下载地址和使用方法请自行Google一下。 部分代码: 1.找出电脑上的串口 static Enumeration portList=CommPortIdent
[单片机]
GD32 485发送异常最常见原因
相信有小伙伴们遇到过这样的问题,在使用GD32进行串口485发送的时候,明明发送了特定长度的数据,但从机就是不响应,现在就让我们来解析下最常见的一个原因。 我们先来看一段代码: 这是采用轮训方式进行485发送的函数,首先将485传输方向设置为发送,然后进行长度为len的数据发送,发送完成后将485传输方向设置为接收。看似这段代码没有问题,但当放在MCU上运行后发现,从机会少收到两个字节的数据。 这是因为,串口是有数据寄存器和移位寄存器,当最后一次判断TBE不为“0”并调用usart_data_transmit函数后,实际上倒数第二个字节的数据正在发送移位寄存器中对外发送,最后一个字节在数据寄存器中,此时如果将485传输方
[单片机]
<font color='red'>GD32</font> 485发送异常最常见原因
RS232串口通信详解
串口是计算机上一种非常通用的设备通信协议。 --------------------------------- 串口的引脚定义: 9芯 信号方向来自 缩写 描述 1 调制解调器 CD 载波检测 2 调制解调器 RXD 接收数据 3 PC TXD 发送数据 4 PC DTR 数据终端准备好 5 GND 信号地 6 调制解调器 DSR 通讯设备准备好 7 PC RTS 请求发送 8 调制解调器 CTS 允许发送 9 调制解调器 RI 响铃指示器 两个串口连接时,接收数据针脚与发送数据针脚相连,彼此交叉,信号地对应相接即可。 --------------------------------- 串口的电气特性:
[嵌入式]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved