12:(标准库)串口通信一:使用USART发送/接收数据

1、串口通信

1.1、简介

   通过串口接收/发送数据。其中串口拥有TX(发送数据)和RX(接收数据)引脚。2个设备通过串口接收/发送数据时,这2个引脚需要交替连接。

在这里插入图片描述

1.2、数据帧

1.2.1、简介

   串口接收/发送的数据是以数据帧进行传输的,那什么是数据帧呢?数据帧 = 起始位+数据位+停止位。起始位是低电平0,停止位是高电平1,而数据位可以是8位/9位。其中数据位中还包含1位校验位(有程序员自己规定)。而传输的时候是先传输数据帧的低位,在传高位

在这里插入图片描述
数据进行传输时,如果不是连续传输时,则2个数据帧之间还有空闲位,它为高电平。

在这里插入图片描述

1.2.2、校验规则

   校验规则分为:奇校验和偶校验,就是查询1的个数。

在这里插入图片描述
例如:发送方设定为奇校验,并且是9位数据位。当发送时,前8位数据位中有偶数个1,那么最后1位校验位则是1,这样就凑成了奇数个1。如果传输时出现了错误,接收方进行校验时,则检测到了偶数个1,则代表传输出现了错误。都是不能检测出是那一位出现了错误。

在这里插入图片描述

1.2.3、停止位的长度

   停止位的长度可以是0.5位/1位/1.5位 /2位。可以由程序员规定。

在这里插入图片描述

1.3、异步通信的波特率

1.3.1、同步通信

   2个设备进行同步通信时,则2个设备之间拥有1个时钟线(CLK)和1个数据线,例如 I2C。当时钟线位低电平时,主设备给数据线上写数据,当位高电平时,不能在写数据,从设备进行数据的读取。从设备读取成功后,给主设备返回一个应答信号,主设备继续写数据。这就是同步通信。

在这里插入图片描述

1.3.2、异步通信

   异步通信和同步通信不同,没有时钟线,拥有2个数据线进行发送(TX)和接收(RX)数据。控制串口通信发送数据的速度是波特率和硬件流控。波特率就是没1s发送码元的个数(9600代表1s传输了9600个码元,传输1个码元占用了0.104ms),在计算机中每一位都代表一个码元。波特率越大,传输速率越快。

在这里插入图片描述
在这里插入图片描述

异步通信没有时钟线,那么通信是怎样数据传输的喃?答案是:进行数据的采集,例如当接收方采用相同的9600的波特率0时,就代表它采集间隔时间是0.104ms。接收方在空闲时进行高密度采集,当采集到一个下降沿时,代表后面的是数据了,所以间隔1.5个码元进行采集(也是高密度采集),刚好采集到数据位第一个码元的正中间,然后后面就每隔0.104ms进行采集。

1.3.3、硬件流控

   硬件流控的作用主要是防止传输的数据丢失,当传输一个数据帧时,等待接收方返回一个反馈信号。如果反馈的是一个低电平,代表接收方可以继续接收数据,然后发送方继续发送数据。

在这里插入图片描述

2、USART

2.1、简介

   USART也是STM32中的一个片上外设,用于异步串口通信。STM32F103C8T6一共有3个USART,分别为USART1,USART2,USART3。其中USART1挂载在RCC_APB2时钟总线上,所以使用时需要打开时钟。而2和3挂载在RCC_APB1时钟总线上面。

在这里插入图片描述

2.2、工作的原理

   当单片机通过串口向外发送数据时,CPU向发送数据寄存器TDR写入数据,写入的时候是并行写入,然后进入并行转串行。打开发送控制器,通过TX引脚进行数据的串行发送。当单片机进行数据的接收时,RX接收来的串行数据,通过串行转并行,写入到接收寄存器RDR,然后CPU进行数据的读取。

在这里插入图片描述

2.3、相关寄存器

  如下图为USART的结构图,他拥有如此多的寄存器,那这些寄存器拥有上面作用喃?交具体我们分析下

