1. 文章简介
在物联网开发过程中,我发现基于OneOS的相关资料较为稀少,尤其是在OneOS更新后,缺乏详尽的官方教程。为了弥补这一空白,并帮助更多的开发者顺利上手OneOS,我决定将自己的学习经验分享出来。本篇文章将详细介绍如何基于OneOS将HC-04蓝牙模块接入万耦天工开发板,旨在为物联网开发者提供一个实用的参考。在文章中,我将从准备工作开始,包括硬件准备、知识储备以及配置准备,随后详细阐述代码编写的思路,帮助读者在实际操作中少走弯路,快速实现蓝牙模块的接入与通讯(是保姆级教程鸭!)希望这篇文章能为广大物联网开发者提供帮助,并共同推动OneOS生态的发展。
本人也是新手开发者,有问题还望指正!
这期内容需要大家具备对串口通讯和OneOS任务创建和执行的基本了解,和OneOS串口设备开发相关的视频资源在“中国移动物联网”账号下可以找到视频讲解,但是那个视频是基于旧版本OneOS Lite和OneOS Cube讲解的,有更新的地方我在文章中会注明,大家可以结合起来看!
资源指路:
OneOS大讲堂系列教学视频第49期-驱动教学:OneOS serial设备接口讲解
OneOS大讲堂系列教学视频第50期-驱动教学:OneOS serial设备编程实战
OneOS大讲堂系列教学视频第7期-内核教学:OneOS动态任务的创建与删除编程实战
2. 准备工作
2.1 硬件准备
- HC-04蓝牙模块
- 万耦天工开发板(正点原子精英板,搭载STM32F103ZET6芯片)
- 开发板配套烧录线
- 杜邦线若干
2.2 软件准备
- Keil MDK (V5.3.1,建议Keil5,Keil6基于OneOS的工程文件编译会有问题)
- STM32CubeMX
- OneOS-Lite-3.2.0
- OneOS-Cube(V2.1.0)
- FlyMCU(烧录用)
- 串口调试助手(我使用的是XCOM V2.60)
2.3 知识准备:OneOS串口设备相关知识
2.3.1 Serial设备简介
Serial设备对应uart硬件的应用抽象层,应用程序通过OneOS提供的Serial设备管理接口来访问串口硬件
2.3.2 Serial设备接口
现在让我们看看OneOS提供了哪些Serial设备接口吧!
注意!此处并非函数原型,更多详细介绍请到官方文档查询!
指路:OneOS Lite V3.2.0--驱动--Serial设备 官方文档
- os_device_find()-----查找Serial设备
- os_device_open()-----打开Serial设备
- os_device_control()-----配置Serial设备
- os_device_write_nonblock()-----非阻塞方式写数据
- os_device_read_nonblock()-----非阻塞方式读数据
- os_device_write_block()-----阻塞方式写数据
- os_device_read_block()-----阻塞方式读数据
- os_device_close()-----关闭Serial设备
其中着重介绍三个Serial接口(敲黑板!)
(1)os_device_control:配置串口设备,如波特率、数据位、校验位、停止位等参数
串口设备的配置一定要与自己要接入模块的串口通信的配置参数匹配!
这里,我的串口设备是HC-04蓝牙模块,根据汇承官方提供的参数规格书,HC-04默认出厂参数为波特率9600N81,即波特率9600 byte/s,没有校验位,有8位数据位,停止位为1,后续我在配置串口设备的时候就应该按照蓝牙模块的参数配置。
函数原型:
os_err_t os_device_control(os_device_t *dev, int cmd, void *arg);
参数 | 说明 |
dev | 设备指针 |
cmd | 命令控制字,可取值:OS_DEVICE_CTRL_CONFIG |
arg | 控制的参数,可取类型: struct serial_configure |
返回 | 说明 |
OS_SUCCESS | 设备控制成功 |
其他错误码 | 设备控制失败 |
控制参数结构体 struct serial_configure 原型:
/*该原型可以在文件"serial.h"中找到
该文件还定义了默认的控制参数结构体(OS_SERIAL_CONFIG_DEFAULT)可供开发者使用*/
struct serial_configure
{
uint32_t baud_rate; /*波特率*/
uint32_t data_bits : 4; /*数据位*/
uint32_t stop_bits : 2; /*停止位*/
uint32_t parity : 2; /*校验位*/
uint32_t bit_order : 1; /*LSB和MSB谁先*/
uint32_t invert : 1;
uint32_t rx_bufsz : 16; /*RX缓冲区大小*/
uint32_t tx_bufsz : 16; /*TX缓冲区大小*/
uint32_t reserved : 6; /*保留位*/
};
配置参数可取值:
/*同样可在"serial.h"文件中找到
#define BAUD_RATE_2400 2400
#define BAUD_RATE_4800 4800
#define BAUD_RATE_9600 9600
#define BAUD_RATE_19200 19200
#define BAUD_RATE_38400 38400
#define BAUD_RATE_57600 57600
#define BAUD_RATE_115200 115200
#define BAUD_RATE_230400 230400
#define BAUD_RATE_460800 460800
#define BAUD_RATE_921600 921600
#define BAUD_RATE_2000000 2000000
#define BAUD_RATE_3000000 3000000
#define DATA_BITS_5 5
#define DATA_BITS_6 6
#define DATA_BITS_7 7
#define DATA_BITS_8 8
#define DATA_BITS_9 9
#define STOP_BITS_1 0
#define STOP_BITS_2 1
#define STOP_BITS_3 2
#define STOP_BITS_4 3
(2)os_device_write_nonblock:向串口中非阻塞写入数据
函数原型:
os_ssize_t os_device_write_nonblock( os_device_t *dev,
os_off_t pos,
const void *buffer,
os_size_t size);
参数 | 说明 |
dev | 设备指针 |
pos | 写入数据偏移量,此参数串口设备未使用,故置0 |
buffer | 内存缓冲区指针,放置要写入的数据 |
size | 写入的数据大小 |
返回 | 说明 |
>=0 | 返回实际写入的数据量 |
<0 | 返回的错误码 |
(3) os_device_read_nonblock:从串口中非阻塞读取数据
函数原型:
os_ssize_t os_device_read_nonblock(os_device_t *dev,
os_off_t pos,
void *buffer,
os_size_t size);
参数 | 说明 |
dev | 设备指针 |
pos | 读取的数据偏移量,此参数串口设备未使用,故写0 |
buffer | 缓冲区指针,读取的数据将被保存在缓冲区中 |
size | 读取的数据大小 |
返回 | 说明 |
>=0 | 返回实际读取的数据量 |
<0 | 返回错误码 |
2.3.3 串口设备配置流程
- 使能串口(使用STM32CubeMX图形化界面快速完成)
- 系统配置(借助OneOS-Cube图形化配置)
- 查找Serial设备
- 打开Serial设备
- 代码内配置Serial设备各项参数(如波特率、数据位等)
2.4 配置准备
2.4.1 使用OneOS-Cube生成MDK工程
- 下载OneOS Lite V3.2.0源码包,下载完成后打开文件夹OneOS-Lite-V3.2.0,找到project文件夹,右键后选择“OneOS_Cube_2.1.0”,打开OneOS-Cube命令行操作界面
- 输入“oos project”(创建工程命令)
- 选择芯片品牌系列型号等信息,按S键保存配置,按ESC键退出并开始生成工程代码
完成后你就会发现自己project文件夹下出现了一个以你的开发板型号命名的文件夹!
2.4.2 STM32CubeMX使能串口
- 打开刚生成的以你的开发板型号命名的文件夹(我的开发板型号为stm32f103zet6-atk-elite)
- 找到board文件夹
- 打开CubeMX_Config文件夹
- 找到STM32CubeMX文件并打开,这是我们开发板的STM32CubeMX工程
本人在此用的是开发板上的UART4连接蓝牙模块(UART4_TX对应PC10,UART4_RX对应PC11)故使能UART4
- 左侧栏找到Connectivity并点开扩展项,点击你想使用的串口(我选择UART4)
- 配置通信模式Mode和NVIC设置:通信模式选用Asynchronous(异步通信)并使能global_interrupt
- 配置参数:按照你的模块的规格参数来配置,我这里使用的HC-04蓝牙模块默认出厂参数为波特率9600N81,所以我要配置波特率为9600 byte/s,没有校验位,8位数据位和停止位为1
- 全部完成后,点击右上角GENERATE CODE,生成代码文件,随后出现的对话框不用管,直接点击Close关闭STM32CubeMX工程即可
2.4.3 OneOS-Cube完成系统配置(可选)
再次回到以你开发板命名的文件夹下,右键选择“OneOS_Cube_2.1.0”,进行系统配置。
- 输入menuconfig或oos config进入配置页面
- 按“↓”键,找到Driver,回车,进入Driver配置页面
- 一直按“↓”键,找到Serial,回车,进入串口配置页面
- 可以配置串口设备发送/接收缓存区的大小(我这里配置的2048,默认64)
- 完成后,按下“q”退出,确认保存后退出配置
- 输入oos init -i keil或者oos build生成MDK工程,将新配置保存到已有的MDK工程下
3. 代码编写思路
我要编写一个简单的测试蓝牙模块是否接通的程序,具体任务为向串口uart4发送AT指令"AT",然后读取从串口uart4由蓝牙模块发送回来的回复,如果是“OK”则表明接通成功,如果不是但是有回复则表明接通失败(一般回复“ERROR”),如果超过1s没有收到回复则为回复超时,同样表明接通失败
我们应该初始化Serial设备,然后创建任务完成AT指令的发送与回复的接收,并配合一定的向电脑串口输出的内容来帮助我们得知模块接入是否成功
根据之前在 2.3.3 串口设备配置流程 介绍的内容,我们得知在使用Serial设备前应该:
- 查找Serial设备
- 打开Serial设备
- 对Serial设备进行配置(波特率,数据位,校验位等参数)
故编写uart4串口初始化函数代码,如下:
//初始化uart4串口设备
void setupUart4()
{
os_uart_4 = os_device_find("uart4"); /* 寻找设备 */
os_device_open(os_uart_4); /* 打开设备 */
struct serial_configure config = OS_SERIAL_CONFIG_DEFAULT; /* 设置默认 */
config.baud_rate=9600; /*根据我的HC-04参数,将config的波特率改为9600*/
os_device_control(os_uart_4, OS_DEVICE_CTRL_CONFIG, &config); /* 管理设备 */
os_kprintf("setup Uart4 suc\r\n");
}
随后创建任务函数,完成AT指令的发送与回复的接收
//创建任务函数
static void testBLE(void *arg)
{
arg=arg;
setupUart4();
os_kprintf("Wait 300ms for BLE module to start-up\r\n");
/*上电后等待300ms,待模块开始工作*/
os_task_msleep(300);
/*清空RX_BUF*/
memset(RX_BUF,0,sizeof(RX_BUF));
/*向蓝牙模块发送AT指令:AT,测试连接*/
os_device_write_nonblock(os_uart_4,0,"AT\r\n",sizeof("AT\r\n"));
os_kprintf("sent cmd 'AT'\r\n");
/*设置等待蓝牙模块回复时间,按照规格书说明,会在1s内回复(1000ms)*/
uint32_t timeout=1000;
/*收取回复标志,若收到则置1,没收到置0*/
uint32_t flag=0;
while(timeout--)
{
if(os_device_read_nonblock(os_uart_4,0,RX_BUF,RX_MAX)>0)
{
if(strstr((char*)RX_BUF,"OK")) /*如果收到的回复是OK*/
{
os_kprintf("Module OK! Waited for %dms\r\n",1000-timeout);
os_kprintf("Now we can test more AT commands!\r\n");
flag=1;
break;
}
else{ /*如果收到的不回复是OK*/
os_kprintf("Module not OK! Waited for %dms\r\n",1000-timeout);
os_kprintf((char*)RX_BUF);
flag=1;
break;
}
}
os_task_msleep(1); /*延时1ms,完成等待*/
}
if(!flag) os_kprintf("timeout!"); /*回复超时*/
memset(RX_BUF,0,RX_MAX); /*清空RX_BUF*/
}
最后在主函数中创建任务并执行就可以啦!
可能遇到的问题:
- 跟着视频中的声明变量报错:视频中很多int型声明使用的是os_uint8_t类似的表达,新版本OneOS已经废除了这样的数据类型,改为uint8_t等
- 任务的创建函数有所变化,要看最新版本文档的讲解,与视频中的演示有区别
4. 编译、烧录与测试
4.1 编译
完成代码编写后,找到左上角的Build选项,点击开始编译工程,生成.hex文件
4.2 烧录
使用FlyMCU进行烧录:
- 使用万耦天工开发板适配的烧录线连接电脑和开发板(电脑要提前装好CH340的驱动)
- 打开FlyMCU,按下开发板电源键(如果有)给开发板上电,否则无法烧录哦!
- 完成各种配置勾选,装入工程文件,然后点击“清除芯片”然后点击“开始编程”
- 完成烧录
可能遇到的问题:
- 找不到CH430驱动的串口:可能是没有安装CH340驱动
- 提示无法打开COM串口:检查是否刚才使用选择的COM串口进行串口调试,如果是,关闭串口调试助手再烧录,再不行就重新插拔烧录线试试
- 芯片连接超时:1)可能是BOOT处跳冒不对,查看开发板的相关手册,找到下载程序应该设置的BOOT0和BOOT1的电平。2)也可能是与电脑通信的串口的跳冒被拔掉了导致的(作者就有一次把USART1跳冒拔了,一直连接超时,后来发现是这个问题,大家可以看看自己有没有这个问题,尝试解决) 3)开发板没上电
4.3 测试
接线:
HC-04 | STM32 |
RX | PC10 |
TX | PC11 |
VCC | 3.3V |
GND | GND |
打开串口调试助手,选择对应的COM串口和合适的波特率(我是COM6和112500 byte/s)打开串口,按下开发板RESET键开始调试
可能遇到的问题:
- 输出乱码:检查设置的波特率是否与和电脑串口通讯的开发板串口的波特率一直
- 无法发送AT指令或者回复不是OK:检查是否向串口写入的是"AT\r\n"(AT指令一定要带回车,否则不能正确收到回复)
5. 奉上main.c源码
#include <board.h>
#include <os_task.h>
#define RX_MAX 20 /*声明接收缓冲区接收字节数*/
uint8_t RX_BUF[RX_MAX]; /*声明接收缓冲区*/
os_device_t *os_uart_4; /*声明一个串口设备os_uart_4*/
//初始化uart4串口设备
void setupUart4()
{
os_uart_4 = os_device_find("uart4"); /* 寻找设备 */
os_device_open(os_uart_4); /* 打开设备 */
struct serial_configure config = OS_SERIAL_CONFIG_DEFAULT; /* 设置默认 */
config.baud_rate=9600; /*根据我的HC-04参数,将config的波特率改为9600*/
os_device_control(os_uart_4, OS_DEVICE_CTRL_CONFIG, &config); /* 管理设备 */
os_kprintf("setup Uart4 suc\r\n");
}
//创建任务函数
static void testBLE(void *arg)
{
arg=arg;
setupUart4();
os_kprintf("Wait 300ms for BLE module to start-up\r\n");
/*上电后等待300ms,待模块开始工作*/
os_task_msleep(300);
/*清空RX_BUF*/
memset(RX_BUF,0,sizeof(RX_BUF));
/*向蓝牙模块发送AT指令:AT,测试连接*/
os_device_write_nonblock(os_uart_4,0,"AT\r\n",sizeof("AT\r\n"));
os_kprintf("sent cmd 'AT'\r\n");
/*设置等待蓝牙模块回复时间,按照规格书说明,会在1s内回复(1000ms)*/
uint32_t timeout=1000;
/*收取回复标志,若收到则置1,没收到置0*/
uint32_t flag=0;
while(timeout--)
{
if(os_device_read_nonblock(os_uart_4,0,RX_BUF,RX_MAX)>0)
{
if(strstr((char*)RX_BUF,"OK")) /*如果收到的回复是OK*/
{
os_kprintf("Module OK! Waited for %dms\r\n",1000-timeout);
os_kprintf("Now we can test more AT commands!\r\n");
flag=1;
break;
}
else{ /*如果收到的不回复是OK*/
os_kprintf("Module not OK! Waited for %dms\r\n",1000-timeout);
os_kprintf((char*)RX_BUF);
flag=1;
break;
}
}
os_task_msleep(1); /*延时1ms,完成等待*/
}
if(!flag) os_kprintf("timeout!"); /*回复超时*/
memset(RX_BUF,0,RX_MAX); /*清空RX_BUF*/
}
int main(void)
{
/*创建任务并执行*/
os_task_id task;
task = os_task_create(OS_NULL, OS_NULL, 512, "testBLE",testBLE , OS_NULL, 3);
OS_ASSERT(task);
os_task_startup(task);
return 0;
}