串口通信

USART为通用同步/ 异步收发器。stm32F103RC内置了3个通用同步/异步收发器(USART1、USART2和USART3),和2个通用异步收发器(UART4和UART5)。
这5个接口提供异步通信、支持IrDA SIR ENDEC传输编解码、多处理器通信模式、单线半双工通信模式和LIN主/从功能。USART1接口通信速率可达4.5兆位/秒,其他接口的通信速率可达2.25兆位/秒。USART1、USART2和USART3接口具有硬件的CTS和RTS信号管理、兼容ISO7816的智能卡模式和类SPI通信模式,除了UART5之外所有其他接口都可以使用DMA操作。

串行通信工作原理

串行通信,即数据一位一位得按顺序发送。并行通信,即数据的各位同时发送。


串口通信_第1张图片
串行通信和并行通信.PNG

串口通信_第2张图片
串行通信和并行通信对比.PNG

串行通讯的分类

  1. 按通讯方式
    同步通讯:带时钟同步信号传输。比如:SPI(全双工),IIC(半双工)通信接口。
    异步通讯:不带时钟同步信号。比如:UART(全双工),单总线(半双工)。
  2. 按数据传送方向
    单工:只支持数据在一个方向上传输;
    半双工:允许数据在两个方向上传输,某一时刻只有一个传输方向;
    全双工:允许数据同时在两个方向上传输。
    TTL RS232接口等指的是电平标准(不是硬件,硬件都是GND/RX/TX)(单片机的电平标准(TTL电平):+5V表示1,0V表示0;Rs232的电平标准:+15/+13 V表示0,-15/-13表示1。)故如果需要在两种不同接口之间实现串口通讯需要电平转换电路。PL2303、CP2102、CH340芯片是 USB 转 TTL串口 的芯片,MAX232芯片是 TTL电平与RS232电平的专用双向转换芯片,可以TTL转RS-232,也可以RS-232转TTL。


    串口通信_第3张图片
    不同电平比较.PNG

    UART串口通信的数据包以帧为单位,常用的帧结构为:1位起始位+8位数据位+1位奇偶校验位(可选)+1位停止位。奇校验是指每帧数据中,包括数据位和奇偶校验位在内的全部9个位中1的个数必须是奇数,偶校验同理。

STM32F103串口

串口通信_第4张图片
USART框图.PNG

RX:接收数据串行输。通过过采样技术来区别数据和噪音,从而恢复数据。
TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,
并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发
送和接收。
● 总线在发送或接收前应处于空闲状态
● 一个起始位
● 一个数据字(8或9位),最低有效位在前
● 0.5,1.5,2个的停止位,由此表明数据帧的结束
● 使用分数波特率发生器 —— 12位整数和4位小数的表示方法。
● 一个状态寄存器(USART_SR)
● 数据寄存器(USART_DR)
● 一个波特率寄存器(USART_BRR),12位的整数和4位小数
● 一个智能卡模式下的保护时间寄存器(USART_GTPR)
同步模式中需要下列引脚:
● CK:发送器时钟输出。此引脚输出用于同步传输的 时钟, (在Start位和Stop位上没有时钟
脉冲,软件可选地,可以在最后一个数据位送出一个时钟脉冲)。数据可以在RX上同步被接
收。这可以用来控制带有移位寄存器的外部设备(例如LCD驱动器)。时钟相位和极性都是软
件可编程的。在智能卡模式里,CK可以为智能卡提供时钟。
IrDA模式里需要下列引脚:
● IrDA_RDI: IrDA模式下的数据输入。
● IrDA_TDO: IrDA模式下的数据输出。
下列引脚在 硬件流控模式中需要:
● nCTS: 清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送。
● nRTS: 发送请求,若是低电平,表明USART准备好接收数据
串口通信_第5张图片
字长设置.PNG

