基于USART串口的蓝牙通信在STM32F4上的实现

目录

一、硬件资源

连接方案

其他配置

二、实验原理

基本定义

USART介绍

USART工作原理

数据发送

数据接收

蓝牙HM-10配置

三、代码部分

usart.c

usart.h

Serial.c

Serial.h

main.c

结语


一、硬件资源

STM32F401,OLED,蓝牙hm10

连接方案

设备1的TX与设备2的RX连接,这样设备1发送的数据可以被设备2接收到。

由引脚复用表,我们选择PB6和PB7分别作为TX和RX,那么PB6接蓝牙的RX,PB7接蓝牙的TX。

其他配置

可以在手机上下载一个蓝牙BLE助手,用来与STM通信。进入软件后寻找要配对的蓝牙,注意要先将手机定位开启,然后就可以收发数据了。

二、实验原理

基本定义

波特率∶串口通信的速率。因此发生方和接收方要设置相同的波特率。

波特率计算公式: baud = fck/(16* USARTDIV)

起始位 : 标志一个数据帧的开始,固定为低电平。

停止位∶用于数据帧间隔,固定为高电平。为下一次的起始位奠定基础。
数据位︰数据帧的有效载荷,1为高电平,0为低电平,低位先行

校验位:用于数据验证,根据数据位计算得来。保证数据在一定范围内的正确性。
八位数据的格式

在八位的基础上可以加一位奇偶校验位

以奇校验为例,就是九位数据位最终1的个数为奇数个,比如数据为0000 1001,有偶数个1,那么校验位为1,使得最终1的个数为奇数个。

当然,并不是说八位就一定无校验位,九位就一定有校验位,只不过8位恰好为一字节,为了方便我们这样设置。

USART介绍

USART (Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2);可选校验位(无校验/奇校验/偶校验)

还可以选择硬件流控制,A发送数据,B接收数据,如果B还未处理完数据A就继续发送过来会导致数据丢失。硬件流控制就是B告诉A是否可以继续发送。

USART工作原理

简化后如下图

数据发送

发送数据时数据由数据线进入TDR,送入移位寄存器后右移(低位先行)送到TX,下一个数据发过来时要查看TXE(TX Empty)标志位,若为1,说明TDR已经空了,可以放入下一个数据。

数据接收

数据经RX进入后送至移位寄存器,先进入最高位,右移使得数据形式不变,之后进入RDR,全部进入后RXNE(RX Not Empty)置1,表示接收到数据。

由于设备不知道另一设备什么时候发数据,接收数据的方式有两种。第一,轮询模式,CPU一直查询是否有数据发来,占用CPU资源。第二,中断,有数据发来时暂停CPU任务,转而执行中断任务,涉及到中断函数和NVIC的配置。

蓝牙HM-10配置

蓝牙的设置由指令控制,具体的指令可以看配对的蓝牙说明书。

常见的设置指令如下

"AT+BAUD[P1]":设置波特率9600
"AT+PARI[P1]"):串口校验位设置
"AT+STOP[P1]:停止位设置
"AT+MODE[P1]":设置工作模式,2为透传+远控模
"AT+NAME[name]"):设置名字
"AT+ROLE[P1]" :设置主从模式,0从1主

如果要查询的话:格式为指令+"?",比如:"AT+BAUD?"用来查询蓝牙的波特率

三、代码部分

usart.c

这部分是对usart相关的配置,包括GPIO,USART和NVIC

#include "stm32f4xx.h"
#include "stdarg.h"
#include "stdio.h"

uint8_t RxFlag;
uint8_t RxData;

void Usart_init(void){
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//USART时钟初始化
	
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);//实现接口的复用功能
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用模式
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;//波特率
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;//字长
	USART_InitStruct.USART_StopBits = USART_StopBits_1;//一位停止位
	USART_InitStruct.USART_Parity = USART_Parity_No;//校验位
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_Init(USART1, &USART_InitStruct);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC输出
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置优先级为2
	
	NVIC_InitTypeDef NVIV_InitStruct;
	NVIV_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIV_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIV_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIV_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIV_InitStruct);
	USART_Cmd(USART1,ENABLE);
}

