STM32学习笔记---USART篇

目录

一、什么是串口

1、介绍        

2、USART的通信方式

3、USART的数据帧格式

4、USART通信原理图

5、串口通信的四要素

6、USART和UART的区别

二、如何配置串口

1、串口控制器框图

1.1 数据传输框图部分

①发送部分框图

②接收部分框图

1.2 波特率部分框图

1.3 通信协议控制部分

2、串口寄存器描述

2.1 状态寄存器 (USART_SR)

2.2 数据寄存器 (USART_DR)

2.3 波特率寄存器 (USART_BRR)

2.4 控制寄存器 1 (USART_CR1)

2.5 控制寄存器 2 (USART_CR2)

3、程序设计思路

①总体思路

②详细思路

三、具体使用串口

需求1:MCU与PC之间互相进行通信

需求2:PC发送字符控制LED

需求3:PC端上位机显示按键键值

需求4:  串口发送一个字符串,封装成函数

需求5:  串口接收一个字符串,封装成函数

需求6:PC控制开发板的灯、流水灯

需求7:板A按键控制板B的LED灯

四、补充内容

1、printf重定向函数

1.1 printf函数添加方法

1.2 怎么用

1.3 什么时候用

1.4 有什么用

1.5 说明

2、复用与重映射

2.1 复用与重映射的区别

2.2 复用功能怎么配置

3、STM32F103与GD32F407串口的配置


一、什么是串口

1、介绍        

                USART:(Universal Synchronous/Asynchronous Receiver/Transmitter)  通用同步/异步串行接收/发送器

                UART:(Universal Asynchronous Receiver/Transmitter)  通用异步串行接收/发送器

                串口是设备双方互相进行数据传输的一种通信协议,是众多通信协议中的其中一种,并且是使用频率比较高的一种通讯协议;串口中最重要的是两个设备间需要设置同一个波特率,否则无法进行同步,从而导致数据传输失败;串口通信通过转换芯片可以转换成 USB通信/232通信/485通信.

2、USART的通信方式

        异步串行全双工,并且串口通信只能一个设备对应另一个设备(一主一从),不能一对多

3、USART的数据帧格式

        数据帧格式:起始位(1位)+数据位(8位)+校验位(1位)+停止位(0.5位/1位/1.5位/2位)

        或数据帧格式(不用校验位):起始位(1位)+数据位(8位)+停止位(0.5位/1位/1.5位/2位)

        串口数据帧最多包含12个位

4、USART通信原理图

5、串口通信的四要素

        波特率+数据位+检验位+停止位

6、USART和UART的区别

        USART可以在同步或者异步通信状态下使用;而UART仅可以在异步通信状态下使用。

故当在异步通信的情况下,可以用USART.

二、如何配置串口

1、串口控制器框图

1.1 数据传输框图部分

①发送部分框图

发送数据中遇到的问题:

发送数据过程:内核写入数据到TDR,TDR并行自动载入到发送移位寄存器,发送移位寄存器再串行发送出去。

封装串口发送一字节函数思路:

void 串口发送一字节函数(要发送的数据)

{

   //等待之前发送的数据发送完成(等待状态寄存器的发送完成位(TC)置1)

    ①该位由软件序列清零(读取 USART_SR 寄存器,然后写入USART_DR 寄存器)

    ②TC 位也可以通过向该位写入‘0’来清零

   //将数据写入到发送TDR

}

②接收部分框图

接收数据中遇到的问题:

数据接收过程:数据串行接收到接收移位寄存器,接收移位寄存器再并行自动载入到RDR,最后内核读取RDR。

封装串口接受一字节函数思路:

返回值类型 串口接受一字节函数(void)

{

//等待之前接收的数据接收完成(等待状态寄存器的接收完成位(RXNE)置1)

①通过对 USART_DR 寄存器执行读入操作将该位清零。

 RXNE 标志也可以通过向该位写入零来清零。

//读取RDR并写入变量中

//返回变量

}

1.2 波特率部分框图

红色框:整数    紫色框:小数

fCK : 串口所挂载在总线上的时钟频率

USARTDIV= fCK/(8*(2-OVER8))/bps

波特率写入值的计算:需要将通过波特率计算出来的结果写入USART_BRR寄存器

方法1:

整数部分:

DIV_M=(强转取整数)USARTDIV

小数部分:

小数计算方法

化简后得:DIV_F= (强转取整数)(USARTDIV - DIV_M)*8*(2-OVER8)

