目录
一、硬件资源
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'则关灯。