赤兔板CH32V307蓝牙模块教程(下)
前言
不知不觉已经来到了蓝牙模块教程的下篇了,这篇教程主要涉及到的是两块赤菟板直接的蓝牙连接,我将先从主从机之间的设定调试,再到例程,系统的介绍板子和板子之间的蓝牙连接。老规矩,基础薄弱的同学可以移步论坛的赤菟教程目录:传送门,本篇会用到板子上的按钮,LCD屏幕。如果前两篇没看过的可以从这里转过去:上篇传送门、中篇传送门。
模块配置及工作模式说明
这部分内容主要参考上篇推荐下载到的CH9141EVT.zip蓝牙模块资料包里面的使用说明和CH9141DS1.pdf蓝牙模块数据手册。
配置参数
配置方式有两种:一是在设备模式下可以通过蓝牙进行配置和控制,二是通过串口进入AT配置模式进行配置。
蓝牙配置仅在从机模式下使用,广播模式和主机模式不可使用,配置命令可以使用例程里的接口进行操作,配置后芯片会自动保存参数,并在下一次启动时生效。本次的教程不涉及这种方法,有兴趣的同学可以自行研究。
本次教程主要使用串口配置。串口配置是在串口进入AT模式下进行配置,AT引脚。串口进行AT操作时,必须等到芯片应答后才可以发送下一包数据,不可以连续发送。数据统一采用ASCII字符传输,HEX形式的参数也是转换成ASCIl形式。
配置参数修改时有些不是立即生效需要重启后生效,建议将参数全部修改后重启芯片。
主机模式
主机模式仅支持同样是CH914x从机模式芯片,主机模式支持扫描连接和直接MAC地址连接,扫描回显的设备只显示CH914x从机芯片,扫描结束后可以使用扫描回显的结果进行序号连接,连接时在AT模式下输入序号和从机连接密码。也可以不进行扫描,直接输入需要连接的从机的MAC地址和密码,芯片会自动去连接该设备。主机模式支持设置自动连接的MAC,如果设置该项,主机在初始化完成后会自动连接该设备,这种方式不需要扫描等操作。
从机模式
从机模式下芯片会发送固定的广播数据,支持修改扫描应答数据离的芯片名称,默认广播间隔为100ms,从机支持四种基本蓝牙服务,其中透传服务UUID为0xFFF0。
通讯的UUID是0xFFF1、0xFFF2、0xFFF3说明参考下面表格
UUID | 属性 | 说明 |
0xFFF1 | 通知 | 串口接收数据将通过该通道发送给主机,主机需要开启通知,数据会以MTU大小封包,超过的将会被芯片分包发送。 |
0xFFF2 | 只写 | 主机发送数据通道,发送数据将会在串口发送出去 |
0xFFF3 | 读、写 | 配置通道,IO同步等功能 |
这其实也解释了之前连手机端的时候那时配置的含义。
恢复出厂设置
芯片提供一个恢复出厂设置引脚(RELOAD/LED),芯片上电后会检测恢复出厂设置引脚,检测到该引脚持续拉低2秒后芯片会恢复出厂设置,之后芯片就以出厂设置的参数运行。RELOAD检测处理后该引脚会复用为LED输出引脚。
AT指令相关
先前也说明了,这次教程的主从机配置将使用串口配置,也正如上篇所说的,沁恒官方给的蓝牙配置助手AT指令相关的内容已经失效,所以只能考虑直接对蓝牙模块输入AT指令进行配置。进行串口配置方法一般有两种:第一种就是拉低PA7引脚直接进入AT模式,第二种就是通过串口指令对其进行AT指令的操作,我们这里为了方便演示相关AT指令,选择第二种方法。
端口功能 | 引脚功能 | 功能 | 备注 |
蓝牙 CH9141 | PC2 | UART7_TX | CH9141_RX |
| PC3 | UART7_RX | CH9141_TX |
| PA7 | BLE_AT | BLE控制管脚 0为AT模式,1为透传模式 |
| PC13 | BLE_SLEEP | 低电平有效,低功耗模式 |
配置程序AT_BLE
#include "debug.h"
/*******************************************************************************
* Function Name : USARTx_CFG
* Description : Initializes the USART peripheral.
* 描述 : 串口初始化
* Input : None
* Return : None
*******************************************************************************/
void USARTx_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART7, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* USART7 TX-->C2 RX-->C3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX,输入上拉
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* USART2 TX-->PA2 RX-->PA3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX,输入上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位 8
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位 1
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //使能 RX 和 TX
USART_Init(USART2, &USART_InitStructure);
USART_Init(UART7, &USART_InitStructure);
USART_Cmd(UART7, ENABLE);
USART_Cmd(USART2, ENABLE); //开启UART
}
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d \t---- From debug : UART%d\r\n",SystemCoreClock,DEBUG);
USARTx_CFG(); /* USART INIT */
int i = 0;
char str[]="Loop back from USART2.\r\n"; //发送一条提示语
while(str[i]){
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); //等待 上次发送 结束
USART_SendData(USART2, str[i]); //发送数据
i++;
}
Delay_Ms(500);
int recv1,recv2;
char a=0,b=0;
while(1){
//把串口接收到的数据发给蓝牙
if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET) //等待接收数据
{
recv1 = USART_ReceiveData(USART2);
a=1;
} //读取接收到的数据
if(USART_GetFlagStatus(UART7, USART_FLAG_TXE) == SET && a == 1) //等待 上次发送 结束
{
USART_SendData(UART7, recv1);
a=0;
} //发送数据
//把蓝牙接收到的数据发给串口
if(USART_GetFlagStatus(UART7, USART_FLAG_RXNE) == SET) //等待接收数据
{
recv2 = USART_ReceiveData(UART7); //读取接收到的数据
b = 1;
}
if(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == SET && b == 1) //等待 上次发送 结束
{
USART_SendData(USART2, recv2); //发送数据
b = 0;
}
}
}
该段程序仅用于对蓝牙模块进行蓝牙配置,细心的同学可能发现,按我教程中篇的说法来说,直接通过CPU传输数据,应该使用UART1,而这里却仍然使用的是UART2,确实,虽然是直接传输,但是我这里并没有用printf函数,所以使用的还是UART2,所以同学们的跳线帽仍然接UART2。
AT指令
将AT_BLE这段程序写入CH32V307之后便可以打开串口调试工具了,便可以开始对蓝牙模块进行AT指令的配置了。
- 发送基本格式:< AT >< + >< 命令码 >< 操作符 >< 参数 >< {CR}{LF} >
- 说明:基本格式是大部分命令码,部分命令有所区别,具体见下面的命令集。其中{CR}{LF}对应的是字符格式定义的“\r”“\n”,十六进制为:0x0D,0x0A即ASCIl中的回车符和换行符,命令中{CR}{LF}作为一个分隔符和结束符使用。
- 返回参数格式:< 参数 >< {CR}{LF} >< OK >< {CR}{LF} >
正确状态返回:< OK >< {CR}{LF} >
错误状态返回:< {CR}{LF} >< ERR: >< 错误码 >< {CR}{LF} >
- 说明:错误码是两个ASCII字符组成的一个HEX形式,如错误码为字符“01”即表示十六进制的0x01。目前的错误码及表示的含义如下表所示:
错误码 | 含义 |
01 | 缓存错误:当前芯片没有缓存来进行应答,可以稍后重试 |
02 | 参数错误:发送的AT指令部分参数不符合规范,注意芯片不会对使用参数进行判定需要外部保证基本的正确性 |
03 | 命令不支持:命令在当前模式下不支持,比如说在广播模式下发送连接命令等 |
04 | 命令不可执行:命令暂时不能执行,可以稍后重试,一般是没有足够的缓存处理这次命令,芯片在忙 |
指令 | 说明 |
AT... | 进入AT配置 |
AT+EXIT | 退出AT配置 |
AT+SAVE | 保存当前参数 |
AT+MAC | 查询本地MAC地址 |
AT+BLESTA | 查询蓝牙状态 |
AT+BLEMODE | 查询/设置蓝牙工作模式 |
AT+PASEN | 查询/设置密码使能 |
AT+PASS | 查询/设置密码 |
AT+SCAN | 主机扫描指令 |
AT+LINK | 根据序号连接指定蓝牙设备 |
AT+CONADD | 查询/设置默认连接参数 |
AT+CLRCONADD | 清空默认连接参数 |
PS:综合之前提到的发送格式,简单点说就是在这些指令后面加个回车即可。
具体操作
当设置好波特率115200,打开WCHLink串口后,发送AT...(别忘记回车),得到OK的返回值之后说明成功进入了AT模式;之后查询蓝牙工作模式,由下图可知,查询参数时在指令后面加个问号(英文输入法)即可。
这里便牵扯出第一个知识点,在默认的情况下,该蓝牙模块的工作模式就是从机模式;蓝牙工作状态模式参数0为广播模式,1为主机模式,2为从机模式(也称设备模式)。
然后查询蓝牙状态,具体含义可以看下图,其中从机模式未连接时板子上的STATE灯闪烁,从机模式未连接时板子上的STATE灯灭,连接上时板子上的STATE灯长亮。
蓝牙状态
接着查询MAC地址,这里请记下,方便主机连接时使用。
接下来分别是查询密码使能和设置密码使能,这里便是第二个知识点,设置参数的时候直接“=”加上你想设置的参数即可。
接下来查询密码,可以知道默认的密码是000000,之后连接主机会用到(当然,你也可以自己设定一个你喜欢的密码,格式同上)。
最后退出AT模式,从机的配置即完成了,可以烧录对应的程序了。(重新上电之后在串口调试工具上要关闭串口,重新打开)
主机配置的方法和从机差不多就不再详细演示。先将AT_BLR烧录进你选用作为主机的板子,再进入AT模式,然后输入AT+BLEMODE=1和AT+SAVE之后直接退出AT模式,重新上电,看到板子上蓝牙模块下面的STATE灯灭了,说明成功进去主机模式。
重新进入AT模式,打开扫描(提前先给从机接电),可以看到他反馈给你一个列表,当前搜到的可以连接的设备,直接输入连接的命令输入设备序号加逗号加之前看到的密码,便会显示连接成功,这时你会发现,从机上STATE灯由闪烁变为长亮,主机上STATE灯由熄灭变为长亮,主从机连接完成。
当然这还不算完,这样产生的连接,从机重新上电之后还在,但是主机断开重新上电主机就要重新再进AT指令连接,这样会显得麻烦,可以直接设置默认连接参数,这里便需要用到之前查询到的MAC地址了,这里并不能直接输入设备序号(同样的如果你不扫描,想直接连接从机,也可以直接输入MAC地址,替换之前命令里面的设备序号即可,这样稍微麻烦点),设置成功后,只要两个设备接电,便会自动连接蓝牙。
演示程序
说了这么多,我觉得同学们可能感觉没什么实感,这里提供两段程序,分别烧录进主从机,便可以从他们之间的交互深刻连接主从机之间的蓝牙通信。
主机程序
#include "debug.h"
#include "lcd.h"
#include "stdarg.h"
#define up 1
#define down 2
#define left 3
#define right 4
#define sel 5
#define sw1 6
#define sw2 7
uint8_t key = 0;
uint8_t r = 0;
/* Global define */
#define RXBUF_SIZE 1024 // DMA buffer size
#define size(a) (sizeof(a) / sizeof(*(a)))
/* Global Variable */
u8 TxBuffer[] = " ";
u8 RxBuffer[RXBUF_SIZE]={0};
/*******************************************************************************
* Function Name : USARTx_CFG
* Description : Initializes the USART peripheral.
* 描述 : 串口初始化
* Input : None
* Return : None
*******************************************************************************/
void USARTx_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART7, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
/* USART7 TX-->C2 RX-->C3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX,输入上拉
GPIO_Init(GPIOC, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位 8
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位 1
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //使能 RX 和 TX
USART_Init(UART7, &USART_InitStructure);
DMA_Cmd(DMA2_Channel9, ENABLE); //开启接收 DMA
USART_Cmd(UART7, ENABLE); //开启UART
}
/*******************************************************************************
* Function Name : DMA_INIT
* Description : Configures the DMA.
* 描述 : DMA 初始化
* Input : None
* Return : None
*******************************************************************************/
void DMA_INIT(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
// TX DMA 初始化
DMA_DeInit(DMA2_Channel8);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&UART7->DATAR); // DMA 外设基址,需指向对应的外设
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)TxBuffer; // DMA 内存基址,指向发送缓冲区的首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 方向 : 外设 作为 终点,即 内存 -> 外设
DMA_InitStructure.DMA_BufferSize = 0; // 缓冲区大小,即要DMA发送的数据长度,目前没有数据可发
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址自增,禁用
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增,启用
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据位宽,8位(Byte)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据位宽,8位(Byte)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式,发完结束,不循环发送
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级最高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // M2P,禁用M2M
DMA_Init(DMA2_Channel8, &DMA_InitStructure);
// RX DMA 初始化,环形缓冲区自动接收
DMA_DeInit(DMA2_Channel9);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&UART7->DATAR);
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RxBuffer; // 接收缓冲区
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 方向 : 外设 作为 源,即 内存 <- 外设
DMA_InitStructure.DMA_BufferSize = RXBUF_SIZE; // 缓冲区长度为 RXBUF_SIZE
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式,构成环形缓冲区
DMA_Init(DMA2_Channel9, &DMA_InitStructure);
}
/*******************************************************************************
* Function Name : GPIO_CFG
* Description : Initializes GPIOs.
* 描述 : GPIO 初始化
* Input : None
* Return : None
*******************************************************************************/
void GPIO_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// CH9141 配置引脚初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
/* BLE_sleep --> C13 BLE_AT-->A7 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/*******************************************************************************
* Function Name : uartWriteBLE
* Description : send data to BLE via UART7 向蓝牙模组发送数据
* Input : char * data data to send 要发送的数据的首地址
* uint16_t num number of data 数据长度
* Return : RESET UART7 busy,failed to send 发送失败
* SET send success 发送成功
*******************************************************************************/
FlagStatus uartWriteBLE(char * data , uint16_t num)
{
//如上次发送未完成,返回
if(DMA_GetCurrDataCounter(DMA2_Channel8) != 0){
return RESET;
}
DMA_ClearFlag(DMA2_FLAG_TC8);
DMA_Cmd(DMA2_Channel8, DISABLE ); // 关 DMA 后操作
DMA2_Channel8->MADDR = (uint32_t)data; // 发送缓冲区为 data
DMA_SetCurrDataCounter(DMA2_Channel8,num); // 设置缓冲区长度
DMA_Cmd(DMA2_Channel8, ENABLE); // 开 DMA
return SET;
}
/*******************************************************************************
* Function Name : uartWriteBLEstr
* Description : send string to BLE via UART7 向蓝牙模组发送字符串
* Input : char * str string to send
* Return : RESET UART7 busy,failed to send 发送失败
* SET send success 发送成功
*******************************************************************************/
FlagStatus uartWriteBLEstr(char * str)
{
uint16_t num = 0;
while(str[num])num++; // 计算字符串长度
return uartWriteBLE(str,num);
}
/*******************************************************************************
* Function Name : uartReadBLE
* Description : read some bytes from receive buffer 从接收缓冲区读出一组数据
* Input : char * buffer buffer to storage the data 用来存放读出数据的地址
* uint16_t num number of data to read 要读的字节数
* Return : int number of bytes read 返回实际读出的字节数
*******************************************************************************/
uint16_t rxBufferReadPos = 0; //接收缓冲区读指针
uint32_t uartReadBLE(char * buffer , uint16_t num)
{
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9); //计算 DMA 数据尾的位置
uint16_t i = 0;
if (rxBufferReadPos == rxBufferEnd){
// 无数据,返回
return 0;
}
while (rxBufferReadPos!=rxBufferEnd && i < num){
buffer[i] = RxBuffer[rxBufferReadPos];
i++;
rxBufferReadPos++;
if(rxBufferReadPos >= RXBUF_SIZE){
// 超出缓冲区,回零
rxBufferReadPos = 0;
}
}
return i;
}
/*******************************************************************************
* Function Name : uartReadByteBLE
* Description : read one byte from UART buffer 从接收缓冲区读出 1 字节数据
* Input : None
* Return : char read data 返回读出的数据(无数据也返回0)
*******************************************************************************/
char uartReadByteBLE()
{
char ret;
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9);//计算 DMA 数据尾的位置
if (rxBufferReadPos == rxBufferEnd){
// 无数据,返回
return 0;
}
ret = RxBuffer[rxBufferReadPos];
rxBufferReadPos++;
if(rxBufferReadPos >= RXBUF_SIZE){
// 超出缓冲区,回零
rxBufferReadPos = 0;
}
return ret;
}
/*******************************************************************************
* Function Name : uartAvailableBLE
* Description : get number of bytes Available to read from the UART buffer 获取缓冲区中可读数据的数量
* Input : None
* Return : uint16_t number of bytes Available to readd 返回可读数据数量
*******************************************************************************/
uint16_t uartAvailableBLE()
{
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9);//计算 DMA 数据尾的位置
// 计算可读字节
if (rxBufferReadPos <= rxBufferEnd){
return rxBufferEnd - rxBufferReadPos;
}else{
return rxBufferEnd +RXBUF_SIZE -rxBufferReadPos;
}
}
void GPIO_INIT(){
GPIO_InitTypeDef GPIO_InitTypdefStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitTypdefStruct.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitTypdefStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitTypdefStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitTypdefStruct);
GPIO_InitTypdefStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitTypdefStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitTypdefStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitTypdefStruct);
GPIO_InitTypdefStruct.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_13;
GPIO_InitTypdefStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitTypdefStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitTypdefStruct);
}
uint8_t Basic_Key_Handle( void )
{
uint8_t keyval = 0;
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_4 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_4 ) )
{
keyval = sw1;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_5 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_5 ) )
{
keyval = sw2;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_1 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_1 ) )
{
keyval = up;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_2 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_2 ) )
{
keyval = down;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_3 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_3 ) )
{
keyval = right;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_6 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_6 ) )
{
keyval = left;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_13 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_13 ) )
{
keyval = sel;
}
}
}
}
}
}
}
}
return keyval;
}
void movecircle(){
uint8_t getkey =0;
getkey = Basic_Key_Handle();
//if (getkey) {
key=getkey;
//}
switch (key)
{
case sw1:r = 1;break; //按下swl就减小圆半径
case sw2:r = 2;break; //按下sw2就增大圆半径
default:break;
}
}
char buffer[100];
void mon_log(char* format, ...)
{
va_list vArgList;
va_start(vArgList, format);
vsnprintf(buffer, 100, format, vArgList);/*把可变参数表中的数据转成字符存到buffer中,每个参数间用','隔开 */
va_end(vArgList);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
DMA_INIT();
USARTx_CFG(); /* USART INIT */
USART_DMACmd(UART7,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
lcd_init();
lcd_set_color(BLACK,WHITE);
lcd_show_string(50, 0, 32,"Yuimerlin");
lcd_set_color(BLACK,RED);
lcd_show_string(0, 32, 16,"master");
delay_ms(100);
GPIO_INIT();
GPIO_CFG();
GPIO_WriteBit(GPIOC, GPIO_Pin_13,SET); //enable CH9141
Delay_Ms(1000);
while(1){
Delay_Ms(200);
movecircle();
if (r == 1) {
r=0;
mon_log("minus one"); //封装字符串
uartWriteBLEstr("-"); //蓝牙发送
}
if (r == 2){
r=0;
mon_log("plus one "); //封装字符串
uartWriteBLEstr("+"); //蓝牙发送
}
lcd_set_color(BLACK,GREEN); //屏幕显示
lcd_show_string(50, 120, 32,buffer);
}
}
这部分新东西不多,基本上跟中篇介绍的例程挂钩,仅讲解一下出现的新东西,忘记的同学请回到中篇浏览:传送门
#define up 1
#define down 2
#define left 3
#define right 4
#define sel 5
#define sw1 6
#define sw2 7
uint8_t key = 0;
uint8_t r = 0;
uint8_t Basic_Key_Handle( void )
{
uint8_t keyval = 0;
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_4 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_4 ) )
{
keyval = sw1;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_5 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_5 ) )
{
keyval = sw2;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_1 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_1 ) )
{
keyval = up;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_2 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_2 ) )
{
keyval = down;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_3 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_3 ) )
{
keyval = right;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_6 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_6 ) )
{
keyval = left;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_13 ) )
{
Delay_Ms(10);
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_13 ) )
{
keyval = sel;
}
}
}
}
}
}
}
}
return keyval;
}
void movecircle(){
uint8_t getkey =0;
getkey = Basic_Key_Handle();
//if (getkey) {
key=getkey;
//}
switch (key)
{
case sw1:r = 1;break; //按下swl就减小圆半径
case sw2:r = 2;break; //按下sw2就增大圆半径
default:break;
}
}
这部分即为板子的按钮驱动函数,不止sw1和sw2同学们也可以按照里面的函数驱动旁边的五相开关,函数Basic_Key_Handle即为消除按键抖动。
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
DMA_INIT();
USARTx_CFG(); /* USART INIT */
USART_DMACmd(UART7,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
lcd_init();
lcd_set_color(BLACK,WHITE);
lcd_show_string(50, 0, 32,"Yuimerlin");
lcd_set_color(BLACK,RED);
lcd_show_string(0, 32, 16,"master");
delay_ms(100);
GPIO_INIT();
GPIO_CFG();
GPIO_WriteBit(GPIOC, GPIO_Pin_13,SET); //enable CH9141
Delay_Ms(1000);
while(1){
Delay_Ms(200);
movecircle();
if (r == 1) {
r=0;
mon_log("minus one"); //封装字符串
uartWriteBLEstr("-"); //蓝牙发送
}
if (r == 2){
r=0;
mon_log("plus one "); //封装字符串
uartWriteBLEstr("+"); //蓝牙发送
}
lcd_set_color(BLACK,GREEN); //屏幕显示
lcd_show_string(50, 120, 32,buffer);
}
}
main函数中先初始化各模块,检测按键,根据标志符r给蓝牙发送+或者-字符,同时给数组赋值,让指令名显示在LCD屏幕上。
从机程序
#include "debug.h"
#include "lcd.h"
/* Global define */
#define RXBUF_SIZE 1024 // DMA buffer size
#define size(a) (sizeof(a) / sizeof(*(a)))
/* Global Variable */
u8 TxBuffer[] = " ";
u8 RxBuffer[RXBUF_SIZE]={0};
uint8_t r = 0;
/*******************************************************************************
* Function Name : USARTx_CFG
* Description : Initializes the USART peripheral.
* 描述 : 串口初始化
* Input : None
* Return : None
*******************************************************************************/
void USARTx_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART7, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
/* USART7 TX-->C2 RX-->C3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX,输入上拉
GPIO_Init(GPIOC, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位 8
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位 1
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //使能 RX 和 TX
USART_Init(UART7, &USART_InitStructure);
DMA_Cmd(DMA2_Channel9, ENABLE); //开启接收 DMA
USART_Cmd(UART7, ENABLE); //开启UART
}
/*******************************************************************************
* Function Name : DMA_INIT
* Description : Configures the DMA.
* 描述 : DMA 初始化
* Input : None
* Return : None
*******************************************************************************/
void DMA_INIT(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
// TX DMA 初始化
DMA_DeInit(DMA2_Channel8);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&UART7->DATAR); // DMA 外设基址,需指向对应的外设
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)TxBuffer; // DMA 内存基址,指向发送缓冲区的首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 方向 : 外设 作为 终点,即 内存 -> 外设
DMA_InitStructure.DMA_BufferSize = 0; // 缓冲区大小,即要DMA发送的数据长度,目前没有数据可发
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址自增,禁用
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增,启用
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据位宽,8位(Byte)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据位宽,8位(Byte)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式,发完结束,不循环发送
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级最高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // M2P,禁用M2M
DMA_Init(DMA2_Channel8, &DMA_InitStructure);
// RX DMA 初始化,环形缓冲区自动接收
DMA_DeInit(DMA2_Channel9);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&UART7->DATAR);
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RxBuffer; // 接收缓冲区
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 方向 : 外设 作为 源,即 内存 <- 外设
DMA_InitStructure.DMA_BufferSize = RXBUF_SIZE; // 缓冲区长度为 RXBUF_SIZE
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式,构成环形缓冲区
DMA_Init(DMA2_Channel9, &DMA_InitStructure);
}
/*******************************************************************************
* Function Name : GPIO_CFG
* Description : Initializes GPIOs.
* 描述 : GPIO 初始化
* Input : None
* Return : None
*******************************************************************************/
void GPIO_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// CH9141 配置引脚初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
/* BLE_sleep --> C13 BLE_AT-->A7 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/*******************************************************************************
* Function Name : uartWriteBLE
* Description : send data to BLE via UART7 向蓝牙模组发送数据
* Input : char * data data to send 要发送的数据的首地址
* uint16_t num number of data 数据长度
* Return : RESET UART7 busy,failed to send 发送失败
* SET send success 发送成功
*******************************************************************************/
FlagStatus uartWriteBLE(char * data , uint16_t num)
{
//如上次发送未完成,返回
if(DMA_GetCurrDataCounter(DMA2_Channel8) != 0){
return RESET;
}
DMA_ClearFlag(DMA2_FLAG_TC8);
DMA_Cmd(DMA2_Channel8, DISABLE ); // 关 DMA 后操作
DMA2_Channel8->MADDR = (uint32_t)data; // 发送缓冲区为 data
DMA_SetCurrDataCounter(DMA2_Channel8,num); // 设置缓冲区长度
DMA_Cmd(DMA2_Channel8, ENABLE); // 开 DMA
return SET;
}
/*******************************************************************************
* Function Name : uartWriteBLEstr
* Description : send string to BLE via UART7 向蓝牙模组发送字符串
* Input : char * str string to send
* Return : RESET UART7 busy,failed to send 发送失败
* SET send success 发送成功
*******************************************************************************/
FlagStatus uartWriteBLEstr(char * str)
{
uint16_t num = 0;
while(str[num])num++; // 计算字符串长度
return uartWriteBLE(str,num);
}
/*******************************************************************************
* Function Name : uartReadBLE
* Description : read some bytes from receive buffer 从接收缓冲区读出一组数据
* Input : char * buffer buffer to storage the data 用来存放读出数据的地址
* uint16_t num number of data to read 要读的字节数
* Return : int number of bytes read 返回实际读出的字节数
*******************************************************************************/
uint16_t rxBufferReadPos = 0; //接收缓冲区读指针
uint32_t uartReadBLE(char * buffer , uint16_t num)
{
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9); //计算 DMA 数据尾的位置
uint16_t i = 0;
if (rxBufferReadPos == rxBufferEnd){
// 无数据,返回
return 0;
}
while (rxBufferReadPos!=rxBufferEnd && i < num){
buffer[i] = RxBuffer[rxBufferReadPos];
i++;
rxBufferReadPos++;
if(rxBufferReadPos >= RXBUF_SIZE){
// 超出缓冲区,回零
rxBufferReadPos = 0;
}
}
return i;
}
/*******************************************************************************
* Function Name : uartReadByteBLE
* Description : read one byte from UART buffer 从接收缓冲区读出 1 字节数据
* Input : None
* Return : char read data 返回读出的数据(无数据也返回0)
*******************************************************************************/
char uartReadByteBLE()
{
char ret;
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9);//计算 DMA 数据尾的位置
if (rxBufferReadPos == rxBufferEnd){
// 无数据,返回
return 0;
}
ret = RxBuffer[rxBufferReadPos];
rxBufferReadPos++;
if(rxBufferReadPos >= RXBUF_SIZE){
// 超出缓冲区,回零
rxBufferReadPos = 0;
}
return ret;
}
/*******************************************************************************
* Function Name : uartAvailableBLE
* Description : get number of bytes Available to read from the UART buffer 获取缓冲区中可读数据的数量
* Input : None
* Return : uint16_t number of bytes Available to readd 返回可读数据数量
*******************************************************************************/
uint16_t uartAvailableBLE()
{
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9);//计算 DMA 数据尾的位置
// 计算可读字节
if (rxBufferReadPos <= rxBufferEnd){
return rxBufferEnd - rxBufferReadPos;
}else{
return rxBufferEnd +RXBUF_SIZE -rxBufferReadPos;
}
}
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
DMA_INIT();
USARTx_CFG(); /* USART INIT */
USART_DMACmd(UART7,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
lcd_init();
lcd_set_color(BLACK,WHITE);
lcd_show_string(50, 0, 32,"Yuimerlin");
lcd_set_color(BLACK,RED);
lcd_show_string(0, 32, 16,"follower");
delay_ms(100);
GPIO_CFG();
// GPIO_WriteBit(GPIOA, GPIO_Pin_7,RESET); //进入 AT
GPIO_WriteBit(GPIOC, GPIO_Pin_13,SET); //enable CH9141
Delay_Ms(1000);
while(1){
Delay_Ms(200);
lcd_set_color(BLACK,BLACK); //删除上一个圆
lcd_draw_circle(120, 112 , r);
int num = uartAvailableBLE();
if (num > 0 ){ //读取收到的数据
char buffer[100];
uartReadBLE(buffer , num);
if ((buffer[0] == '-') && r > 0) {//接收到减小的命令,减小圆半径
r--;
}
if ((buffer[0] == '+') && r < 40) {//接收到增加的命令,增加圆半径
r++;
}
}
lcd_set_color(BLACK,WHITE); //显示当前圆
lcd_draw_circle(120, 112 , r);
lcd_set_color(BLACK,GREEN);
lcd_show_string(0, 208, 16,"The radius of the circle is %2d", r);//显示半径
// GPIO_WriteBit(GPIOA, GPIO_Pin_7,SET); // 退出AT。可用手机或电脑连接CH9141,测试数据收发
}
}
void lcd_draw_circle(u16 x0, u16 y0, u8 r)
变量 | 意义 |
x0 | 圆心所在屏幕的x轴坐标 |
y0 | 圆心所在屏幕的x轴坐标 |
r | 圆的半径 |
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
DMA_INIT();
USARTx_CFG(); /* USART INIT */
USART_DMACmd(UART7,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
lcd_init();
lcd_set_color(BLACK,WHITE);
lcd_show_string(50, 0, 32,"Yuimerlin");
lcd_set_color(BLACK,RED);
lcd_show_string(0, 32, 16,"follower");
delay_ms(100);
GPIO_CFG();
// GPIO_WriteBit(GPIOA, GPIO_Pin_7,RESET); //进入 AT
GPIO_WriteBit(GPIOC, GPIO_Pin_13,SET); //enable CH9141
Delay_Ms(1000);
while(1){
Delay_Ms(200);
lcd_set_color(BLACK,BLACK); //删除上一个圆
lcd_draw_circle(120, 112 , r);
int num = uartAvailableBLE();
if (num > 0 ){ //读取收到的数据
char buffer[100];
uartReadBLE(buffer , num);
if ((buffer[0] == '-') && r > 0) {//接收到减小的命令,减小圆半径
r--;
}
if ((buffer[0] == '+') && r < 40) {//接收到增加的命令,增加圆半径
r++;
}
}
lcd_set_color(BLACK,WHITE); //显示当前圆
lcd_draw_circle(120, 112 , r);
lcd_set_color(BLACK,GREEN);
lcd_show_string(0, 208, 16,"The radius of the circle is %2d", r);//显示半径
// GPIO_WriteBit(GPIOA, GPIO_Pin_7,SET); // 退出AT。可用手机或电脑连接CH9141,测试数据收发
}
main函数部分也是先初始化各模块,通过读取蓝牙传输过来的+或者-,同时改变圆的大小(限定半径在040之间),同时显示当前圆半径大小。
效果演示
后话
到此蓝牙教程下就结束,希望这篇教程对您的赤菟板间蓝牙模块连接的的学习有所帮助。
整个蓝牙教程到这也完结了,这次的教程仅说明了大家可能会用的较多的部分,细致的地方没有说到的还请谅解,基本上所有出现的问题在数据手册上均有涉及,上面的介绍也相当完整,如果想深入探究可以作为参考。
老规矩,这次教程所涉及到的程序和之前一样放到了gitee仓库上文件名为:
欢迎大家参考!