void Usart_SendByte(uint8_t byte){
	//等TDR的数据全部进入移位寄存器
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

	USART_SendData(USART1, byte);
}
void Usart_SendArray(uint8_t* Array,uint16_t length){
	for(int i=0;i<length;i++){
		Usart_SendByte(Array[i]);
	}
	
}
void Usart_SendString(char* str){//字符串有结束标志位,不用长度
	for(int i=0;str[i] != '\0';i++){
		Usart_SendByte(str[i]);
	}
	
}
uint32_t Pow(uint32_t x){//求10的x次方
	uint32_t ans=1;
	for(uint8_t i=0;i<x;i++){
		ans*=10;
	}
	return ans;
}
	
	
void Usart_SendNumber(uint32_t Number,uint16_t Length){//为了发送大整数
	uint32_t ans=Pow(Length-1);
	for(int i=0;i<Length;i++){
		
		Usart_SendByte(Number / ans % 10+ '0');
		ans/=10;
	}
	
}
//先勾选Use Micro LIB为keil嵌入式平台优化的一个精简库
int fputc(int ch,FILE *f){//该函数为print函数的底层,prinf()函数重定向,将打印的东西输出到串口
	Usart_SendByte(ch);
	return ch;
}
void Usart_Printf(char *format, ...){//参数用来接收格式化字符串,...接收可变参数列表
	char String[100];
	va_list arg;//参数表,类型包含在stdarg头文件
	va_start(arg,format);
	vsprintf(String,format,arg);
	va_end(arg);//释放参数列表
	
	Usart_SendString(String);
	
}
uint8_t GetRxFlag(){
	if(RxFlag == 1){
		RxFlag=0;
		return 1;
	}
	return 0;
}
uint8_t GetRxData(){
	return RxData;
}
void USART1_IRQHandler(){//重写中断函数
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){
		RxData = USART_ReceiveData(USART1);
		RxFlag = 1;
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);//对RXNE清零
	}
	
}
usart.h
#ifndef USART_H
#define USART_H

#include<stdio.h>
void Usart_init(void);
void Usart_SendByte(uint8_t byte);
void Usart_SendArray(uint8_t* Array,uint16_t length);
void Usart_SendString(char* str);
void Usart_SendNumber(uint32_t Number,uint16_t Length);
int fputc(int ch,FILE *f);
void Usart_Printf(char *format, ...);
uint8_t GetRxFlag(void);
uint8_t GetRxData(void);
	
	
#endif
Serial.c

这部分是对蓝牙的设置

#include "stm32f4xx.h"                  // Device header
#include "usart.h"
#include "Delay.h" 

void Serial_Init(){
	Usart_init();
	
	Usart_SendString("AT");
	Usart_SendString("AT+BAUD[0]");//设置波特率9600
	Usart_SendString("AT+PARI[0]");//无串口校验
	Usart_SendString("AT+STOP[0]");//一个停止位
	Usart_SendString("AT+MODE[2]");//工作模式,2为透传+远控模式
	Usart_SendString("AT+NAME[MLT-BT05]");
	Usart_SendString("AT+ROLE[1]");//设置主从模式,0从1主

}

Serial.h
#ifndef SERIAL_H
#define SERIAL_H

void Serial_Init();

#endif
main.c
#include "stm32f4xx.h"                  // Device header
#include "Delay.h"
#include "oled.h"
#include "usart.h"
#include "Serial.h"

uint8_t Data;

int main(void){
	OLED_Init();
	Serial_Init();//其中包含了usart的初始化
	OLED_ShowString(1,1,"RxData");
	
	while(1){
        //发送
		Usart_SendString("Hello!\r\n");
		
				
		//轮询模式
		/*if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET){//不断查询RXNE标志位
			Data = USART_ReceiveData (USART1);
			OLED_ShowHexNum(2,1,Data,2);
		}*/
        
        //中断模式
		/*if(GetRxFlag() == 1){//标志位为1,说明收到数据
			Data = GetRxData();
			Usart_SendByte(Data);
			OLED_ShowHexNum(1,8,Data,2);
			
		}*/
	}
}


结语

这样我们就可以通过蓝牙做一些小设计了,比如控制灯的开关,手机发送给STM  'o',STM接收到数据后判断,如果为'o',则打开灯,收到 'f'则关灯。

物联沃分享整理
物联沃-IOTWORD物联网 » 基于USART串口的蓝牙通信在STM32F4上的实现

发表评论