可配置的停止位
随每个字符发送的停止位的位数可以通过控制寄存器2的位13、12进行编程。

  1. 1个停止位:停止位位数的默认值。
  2. 2个停止位:可用于常规USART模式、单线模式以及调制解调器模式。
  3. 0.5个停止位:在智能卡模式下接收数据时使用。
  4. 1.5个停止位:在智能卡模式下发送和接收数据时使用。
    配置步骤:
  5. 通过在USART_CR1寄存器上置位UE位来激活USART
  6. 编程USART_CR1的M位来定义字长。
  7. 在USART_CR2中编程停止位的位数。
  8. 如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中的描述配置DMA寄存器。
  9. 利用USART_BRR寄存器选择要求的波特率。
  10. 设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送。
  11. 把要发送的数据写进USART_DR寄存器(此动作清除TXE位)。在只有一个缓冲器的情况下,对每个待发送的数据重复步骤7。
  12. 在USART_DR寄存器中写入最后一个数据字后,要等待TC=1,它表示最后一个数据帧的传输结束。当需要关闭USART或需要进入停机模式之前,需要确认传输结束,避免破坏最后一次传输。
    USART寄存器介绍
    1、 状态寄存器(USART_SR)


    串口通信_第6张图片
    状态寄存器.PNG

    串口通信_第7张图片
    表1.PNG

    串口通信_第8张图片
    表2.PNG

    2、 数据寄存器(USART_DR)


    串口通信_第9张图片
    数据寄存器.PNG

    串口通信_第10张图片
    USART_DR.PNG

    3、 波特比率寄存器(USART_BRR)
    串口通信_第11张图片
    波特比特率寄存器.PNG

    串口通信_第12张图片
    波特率计算.PNG

上式中,fpclkx 是给串口的时钟(PCLK1 用于 USART2、3、4、5,PCLK2 用于 USART1);
USARTDIV 是一个无符号定点数。
4、 控制寄存器 1(USART_CR1)


串口通信_第13张图片
控制寄存器表1.PNG
串口通信_第14张图片
控制寄存器表2.PNG
串口通信_第15张图片
控制寄存器表3.PNG

CR2和CR3主要用于同步串行控制和流控制。(不做介绍)其中USART_CR2的第[13:12]位为STOP位,为00b表示1位停止位(默认),01b表示0.5位停止位,10b表示2位停止位,11b表示1.5位停止位。

开发板实验及例程分析

开发板自带的usart文件夹内包含了 usart.c 和 usart.h 两个文件。这两个文件用于串口的初始化和中断接收。这里只是针对串口 1。usart.c里面包含了2个函数一个是void USART1_IRQHandler(void);另外一个是void uart_init(u32pclk2,u32 bound);里面还有一段对串口 printf 的支持代码,如果去掉,则会导致 printf 无法使用,虽然软件编译不会报错,但是硬件上 STM32 是无法启动的,这段代码不能修改。
当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2个字节组成:0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待 0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到0X0D,那么在接收数据超过USART_REC_LEN的时候,则会丢弃前面的数据,重新接收。

寄存器

1、USART1_IRQHandler 函数

#if EN_USART1_RX //如果使能了接收
//串口 1 中断服务程序
//注意,读取 USARTx->SR 能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大 USART_REC_LEN 个字节.
//接收状态
//bit15,接收完成标志
//bit14,接收到 0x0d
//bit13~0,接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void USART1_IRQHandler(void)
{
u8 res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntEnter();
#endif
if(USART1->SR&(1<<5)) //接收到数据
{
res=USART1->DR;
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}else //还没收到 0X0D
{
if(res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=res;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntExit();
#endif
}
#endif

该部分为串口1的中断服务函数。先判断状态寄存器是否接收到数据,接收到数据把数据放入一段缓存区中。每次结束后,USART_RX_STA清零,再存数据时会覆盖之前的存放位置。(USART_RX_STA类似于跟踪每次存储的数据(8位或9位的帧))
2、uart_init 函数

//初始化 IO 串口 1
//pclk2:PCLK2 时钟频率(Mhz)
//bound:波特率
void uart_init(u32 pclk2,u32 bound)
{
float temp;
u16 mantissa;
u16 fraction;
temp=(float)(pclk2*1000000)/(bound*16);//得到 USARTDIV
mantissa=temp; //得到整数部分
fraction=(temp-mantissa)*16; //得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2; //使能 PORTA 口时钟
RCC->APB2ENR|=1<<14; //使能串口时钟
GPIOA->CRH&=0XFFFFF00F;//IO 状态设置
GPIOA->CRH|=0X000008B0;//IO 状态设置
RCC->APB2RSTR|=1<<14; //复位串口 1
RCC->APB2RSTR&=~(1<<14);//停止复位
//波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C; //1 位停止,无校验位.
#if EN_USART1_RX //如果使能了接收
//使能接收中断
USART1->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART1_IRQn,2);//组 2,最低优先级
#endif
}

先配置PA9和PA10所在的IO口,时钟配置,分别为下拉输入配置和复用开漏输出配置。RCC->APB2RSTR为为时钟APB2外设的复位寄存器,复位串口1,停止复位。设置波特率(9600为234,115200为271)。设置串口控制寄存器(第12位设置M位(0表示8,1表示9),13位表示开启串口、5位表示接受数据开启中断,3位表示开启发送单元、2位表示接收有效位)0X200C=0010000000001100开启串口,开启发送和接收单元。该程序在添加了条件判断后,使能了接收中断。并调用正点原子的中断管理函数。
3、主函数

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
int main(void)
{
u8 t; u8 len;
u16 times=0;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600); //串口初始化为 9600
LED_Init(); //初始化与 LED 连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n 您发送的消息为:\r\n");
for(t=0;tDR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nALIENTEK MiniSTM32 开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁 LED,提示系统正在运行.
delay_ms(10);
}
}
}