波特率写入的值=DIV_M+DIV_F

示例代码:

方法2:

注意:此时得到的值带有小数部分,要想写入寄存器(寄存器只能存整数),二进制要整体左移四位,故十进制要扩大16倍

变形得到:USARTDIV= (fCK/(8*(2-OVER8))/bps)*16

波特率写入的值 = fCK / 波特率         //注意此时是16倍过采样

波特率写入的值 = fCK / 波特率 * 2;     //注意此时是8倍过采样

示例代码:

1.3 通信协议控制部分

      看下面的串口寄存器描述

2、串口寄存器描述

串口相关的寄存器


需要根据寄存器手册知道标准串口配置用哪个寄存器的哪些位

2.1 状态寄存器 (USART_SR)

--------------指示UART控制器运行的状态

位6 TC:发送完成 (Transmission complete)

通过检测此为是0表示之前的数据发送还没有完成,不能再次写数据到DR,需要等待

如果此位是1,表示之前的数据发送完成,可以再次写数据到DR,

位5 RXNE:读取数据寄存器不为空 (Read data register not empty)

在接收外界数据的时候,通过此位判断是否可以读数据

0  表示没有接收到数据                需要等待

1  表示已经接收到完整的数据     可以读

2.2 数据寄存器 (USART_DR)

---------包括发送数据寄存器(只读)和接收数据寄存器(只写)

2.3 波特率寄存器 (USART_BRR)

波特率写入的值 = fCK / 波特率         //注意此时是16倍过采样

波特率写入的值 = fCK / 波特率 * 2;     //注意此时是8倍过采样

具体方法在1.2波特率部分框图

2.4 控制寄存器 1 (USART_CR1)

位15  过采样倍数  一般选择16倍

位13 使能UART控制器

位12 字长  8位数据位

位3  发送使能

位2  接收使能

2.5 控制寄存器 2 (USART_CR2)

位12~位13 停止位  默认一个停止位

以上就是标准串口初始化的配置

3、程序设计思路

①总体思路

串口初始化函数

串口发送一字节函数

串口接收一字节函数

②详细思路

串口初始化函数:

void Usartx_init(u32 bps)

{

/*IO口控制器配置*/

//1、打开GPIOx的时钟

//2、配置GPIOX的模式---复用

//3、配置复用功能(IO复用)

/*Usartx控制器配置*/

//1、打开Usartx的时钟

//CR1

//1、字长--8位数据位(无奇偶检验位)

//2、发送器使能

//3、接收器使能

//CR2

//4、1个停止位

/*波特率配置*/

//波特率写入的值 = fCK / 波特率   16倍过采样

/*USARTx使能*/

}

串口发送一字节函数

{

   //等待之前发送的数据发送完成(等待状态寄存器的发送完成位(TC)置1)

    ①该位由软件序列清零(读取 USART_SR 寄存器,然后写入USART_DR 寄存器)

    ②TC 位也可以通过向该位写入‘0’来清零

   //将数据写入到发送TDR

}

串口接受一字节函数

{

//等待之前接收的数据接收完成(等待状态寄存器的接收完成位(RXNE)置1)

①通过对 USART_DR 寄存器执行读入操作将该位清零。

 RXNE 标志也可以通过向该位写入零来清零。

//读取RDR并写入变量中

//返回变量

}

三、具体使用串口

需求1:MCU与PC之间互相进行通信

分析:

通过原理图用到的串口控制器是串口1

PA9-----USART1_Tx

PA10----USART1_Rx

复用模式

标准串口的初始化:

/*
函数名: Usart1_init
函数功能:串口Usart1初始化
返回值:void
形参:u32 bps
函数说明:
USART1_TX --- PA9
USART1_RX --- PA10
GPIOx->AFR[0] 低
GPIOx->AFR[1] 高
*/

