Loading

手机扫描二维码,就可以通过蓝牙连接智能设备,这是什么原理?

摘自:https://www.zhihu.com/question/33099066/answer/3295112330?utm_id=0

 

作者:Think
链接:https://www.zhihu.com/question/33099066/answer/3295112330
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最近公司有个需求, 就是使用微信小程序控制设备, 让我研究一下可行性, 然后下一代产品加入这个功能.我本人并不喜欢开发小程序和Android应用, 无奈这是公司的命令解决方案那接下来设计解决解决方案, 首先先查看官方文档连接硬件能力 / 蓝牙 / 介绍 (qq.com)这里有一个重点就是小程序的蓝牙能力在小程序中,要使用蓝牙能力(Beacon 除外)必须首先调用 wx.openBluetoothAdapter 初始化蓝牙适配器模块,其生效周期为调用 wx.openBluetoothAdapter 至调用 wx.closeBluetoothAdapter 或小程序被销毁为止。只有在小程序蓝牙适配器模块生效期间,开发者才能够正常调用蓝牙相关的小程序 API,并收到蓝牙模块相关的事件回调(绑定监听不受此限制)。小程序对蓝牙支持情况如下:经典蓝牙:iOS 因系统限制暂无法提供,安卓目前已在规划中。蓝牙低功耗 (BLE):主机模式:基础库 1.1.0(微信客户端 iOS 6.5.6,Android 6.5.7)开始支持。从机模式:基础库 2.10.3 开始支持。蓝牙信标 (Beacon):基础库 1.2.0 开始支持。也就是, 微信小程序只支持低功耗蓝牙, 知道这个信息后, 我们再选择一个低功耗蓝牙模块.我使用汇承的蓝牙模块, 因为他家的模块开机就是广播模式 连接上就是透窗模式, 比较方便. -> 广州汇承信息科技有限公司 (hc01.com)至于为什么不使用esp32这样的可编程无线模块, 因为单片机用不上, 和它交互的控制端是Android工控屏低功耗蓝牙使用使用USB转串口连接硬件<img src="https://picx.zhimg.com/50/v2-322c01cd3b686f5e37e8734cebd94565_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="1280" data-rawheight="1706" data-original-token="v2-4c46044035593ea52aa872d2e9265265" class="origin_image zh-lightbox-thumb" width="1280" data-original="https://pic1.zhimg.com/v2-322c01cd3b686f5e37e8734cebd94565_r.jpg?source=2c26e567"/>使用官方给的扫描软件尝试一下<img src="https://picx.zhimg.com/50/v2-3bca7bcbf2531f5e42a69b1bf19c9f23_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="1280" data-rawheight="1692" data-original-token="v2-c4225e50c445c380cb72e5634c3b910e" class="origin_image zh-lightbox-thumb" width="1280" data-original="https://pic1.zhimg.com/v2-3bca7bcbf2531f5e42a69b1bf19c9f23_r.jpg?source=2c26e567"/>发送数据<img src="https://picx.zhimg.com/50/v2-546fd0acf74073578abd0ddb6292d208_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="1280" data-rawheight="1706" data-original-token="v2-cd47ca9df1a5b7aa1ec3d538feb43c75" class="origin_image zh-lightbox-thumb" width="1280" data-original="https://picx.zhimg.com/v2-546fd0acf74073578abd0ddb6292d208_r.jpg?source=2c26e567"/><img src="https://pica.zhimg.com/50/v2-9b407f64ef32dc65c152d8389d79135b_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="941" data-rawheight="723" data-original-token="v2-40bfd8345d183ea18d84ca16b3a6d914" class="origin_image zh-lightbox-thumb" width="941" data-original="https://picx.zhimg.com/v2-9b407f64ef32dc65c152d8389d79135b_r.jpg?source=2c26e567"/>好了这里蓝牙没有问题 (我并没有做任何设置, 直接上电连接就可以使用了)小程序使用// logs.ts
// const util = require('../../utils/util.js')
import { formatTime } from '../../utils/util'