在这里插入图片描述

  • CR:配置寄存器
    在这里插入图片描述

    UE:USART的总开关,为0,USART禁止,为1,USART打开。需要配置波特率时,也需要打开这个开关。
    TXE: 数据传输开关
    RXE:数据接收开关
    M:数据位长度选择,M = 0:数据位为8位,M = 1:数据位为9位
    STOP:停止位长度选择,00为1位,01为0.5位,10为2位,11为1.5位
    PE/PCE:奇偶校验使能,0关闭校验位,1打开校验位
    PS:奇偶选择位,0偶校验,1奇校验
    
  • BRR:波特率寄存器

    在这里插入图片描述
    主要是用来配置分频器的系数,波特率 = RCC时钟/(分频系数 * 16)。例如需要9600波特率,选用72MHz时钟时,算出分频系数为468.75。
    在这里插入图片描述

  • SR:状态寄存器

在这里插入图片描述

   TXE:发送数据寄存器空,当发送数据寄存器TDR为空时,TXE为1,代表可以向TDR写入数据
   RXNE:接收数据寄存器非空,为0,代表接收数据寄存器RDR没有数据,为1,代表接收数据寄存器有数据,可以把数据读出来
   TC(TDR空&&移位寄存器空):发送完成寄存器,为0代表还在发送,为1代表发送完成
   PE:奇偶校验错,为1,代表传输有出差
   FE:帧格式错,为1,代表帧格式出错
   NE:噪声错,为1,代表信号里面有噪声
   ORE:过载错,为1,代表没有及时的将RDR里面的数据读出来,导致里面的数据被覆盖丢失。

3、标准库编程

3.1、编程接口

   如下图所示,为常用的标准库编程接口。

在这里插入图片描述

  • USART_Init()

    在这里插入图片描述
    在这里插入图片描述

    USART_InitTypeDef USARTInitStruct;
    USARTInitStruct.USART_WordLength = USART_WordLength_8b;
    USARTInitStruct.USART_Parity = USART_Parity_No;
    USARTInitStruct.USART_StopBits = USART_StopBits_1;
    USARTInitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USARTInitStruct.USART_BaudRate = 9600;
    USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART1 ,&USARTInitStruct);
    
  • USART_Cmd

    在这里插入图片描述
    其实就是CR寄存器中的UE,USART的使能开关。

    USART_Cmd(USART1 ,ENABLE);//使能USART1
    
  • USART_SendData

    在这里插入图片描述

    USART_SendData(USART1,0x5a);//数据是16位,2个字节
    
  • USART_ReceiveData

    在这里插入图片描述

    uint8_t a = USART_ReceiveData(USART1); //将读取到的数据存储在变量a中
    
  • USART_GetFlagStatus

    在这里插入图片描述

    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
    while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);
    if(USART_GetFlagStatus(USART1,USART_FLAG_PE)==SET)
    {
    }
    

4、程序代码

  我们使用USART1让STM32和上位机进行串口通信。

4.1、使用串口发送数据

   USART1的Tx连接的引脚是PA9,Rx连接的引脚是PA10,我们为了增加一下难度,我们使用USART1引脚的重映射。查阅数据手册如下。
在这里插入图片描述
①UART1.c文件的代码如下:

#include "UART.h"

/**
 * 串口1的初始化函数
 */
void UART1_Init(void)
{
    /* 开启串口的UART1的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    /* 开启串口的GPIO的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置串口1的引脚 */
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;// 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 配置串口1的模式 */
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收发模式
    USART_InitStruct.USART_Parity = USART_Parity_No;// 无奇偶校验位
    USART_InitStruct.USART_StopBits = USART_StopBits_1;// 1个停止位
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;// 8个数据位
    USART_Init(USART1, &USART_InitStruct);
    
    /* 使能串口中断 */
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    /* 配置NVIC */
    //优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStruct);

    /* 使能串口1 */
    USART_Cmd(USART1, ENABLE);
}

/**
 * 串口发送一个字节的数据
 */
void USART_SendChar(uint8_t ch)
{
	/* 等待发送数据寄存器为空 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    /* 搬运一个数据 */
    USART_SendData(USART1, ch);
}

/**
 * 串口发送一个字符串的数据
 */
void USART_SendString(uint8_t *str)
{
    /* 发送多个字节的数据 */
    while (*str!= '\0')
    {
        USART_SendChar(*str++);
    }
}

/**
 * 串口发送多个字节的数据
 */
void USART_SendArray(uint8_t *array, uint16_t len)
{
    /* 发送一组数据 */
    for (uint16_t i = 0; i < len; i++)
    {
        USART_SendChar(array[i]);
    }
}

/**
 * 对printf函数进行重定向
 */