void Usart1_init(u32 bps)
{
	//打开GPIOA、Usart1的时钟
	RCC->AHB1ENR |= 1 << 0;
	/*IO口控制器配置*/
	//配置GPIOA的模式---复用
	GPIOA->MODER &= ~(0xf << 18);
	GPIOA->MODER |=   0xa << 18;
	
	//端口输出类型(输出)
	GPIOA->OTYPER &= ~(1<<9);//可不配置
	//端口输出速度	(输出)
	GPIOA->OSPEEDR &= ~(3<<18);//可不配置
	//上下拉配置(输入和输出)
	GPIOA->PUPDR &= ~(0xf << 18);//可不配置
	//配置复用功能(IO复用)
	GPIOA->AFR[1]|= 7 << 4;//将USART1_TX复用到PA9     
	GPIOA->AFR[1]|= 7 << 8;//将USART1_RX复用到PA10
	
	
	
	//打开Usart1的时钟
	RCC->APB2ENR |= 1 << 4;
	/*Usart1控制器配置*/
	//CR1
	USART1->CR1 &= ~(1 << 15);//过采样模式 0:16 倍过采样   1:8 倍过采样
	USART1->CR1 &= ~(1 << 12);//字长--8位数据位(无奇偶检验位)
	USART1->CR1 |= 1 << 3;//发送器使能
	USART1->CR1 |= 1 << 2;//接收器使能
	
	//CR2
	//设置1位停止位
	USART1->CR2 &= ~(3 << 12);
	//波特率配置
	USART1->BRR = 84000000 / bps ;
	
	//USART使能
	USART1->CR1 |= 1 << 13;
}

/*
函数名: Usart1_sendbyte
函数功能:串口发送一字节字符数据函数
返回值:void
形参:u8 byte
函数说明:

*/
void Usart1_sendbyte(u8 byte)
{
   //等待之前发送的数据发送完成(等待状态寄存器的发送完成位(TC)置1)
	 while(!(USART1->SR & (1 << 6)));
	//①该位由软件序列清零(读取 USART_SR 寄存器,然后写入USART_DR 寄存器)
	//②TC 位也可以通过向该位写入‘0’来清零

   //将数据写入到发送TDR
		USART1->DR = byte;
}

/*
函数名: Usart1_recbyte
函数功能:串口接受一字节字符数据函数
返回值:u8
形参:void
函数说明:

*/
u8 Usart1_recbyte(void)
{
	u8 data;
//等待之前接收的数据接收完成(等待状态寄存器的接收完成位(RXNE)置1)
	while(!(USART1->SR & (1 << 5)));
//①通过对 USART_DR 寄存器执行读入操作将该位清零。
//② RXNE 标志也可以通过向该位写入零来清零。
//读取RDR并写入变量中
	data = USART1->DR ;
//返回变量
	return data;
}

需求2:PC发送字符控制LED

PC机发送’O’  LED灯亮

PC机发送’N’  LED灯灭

分析:

电脑与407传输数据,选择串口通信

主控芯片接收字符,并且判断字符,决定灯的亮灭

主函数:

	    u8 Data = Usart1_recbyte();
		if(Data == 'O')
		{
			LED_ON;
		}
		else if(Data == 'N')
		{
			LED_OFF;
		}

需求3:PC端上位机显示按键键值

PC端显示所按按键编号

407按哪个按键,就通过串口把对应的按键编号字符发送过去

分析:

按键扫描函数会返回所按按键的键值,键值十进制形式的数据,由于上位机(串口软件)不能识别十进制的数据(除非使用printf重定向函数),只能识别字符或十六进制的数据,所以把键值的十进制数据转换成字符的形式 (4----’4’),把转换后的字符发送出去,数据+48

主函数:

        u8 Keynum;
		Keynum = Key_scan();
		switch(Keynum)
		{
			case 1:Usart1_sendbyte(Keynum+48);break;
			case 2:Usart1_sendbyte(Keynum+48);break;
			case 3:Usart1_sendbyte(Keynum+48);break;
			case 4:Usart1_sendbyte(Keynum+48);break;
		}

需求4:串口发送一个字符串,封装成函数

分析:

通过循环结构将字符串中的每个字符发出去

什么时候表示要发送的字符串发送完成

字符串的特点是在结束的位置有一个结束字符 ‘\0’, 通过结束字符决定发送完成

最后最好补上个‘\0’

发送一个字符串函数:


/*
函数名: Usart1_sendstr
函数功能:串口发送一字节字符串函数
返回值:void
形参:u8 *str
函数说明:

*/
void Usart1_sendstr(u8 *str)
{
	 while(*str != '\0')
	 {
		 Usart1_sendbyte(*str);
		 str++;
	 }
	*str = '\0';
		
}	

需求5:串口接收一个字符串,封装成函数

分析:

接收一个字符串要一个字符一个字符的接收,通过循环结构调用接收一个字符函数

思考:如何知道接收完成了?

方案1:人为设定一个特殊字符,比如#

