目录
[1 系统架构](#1 系统架构)
[1.1 系统结构](#1.1 系统结构)
[1.2 系统硬件框架结构](#1.2 系统硬件框架结构)
[1.3 蓝牙模块介绍](#1.3 蓝牙模块介绍)
[2 微信小程序实现](#2 微信小程序实现)
[2.1 UI介绍](#2.1 UI介绍)
[2.2 代码实现](#2.2 代码实现)
[3 上位机功能实现](#3 上位机功能实现)
[3.1 通信协议](#3.1 通信协议)
[3.2 系统测试](#3.2 系统测试)
[4 下位机功能实现](#4 下位机功能实现)
[4.1 功能介绍](#4.1 功能介绍)
[4.2 代码实现](#4.2 代码实现)
[4.3 源代码文件](#4.3 源代码文件)
[5 测试](#5 测试)
[5.1 编译和下载代码](#5.1 编译和下载代码)
[5.2 App上数据显示的UI](#5.2 App上数据显示的UI)
概述
本文主要介绍设计一个系统实现如下功能:使用Renesas R7FA8D1BH (Cortex®-M85)和微信小程序App数据传输。笔者介绍了系统设计的架构,包括硬件架构,软件架构,以及上位机的实现代码。并在下位机实现数据发送功能,App正确的解析数据,并通过UI显示数据的功能。
1 系统架构
1.1 系统结构
功能介绍
使用Renesas R7FA8D1BH (Cortex®-M85)的UART端口,与蓝牙模块通信,将数据包发送出去,上位机使用基于微信小程序编写的App,上位机接收到数据之后,解析数据包,并显示在UI上。
1.2 系统硬件框架结构
1)数据采集部分
SHT20: 温湿度传感器,采集环境温度和湿度
ISL29035: 光照传感器,采集环境的光照强度
DS18B20: 高精度温度传感器,设备工作环境温度监测
ADC(Temperatre): MCU内部温度监测
ADC(可调电阻): 模拟数据输入
2)显示部分
OLED: 实时显示传感器的数据
3)模式切换
KEY-UP: 物理按键,正方向切换UI
KEY-DOWM: 物理按键,反方向切换UI
4)数据传输部分
使用蓝牙模块发送数据包,该数据包会被微信小程序接收
1.3 蓝牙模块介绍
HC-08蓝牙串口通信模块是新一代的基于Bluetooth Specification V4.0 BLE 蓝牙协议的数传模块。无线工作频段为 2.4GHz ISM,调制方式是 GFSK。模块最大发射功率为4dBm,接收灵敏度-93dBm,空旷环境下和 手机可以实现 80 米超远距离通信。
其和MCU之间通过串口通信,软件设计也简单便捷,且不需要考虑蓝牙协议栈问题,非常适合做速成产品。
蓝牙模块与MCU之间连接图:
2 微信小程序实现
2.1 UI介绍
接收数据:
1) 显示温度和湿度数据
2) 显示光照数据
3 ) 显示MCU内部温度数据
2.2 代码实现
1)创建detail.wxml文件,实现UI框架
详细代码如下:
html
<view class="connect_box">
<text class='connect_device_name'>{{deviceName}}</text>
<text wx:if="{{connected}}" class="connect_state" catchtap="DisConnectTap">已连接</text>
<text wx:else class="connect_state" catchtap="DisConnectTap" >未连接</text>
</view>
<view class="block-content">
<view class="block-item column">
<view class="item-row slider-box">
<view class="item-title" >
<image class="item-title-icon" src="/images/light_s.png" />温度(℃)
</view>
<view class="input-unit"></view>
<view class="item-title" style="position: relative; left: 30rpx; ">
<image class="item-title-icon" src="/images/light_s.png" />湿度(%)
</view>
<view class="input-unit"></view>
</view>
<view class="item-row">
<view class="input-value">
<input type="digit" value="{{temphtValue}}" disabled="false"/>
</view>
<view class="input-value" style="position: relative; left: -90rpx; ">
<input type="digit" value="{{humidityValue}}" disabled="false"/>
</view>
</view>
</view>
<view class="block-item column">
<view class="item-row slider-box">
<view class="item-title">
<image class="item-title-icon" src="/images/light_s.png" />光照(lux)
</view>
<view class="input-unit"></view>
<view class="item-title" style="position: relative; left: 60rpx; ">
<image class="item-title-icon" src="/images/light_s.png" />SR测距(cm)
</view>
<view class="input-unit"></view>
</view>
<view class="item-row">
<view class="input-value" >
<input type="digit" value="{{luxValue}}" disabled="false"/>
</view>
<view class="input-value" style="position: relative; left: -90rpx; ">
<input type="digit" value="{{srValue}}" disabled="false"/>
</view>
</view>
</view>
<view class="block-item column" style="width: 750rpx; height: 507rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx; position: relative">
<button type="primary" bindtap="run_up" plain="true" style="position: relative; left: 3rpx; top: 5rpx; width: 183rpx; height: 357rpx; display: block; box-sizing: border-box">前进</button>
<button type="primary" bindtap="run_up" plain="true" style="position: relative; left: 0rpx; top: 263rpx; width: 188rpx; height: 326rpx; display: block; box-sizing: border-box">后退</button>
<button type="primary" bindtap="run_up" plain="true" style="position: relative; left: -247rpx; top: 3rpx; width: 183rpx; height: 361rpx; display: block; box-sizing: border-box">左转</button>
<button type="primary" bindtap="run_up" plain="true" style="position: relative; left: 256rpx; top: -82rpx; width: 183rpx; height: 343rpx; display: block; box-sizing: border-box">右转</button>
<button type="primary" bindtap="run_up" plain="true" style="position: relative; left: 5rpx; top: -173rpx; width: 179rpx; height: 361rpx; display: block; box-sizing: border-box">停止</button>
</view>
</view>
2)样式文件
详细代码如下:
css
.connect_box {
width: 100%;
height: 30px;
line-height: 30px;
font-size: 32rpx;
color: #666;
background-color: antiquewhite;
font-family: "Microsoft YaHei";
}
.connect_device_name{
float: left;
padding-left: 10px;
color: #39beff;
}
.connect_state {
float: right;
padding-right: 10px;
text-decoration: underline;
color: #39beff;
}
page {
min-height: 100%;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
background-color: cadetblue;
}
.block {
padding: 15px;
width: 100%;
box-sizing: border-box;
}
.block-title {
font-size: 20px;
margin-bottom: 30px;
}
.block-subtitle {
margin: 15px 0;
font-size: 14px;
color: #666;
}
.block-item {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px 0;
padding: 15px;
font-size: 18px;
border-radius: 8px;
background:azure;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
}
.block-item:last-child {
margin-bottom: 0;
}
.block-item.column {
flex-direction: column;
}
.item-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.item-row + .item-row {
margin-top: 10px;
}
.item-column {
display: flex;
flex-direction: column;
justify-content: space-between;
align-self: stretch;
flex-grow: 1;
}
.item-title {
font-size: 16px;
}
.item-title-icon {
height: 1.2em;
width: 1.2em;
margin-right: 10px;
vertical-align: middle;
}
.slider-box {
flex-grow: 1;
}
.slider {
margin-left: 10px;
margin-right: 20px;
flex-grow: 1;
}
.slider-value {
display: flex;
justify-content: space-around;
align-items: center;
margin: 15px auto;
width: 70%;
}
.input-unit {
text-align: right;
font-size: 14px;
color: #999;
}
.input-value {
display: inline-block;
padding: 0 16px;
border: 1px solid #CCCCCC;
border-radius: 4px;
max-width: 3.5em;
font-size: 16px;
text-align: center;
}
3) JS文件
详细代码如下:
javascript
const utils = require('utils.js')
const ble = require('bluetooth.js')
Page({
/**
* 页面的初始数据
*/
data: {
pageload: false,
connected: false,
send_hex: false,
send_string: true,
send_string_val: 'Hex',
recv_string: true,
recv_string_val: 'Hex',
recv_value: '',
send_number: 0,
recv_number: 0,
recv_hex: true,
try_cnt:0,
rece_string:'',
deviceArray: []
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this;
console.log(options);
this.setData({
pageload:true,
connected: false,
deviceId: options.id,
try_cnt:0,
deviceName: options.name
});
console.log("detail: onLoad");
wx.stopBluetoothDevicesDiscovery({
success: function (res) {
console.log('停止搜索设备', res)
}
})
that.closeBLEConnection(that.data.deviceId);
that.createBLEConnection(that.data.deviceId, that.data.deviceName);
},
onHide () {
var that = this;
// Do something when hide.
// 断开连接
console.log("detail: onHide");
},
onShow:function()
{
// var that = this;
// connect bluetooth
// that.closeBLEConnection(that.data.deviceId);
// that.createBLEConnection(that.data.deviceId, that.data.deviceName);
},
onUnload() {
var that = this;
this.setData({
pageload:true,
connected: false,
try_cnt:0,
});
console.log("page: onUnload ");
that.offBLEMonitor();
that.closeBLEConnection(that.data.deviceId);
that.closeBluetoothAdapter();
wx.showToast({
title: '断开蓝牙',
icon: 'success',
duration: 2000
})
},
DisConnectTap:function()
{
var that = this;
that.setData({
pageload:true,
connected: false,
try_cnt:0,
});
ble.openBluetoothAdapter(that.data.deviceId, that.data.deviceName);
that.createBLEConnection(that.data.deviceId, that.data.deviceName);
},
RecvCleanTap: function () {
this.setData({
recv_value: '',
recv_number: 0
});
},
/**
* 创建连接
* @param {*} deviceId
* @param {*} name
*/
createBLEConnection(deviceId, name)
{
wx.createBLEConnection({
deviceId,
success: (res) => {
console.log('createBLEConnection - success: ', res)
this.getBLEDeviceServices(deviceId)
},
fail: (res) => {
console.error('createBLEConnection - fail: ', res)
if(res.errCode == 10006 ){
this.createBLEConnection(deviceId, name)
}
else{
ble.openBluetoothAdapter(deviceId, name)
this.createBLEConnection(deviceId, name)
}
this.setData({
connected: false,
})
}
})
},
getBLEDeviceServices(deviceId)
{
var that = this;
wx.getBLEDeviceServices({
deviceId,
success: (res) =>
{
console.log('getBLEDeviceServices - success: ', res)
for (let i = 0; i < res.services.length; i++)
{
var ergodic_UUID =res.services[i].uuid; //取出服务里面的UUID
var UUID_slice = ergodic_UUID.slice(4, 8); //截取4到8位
console.log('getBLEDeviceServices, service ID = ', res.services[i].uuid);
if ( res.services[i].isPrimary && (UUID_slice == "FFE0") )
{
that.setData({
serviceId: res.services[i].uuid,
});
break;
}
}
wx.getConnectedBluetoothDevices({
services: res.services,
success: (res) =>
{
console.log("getConnectedBluetoothDevices - success: " + res)
},
fail: (res) => {
console.error('getConnectedBluetoothDevices - fail: ', res)
ble.openBluetoothAdapter(deviceId, that.data.deviceName)
}
})
that.getBLEDeviceCharacteristics(deviceId, that.data.serviceId);
},
fail: (res) => {
console.error('getBLEDeviceServices - fail: ', res)
// try it again
ble.openBluetoothAdapter(deviceId, that.data.deviceName)
that.monitor_connected();
}
});
},
getBLEDeviceCharacteristics(deviceId, serviceId)
{
var that = this;
let falg = false;
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) =>
{
that.setData({
connected: true,
})
console.log('getBLEDeviceCharacteristics success', res.characteristics)
for (let i = 0; i < res.characteristics.length; i++)
{
let item = res.characteristics[i]
console.log('getBLEDeviceCharacteristics, Characteristics ID = ', item.uuid)
// 该特征值:可读
if (item.properties.read)
{
wx.readBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: item.uuid,
})
}
// 该特征值:可写
if (item.properties.write)
{
this.setData({
canWrite: true
})
this._deviceId = deviceId
this._serviceId = serviceId
this._characteristicId = item.uuid
this.writeValue()
}
if (item.properties.notify || item.properties.indicate)
{
that.setData({
characteristicId: item.uuid
});
falg = true;
break;
}
}
if( falg )
{
console.debug('getBLEDeviceCharacteristics - deviceId : ', deviceId)
console.debug('getBLEDeviceCharacteristics - serviceId : ', serviceId)
console.debug('getBLEDeviceCharacteristics - characteristicId: ', that.data.characteristicId)
// read device character value
that.readBLECharacteristicValue(deviceId, serviceId, that.data.characteristicId)
that.notifyBLECharacteristicValueChange(deviceId, serviceId, that.data.characteristicId)
}
},
fail: (res) => {
console.error('getBLEDeviceCharacteristics -- fail: ', res)
this.setData({
connected: false,
})
if (res.errCode === 10006)
{
that.offBLEMonitor();
that.createBLEConnection(deviceId, that.data.deviceName);
}
}
})
},
readBLECharacteristicValue(deviceId,serviceId, characteristicId )
{
wx.readBLECharacteristicValue({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId,
success (res) {
console.log('readBLECharacteristicValue:', res.errCode)
}
})
},
notifyBLECharacteristicValueChange(deviceId,serviceId, characteristicId )
{
var that = this;
wx.notifyBLECharacteristicValueChange({
state: true, // enable notify
deviceId,
serviceId,
characteristicId,
success: (res) => {
console.info('notifyBLECharacteristicValueChange success: ', res.errMsg)
// read data here
// 操作之前先监听,保证第一时间获取数据
wx.onBLECharacteristicValueChange(function(res)
{
that.data.connected = true;
console.info('onBLECharacteristicValueChange', res);
console.info(`characteristic ${res.characteristicId} has changed, now is ${res.value}`);
var result = res.value;
var hex = utils.buf2hex(result);
var _hex_ss = utils.hex2string(hex);
console.info("hex: " + hex);
console.info("string: " + _hex_ss);
that.data.rece_string += _hex_ss;
var recv_number_1 = that.data.recv_number + _hex_ss.length / 2;
var recv_number = Math.round(recv_number_1);
if( that.data.rece_string.includes("log:") && that.data.rece_string.includes(":end")){
that.setData({
recv_number: recv_number,
recv_value: that.data.recv_value + that.data.rece_string,
});
console.info("string: " + that.data.rece_string);
let buff = that.data.rece_string.split(':')
console.log(buff)
that.data.rece_string = ''
let valueList = buff[1].split(',')
that.data.recv_value = "";
that.data.recv_number = 0;
console.log(valueList)
if(valueList.length == 4 ){
that.setData({
temphtValue: valueList[0],
humidityValue: valueList[1],
luxValue: valueList[2],
srValue: valueList[3],
});
}
}
that.monitor_connected();
})
},
fail: (res) => {
console.error('notifyBLECharacteristicValueChange fail: ', res)
that.monitor_connected();
}
})
},
monitor_connected_action()
{
var that = this;
let deviceId = that.data.deviceId;
wx.onBLEConnectionStateChange(function(res) {
// 该方法回调中可以用于处理连接意外断开等异常情况
console.log( "onBLEConnectionStateChange ----- " + `device ${res.deviceId} state has changed, connected: ${res.connected}`)
if( res.deviceId == deviceId && res.connected == false )
{
wx.closeBLEConnection({
deviceId,
success: (res) => {
console.debug('detail: closeBLEConnection success', res);
that.offBLEMonitor();
that.createBLEConnection(deviceId, that.data.deviceName);
},
fail: (res) => {
console.error('detail: closeBLEConnection fail', res);
if (res.errCode === 10006) {
that.offBLEMonitor();
that.createBLEConnection(deviceId, that.data.deviceName);
}
}
})
that.setData({
try_cnt: that.data.try_cnt + 1,
})
}
else{
that.data.try_cnt = 0;
}
})
},
monitor_connected()
{
var that = this;
setTimeout(that.monitor_connected_action, 200);
},
writeValue( val )
{
// 向蓝牙设备发送一个0x00的16进制数据
let buffer = new ArrayBuffer(1);
let dataView = new DataView(buffer);
dataView.setUint8(0, val);
console.debug('getBLEDeviceCharacteristics - deviceId : ', this._deviceId)
console.debug('getBLEDeviceCharacteristics - serviceId : ', this._serviceId)
console.debug('getBLEDeviceCharacteristics - characteristicId: ', this._characteristicId)
wx.writeBLECharacteristicValue({
deviceId: this._deviceId,
serviceId: this._serviceId,
characteristicId: this._characteristicId,
value: buffer,
success: (res) => {
console.debug('writeBLECharacteristicValue success', res);
},
fail: (res) => {
console.error(' writeBLECharacteristicValue fail', res);
this.setData({
connected: false,
})
}
})
},
closeBluetoothAdapter()
{
wx.closeBluetoothAdapter({
success (res) {
console.log(res)
}
})
this.setData({
connected: false,
}),
this._discoveryStarted = false
},
closeBLEConnection( deviceId )
{
wx.closeBLEConnection({
deviceId,
success: (res) => {
console.debug('detail: closeBLEConnection success', res);
},
fail: (res) => {
console.error('detail: closeBLEConnection fail', res);
}
})
this.setData({
connected: false,
canWrite: false,
})
},
run_up:function()
{
var that = this;
var val = 0x18 ;
that.writeValue( val );
},
run_down:function()
{
var that = this;
var val = 0x52;
that.writeValue( val );
},
run_left:function()
{
var that = this;
var val = 0x08;
that.writeValue( val );
},
run_right:function()
{
var that = this;
var val = 0x5a;
that.writeValue( val );
},
whiteLightValueSliderChange:function(e)
{
var that = this;
// 向蓝牙设备发送一个0x00的16进制数据
var val = Math.random() * 255 | 0;
that.writeValue( val );
},
offBLEMonitor(){
this.setData({
connected: false,
}),
wx.offBLEPeripheralConnectionStateChanged();
wx.offBLEConnectionStateChange();
wx.offBLECharacteristicValueChange();
wx.offBLEMTUChange();
}
})
3 上位机功能实现
3.1 通信协议
发送数据协议如下:
log: %d,%d,...,%d**:end**
Demo log如下:
log:25.07,60.69,1312,172.76:end
微信小程序log 调试信息
微信小程序端解析数据方法:
代码第313行: 判断数据包的头(log:)和尾部(:end)
代码第320行:解析字符串
3.2 系统测试
调试log信息,解析到下位机上传的数据
4 下位机功能实现
4.1 功能介绍
1)系统上电之后,首先初始化MCU的资源
2)初始化外围资源,包括各类sensor
- 实时读取传感器的数据
4)实时通过蓝牙模块上传数据
4.2 代码实现
- 发送数据接口
代码48行: 判断字符串是否发送完毕
代码51行:使用串口发送数据
2)发送数据函数
代码67行:格式化数组
代码75行:格式化发送数据
代码77行:发送数据
3)实时获取数据函数
代码36行: 读取DS18B20数据
代码47~48行:读取SHT20数据
代码50行:读取ISL29035的数据
代码52~53行: Mcu内部数据
代码55~61行:打包传感器数据
代码65行:上传数据
4.3 源代码文件
获取和传输数据函数
cpp
void app_getSensorDataPacket( void )
{
static int sec;
rtc_time_t get_time;
float humidity;
float temperature;
unsigned int luxValue;
uint16_t adc_ch1_value;
float adc_temp_value;
bool result;
result = ds18b20NoBlockingProcess();
//DS18B20
if( result )
{
stru_SensorData.ds18b20_value = st_ds1b20val.temperatureVal;
}
user_get_currentRtc(&get_time);
if( get_time.tm_sec != sec )
{
sec = get_time.tm_sec;
sht2x_get_humidy(&humidity);
sht2x_get_temperature(&temperature);
IsL29035_read_lux( &luxValue );
adc_basic_get_value( &adc_ch1_value );
adc_basic_get_temperature( &adc_temp_value );
stru_SensorData.humidity = humidity;
stru_SensorData.temperature = temperature;
stru_SensorData.luxValue = luxValue;
// mcu internel adc and temperature
stru_SensorData.mcuTemp_value = adc_temp_value*100;
stru_SensorData.adc_RawCnt = adc_ch1_value;
// send log to app
bluetooth_sengLog();
}
}
蓝牙模块核心应用程序
cpp
/*
FILE NAME : app_bluetooth.c
Description: app UI
Author : tangmingfei2013@126.com
Date : 2024/06/03
*/
#include "app_bluetooth.h"
#include "app_main.h"
#include "bluetooth.h"
#define PROT_FRAME_LEN 16
Stru_BlueCmd stru_BlueCmd;
static uint8_t recv_buf[PROT_FRAME_LEN];
static uint8_t rev_cnt = 0;
void bluetoothCmd_DataRecvByte(uint8_t data )
{
recv_buf[rev_cnt++] = data;
if( rev_cnt >= PROT_FRAME_LEN)
rev_cnt = 0;
// bluetooth command
stru_BlueCmd.recStatus +=1;
if( stru_BlueCmd.recStatus > PROT_FRAME_LEN )
stru_BlueCmd.recStatus = 0;
if( rev_cnt >= 2 )
{
rev_cnt = 0;
stru_BlueCmd.mcmd = recv_buf[0];
stru_BlueCmd.mode = recv_buf[1];
}
}
static void SendInterface(uint8_t* pu8_buf, uint16_t u16_len )
{
r_sci_b_uart1_sendArry(pu8_buf, u16_len);
}
static void bluetooth_sendString( uint8_t *ss )
{
uint8_t buff[2];
uint8_t index = 0;
while ( ss[index]!='\0' )
{
buff[0]= ss[index];
SendInterface(buff, 1);
index++;
}
}
void bluetooth_sengLog( void )
{
static uint8_t step = 0;
Struc_SensorPack *pSensorData;
uint8_t dataBuff[128];
pSensorData = &stru_SensorData;
switch( step )
{
default:
case 0:
memset((char*)dataBuff, '\0', sizeof(dataBuff));
//sprintf((char*)dataBuff, "log:%d,%.2f,", pSensorData->luxValue,pSensorData->sr_value);
// bluetooth_sendString( dataBuff );
step = 1;
break;
case 1:
//memset((char*)dataBuff, '\0', sizeof(dataBuff));
sprintf((char*)dataBuff, "log:%.2f,%.2f,%d,%.2f:end", pSensorData->temperature*0.01, pSensorData->humidity*0.01,
pSensorData->luxValue,pSensorData->mcuTemp_value*0.01);
bluetooth_sendString( dataBuff );
step = 0;
break;
}
}
/* End of this file */
5 测试
5.1 编译和下载代码
编译和下载代码