Page({
data: {
context: 'null',
},
onSanc() {
var root = this
wx.scanCode({
onlyFromCamera: true,
success(res) {
root.setData({ context: res.result })
var Did = res.result
// wx.closeBluetoothAdapter()
wx.createBLEConnection({
deviceId: Did,
success() {
console.log("connect success")
},
fail(error) {
console.log("connect error", error)
wx.createBLEConnection({
deviceId: Did,
success() {
console.log("connect success")
},
fail(error){
console.log("conect error again", error)
}
})
}
})
}
})
},
onLoad() {
var that = this
wx.openBluetoothAdapter({
success() {
// 初始化成功
wx.getBluetoothAdapterState({
success() {
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: function () {
// 蓝牙设备列表正常
// 获取蓝牙设备列表
},
fail() {
that.setData({ context: '获取蓝牙设备列表失败' })
}
})
},
fail() {
that.setData({ context: '蓝牙适配器状态错误' })
}
})
},
fail() {
// 初始化失败
that.setData({ context: '蓝牙初始化失败' })
}
}),
wx.onBluetoothDeviceFound((res) => {
res.devices.forEach((device) => {
// 这里可以做一些过滤
if(device.name != ''){
console.log('Device Found', device)
}
})
// 找到要搜索的设备后,及时停止扫描
// wx.stopBluetoothDevicesDiscovery()
})
},
})
扫描出的结果<img src="https://pic1.zhimg.com/50/v2-dd916c787fae60319f54990884d0f08c_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="778" data-rawheight="46" data-original-token="v2-dd916c787fae60319f54990884d0f08c" class="origin_image zh-lightbox-thumb" width="778" data-original="https://pica.zhimg.com/v2-dd916c787fae60319f54990884d0f08c_r.jpg?source=2c26e567"/>值得注意的一点是, 小程序连接蓝牙设备使用的是 deviceId, 而deviceId就是蓝牙的MAC地址, 正好小程序有扫码的功能, 可以把蓝牙的MAC地址生成成二维码扫描连接, 上面的代码写好了, 尝试一下.<img src="https://pica.zhimg.com/50/v2-1fd1085a952b55516e416dd349534388_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="770" data-rawheight="40" data-original-token="v2-1fd1085a952b55516e416dd349534388" class="origin_image zh-lightbox-thumb" width="770" data-original="https://picx.zhimg.com/v2-1fd1085a952b55516e416dd349534388_r.jpg?source=2c26e567"/>OK, 连接成功, 这样省的扫描了.最终方案这次演示讲使用微信小程序扫描二维码, 连接蓝牙, 再通过微信小程序控制FPGA的LED灯(可以使用单片机, 我是觉得用FPGA可以少写代码)具体使用流程:进入小程序, 点击按钮扫描, 开始扫描二维码.扫码成功后, 开始连接蓝牙蓝牙连接成功后, 跳转到控制界面点击 OPEN 打开FPGA的流水灯点击 CLOSE 关闭FPGA的流水灯退出界面断开蓝牙嗯~ 加一个在关闭LED时让FPGA返回运行时间的功能吧编码由于微信小程序的框架代码太多了, 我不可能粘贴出所有代码, 所以我只讲关键, 具体怎么做出来完整的APP, 请自行学习, 不要害怕, 微信小程序不难.扫码连接蓝牙跳转页面Page({
// 按钮点击事件
onScan() {
// 扫码
wx.scanCode({
onlyFromCamera: true,
success(res) {
// 打开蓝牙
wx.openBluetoothAdapter({
success() {
wx.showToast({
title: "connecting...",
icon:"loading"
})
// 连接蓝牙
wx.createBLEConnection({
deviceId: res.result,
success() {
// 跳转界面
wx.navigateTo({ url: "../ctrl/ctrl?deviceId=" + res.result })
},
fail(error) {
console.log("connect BLE fail:", error)
wx.showToast({
title: "connect fail"
})
}
})
},
fail(error) {
console.log("open bluetooth fail:", error);
wx.showToast({
title: "open bluetooth fail!",
duration: 1500
})
}
})
},
fail(error) {
console.log("scan fail:", error)
wx.showToast({
title: "connect fail!",
icon: "none",
duration: 1500
})
}

})
}
})
首页非常简单, 主要主要是连接蓝牙, 微信小程序把接口做的也非常简单, 不多说了.控制界面刚才有遗漏的地方, 在看微信文档时有一个服务的概念, 获取输出是这些东西:<img src="https://picx.zhimg.com/50/v2-496321abde40366a822f3a45e09c862c_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="808" data-rawheight="81" data-original-token="v2-496321abde40366a822f3a45e09c862c" class="origin_image zh-lightbox-thumb" width="808" data-original="https://pic1.zhimg.com/v2-496321abde40366a822f3a45e09c862c_r.jpg?source=2c26e567"/>这是因为BLE 使用的是 GATT 协议, 协议具体原理我不讲解了, 有兴趣的话查看参考然后更具服务(Service)查找特性(Characteristic), 结果:<img src="https://picx.zhimg.com/50/v2-009399b534980871c1f5d4c27f1fbffc_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="796" data-rawheight="353" data-original-token="v2-94da8be98ecce681ecc440fadfc3d707" class="origin_image zh-lightbox-thumb" width="796" data-original="https://pic1.zhimg.com/v2-009399b534980871c1f5d4c27f1fbffc_r.jpg?source=2c26e567"/>那我们要使用那个特性行呢, 这里我直接说答案, 使用 FFE1 ,我也不知道 2A00 是干什么, 我也问过官方的技术人员, 他们只给了我使用的参考代码, 然后我看见他们软件写的也是 FFE0 , 而 FFE0 下面只有 FFE1 , 那应该没有错了.<img src="https://pic1.zhimg.com/50/v2-972aa1cfc7da3bf950d9dd780bcd11d1_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="993" data-rawheight="79" data-original-token="v2-972aa1cfc7da3bf950d9dd780bcd11d1" class="origin_image zh-lightbox-thumb" width="993" data-original="https://picx.zhimg.com/v2-972aa1cfc7da3bf950d9dd780bcd11d1_r.jpg?source=2c26e567"/><img src="https://picx.zhimg.com/50/v2-4c7027225b3cd6828ec61e7d955b2ed4_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="1115" data-rawheight="61" data-original-token="v2-4c7027225b3cd6828ec61e7d955b2ed4" class="origin_image zh-lightbox-thumb" width="1115" data-original="https://pic1.zhimg.com/v2-4c7027225b3cd6828ec61e7d955b2ed4_r.jpg?source=2c26e567"/>至于另一个UUID, 应该是其它模块的UUID代码:// pages/ctrl/ctrl.ts
const app = getApp()

