STM32硬件USART——蓝牙与手机传输数据
硬件USART
传输方式:串行传输,低位先行。
传输类型:帧传输。有校验位则9位一帧,无校验位则8位一帧。
波特率:一秒钟能传输的bit个数。
波特率 = 外设时钟÷ 分频系数 ÷16。
外设时钟是指外挂载总线APB1或APB2的时钟,两者不同。
要÷16是因为在读取数据时,一位bit需要16个时钟采样,来防止/判断噪音。所以÷16得到波特率,是为了保证系统时钟能够有足够快的频率,保证对每bit的传输都×16倍时间去检测,得到真正的一位bit发送接收时间。不然假设16MHz的外设时钟,不分频得到波特率,那么实际一位的时间需要16*16Mhz的时钟才能正确发送,但这已经远远超出了外设时钟的上限(个人理解)(1MHz = 1 000 000 Hz)
停止位时间:发送一个字节数据(8bit)之后,停止位占多少个bit时钟周期(0.5,1,1.5,2),一般1个。
字节长度:8位、9位。一般8位不加校验位,9位加校验位。
DR寄存器:内分 发送数据寄存器+发送移位寄存器、接收寄存器+接收移位寄存器。
对软件层USART传输而言,收发都是访问DR地址,不对其内部的地址进行细分。也就是一个地址两种功能,Rdata = DR就是接收;DR = Tdata就是发送。具体逻辑由硬件判断。
发送流程:
①调用函数判断发送数据移位寄存器是否空(标志位TXE—TE Empty空),判断数据有(1)没有(0)从发送数据寄存器被硬件自动转移到发送移位数据寄存器。
②空的话则把数据发送给DR(赋值给DR : DR = Tdata)。
③为了下一次方便发送的话,可以在每次发送后等待TXE=1(即发送移位寄存器空),让下一次数据进来的时候不用等待,直接发送。
接收流程:
①调用函数判断接收数据寄存器是否非空(标志位TXNE — TX Not Empyt ),判断数据有(1)没有(0)从接收移位寄存器被硬件自动转移到接收数据寄存器
②读取值为1(非空)的话,就要读取接收寄存器的数据(Rdata = DR)
③在接收寄存器内数据时(RXNE == 1),对寄存器进行读取,会自动清除标志位
空闲状态:TX高电平
启动与发送:
启动:TX拉低,并保持一个bit周期,在这个TX拉低开始后,会有16个时钟来采样后续的1bit周期内的TX电平,来确定这个启动是真的启动而不是电平不稳导致。
发送:启动之后的8/9bit时间用于数据的发送,流程也是1bit时间有16个时钟采样才确定最终发送数据。
1bit时间内的16个时钟采样吗,分别在第3、5、7、8、9、10这6个时间内采样,由8、9、10决定最终采样结果:3个相同采样电平则是采样结果,其余按2:1多数发送。
在启动模式时,如果3个采样不是全0,而是2个0与1个1,会产生一个噪音标志位NE(NOISE ERROR)
数据发送:在1bit时间的中间时间段拉高SCL,表示发送数据。(在中间更能保证数据是稳定而非翻转不及时导致)
STM32F103C8T6所有的USART资源:
APB2:USART1;
APB1:USART2,USART3;
对应引脚( 不使用—>不考虑其他应引脚)
USART1:RX(PA10),TX(PA9);
USART2:RX(PA3),TX(PA2);
USART3:RX(PB11),TX(PB10);
配置流程:开启外设时钟——>利用结构体初始化
①开启USART1的外设时钟
②定义结构体,初始化结构体参数(波特率、收发模式、停止位时间、奇偶校验、数据字长、数据流控制(没用到?待研究))
③用结构体初始化USART1
NVIC
名字来源:Nested Vectored Interrupt Controller (嵌套向量中断控制器)
在此处的作用:外部中断–单片机作为蓝牙传输接收方接收数据需要
各英文词含义:
①抢占优先级–PreemptionPriority :后发中断是否能插队。
②响应优先级–SubPriority:同发中断谁能先排队。
特别说明:同抢占优先级的中断,则看响应优先级,数值靠前(小)的优先插/排队。
③中断分组–NVIC_PriorityGroup_X(第X组):把16个数值分给抢占优先级、响应优先级。
总共有3位二进制数控制分组(也就是0~15个情况),其中x表示2的x次方,指的是从0~2的x次方大小是抢占优先级的个数,在2的x次方~15是响应优先级的个数。
图片源于(stm32入门篇–中断的初步认识及其优先级和分组_中断优先级和分组-CSDN博客)
④中断通道--IRQChannel:中断通道是指在微控制器(MCU)中,用于处理外部或内部事件的特定路径。当某个事件发生时,微控制器会通过中断通道暂停当前正在执行的程序,转而执行与该事件相关的中断服务程序(ISR),处理完成后返回原来被暂停的位置继续运行。
配置方式:开启串口外部中断 ——> 结构体配置基础参数 ——> 传参结构体初始化NVIC
配置流程:
①开启USART1串口外部输入中断
②设置中断抢占优先级、中断通道、中断通道使能、响应优先级的分组情况
③设置结构体初始化参数,之后用结构体初始化
④开启USART1的时钟
蓝牙(DX-BT24 、XY-MB026A)
透传:把蓝牙作为数据的中转站,蓝牙接收数据后,不对数据进行任何处理,直接将数据传递给接收方。
非透传:蓝牙会对数据进行处理(加密、封装、转格式……)后再发给接收方。
组网:多个蓝牙之间可以相互联系。(?待研究)
特别注意:
1.蓝牙只作为一个中转站,所以不需要任何代码去直接操作蓝牙。
2.不同蓝牙的内部封装可能由于内部API不同,而导致需要不同的传输方案,故需要写传输数据的代码,将数据正确传给蓝牙。
3.蓝牙API:蓝牙模块中的API是一组用于实现蓝牙通信功能的工具和接口。这些API允许应用程序与蓝牙设备进行交互,包括发现、连接、数据传输和设备控制等操作。
接线:USART1的TXD(PA9)接蓝牙的RXD
RXD(PA10)接蓝牙的TXD
实现流程:
①发送——封装一个发送函数实现自定义发送
②接收——在中断函数内部读取外部输入。
由于USART的NVIC中断被配置为外部输入中断,所以一旦由外部数据的输入,就会进入中断,可以根据这个关系直接在中断函数内完成读取输入数据操作。
主要代码
USART1及其NIVC、GPIO口的初始化
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*中断输出配置*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
发送一个字节数据
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
接收一个字节数据+USART1中断函数
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
Serial_RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Serial_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
作者:QL.ql