3.16 CC2541BLE 蓝牙 4.0 串口模块(来自 zhikun 的贡献)
3.16.1 模块来源
采购链接: https://detail.tmall.com/item.htm?_u=i21i2tra84c&id=520650637220&spm=a1z09.2.0.0.45752e8dJrVP2c
3.16.2 规格参数
工作电压:3.3V
工作电流:<20mA
通信方式:UART
模块尺寸:26.7(H) x 13(V)MM
传输距离:<10m
引脚数量:4
3.16.3 模块原理
通过串口控制蓝牙芯片,进行无线传输,支持低功率,方便做 IOT 应用。
3.16.4 移植工程
3.16.4.1 引脚选择
| CC2541 | 立创-梁山派 |
|---|---|
| UART_TX | PB11 |
| UART_RX | PB10 |
| VCC | 3V3 |
| GND | GND |
按照引脚选择进行 IO 配置和驱动,编写驱动函数,最后通过手机蓝牙 APP 控制蓝牙模块,告知开发板进行 LED2 亮灭控制。这里为了节省时间直接在立创开发板的资料中(梁山 Pi 开发板资料\资源包\08 代码例程-视频注释版-GD32F470 版\005 串口打印信息)005 例子作为工程模板进行开发。
3.16.4.2 CC2541 驱动移植
串口配置
这里采用 CC2541 进行的串口通信,因此使用了开发板的 USART2(PB11、PB10)与之通信,PA9、PA10 作为 USART0 进行调试信息打印。具体配置如下。
bsp_usart.h 文件,增加了 USART2
#ifndef _BSP_USART_H
#define _BSP_USART_H
#include "gd32f4xx.h"
#include "systick.h"
#define BSP_USART_TX_RCU RCU_GPIOA // 串口TX的端口时钟
#define BSP_USART_RX_RCU RCU_GPIOA // 串口RX的端口时钟
#define BSP_USART_RCU RCU_USART0 // 串口0的时钟
#define BSP_USART_TX_PORT GPIOA // 串口TX的端口
#define BSP_USART_RX_PORT GPIOA // 串口RX的端口
#define BSP_USART_AF GPIO_AF_7 // 串口0的复用功能
#define BSP_USART_TX_PIN GPIO_PIN_9 // 串口TX的引脚
#define BSP_USART_RX_PIN GPIO_PIN_10 // 串口RX的引脚
#define BSP_USART USART0 // 串口0
#define USART_RECEIVE_LENGTH 256
extern uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
extern uint8_t g_recv_length; // 接收数据长度
extern uint8_t g_recv_complete_flag; // 接收完成标志位
void usart0_gpio_config(uint32_t band_rate); // 配置串口0
void usart0_send_data(uint8_t ucch); // 发送一个字符
void usart0_send_string(uint8_t *ucstr); // 发送一个字符串
void usart2_gpio_config(uint32_t band_rate); // 配置串口2
void usart2_send_data(uint8_t ucch); // 发送一个字符
void usart2_send_string(uint8_t *ucstr); // 发送一个字符串
#endif /* BSP_USART_H */2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
bsp_usart.c 文件,增加 USART2 功能,支持中断接收。
/********************************************************************************
* 文 件 名: bsp_usart.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2022年04月14日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "bsp_usart.h"
#include "stdio.h"
uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
uint8_t g_recv_length = 0; // 接收数据长度
uint8_t g_recv_complete_flag = 0; // 接收数据完成标志位
/************************************************
函数名称 : usart_gpio_config
功 能 : 串口配置GPIO
参 数 : band_rate:波特率
返 回 值 : 无
作 者 : LC
*************************************************/
void usart0_gpio_config(uint32_t band_rate)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_USART_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_USART_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_USART_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_USART_TX_PORT,BSP_USART_AF,BSP_USART_TX_PIN);
gpio_af_set(BSP_USART_RX_PORT,BSP_USART_AF,BSP_USART_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_USART_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_USART_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_USART_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_USART_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_USART_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_USART); // 复位串口
usart_baudrate_set(BSP_USART,band_rate); // 设置波特率
usart_parity_config(BSP_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_USART,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_USART); // 使能串口
usart_transmit_config(BSP_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_USART,USART_RECEIVE_ENABLE); // 使能串口接收
}
/************************************************
函数名称 : usart_send_data
功 能 : 串口重发送一个字节
参 数 : ucch:要发送的字节
返 回 值 :
作 者 : LC
*************************************************/
void usart0_send_data(uint8_t ucch)
{
usart_data_transmit(BSP_USART,(uint8_t)ucch); // 发送数据
while(RESET == usart_flag_get(BSP_USART,USART_FLAG_TBE)); // 等待发送数据缓冲区标志置位
}
/************************************************
函数名称 : usart_send_String
功 能 : 串口发送字符串
参 数 : ucstr:要发送的字符串
返 回 值 :
作 者 : LC
*************************************************/
void usart0_send_string(uint8_t *ucstr)
{
while(ucstr && *ucstr) // 地址为空或者值为空跳出
{
usart0_send_data(*ucstr++); // 发送单个字符
}
}
//*******************************************************************
//串口2实现开始
//增加串口2配置与驱动
//2023/9/26
/************************************************
函数名称 : usart2_gpio_config
功 能 : 串口配置GPIO
参 数 : band_rate:波特率
返 回 值 : 无
作 者 : kwin
*************************************************/
void usart2_gpio_config(uint32_t band_rate)
{
/* 开启时钟 */
rcu_periph_clock_enable(RCU_USART2); // 开启串口时钟
rcu_periph_clock_enable(RCU_GPIOB); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(GPIOB,GPIO_AF_7,GPIO_PIN_11);
gpio_af_set(GPIOB,GPIO_AF_7,GPIO_PIN_10);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(GPIOB,GPIO_MODE_AF,GPIO_PUPD_PULLUP,GPIO_PIN_10);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(GPIOB, GPIO_MODE_AF,GPIO_PUPD_PULLUP,GPIO_PIN_11);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(GPIOB,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_10);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(GPIOB,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_11);
/* 配置串口的参数 */
usart_deinit(USART2); // 复位串口
usart_baudrate_set(USART2,band_rate); // 设置波特率
usart_parity_config(USART2,USART_PM_NONE); // 没有校验位
usart_word_length_set(USART2,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(USART2,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(USART2); // 使能串口
usart_transmit_config(USART2,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(USART2,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(USART2_IRQn, 2, 2); // 配置中断优先级
usart_interrupt_enable(USART2,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(USART2,USART_INT_IDLE); // 空闲检测中断
}
/************************************************
函数名称 : usart2_send_data
功 能 : 串口重发送一个字节
参 数 : ucch:要发送的字节
返 回 值 :
作 者 : kwin
*************************************************/
void usart2_send_data(uint8_t ucch)
{
usart_data_transmit(USART2,(uint8_t)ucch); // 发送数据
while(RESET == usart_flag_get(USART2,USART_FLAG_TBE)); // 等待发送数据缓冲区标志置位
}
/************************************************
函数名称 : usart2_send_String
功 能 : 串口发送字符串
参 数 : ucstr:要发送的字符串
返 回 值 :
作 者 : kwin
*************************************************/
void usart2_send_string(uint8_t *ucstr)
{
while(ucstr && *ucstr) // 地址为空或者值为空跳出
{
usart2_send_data(*ucstr++); // 发送单个字符
}
}
//串口2实现完成
/************************************************
函数名称 : fputc
功 能 : 串口重定向函数
参 数 :
返 回 值 :
作 者 : LC
*************************************************/
int fputc(int ch, FILE *f)
{
usart0_send_data(ch);
// 等待发送数据缓冲区标志置位
return ch;
}
/************************************************
函数名称 : USART2_IRQHandler
功 能 : 串口接收中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : kwin
*************************************************/
void USART2_IRQHandler(void)
{
if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) == SET) // 接收缓冲区不为空
{
g_recv_buff[g_recv_length++] = usart_data_receive(USART2); // 把接收到的数据放到缓冲区中
}
if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_IDLE) == SET) // 检测到帧中断
{
usart_data_receive(USART2); // 必须要读,读出来的值不能要
g_recv_buff[g_recv_length] = '\0'; // 数据接收完毕,数组结束标志
g_recv_complete_flag = 1; // 接收完成
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
3.16.4.3 CC2541 驱动文件
这里主要是对 CC2541 的通信确认,以及信息读取。响应的指令还有很多,大家在这个基础上可以实现更多的函数功能.
bsp_cc2541.h 文件
#ifndef _BSP_CC2541_H
#define _BSP_CC2541_H
#include "gd32f4xx.h"
#include "systick.h"
uint8_t IsOK(void);//测试AT指令是否返回OK
void Get_Ver(void);//获取模块版本号
void Get_Addr(void);//获取模块MAC地址
void Get_Name(void);//获取模块名称
void Get_Pin(void);//获取模块Pin码
#endif /* _BSP_CC2541_H */2
3
4
5
6
7
8
9
10
bsp_cc2541.c 文件
/********************************************************************************
* 文 件 名: bsp_CC2541.c
* 版 本 号: 初版
* 修改作者: kwin
* 修改日期: 2023年049月26日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "bsp_usart.h"
#include "bsp_CC2541.h"
/************************************************
函数名称 : IsOK
功 能 : 测试AT指令是否返回OK
参 数 :
返 回 值 : 1:OK,0:Fail
作 者 : kwin
*************************************************/
uint8_t IsOK(void)
{
uint8_t i=0;
usart2_send_string("AT\r\n");
while(g_recv_complete_flag==0)
{
delay_1ms(1);
i++;
if(i>200) return 0;
}
g_recv_complete_flag = 0; // 等待下次接收
g_recv_length = 0; // 清空长度
if((g_recv_buff[0]=='O')&&(g_recv_buff[1]=='K'))
return 1;
else
return 0;
}
/************************************************
函数名称 : Get_Ver
功 能 : 获取模块版本号
参 数 :
返 回 值 :
作 者 : kwin
*************************************************/
void Get_Ver(void)
{
uint8_t i=0;
usart2_send_string("AT+VERSION\r\n");
while(g_recv_complete_flag==0)
{
delay_1ms(1);
i++;
if(i>200) return;
}
g_recv_complete_flag = 0; // 等待下次接收
g_recv_length = 0; // 清空长度
printf("\r\n%s",g_recv_buff);
}
/************************************************
函数名称 : Get_Addr
功 能 : 获取模块MAC地址
参 数 :
返 回 值 :
作 者 : kwin
*************************************************/
void Get_Addr(void)
{
uint8_t i=0;
usart2_send_string("AT+LADDR\r\n");
while(g_recv_complete_flag==0)
{
delay_1ms(1);
i++;
if(i>200) return;
}
g_recv_complete_flag = 0; // 等待下次接收
g_recv_length = 0; // 清空长度
printf("\r\n%s",g_recv_buff);
}
/************************************************
函数名称 : Get_Name
功 能 : 获取模块名称
参 数 :
返 回 值 :
作 者 : kwin
*************************************************/
void Get_Name(void)
{
uint8_t i=0;
usart2_send_string("AT+NAME\r\n");
while(g_recv_complete_flag==0)
{
delay_1ms(1);
i++;
if(i>200) return;
}
g_recv_complete_flag = 0; // 等待下次接收
g_recv_length = 0; // 清空长度
printf("\r\n%s",g_recv_buff);
}
/************************************************
函数名称 : Get_Pin
功 能 : 获取模块Pin
参 数 :
返 回 值 :
作 者 : kwin
*************************************************/
void Get_Pin(void)
{
uint8_t i=0;
usart2_send_string("AT+PIN\r\n");
while(g_recv_complete_flag==0)
{
delay_1ms(1);
i++;
if(i>200) return;
}
g_recv_complete_flag = 0; // 等待下次接收
g_recv_length = 0; // 清空长度
printf("\r\n%s",g_recv_buff);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
3.16.4.4 main 函数实现
在 main 函数中实现验证驱动函数验证,无限循环中实现等待蓝牙连接以及数据收发。通过手机发送蓝牙指令控制 LED2 亮灭。开发板收到手机指令并返回信息给手机,同时向上位机打印提示信息。
| 指令 | LED2 状态 |
|---|---|
| LEDk | 亮 |
| LEDg | 灭 |
大家可以通过此例,实现更多的功能或者任务。
具体代码如下:
/********************************************************************************
* 文 件 名: main.c
* 版 本 号: 初版
* 修改作者: kwin
* 修改日期: 2023年09月26日
* 功能介绍:1-现验证驱动函数验证;
2-无限循环中实现等待蓝牙连接以及数据收发;
3-通过手机发送蓝牙指令控制LED2亮灭;
4-开发板收到手机指令并返回信息给手机;
5-同时向上位机打印提示信息。
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "bsp_led.h"
#include "sys.h"
#include "bsp_usart.h"
#include "bsp_CC2541.h"
/************************************************
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
作 者 : kwin
*************************************************/
int main(void)
{
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
usart0_gpio_config(115200U); // 串口0初始化
usart2_gpio_config(9600U); // 串口2初始化
LED2OFF;
printf("AT test begin \r\n");
if(IsOK())
printf("\r\nOK\r\n");
else
printf("\r\nAT failed\r\n");
Get_Ver();
Get_Addr();
Get_Pin();
Get_Name();
while(1)
{
delay_1ms(100);
//LED2TOGGLE;
if(g_recv_complete_flag)
{
if(g_recv_length>4)
{
if((g_recv_buff[0]=='L')&&(g_recv_buff[1]=='E')&&(g_recv_buff[2]=='D'))
{
if(g_recv_buff[3]=='k')
{
LED2ON;
printf("\r\nLED2 ON\r\n");
usart2_send_string("LED2 is opened\r\n");
}else if(g_recv_buff[3]=='g')
{
LED2OFF;
printf("\r\nLED2 OFF\r\n");
usart2_send_string("LED2 is closed\r\n");
}
}
}else
{
printf("REC:%s",g_recv_buff);
}
g_recv_complete_flag = 0; // 等待下次接收
g_recv_length = 0; // 清空长度
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
3.16.5 移植验证
代码编译并下载到开发板中,可以看到显示与代码实现一致.
源代码打包:
通过网盘分享的文件:立创·梁山派GD32F470ZGT6开发板【模块移植手册代码】
链接: https://pan.baidu.com/s/1pp44yjD1Dhh7U9iZ2a11IA 提取码: LCKF
3.16.6 手机蓝牙 APP 下载
我的红米,打开应用商店,搜 e 调试,这是我试了好多,才觉得这个最好用。大家不要下载错误。
这是电脑下载链接:
这是我自己下载的安装文件,大家谨慎使用,若有问题概不负责,尤其钱财、诈骗等。强烈建议大家从安全的应用商店下载。实在找不到,可以再考虑下面这个。