Page({

/**
* 页面的初始数据
*/
data: {
txtState: "close"
},

myData: {
deviceId: '',
serviceId:'',
charateristicId: ''
},

/**
* 生命周期函数--监听页面加载
*/
onLoad(res) {
console.log("deviceID:", res.deviceId)
// 获取deviceId
this.myData.deviceId = res.deviceId || ''
if (this.myData.deviceId == "") {
wx.showToast({ title: "deviceID is null" })
}
else {
var that = this
// 获取服务
wx.getBLEDeviceServices({
deviceId: this.myData.deviceId,
success(services) {
console.log("service length:", services.services.length)
for (let service of services.services) {
if (service.uuid == "0000FFE0-0000-1000-8000-00805F9B34FB" ||
service.uuid == "49535343-FE7D-4AE5-8FA9-9FAFD205E455") {
console.log("service:", service.uuid)
// 保存服务ID
that.myData.serviceId = service.uuid
// 获取特性
wx.getBLEDeviceCharacteristics({
deviceId: that.myData.deviceId,
serviceId: that.myData.serviceId,
success(characteristics) {
console.log("characteristics leght:", characteristics.characteristics.length)
for (let characteristic of characteristics.characteristics) {
console.log("charateristic:", characteristic)
if(characteristic.uuid == '0000FFE1-0000-1000-8000-00805F9B34FB' ||
characteristic.uuid == '49535343-8841-43F4-A8D4-ECBE34729BB3'){
// 保存特性ID
that.myData.charateristicId = characteristic.uuid
console.log("read="+characteristic.properties.read)
console.log("write="+characteristic.properties.write)
console.log("notify="+characteristic.properties.notify)
console.log("indicate="+characteristic.properties.indicate)
if(characteristic.properties.notify || characteristic.properties.indicate){
wx.notifyBLECharacteristicValueChange({
deviceId: that.myData.deviceId,
serviceId: that.myData.serviceId,
characteristicId: that.myData.charateristicId,
state:true
})
}
break
}
}
},
fail(error) {
console.error("get characteristics error:", error)
}
})
break
}
}
}
})
// 监听
wx.onBLECharacteristicValueChange((res)=>{
var time = new Uint32Array(res.value)
console.log("recevie data:", time)
this.setData({
txtState: time[0].toString()
})
})
}
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
wx.closeBLEConnection({deviceId : this.myData.deviceId})
wx.closeBluetoothAdapter()
},

// 发送
sendData(str: String){
var buffer = new ArrayBuffer(1)
var dataView = new DataView(buffer)
for (let i = 0; i < str.length; i++){
dataView.setUint8(0, str.charCodeAt(i))
wx.writeBLECharacteristicValue({
deviceId: this.myData.deviceId,
serviceId: this.myData.serviceId,
characteristicId: this.myData.charateristicId,
value: buffer,
fail(error){
console.error("send fail:", error)
}
})
}
},

onOpen(){
this.sendData("O")
this.setData({
txtState: "open"
})
},

onClose(){
this.sendData("C")
}
})
结果<img src="https://pica.zhimg.com/50/v2-62df7fbdc8349f0de5efddafcebf3af2_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="927" data-rawheight="709" data-original-token="v2-9464af0231f2d4d791c07f98eec4fae8" class="origin_image zh-lightbox-thumb" width="927" data-original="https://pic1.zhimg.com/v2-62df7fbdc8349f0de5efddafcebf3af2_r.jpg?source=2c26e567"/><img src="https://picx.zhimg.com/50/v2-8061d46ce9778afd6ebaf8c3d16bf93d_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="773" data-rawheight="57" data-original-token="v2-8061d46ce9778afd6ebaf8c3d16bf93d" class="origin_image zh-lightbox-thumb" width="773" data-original="https://pic1.zhimg.com/v2-8061d46ce9778afd6ebaf8c3d16bf93d_r.jpg?source=2c26e567"/>好了, 小程序这边完事了, 接下来是FPGA这边了FPGA代码文件结构:<img src="https://pica.zhimg.com/50/v2-7fa3e52b144063bf9e79e1da630d3d7f_720w.jpg?source=2c26e567" data-caption="" data-size="small" data-rawwidth="201" data-rawheight="150" data-original-token="v2-341acac24e3bef5a73f80396afb7ad11" class="content_image" width="201"/>使用led, uart_rx, uart_tx以前的代码编写一个FPGA程序.module top (
input clk,
input rst_n,

