STM32F103(六)——USART程序

前言

——本章为对串口通信的一种(USART)进行总结

————————————————————————————————————————

目录

—— 1.串行通信的背景

—— 2.配置与分析串口

—— 3.控制程序

—————————————————————————————————————————

一、串行通信的背景

1.  处理器 与 外部设备通信有两种方式

优缺点对比
方式 串行 并行
原理 数据同时传输 数据按位顺序传输
优点 速度快 速度慢
缺点 占用引脚多 占用引脚少

2. 串行通信的分类

·   单工   ——数据只支持在一个方向上传输

·   半双工——允许在两个方向上传输,但是在某一时刻,只能在一个方向上传输,本质是一种切                          换方向的单工

·   全双工——允许在两个方向上传输

——————————————————

·   同步  ——发送方发送数据后,等待对方响应后才可以发下一个数据

·   异步 ——发送方发送数据后,不等待对方响应直接发送下一个数据

下面给出图例,让大家更好理解

 ——————

我们要说的 USART 就是属于    全双工——异步通讯

——————————

3.对应引脚

USART对应引脚
                                               名称             默认复用功能
PA2 USART2_TX
PA3 USART2_RX
PB10 USART3_TX
PB11 USART3_RX
PA9 USART1_TX
PA10 USART1_RX

我们需要注意以下引脚和他对应的通道

———————————————————————————————————————

二、配置与分析串口

1. 配置流程

这个流程是比较统一的,我们就按照这个顺序配置

1    ——————    串口时钟、GPIOA时钟使能
2    ——————    GPIOA端口模式设置
3    ——————    串口参数初始化
4    ——————   开启中断并且初始化NVIC
5    ——————   使能串口
6    ——————   编写中断处理函数

————

2.按步分析

定义我们的初始化函数,我们定义一个变量(bound)之后作为我们的波特律

void USART1_Init(u32 bound)

————————————

(1)时钟使能

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE);

我们的板子使用的是  PA9 PA10  我们需要打开时钟与USART通道1

————————————

(2)GPIOA的端口配置

    GPIO_InitTypeDef GPIO_InitStruct;
    //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);  
    //USART1_RX	  PA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //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);  
    //USART1_RX	  PA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure); 

我们的的发送和接受数据的端口所需要的模式并不一样,所以我们把他们分开初始化,说明一下

但要注意他们的模式是固定的,如果弄错了就会出现错误,而 RX 里因为是接受数据,所以就不需要输出的速率,所以我们没有 speed

(这里可能会有人会出现报错,可以在 C/C++里面把 c99 打开)

——————————————————————————————————

(3)串口参数初始化

这一部分在这里比较重要,这是我们涉及的新的部分

USART_InitStructure.USART_BaudRate = bound;

这是设置我们的 波特率  最开始我们所设置的 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;	//收发模式

接着这四个比较固定,我们就这么设置即可,以后也不需要怎么更改

————

(4)开启中断并初始化NVIC

这节参考前面的中断文章,不过多说明

	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
	NVIC_InitStructure.NVIC_IRQChannel =USART1_IRQn ;//设置通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //设置抢占优先
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//设置响应优先
	NVIC_InitStructure.NVIC_IRQChannelCmd= ENABLE;//使能
	NVIC_Init(&NVIC_InitStructure);

————————

(5)使能串口

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

——————

(6)编写中断函数

比较需要注意的是,中断函数的名字不能更改,如果我们不需要中断不用设置他也行

我们接下来看看 洋桃电子 所给出的中断函数

void USART1_IRQHandler(void){ //串口1中断服务程序(固定的函数名不能修改)	
	u8 Res;
	//以下是字符串接收到USART1_RX_BUF[]的程序,(USART1_RX_STA&0x3FFF)是数据的长度(不包括回车)
	//当(USART1_RX_STA&0xC000)为真时表示数据接收完成,即超级终端里按下回车键。
	//在主函数里写判断if(USART1_RX_STA&0xC000),然后读USART1_RX_BUF[]数组,读到0x0d 0x0a即是结束。
	//注意在主函数处理完串口数据后,要将USART1_RX_STA清0
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){  //接收中断(接收到的数据必须是0x0d 0x0a结尾)		
		Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
		printf("%c",Res); //把收到的数据以 a符号变量 发送回电脑		
		if((USART1_RX_STA&0x8000)==0){//接收未完成			
			if(USART1_RX_STA&0x4000){//接收到了0x0d				
				if(Res!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
				else USART1_RX_STA|=0x8000;	//接收完成了 
			}else{ //还没收到0X0D					
				if(Res==0x0d)USART1_RX_STA|=0x4000;
				else{
					USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res ; //将收到的数据放入数组
					USART1_RX_STA++;	//数据长度计数加1
					if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		}   		 
	} 
} 

这个中断函数,是我们使用串口发送所使用程序,接下来我们再给出一个串口接收所使用的中断函数

void USART1_IRQHandler(void){ //串口1中断服务程序(固定的函数名不能修改)	
	u8 a;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){  //接收中断(接收到的数据必须是0x0d 0x0a结尾)		
		a =USART_ReceiveData(USART1);//读取接收到的数据
		printf("%c",a); //把收到的数据发送回电脑		  
	} 
} 

——————————

我们来看看他的主程序

int main (void){//主程序
	u8 a;
	//初始化程序
	RCC_Configuration(); //时钟设置

	USART1_Init(115200); //串口初始化(参数是波特率)

	//主循环
	while(1){

		//查询方式接收
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){  //查询串口待处理标志位
			a =USART_ReceiveData(USART1);//读取接收到的数据
			printf("%c",a); //把收到的数据发送回电脑		  
		}
		 

//      delay_ms(1000); //延时
	}
}