接收完成后人为加结束字符,形成一个完整字符串

方案2:接收中断和空闲中断

中断篇补充

发送一个字符串函数:

/*
函数名: Usart1_recstr
函数功能:串口接收一字节字符串函数
返回值:void
形参:u8 *str
函数说明:

*/
void Usart1_recstr(u8 *str)
{
	while(1)
	{
		*str = Usart1_recbyte();
		if(*str == '#')
		{
			break;
		}
		str++;
		
	}
	*str = '\0';
	
	
	
}

需求6:PC控制开发板的灯、流水灯

STM32F407(MCU)接收字符串命令开关灯

        Usart1_recstr(buff);
		if(strcmp((char const*)buff,"OPEN") == 0)
		{
			LED_ON;
		}
		if(strcmp((char const*)buff,"OFF") == 0)
		{
			LED_OFF;
		}
		//strcmp(数组名1,数组名2) 字符串比较(按照字符的ASCLL码比较'\0'之前的字符)  
		//数组名1>数组名2  1    数组名1=数组名2  0    数组名1<数组名2  -1

为什么不需要memset清数组?

//strcmp(数组名1,数组名2) 字符串比较(按照字符的ASCLL码比较'\0'之前的字符)  

//数组名1>数组名2  1    数组名1=数组名2  0    数组名1<数组名2  -1

需求7:板A按键控制板B的LED灯

板A按键控制器板B的LED灯,

板B将开状态的LED灯编号反馈给上位机

分析:

分清楚哪个是发送板,哪个是接收板,串口1是板子与PC通讯的,串口2是两个板子之间的通讯。

紧接着把程序一段段分别烧录到发送板和接收板里。

	//发送板
	#ifdef First
	u8 Keynum;
	Keynum = Key_scan();
  switch(Keynum)
	{
		case 1:Usart2_sendbyte(Keynum);break;
		case 2:Usart2_sendbyte(Keynum);break;
		case 3:Usart2_sendbyte(Keynum);break;
		case 4:Usart2_sendbyte(Keynum);break;
	}
	//接收板
	#else
	u8 data;
	data = Usart2_recbyte();
	switch(data)
	{
		case 1:LED1_OVERTURN;Usart1_sendbyte(data+48);break;
		case 2:LED2_OVERTURN;Usart1_sendbyte(data+48);break;
		case 3:LED3_OVERTURN;Usart1_sendbyte(data+48);break;
		case 4:LED4_OVERTURN;Usart1_sendbyte(data+48);break;
	}
	#endif

四、补充内容

1、printf重定向函数

1.1 printf函数添加方法

方法1:

①勾选微库   如果不勾选,主函数不执行

小补充:

什么是微库?---MicroLib是针对以C语言编写的基于ARM嵌入式应用程序的高度优化的库

MicroLib 和标准 C 库之间的主要区别在于:

MicroLib 专为深度嵌入式应用而设计。

MicroLib 经过优化,与使用 ARM 标准库相比,使用更少的代码和数据存储器。

MicroLib 最大程度优化代码量,可能会导致有些库函数运行速度更慢

MicroLib 和 ARM 标准库都包含在 Keil MDK-ARM 中。
我们如果要使用,直接在keil中使用即可。

②包含”stdio.h”后编译

③Ctrl+F查找 fputc

④找到fputc函数的外部声明

⑤修改并调用fputc函数

方法2:

直接粘贴这段代码到Usartx.c里

#if 1
	#pragma import(__use_no_semihosting)             
	//标准库需要的支持函数                 
	struct __FILE 
	{ 
		int handle; 
		/* Whatever you require here. If the only file you are using is */ 
		/* standard output using printf() for debugging, no file handling */ 
		/* is required. */ 
	}; 
	/* FILE is typedef’ d in stdio.h. */ 
	FILE __stdout;       
	//定义_sys_exit()以避免使用半主机模式    
	int _sys_exit(int x) 
	{ 
		x = x; 
	} 
	//重定义fputc函数 
	int fputc(int ch, FILE *f)
	{      
		while((USART1->SR&(1<<6))==0);//循环发送,直到发送完毕   
		USART1->DR = (u8) ch;      
		return ch;
	}
	#endif

包含”stdio.h”后编译