int fputc(int ch, FILE *f)
{
    /* 等待发送数据寄存器为空 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    /* 发送一个字节的数据 */
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}

②UART.h文件的代码如下:

#ifndef __UART_H
#define __UART_H
#include "stm32f10x.h"
#include "stdio.h"

void UART1_Init(void);
void USART_SendChar(uint8_t ch);
void USART_SendString(uint8_t *str);
void USART_SendArray(uint8_t *array, uint16_t len);

#endif

③主函数main.c文件的代码如下:

#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"

int main(void)
{
	UART1_Init();
    
    uint8_t Data = 'A';
    USART_SendChar(Data);//发送一个字节
    
    uint8_t string[] = "你好啊";
    USART_SendString(string);//发送一串字符
    
    uint8_t Datas[] = {'A','B','C','D'};
    USART_SendArray(Datas,sizeof(Datas)/sizeof(uint8_t));//发送多个字节
    
    printf("nihaos");//printf打印发送
	
	while(1)
    {
        
    }
}

在这里插入图片描述

4.2、使用串口接收数据

数据接收一般使用串口的接收中断进行数据的接收。
实验:串口接收多少数据,原样将数据通过串口发送出去
①UART1.c文件的代码如下:

#include "UART.h"


uint8_t Buffer[256];//定义一个256个字节存储空间,用于存储串口接收到的数据
uint16_t Index = 0; //Buffer的下标

/**
 * 串口1的初始化函数
 */
void UART1_Init(void)
{
    /* 开启串口的UART1的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    /* 开启串口的GPIO的时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 配置串口1的引脚 */
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;// 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 配置串口1的模式 */
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 不使用硬件流控制
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收发模式
    USART_InitStruct.USART_Parity = USART_Parity_No;// 无奇偶校验位
    USART_InitStruct.USART_StopBits = USART_StopBits_1;// 1个停止位
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;// 8个数据位
    USART_Init(USART1, &USART_InitStruct);
    
    /* 使能串口中断 */
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//使能接收数据中断,即RXNE置1触发中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//使能接收空闲帧中断,代表没有数据来
    
    /* 配置NVIC */
    //优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStruct);
    
    /* 使能串口1 */
    USART_Cmd(USART1, ENABLE);
}

/**
 * 串口发送一个字节的数据
 */
void USART_SendByte(uint8_t ch)
{
    /* 发送一个字节的数据 */
    USART_SendData(USART1, ch);

    /* 等待发送数据寄存器为空 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

/**
 * 串口发送一个字符串的数据
 */
void USART_SendString(uint8_t *str)
{
    /* 发送多个字节的数据 */
    while (*str!= '\0')
    {
        USART_SendByte(*str++);
    }
}

/**
 * 串口发送多个字节的数据
 */
void USART_SendArray(uint8_t *array, uint16_t len)
{
    /* 发送一组数据 */
    for (uint16_t i = 0; i < len; i++)
    {
        USART_SendByte(array[i]);
    }
}

/**
 * 对printf函数进行重定向
 */
int fputc(int ch, FILE *f)
{
    /* 发送一个字节的数据 */
    USART_SendData(USART1, (uint8_t)ch);

    /* 等待发送数据寄存器为空 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

    return ch;
}

/**
 * 中断服务函数
 */
void USART1_IRQHandler(void)
{
    uint8_t Receive_Data;
    /* 判断是否为接收中断 */
    if (USART_GetFlagStatus(USART1, USART_IT_RXNE))
    {   
        if(Index > 256)//如果接收到的数据个数大于数组范围
        {
            USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);//停止接收,防止数组越界
        }
        /*将接收到的数据存储到Buffer里面*/
        Buffer[Index++] = USART_ReceiveData(USART1);//不用清除RXNE标志位,因为读DR就是在清除   
    }
    
    /* 判断是否的空闲帧中断 */
    if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE))
    {
        Receive_Data = USART1->SR;  
        Receive_Data = USART1->DR;//清除标志位
        
        USART_SendArray(Buffer, Index); //将接收到的数据发送出去
        memset(Buffer,0,Index);         //清除数组
        Index = 0;                      //让数组下标为0
    }  
}

②主函数main.c文件的代码如下:

#include "stm32f10x.h"                 
#include "UART.h"

int main(void)
{
	UART1_Init();
	while(1)
    {

    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值