需要注意的是,如果我们使用的接收字符(接收我们在串口助手上写的),就需要关闭以下我们的中断函数,这样就不会进入中断了(USART_ITConfig)

我们再来看看主函数里  while 循环里调用的两个函数

1.USART_GetFlagStatus   作一个标志位,判断这个位置是否为1,我们查询了数据手册,让我们更深入的了解一下这个函数

 而下面就是我们标志位的内容

当这些完成之后,就会置 1 ,所以上面的主函数里,我们就是判断 数据寄存器是否是 1,如果是1的话,我们就 进行下面的操作,把这些数据给发送出去。

而接下来的 USART_ReceiveData 就是用来接收我们发送的数据,他只有一个参数,就是用来选择我们要接收的串口

——————————————————————————————————————————

上面的方法虽然好,但是他有一个比较严重的缺点,也就是失去的实时性,我们需要在查询的时候做我们的任务,所以我们就需要使用中断的方法。

我们打开我们的中断使能,然后把之前的 中断函数改成我们上面所给的接收数据的中断,这样我们既能做主函数,也能马上执行我们所发送的,这里就使用了一个新的函数

USART_GetITStatus 我们看看这个函数

而下面就是他的内容

 我们在此处选择的是接收中断事件,我们开启中断之后,就选择这个中断的内容,当接收信息时,我们的标志位置 1 ,然后进入下一步操作,读取我们收到的信息,然后用 printf 函数打印出来

_________________________________________________________________

这样子我们就可以持续的接收我们所发送的字符串了,为了方便,我们再给出已经写好的发送字符串的函数 (函数出自 海创电子)

void USART_SendString( USART_TypeDef * USARTx, char *str)
{
     while(*str!='\0')
     {
         USART_SendByte( USARTx, *str++ );	
     }
     while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
}
uint8_t USART_ReceiveByte(USART_TypeDef* USARTx)
{
  while(USART_GetFlagStatus(USARTx,USART_FLAG_RXNE)==RESET);
  
  return (uint8_t)USART_ReceiveData(USART1);

}

——————————————————————————————————————————

当然我们前面还有一种方法没有说,也就是我们的  printf  函数,他能将我们所写的打印在我们的串口上,当然,要想使用   printf函数   就和我们之前说的一样,他需要一个重定向,我们就需要

fputc 和 fgetc

#pragma import(__use_no_semihosting)             
                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;          
void _sys_exit(int x) 
{ 
	x = x; 
} 

int fputc(int ch, FILE *f)
{
		
		USART_SendData(USART1, (uint8_t) ch);			
		while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);			
		return (ch);
}


int fgetc(FILE *f)
{		
		while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
		return (int)USART_ReceiveData(USART1);
}

在我们编写好的  usart.c 文件下面补充上这个就可以了,还需要注意的是,printf函数所打印出来的东西和 c语言很相似,我们需要对要打印出来的东西进行一个说明

u32     a 定义32位无符号变量a
u16     a 定义16位无符号变量a
u8       a 定义8位无符号变量a
vu32   a 定义易变的32位无符号变量a
vu16     a 定义易变的 16位无符号变量a
vu8     a 定义易变的 8位无符号变量a
uc32     a 定义只读的32位无符号变量a
uc16     a 定义只读 的16位无符号变量a
uc8     a 义只读 的8位无符号变量a
%d 十进制有符号整数
%u 十进制无符号整数
%f 浮点数
%s 字符串
%c 单个字符
%p 指针的值
%e 指数形式的浮点数
%x, %X 无符号以十六进制表示的整数
%o 无符号以八进制表示的整数
%g 自动选择合适的表示法
%p 输出地址符

————————————————————————————————————————-

三、控制程序

使用 串口来远程控制我们的单片机,同时也可以把单片机的状态展示在我们的串口上

这个程序是通过上面的串口接收程序来改写的

我们来看看主程序

#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "buzzer.h"
#include "usart.h"


int main (void){//主程序
	u8 a;
	//初始化程序
	RCC_Configuration(); //时钟设置
	LED_Init();//LED初始化
	KEY_Init();//按键初始化
	BUZZER_Init();//蜂鸣器初始化
	USART1_Init(115200); //串口初始化(参数是波特率)

	//主循环
	while(1){

		//查询方式接收
		if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){  //查询串口待处理标志位
			a =USART_ReceiveData(USART1);//读取接收到的数据
			switch (a){
				case '0':
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED控制
					printf("%c:LED1 OFF ",a); //
					break;
				case '1':
					GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED控制
					printf("%c:LED1 ON ",a); //
					break;
				case '2':
					BUZZER_BEEP1(); //蜂鸣一声
					printf("%c:BUZZER ",a); //把收到的数据发送回电脑
					break;
				default:
					break;
			}		  
		}

		//按键控制
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
			delay_ms(20); //延时20ms去抖动
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开 
				printf("KEY1 "); //
			}
		}		 
		if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平
			delay_ms(20); //延时20ms去抖动
			if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平
				while(!GPIO_ReadInputDataBit(KEYPORT,KEY2)); //等待按键松开 
				printf("KEY2 "); //
			}
		}		 

//      delay_ms(1000); //延时
	}
}

我们进入到 while 循环里面,一样的,我们通过 USART1 这个通道来读取我们所发送的字符,并把这个值赋值给 a ,然后再通过 a 的值 ,使用  switch  语句来选择我们要执行的部分

例如 : 我们在串口上面输入 1 ,就可以点亮我们的灯(这是远程控制)

下面的   if  语句就是用到记录我们 单片机的状态,当按键按下(前面是正常的按键程序),我们就会执行后面的  printf 语句

————————————————————————————————————-————

猜你喜欢

转载自blog.csdn.net/ArtoriaLili/article/details/122520259