1.2 怎么用

        需要添加一个printf重定向函数,跟C语言的用法一样。

        在C语言中使用的是printf打印到输出窗口。而我们用keil进行编程的时候也是使用的C语言所以也可以使用printf,但是我们知道,keil中没有输出窗口。那我们如何使用printf呢?这里我们只需要重定向一下,将printf重定向到USART1(串口1)–这样我们就能通过串口1将信息打印到上位机(串口助手)。这里也有一个小知识点:下载程序也是通过串口1下载到单片机的内存中。

1.3 什么时候用

       使用printf函数仅能向串口助手发送,并且printf函数可在串口助手上打印任意字符或数字

       当串口与电脑同通信时,串口软件不能识别十进制的数据(除非使用printf重定向函数),只能识别字符或十六进制的数据;当两个设备通信互相传输数据时,用串口发送。

1.4 有什么用

        开发产品的时候调试程序使用

        通过打印一句话证明CPU是否执行这段程序;通过打印产生的数据显示在串口软件上,分析数据。

1.5 说明

 ①fputc函数的作用是从指定的文件中读取一个字符, 读取成功时会返回读取到的字符,读取到文件末尾或读取失败时返回EOF

fputc不需要声明和调用并且printf换行需要”\r\n”

2、复用与重映射

2.1 复用与重映射的区别

①IO复用:IO引脚身兼多职的现象  同一个IO口可以被多个模块(片上外设)使用(不同时)

②重映射:将某个模块(片上外设)的复用功能移动到其它IO口上  

             芯片上某一个引脚可能具有两种或两种以上的复用功能时,当这两种复用功能发生冲突时,将IO口上的其中一个功能映射到其它IO口上就能解决这个冲突了,这就是重映射的作用 

2.2 复用功能怎么配置

USART属于片上外设

如果片上外设需要与外界建立通信需要借助GPIO,故需要IO复用

说明:

每个IO口都有自己固定的复用功能,并不是每个IO口都可以复用任何功能

如何知道IO口具体的复用功能------------查表(数据手册)

如何配置复用功能

①确定IO(根据具体的复用功能确定IO)   

根据自己需要的复用功能去查看哪个管脚支持这个复用功能(STM32F407VET6)     通常通过原理图就可以确定/数据手册--->以USART1为例子

USART1_TX   ---  PA9     ---   复用模式

USART1_RX   ---  PA10    ---   复用模式

②确定复用功能低位寄存器 和 复用功能高位寄存器 

低位

高位

看复用功能高位寄存器     PA9:[7-4]         PA10:[11-8]   

③确定标识

USART1对应标识为AF7

PA9:[7-4]       -->  AF7    

PA10:[11-8]     -->  AF7

④确定数值    AF7 : 7

高位AFR[1]

低位AFR[0]

去查看具体寄存器下面的说明(中文参考手册)   

            GPIOA->  AFR[1]|= 7 << 4;     //将PA9复用到USART1      

            GPIOA->  AFR[1] |= 7 << 8;     //将PA10复用到USART1

3、STM32F103与GD32F407串口的配置

3.1 STM32F103串口初始化函数:

以USART1为例子

void Usartx_init(u32 bps)

{

/*IO口控制器配置*/

复用重映射和调试I/O配置寄存器(AFIO_MAPR):

//1、打开GPIOA/GPIOB的时钟和AFIO的时钟    开不开AFIO时钟取决于是否使用重映射

//2、配置GPIOA/GPIOB的模式

// 配置PA9/PB6为复用推挽输出(USART1_TX)

// 配置PA10/PB7为浮空输入(USART1_RX)

//3、配置复用功能(IO复用)

PA9、PA10

复用重映射和调试I/O配置寄存器  (AFIO_MAPR) USART1_REMAP = 0  (本身复用功能---重映像配置可不配置)

PB6、PB7

复用重映射和调试I/O配置寄存器   (AFIO_MAPR) USART1_REMAP = 1 (重映射复用功能

/*波特率配置*/

//波特率写入的值 = fCK / 波特率   16倍过采样

/*Usart1控制器配置*/

//1、打开Usart1的时钟

//CR1

//1、字长--8位数据位(无奇偶检验位)

//2、发送器使能

//3、接收器使能

//CR2

//4、1个停止位

/*USART1使能*/

}

发送一字节函数与接收一字节函数的与STM32F407的一样

总结:STM32F103的串口配置只有IO口控制器配置部分与STM32F407不一样,其它的都差不多

3.2 GD32F407串口初始化函数:

串口初始化、发送数据、接收数据的配置方法与STM32F407一样,注意串口号是从0到5,共6个串口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值