input uart_rx_pin,
output uart_tx_pin,

input uart_rx_pin_2,
output uart_tx_pin_2,

output [5:0] led
);

// assign uart_rx_pin_2 = uart_tx_pin;
assign uart_tx_pin_2 = uart_rx_pin;

parameter CLK = 27;
parameter BAUD_RATE = 9600;

localparam IDEL = 'b00;
localparam SEND = 'b01;
localparam OPEN = 'b10;
localparam CLOSE = 'b11;
reg [1:0] state;

wire [7:0] rx_data;
wire rx_data_valid;
wire rx_data_ready;

reg [7:0] tx_data;
reg tx_data_valid;
wire tx_data_ready;

reg led_en;

reg [31:0] timer;
reg [31:0] count;

assign rx_data_ready = 1'b1;

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_en <= 1'b0;
timer <= 0;
count <= 0;
tx_data <= 8'd0;
tx_data_valid <= 1'b0;
state <= IDEL;
end else begin
case (state)

IDEL: begin
led_en <= 1'b0;
timer <= 0;
count <= 0;
tx_data_valid <= 1'b0;
state <= (rx_data_valid && rx_data == 'h4F) ? OPEN : state;
end
OPEN: begin
led_en <= 1'b1;
if (timer <= 27_000_000 - 1) begin
timer <= 0;
count <= count + 1;
end else timer <= timer + 1;
state <= (rx_data_valid && rx_data == 'h43) ? CLOSE : state;
end
CLOSE: begin
led_en <= 1'b0;
timer <= 0;
state <= SEND;
end
SEND: begin
tx_data_valid <= 1'b1;
tx_data <= count[(timer*8+7)-:8];
timer <= (tx_data_valid && tx_data_ready) ? timer + 1 : timer;
state <= (tx_data_ready && timer == 3) ? IDEL : state;
end
default: state <= IDEL;


endcase
end
end

 


uart_rx #(
.CLK_FRE(CLK),
.BAUD_RATE(BAUD_RATE)
) u_rx (
.clk(clk),
.rst_n(rst_n),
.rx_data(rx_data),
.rx_data_valid(rx_data_valid),
.rx_data_ready(rx_data_ready),
.rx_pin(uart_rx_pin)
);

uart_tx #(
.CLK_FRE(CLK),
.BAUD_RATE(BAUD_RATE)
) u_tx (
.clk(clk),
.rst_n(rst_n),
.tx_data(tx_data),
.tx_data_valid(tx_data_valid),
.tx_data_ready(tx_data_ready),
.tx_pin(uart_tx_pin)
);

