• Home
Name Date Size #Lines LOC

..--

entry/12-May-2024-310287

.gitignoreD12-May-2024119 1111

README.mdD12-May-202413.3 KiB306288

build-profile.json5D12-May-20241 KiB4040

hvigorfile.tsD12-May-2024165 21

package.jsonD12-May-2024390 1918

README.md

1# uart手势模块的检测app
2## 作品介绍
3通过NAPI框架实现,检测手势后调用js端的回调函数
4## 开发环境
51. 搭载OpenHarmony-3.2-Beat5版本的Unionpi Tiger开发板
62. DevEco Studio 3.1.0.0
73. uart手势识别带触摸传感器
8## uart简介
9UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种双向、串行、异步的通信总线,仅用一根数据接收线和一根数据发送线就能实现全双工通信。典型的串口通信使用3根线完成,分别是:发送线(TX)、接收线(RX)和地线(GND),通信时必须将双方的TX和RX交叉连接并且GND相连才可正常通信
10## 手势识别带触摸传感器简介
11商品链接:[**Gravity: 手势识别带触摸传感器**](https://www.dfrobot.com.cn/goods-1994.html)
12
13
14这款是一款集成了手势识别功能和触摸识别功能的传感器模块,它的检测距离为30cm,距离0-30cm可调。其中,能够识别向右、向左、向后、向前、下压、上拉及上拉下压后手松开共7种手势,以及5路的触摸信号,并具有自动睡眠和唤醒的功能。模块自带手势识别算法,输出数据简洁可靠,可通过串口直接与arduino及树莓派等控制器或上位机通讯。用于智能灯、人机交互、智能小车、趣味游戏等多功能远距离手势控制端。
15
16![1.jpg](../figures/uart/1.jpg)
17### 接线方式
18​ **3.3V —— 正极**
19
20​ **RX —— TX**
21
22​ **TX —— RX**
23
24​ **GND —— 负极**
25
26### 通信参数及方式
27#### 通信参数
28串口波特率 9600;
298 个数据位;
30无校验位;
311 个停止位;
32#### 通信方式
33格式:
340xAA,数据码,检验码,0x55;
35帧头:0xAA;
36帧尾:0x55;
37
38数据码:
39右:0x01;
40左:0x02;
41后:0x03;
42前:0x04;
43下压:0x05;
44上拉:0x06;
45上拉下压后手松开:0x07;
46触摸 1:0x21;
47触摸 2:0x22;
48触摸 3:0x23;
49触摸 4:0x24;
50触摸 5:0x25;
51
52校验码:
53校验码为数据码的反码,比如做右动作时,数据码为 0x01,校验码为 0xFE。
54## uart实现
55由于hdf框架的uart驱动无法使用(前面初始化部分返回都为success,read却一直返回-17,也没有读到数据),经过修改适配层源码后解决了这一问题,通过对hdf uart部分源码的研究,发现适配层没有编写`int32_t (*SetTransMode)(struct UartHost *host, enum UartTransMode mode)`的相关代码,只是写了个空实现,这导致hdf uart部分不能实现阻塞模式的读取,因此先实现这一部分,代码如下:
56
57主要思路是根据参数修改file mode的O_NDELAY,然后设置阻塞模式需要的两个参数VMIN和VTIME分别为1和0(读到一个字节就结束阻塞,不设置超时时间)
58```C++
59static int32_t UartAdapterSetTransMode(struct UartHost *host, enum UartTransMode mode)
60{
61  struct termios termios;
62  struct file *fp = NULL;
63  if (host == NULL) {
64      return HDF_ERR_INVALID_OBJECT;
65  }
66  fp = (struct file *)host->priv;
67  if (UartAdapterIoctlInner(fp, TCGETS, (unsigned long)&termios) < 0) {
68      HDF_LOGE("tcgets fail");
69      return HDF_FAILURE;
70  }
71  if(mode==UART_MODE_RD_BLOCK){
72        fp->f_flags&=~O_NDELAY;
73        termios.c_cc[VMIN] = 1;
74        termios.c_cc[VTIME] = 0;
75  }else if(mode==UART_MODE_RD_NONBLOCK){
76      fp->f_flags|=O_NDELAY;
77      termios.c_cc[VMIN] = 0;
78      termios.c_cc[VTIME] = 0;
79  }else{
80        return HDF_ERR_INVALID_PARAM;
81  }
82  if (UartAdapterIoctlInner(fp, TCSETS, (unsigned long)&termios) < 0) {
83      HDF_LOGE("tcsets fail");
84      return HDF_FAILURE;
85  }
86  return HDF_SUCCESS;
87}
88```
89然后使用UartOpen()打开串口,设置好参数和阻塞模式后就可以调用UartRead()成功读到数据了
90## NAPI层
91### 实现原理
92js层可设置相应的回调函数(触摸模块的事件处理函数),为了监控触摸事件,在注册napi函数之后,开启一个新的线程。该线程会一直读取串口数据,当串口有数据到来时,如果js层之前已经设置过回调函数,则调用该回调函数。但由于只能由主线程才能调用`napi_call_funtion`(如果跨线程调用会crash),需要借助napi框架`threadsafe_funtion`的相关函数,设置回调时,首先调用`napi_create_threadsafe_function`将js传来的回调函数转换为一个`napi_threadsafe_function`,然后串口读取线程调用`napi_call_threadsafe_function`来间接调用js层的回调函数
93### 实现代码
94#### 设置js回调函数
95```C++
96static napi_value onTouch(napi_env env, napi_callback_info cb)
97{
98    napi_value ret;
99    napi_get_undefined(env, &ret);
100    // 获取js传来的参数
101    size_t argc = 1;
102    napi_value argv;
103    napi_get_cb_info(env, cb, &argc, &argv, nullptr, nullptr);
104    // 校验参数数量
105    NAPI_ASSERT(env, argc == 1, "requires 1 parameter");
106    // 校验参数类型
107    napi_valuetype type;
108    napi_typeof(env, argv, &type);
109    NAPI_ASSERT(env, type == napi_function, "frist parameter type requires callback");
110    // 创建传来的回调函数的引用
111    napi_ref funRef;
112    napi_create_reference(env, argv, 1, &funRef);
113    // 创建资源和资源名
114    napi_value resource, resourceName;
115    napi_get_null(env, &resource);
116    napi_create_string_latin1(env, "touchCallback", NAPI_AUTO_LENGTH, &resourceName);
117    callbackMutex.lock();
118    if (touchCallback != nullptr) {
119        // 如果先前设置过回调函数,释放先前回调函数的相关资源
120        napi_ref funRef;
121        napi_get_threadsafe_function_context(touchCallback, (void**)&funRef);
122        napi_delete_reference(env, funRef);
123        napi_release_threadsafe_function(touchCallback, napi_tsfn_release);
124    }
125    // 创建threadsafe_function
126    napi_create_threadsafe_function(env, argv, resource, resourceName, 0, 1,
127        nullptr, nullptr, funRef, callfunc, &touchCallback);
128    callbackMutex.unlock();
129    return ret;
130}
131```
132#### threadsafe_function的回调函数
133napi_call_threadsafe_function实际上并不会直接调用js的回调函数,而是通知主线程,让主线程调用创建threadsafe_function时设置的回调函数,这个回调函数负责将c/c++的参数转换为js的参数,然后才会调用真正的js回调函数(这些都只能在主线程做),代码如下:
134```C++
135static void callfunc(napi_env env, napi_value js_callback, void* context, void* data)
136{
137    // 将*data转换为对应的类型
138    uint8_t* codePtr = reinterpret_cast<uint8_t*>(data);
139    // 从content中取出指向js回调函数的引用
140    napi_ref funRef = reinterpret_cast<napi_ref>(context);
141    napi_value code, result, global, fun;
142    napi_get_global(env, &global);
143    // 将事件码转换为js变量
144    napi_create_uint32(env, *codePtr, &code);
145    // 取出引用指向的js回调函数
146    napi_get_reference_value(env, funRef, &fun);
147    delete codePtr;
148    // 调用js回调函数
149    napi_call_function(env, global, fun, 1, &code, &result);
150}
151```
152#### 串口读取线程调用threadsafe_function函数
153该线程会一直读取串口的数据,并调用`napi_call_threadsafe_function`函数,代码如下:
154```C++
155static void readTouchLoop()
156{
157    while (true) {
158        int32_t res = uart1->readTouch();
159        if (res != -1) {
160            uint8_t* codePtr = new uint8_t;
161            *codePtr = static_cast<uint8_t>(res);
162            callbackMutex.lock();
163            if (touchCallback != nullptr) {
164                napi_call_threadsafe_function(touchCallback, codePtr, napi_tsfn_nonblocking);
165            }
166            callbackMutex.unlock();
167        }
168    }
169}
170```
171#### 设置触摸事件枚举
172为了方便app层知晓事件码对应了什么事件,napi层需要设置触摸事件的枚举。具体方法是创建EventCode对象,然后将各个触摸事件的枚举值设置为对象的静态字段,然后添加进导出对象中。
173```C++
174static napi_value defineEventCode(napi_env env)
175{
176    using namespace Uart;
177    napi_value eventCode, right, left, back, forward, pullUp, pullDown, pullMove,
178        touch1, touch2, touch3, touch4, touch5;
179    // 创建EventCode对象
180    napi_create_object(env, &eventCode);
181    // 用c++的常量创建对应的js变量
182    napi_create_uint32(env, static_cast<uint32_t>(eventCode::right), &right);
183    napi_create_uint32(env, static_cast<uint32_t>(eventCode::left), &left);
184    napi_create_uint32(env, static_cast<uint32_t>(eventCode::back), &back);
185    napi_create_uint32(env, static_cast<uint32_t>(eventCode::forward), &forward);
186    napi_create_uint32(env, static_cast<uint32_t>(eventCode::pullUp), &pullUp);
187    napi_create_uint32(env, static_cast<uint32_t>(eventCode::pullDown), &pullDown);
188    napi_create_uint32(env, static_cast<uint32_t>(eventCode::pullMove), &pullMove);
189    napi_create_uint32(env, static_cast<uint32_t>(eventCode::touch1), &touch1);
190    napi_create_uint32(env, static_cast<uint32_t>(eventCode::touch2), &touch2);
191    napi_create_uint32(env, static_cast<uint32_t>(eventCode::touch3), &touch3);
192    napi_create_uint32(env, static_cast<uint32_t>(eventCode::touch4), &touch4);
193    napi_create_uint32(env, static_cast<uint32_t>(eventCode::touch5), &touch5);
194    // 将js变量设置为对象的静态成员
195    napi_property_descriptor desc[] = {
196        DECLARE_NAPI_STATIC_PROPERTY("right", right),
197        DECLARE_NAPI_STATIC_PROPERTY("left", left),
198        DECLARE_NAPI_STATIC_PROPERTY("back", back),
199        DECLARE_NAPI_STATIC_PROPERTY("forward", forward),
200        DECLARE_NAPI_STATIC_PROPERTY("pullUp", pullUp),
201        DECLARE_NAPI_STATIC_PROPERTY("pullDown", pullDown),
202        DECLARE_NAPI_STATIC_PROPERTY("pullMove", pullMove),
203        DECLARE_NAPI_STATIC_PROPERTY("touch1", touch1),
204        DECLARE_NAPI_STATIC_PROPERTY("touch2", touch2),
205        DECLARE_NAPI_STATIC_PROPERTY("touch3", touch3),
206        DECLARE_NAPI_STATIC_PROPERTY("touch4", touch4),
207        DECLARE_NAPI_STATIC_PROPERTY("touch5", touch5),
208    };
209    napi_define_properties(env, eventCode, sizeof(desc) / sizeof(desc[0]), desc);
210    // 返回枚举对象
211    return eventCode;
212}
213```
214## app层
215先编写对应的.d.ts定义文件
216```TypeScript
217declare namespace uart_ctl {
218  enum EventCode {
219    right,
220    left,
221    back,
222    forward,
223    pullUp,
224    pullDown,
225    pullMove,
226    touch1,
227    touch2,
228    touch3,
229    touch4,
230    touch5
231  }
232
233  function onTouch(callback: (event: EventCode) => void): void;
234
235  function closeTouch(): void;
236}
237
238export default uart_ctl;
239```
240然后在index.ets设置回调函数
241```TypeScript
242.onAppear(() => {
243  uart_ctl.onTouch((event) => {
244    switch (event) {
245      case uart_ctl.EventCode.right:
246        Prompt.showToast({ message: "get event right" });
247        break;
248      case uart_ctl.EventCode.left:
249        Prompt.showToast({ message: "get event left" });
250        break;
251      case uart_ctl.EventCode.back:
252        Prompt.showToast({ message: "get event back" });
253        break;
254      case uart_ctl.EventCode.forward:
255        Prompt.showToast({ message: "get event forward" });
256        break;
257      case uart_ctl.EventCode.pullUp:
258        Prompt.showToast({ message: "get event pull up" });
259        break;
260      case uart_ctl.EventCode.pullDown:
261        Prompt.showToast({ message: "get event pull down" });
262        break;
263      case uart_ctl.EventCode.pullMove:
264        Prompt.showToast({ message: "get event pull move" });
265        break;
266      case uart_ctl.EventCode.touch1:
267        Prompt.showToast({ message: "get event touch1" });
268        break;
269      case uart_ctl.EventCode.touch2:
270        Prompt.showToast({ message: "get event touch2" });
271        break;
272      case uart_ctl.EventCode.touch3:
273        Prompt.showToast({ message: "get event touch3" });
274        break;
275      case uart_ctl.EventCode.touch4:
276        Prompt.showToast({ message: "get event touch4" });
277        break;
278      case uart_ctl.EventCode.touch5:
279        Prompt.showToast({ message: "get event touch5" });
280        break;
281      default:
282        Prompt.showToast({ message: "error" });
283        break;
284    }
285  });
286})
287```
288### 遇到的问题
289问题1:
290问题描述:在napi层调用hilog打日志,但怎么都看不到日志
291问题原因:OpenHarmony-3.2-Beat5存在bug,native层的所有hilog都看不到。
292解决方案: OpenHarmony系统引入了默认日志级别,修改为D即可
293
294
295问题2:
296问题描述:在callfunc函数中,方舟运行时传来的js_callback是一个无效的对象,当调用napi_call_function传入它时会直接crash
297问题原因:通过打印日志(打印一开始的js回调函数地址,调用napi_call_function时js_callback的地址,真正的js回调函数的地址),发现从传入js回调函数到调用js回调函数这个过程中,地址发生了变化,推测是在这其中的时间触发了gc,而gc移动了js回调函数的位置,而方舟运行时没有更新这个地址,仍然传入了旧的地址,旧的地址指向的内存已经被gc释放,s导致crash
298
299![2.jpg](../figures/uart/2.jpg)
300解决方案:不使用js保存的js_callback,而是手动将js回调函数的引用存入context中,然后在callfunc函数里再取出来
301
302问题3:
303问题描述: 开机后,使用hdf读取uart数据读取不到,需要使用linux接口操作uart的程序提前打开关闭设备后,才能正常读取数据
304
305## 作品展示
306![3.jpg](../figures/uart/3.jpg)