主函数把接收到的数据发送出来(写DR寄存器)。

库函数

思路与寄存器类似。
1、 USART1_IRQHandler 函数

void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
//接收中断(接收到的数据必须是 0x0d 0x0a 结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到 0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntExit();
#endif
}

2、 uart_init 函数

void uart_init(u32 bound){
//GPIO 端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA
|RCC_APB2Periph_AFIO, ENABLE); //使能 USART1,GPIOA 时钟
//以及复用功能时钟
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.9 发送端
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10 接收端
//Usart1 NVIC 中断配置 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //对应中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //中断优先级配置
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl=
USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx |USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
USART_Cmd(USART1, ENABLE); //使能串口
}

3、主函数

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
int main(void)
{
u8 t;
u8 len;
u16 times=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断分组
uart_init(9600); //串口初始化为 9600
LED_Init(); //初始化与 LED 连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n 您发送的消息为:\r\n");
for(t=0;tDR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nALIENTEK MiniSTM32 开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁 LED,提示系统正在运行.
delay_ms(10);
}
}
}

分析

目标实现:用PC上的串口助手和单片机串口1进行通讯,双方能够接受发送数据。(单片机发送的数据位从PC处接受的数据,故需要把接受到的数据存储起来。发送停止标识为回车。)

步骤:
1、串口初始化,配置串口通信的波特率、设置串口数据帧的格式开启串口接受中断、打开接收和发送单元。
2、接收数据。接收到数据后,开启中断服务函数,把数据存储起来,并判断接收是否完成。(通过串口接收中断实现,需要开启串口接收中断)
3、发送数据。判断前一个数据是否发送完成,发送完成即可发送本次数据。

STM32Cube实例

1)printf函数重定向(printf内部函数要调用fputc函数,故通过对库函数fputc的重定义,即可实现。)

#include "stdio.h"
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0xFFFF);
    return ch;
}

2)使用中断接收
1、开启接收中断(如HAL_UART_Receive_IT(&huart2, (uint8_t *)kRxBuffer, 10),为开启串口2的中断,把接收到的数据放在kRxBuffer,当接收到10个数据后调用callback函数。串口接收完数据后会关闭使能,故在回调函数中需要再写一次该函数。)
2、在回调函数中实现收到数据之后的操作
3、发送中断类似。使用HAL_UART_Transmit_IT函数发送指定长度的数据,并使能发送中断,发送到一半和发送结束会触发中断(相关的回调函数是HAL_UART_TxHalfCpltCallback()HAL_UART_TxCpltCallback())中断触发后发送中断使能会被清除,然后调用回调函数,回调函数执行完成之后结束本次发送。
开启接收中断

 HAL_UART_Receive_IT(&huart1,(uint8_t*)&aRxBuffer,1);

回调函数(部分代码)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
        Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
    
        if((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
        {
            HAL_UART_Transmit(&huart1, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
            Uart1_Rx_Cnt = 0;
            memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //清空数组
        }
}

你可能感兴趣的:(串口通信)