led led_m (
.clk (clk),
.rstn(led_en),
.led (led)
);


endmodule
物理约束<img src="https://picx.zhimg.com/50/v2-e4197165f3f75d7320d5504e02f4f0a5_720w.jpg?source=2c26e567" data-caption="" data-size="normal" data-rawwidth="1640" data-rawheight="373" data-original-token="v2-211d10116e395c8fb4e6cc2cb2c82389" class="origin_image zh-lightbox-thumb" width="1640" data-original="https://picx.zhimg.com/v2-e4197165f3f75d7320d5504e02f4f0a5_r.jpg?source=2c26e567"/>完成后就可以打开小程序, 扫描根据蓝牙的MAC地址生成的二维码, 控制FPGA本文章是指思路演示, 更具实际修改代码.参考[1] 小程序简介 | 微信开放文档 (qq.com)[2] 连接硬件能力 / 蓝牙 / 介绍 (qq.com)[3] 微信小程序 — 常用的几种提示弹窗_icon: 'success',-CSDN博客[4] 小程序里页面跳转的两种方式-腾讯云开发者社区-腾讯云 (tencent.com)[5] 微信小程序:页面跳转时传递数据到另一个页面_微信开发者工具怎么把数据显示在另一个页面中-CSDN博客[6] 蓝牙BLE: GATT Profile 简介(GATT 与 GAP) - 夜行过客 - 博客园 (cnblogs.com)[7] HC-42规格书.pdf : 广州汇承信息科技有限公司 (hc01.com)发布于 2023-11-20 08:37・IP 属地天津

posted @ 2024-04-30 11:36  Sam Xiao  阅读(216)  评论(0)    收藏  举报