一种基于低功耗蓝牙的智能点阵屏系统

这是我开发的一个基于南京沁恒公司的无线型青稞RISC-V MCU: CH32V208WBU6 开发的智能点阵屏。点阵屏能够通过低功耗蓝牙(BLE)从手机/PC 接收显示信息,支持四种滚动显示模式,颜色修改,具有本地存储功能。汉字滚动显示方式可以通过手机和本地按键设置。

作为项目展示和方便参考,在此对结果进行展示以及对全过程进行复现。

结果展示

智能点阵屏能够在屏幕上显示存储等汉字

可以使用手机 App 发送汉字,通过蓝牙投射到显示屏上,并且能修改屏幕滚动参数和颜色等。

在屏幕上结果为

智能点阵屏支持的范围文字类型为 GBK 编码中的所有字符,因此一些特殊符号同样可以打印出来,如下图

实现过程

我个人本身的技术栈是偏软件的,而且个人成组,没有队友,因此在完成此项目的过程也恶补了不少硬件相关知识。在此对实现过程进行记录。

硬件平台

对本实验设计到的所有硬件进行拆分,可以大致分为

  • 单片机:用于实现大多数核心功能,需要能够驱动显示器,以及通过蓝牙与其他设备通信
  • 显示屏:显示汉字
  • 蓝牙模块:大多数单片机是没有板载蓝牙模块的,但是我使用的单片机 CH32V208WBU6 有板载蓝牙,因此不需要额外接入蓝牙模块
  • 下载调试器:向 MCU 烧写程序
  • 手机:作为蓝牙中心设备向 MCU 传输数据和指令

单片机

南京沁恒公司的无线型青稞 RISC-V MCU: CH32V208WBU6,其系统框图如下

板载 BLE 5.3 模块,可用于数据通信。这里写的是 128 KB 和 64 KB SRAM,但实际编程中可以利用到 160KB 的 FLASH 。这里的 160KB FLASH 为之后痛苦的调试埋下了伏笔。如果读者希望复现此项目,强烈建议选购存储空间足够的单片机。

一般来说,单片机的厂家都会提供实现各种功能的例程,沁恒公司在这方面做得非常好,例程和开发手册非常详细,相关信息均可以在官网找到。

显示屏

我选用的显示屏是一款 1.8 寸 RGB 彩色 TFT 液晶 LCD 显示屏,分辨率 160*128,接口为 SPI。工作电压 3.3 V,驱动芯片 ST7735。在选型时最重要的参数就是分辨率,尺寸,接口和工作电压。本项目显示的点阵为 16×16 的像素,因此 160*128 能基本满足要求。

在选硬件的时候需要特别注意显示屏的接口能否与 MCU 进行通信。一些显示屏的 SPI 是排线型,如下图

这种接口是无法直接插到 MCU 上的,个人建议选择排针 8 pin 的 SPI 接口,例如

左侧的 8 个针脚可以使用杜邦线连接到 MCU 上

蓝牙模块

我使用的单片机 CH32V208WBU6 有板载 BLE 5.3 模块,因此不需要额外接入蓝牙模块。

由于 BLE 相比蓝牙协议,更加强调低功耗,因此传输速率,距离和 MTU 等参数是不如普通的蓝牙协议的。我的项目中传输文字时,速度大约只有 2 个字每秒,这也是使用体验受制的一大因素。

下载调试器

大多数 MCU 只能通过串口将程序烧写到 FLASH 中,而 PC 一般没有串口,因此一般厂商都会有下载调试器,一端为 USB 公口,一端为串口针公口,可以将数据写入到 MCU 中。同时单片机在执行 PRINT 语句时,也会将结果通过串口传输到电脑端。

我使用的 MCU 是沁恒公司的,下载调试器自然直接选用该公司的 WCH-Link

手机

要实现手机 APP 传输数据自然是需要一台手机的。对于使用 iPhone 的情况来说,iOS 上的 App,只能使用 Xcode 进行 iOS 开发。注意,在 Windows 的机器上进行 iOS 开发是非常困难的,因此如果是 Windows+iOS 的组合,建议找一台安卓备用机开发安卓的应用程序。

杂项

为了将这些硬件串联起来,可能还会需要用到

  • 杜邦线
  • 电烙铁、焊锡、松香、电工胶带
  • 220V 转 5V 适配器,3.3V 降压模块

接线

对我而言需要连接的核心就是三部分:下载调试器-MCU-显示屏,用杜邦钱连接即可。

调试器到 MCU,对于我的 WCH-Link 到 CH32V208WBU6 的情况,接线如下

  • GND – GND
  • 3.3V – 3.3V
  • SWDIO – PA13
  • SWCLK – PA14

MCU 到显示屏,是 SPI 接口,接线如下

  • GND – GND
  • VCC – 3.3V
  • SCL(SDK) – PB13
  • SDA(MOSI) – PB15
  • RES(RST) – PB11
  • DC – PB10
  • CS – PB12
  • BL(LED) – PB9

开发环境

在此项目中,代码主要部分可以分为 MCU 代码和 iOS 代码。

由于使用的单片机是沁恒公司的,因此我在开发 MCU 部分代码使用的是这个公司推荐的 MountRiver IDE,但实际上这个 IDE 和 eclipse IDE 基本上是一样的,因此并不存在太大的上手门槛。

iOS 代码部分,由于我恰好有一台 MacBook,因此可以提供 Xcode 的开发环境来调试,并将 App 下载到手机。

将 App 下载到 iPhone 这一过程需要有「证书」,而证书又需要一个 Apple 开发者账号。我在尝试使用国区 Apple ID 创建一个开发者账号时发现一直无法获取证书,但是用了自己美区的 Apple ID 一下子就成功获取证书了,原因不明。

将 App 下载到 iPhone 这一过程只需要免费的 Apple 开发者计划即可,不需要花 99 刀每年成为会员。

LCD 部分调试

连接好硬件部分,就可以开始调试了。将连接好的 WCH-Link 插入电脑 USB 口,理论上在设备管理器中就可以出现一个「端口(COM 和 LPT)」

然后再下载一个叫做 SSCOM 的串口调试软件,可以读取来自 MCU 的串口向电脑回传的数据,可以用于调试。选定 COM4 后打开串口即可。如果有 MCU 执行了类似于 PRINT 的指令,其结果就会通过串口打印。

由于我的 MCU 最大支持编程的空间只有 160 KB,而光是调用 libwchble.a 这个库就需要消耗 150 KB 多,因此留给我的代码空间非常受限。为了能够在螺蛳壳里做道场,我将所有 PRINT 的函数注释掉来最小化代码空间。如果复现时确实需要调试数据,则建议将 LCD/LCD_config.h 中的 MAX_ROWS 和 WORD_PER_LINE 等参数调小,来减少数组 BITMAP 占用的空间,否则直接加入 PRINT 函数会导致 FLASH 空间溢出的错误。

为了检查硬件情况,可以试着在 MountRiver IDE 中 File-New-MountRiver Project 创建一个项目,并且找到自己的芯片型号,这里我的型号是 CH32V208WBU6

创建完成后,确保 WCH-Link 已经连接,直接点击左上角的锤子进行编译

然后点击左上角第二个进行运行,即通过 WCH-Link 将程序烧写到板子上。在 SSCOM 中如果读取到对应的数据则说明运行成功。

MCU 开发

调试完成后,即可开始项目的开发。对于 MCU 部分,要实现的需求可分为两部分。

  1. 将一个点阵数组,以汉字的格式打印到 LCD 屏幕上
  2. 发出蓝牙信号供其他设备连接,连接后能够接受其他设备写入的点阵数据,并将数据交给 LCD 部分的代码来打印

对于这两部分,沁恒公司都提供了对应的例程。考虑到 MCU 的存储空间有限,因此无法在 MCU 中存储字符到点阵的转换文件,只能先由手机完成转换,再直接将点阵数据传输到 MCU 上

LCD 部分

对于 LCD 部分,其底层的电路代码都已经实现,作为用户只需要直接调用几个最重要的函数即可。

一般的 RGB 彩色空间,每个彩色分量都有 2^8=256 个灰度级,即需要 8 位二进制数来表示一个彩色分量。而在这里,对于支持 RGB 彩色的 LCD 而言,颜色可以用一个 4 位十六进制,即 16 位 2 进制表示。因此不可能做到每个二进制都分配到 8 位灰度,而是对其进行划分,图像质量自然有一定的损失。

在本项目中,15~11 位表示 5 位红色分量 R,10~5 表示 6 位绿色分类 G,4~0 表示 5 位蓝色分量 B

对于此项目而言,最重要的函数有以下几个

LCD_Init(); // 用于初始化 LCD
LCD_direction(1);  // 用于指定屏幕显示方向。这里我使用的是参数 1,即表示横向显示
LCD_Clear(0x0000);  // 清屏,0x0000 即纯黑色
LCD_SetWindows(uint16_t xStar, uint16_t yStar, uint16_t xEnd, uint16_t yEnd); // 设置显示窗口。
Lcd_WriteData_16Bit(uint16_t Data) // 将某个颜色写入到某个像素中

调用这些函数即可在 LCD 上显示图像。

  1. 先使用 LCD_SetWindows 设定窗口的起始坐标和结束坐标,这两个坐标会绘制出一个长方形,接下来的所有绘制都在此窗口内完成。注意,这个开始和结束坐标都是包含在窗口内的
  2. 反复调用 Lcd_WriteData_16Bit,即可将数据写入某个像素。其规律大概是在上面设置的窗口中,每调用一次,从左到右从上往下移动一个像素,向该像素写入某个颜色。例如要在一个 (xStar, yStar) 到 (xEnd, yEnd) 的窗口内画一个纯白色矩形,则代码为
LCD_SetWindows(xStar, yStar, xEnd, yEnd);
for (row = 0; row < yEnd - yStar + 1; row++) {
    for (col = 0; col < xEnd - xStar + 1; col++) {
        Lcd_WriteData_16Bit(0xFFFF);
    }
}

按照此逻辑,就可以在 LCD 屏幕上绘制图形了。

了解了如何绘制图形,就需要选择恰当的数据结构,来将汉字点阵打印到 LCD 屏幕上。本项目采用的汉字点阵为 16×16,则理论上需要 256 bit,即 32 字节来表示一个汉字。我选用的表示方式为,按照从左到右从上到下顺序,每横向 8 个像素点为一个字节的表示方式,即顺序为第一行左 8 个像素,第一行右 8 个像素,第二行左 8 个像素,第二行右 8 个像素……第十六行左 8 个像素,第十六行右 8 个像素的形式,组织成一个 uint8_t 类型的长为 32 的数组。

我实现汉字显示的核心代码如下,首先是 fill_screen_buffer 函数

此函数遍历汉字缓冲区的每一个汉字,然后将其对应地写入一个屏幕缓冲区 screen_buffer

这里的 screen_buffer 是一个形状为 [SCREEN_HEIGHT/8][SCREEN_WIDTH] 的数组,每个元素表示了纵向 8 个像素的亮暗。之所以采用这么拧巴的定义方式,是因为存储空间实在有限,不能让每个像素都分配一个 8 位整数,而这里屏幕的高度恰好为 128,是一个 8 的倍数,因此才使用了这么奇怪的定义。

理论上其实是不需要屏幕缓冲区这一步的,可以在 fill_screen_buffer 函数中直接进行打印,但是修改之后调试发现了很多莫名其妙的bug,在计算速度上也没有多明显的改善,因此最后放弃了修改,沿用了原来的代码。

然后对于 draw_screen_buffer,则是读取屏幕缓冲区的数据,按照指定的颜色,调用相关底层函数,将指定颜色的屏幕缓冲区数据输出到屏幕上。

最后是最上层的 scroll_hanzi_display 函数。周期性地调用此函数,并且指明滚动方向和步长,即可实现文字在屏幕上的滚动。

目前的代码有一个已知问题:使用自顶向下或自底向上滚动时,最后一行文字经过屏幕最上方时,偶尔会出现一条闪烁,目前还没有找到此 bug 的成因,如果读者能发现,务必与我联系

蓝牙传输

蓝牙传输应该是 MCU 部分最困难最复杂的代码了。蓝牙协议栈本身有非常多的前置知识需要了解,因此光是理论知识部分就需要足够的时间精力去学习和了解。

我的 MCU CH32V208WBU6 搭载的是 BLE(Bluetooth Low Energy),即蓝牙低功耗,有一套自己的协议栈,从底层物理层到上层涉及了非常多的概念,有空写一篇文章讲讲蓝牙低功耗协议栈。

此外,沁恒公司的 MCU 使用 TMOS(Task Management Operating System) 作为一种多任务时间片轮转的系统,类似于操作系统。有空写一篇文章讲讲这个系统。由于接收蓝牙数据和屏幕的滚动显示两个任务是异步执行的,因此本项目用到的 BLE 同样需要基于 TMOS 来实现。

对于我们的需求,即在不影响文字显示的同时,调用 BLE 模块向外广播蓝牙信号,允许其他设备建立连接,并传输数据。同时 MCU 要能够接受这些数据,将其转换为点阵或指令,实时体现在显示上。

如果对蓝牙协议稍有了解,就可以知道这个功能对应的 Role 为 Peripheral,可以翻译为从机或外围设备。沁恒公司提供了例程来实现从机的基本功能。将例程烧录到板子上即可用手机或电脑搜索到一个叫做 Simple Peripheral 的蓝牙设备。

在 peripheral.c 中,可以修改外围设备被蓝牙扫描到时显示的名字,这里我选用的名字为 Senyao Smart Screen

在开发调试阶段,可以使用 LigthBlue 等蓝牙调试工具,与设备建立连接。建立连接后,可以读到 System Information, Characteristic 等信息。其中有的 Characteristic 是只读的,有的是只写的,有的是可读可写的。

我们的目的也很明确:设置两个只写或可读可写的特征,通过手机向特征写入新的值来实现数据的传输。一个特征用于传输点阵,一个特征用于传输数据。

注意,蓝牙低功耗限制的 MTU 默认为 23 字节,理论上支持的最长 MTU 为 512 字节。在我的代码中,我选择使用一个长为 34 字节的特征来传输单个字。其中前两个字节分别表示了此汉字在汉字矩阵 hanzi_buffer 中的行和列,剩下的 32 个字节则为汉字的信息。为了一次能够传输 34 字节的特征,之后必然涉及到修改 MTU 的过程。

此外还有一个长为 4 字节的特征用于传输滚动指令和颜色

  • 第一个字节表示滚动方向,0 为从左往右,1 为从右往左,2 为从上往下,3 为从下往上
  • 第二个字节表示滚动速度,即每次调用 scroll_hanzi_display 函数时,滚动的像素个数。我在 TMOS 中设定了定时任务,定期调用 scroll_hanzi_display 的时间间隔为 160 个时间单位,TMOS 中每个时间单位长 625 微秒,因此时间间隔约为 0.1 秒,因此第二个字节表示了每 0.1 秒文字在屏幕上滚动的像素个数
  • 第三四个字节分别是颜色的高低字节。上文讲过,LCD 屏幕的颜色由 4 位十六进制数表示,即需要两个字节。这里用三四字节来表示颜色的高低字节。

理清思路后,就需要在 BLE 协议栈的各个层面对这两个特征进行定义。在 gattprofile.C 中,先对 GATT Profile 服务的 UUID,以及两个特征的 UUID 进行定义。这里我选定的两个特征 UUID 分别为 0xFFE1 和 0xFFE2,这在之后的 iOS 部分的代码中,只需要掌握这两个特征 UUID,就知道该向什么特征写入数据了。

在 gattprofile.h 头文件中,可以定义两个特征的长度

然后是定义 Profile 服务和特征的一些基本信息。其中 Properties 为读写权限,GATT_PROP_READ | GATT_PROP_WRITE 表示读写权限,即将读和写对应的独热编码作或运算,则得到允许读写的编码。

这些信息需要拼成一个 Attribute Table,用于之后注册服务

然后用这个表来注册服务并添加服务

为了让其他函数能对这两个特征值进行修改,需要定义一个设置参数的函数

以及能够对特征值进行读写的控制块函数

写函数则为

接下来,进入 peripheral.c 进行修改即可。在 Peripheral_Init() 函数中,可以调用刚刚提到的函数,对特征值进行初始化

在 Peripheral_ProcessEvent 函数中,则需要设置如何处理滚动事件。上文已经提到过,TMOS 每 0.1 秒触发一次 SBP_SCROLL_UPDATE_EVT,而具体实践就是用 TMOS 中的 tmos_start_task 来开启一个任务计时器,每隔 SBP_SCROLL_EVT_PERIOD 就会触发事件,事件触发后,由 Peripheral_ProcessEvent 中对应的处理框架来对事件处理,如下图

Event 是一串独热编码,则 events & SBP_SCROLL_UPDATE_EVT = 1 则表示滚动更新事件发生。在这里的处理方式,我首先调用之前在 LCD 部分写好的函数 scroll_hanzi_display,然后用 tmos_start_task 重新开始一个计时器,并且用 events ^ SBP_SCROLL_UPDATE_EVT 来清除掉对应的事件位,表示此事件处理完成。

然后需要处理 MTU 的问题。上文提到过,传输文字使用的是一个 34 字节的特征,但 BLE 协议默认的 MTU 仅为 23 字节,而且协议开销也占用了 3 个字节,因此实际用于传输特征的只有 20 个字节。此现象可以通过 LightBlue 等调试软件来发现。如果设置一个特征的长度为 20,则可以在 LightBlue 中正常读取到一串 40 位 16 进制数,而一旦设置特征长度为 21 及以上,则在 LightBlue 中的读取结果为 No Value,表明超出了 MTU 规定的长度,数据无法正常传输。

为了解决这个问题,我们可以在建立连接时与中心设备(即手机)进行协商 MTU 更改。具体实现只需要这 3 行代码。

即可调用请求,将 MTU 修改为 247,方便我们之后的传输。

最后就是蓝牙部分最核心的代码,即对接收到的数据进行处理和分析,并且交给 LCD 部分代码进行处理,如下图,根据不同的特征值的 paramID 进行分类。

如果出现变化的是特征 1,即我们用于文字传输的特征,则首先读取到特征值,并且将其写入内存对应的位置,然后根据我们的规定,第 0 和第 1 个元素分别代表了这个汉字在汉字矩阵中的行和列位置。如果第 0 和第 1 个元素同时为 0,则表示这是第 0 行第 0 列。

在手机端,我的传输都是从第 0 行第 0 列开始的,如果读取到了一个汉字来自第 0 行第 0 列,则表示这是一段新的文字,所以这里我调用了 memset(BITMAP, 0, sizeof(BITMAP)),来将上一次的汉字点阵数据清空,并且重新写入数据。

num_row 和 num_col 分别表示了当前汉字矩阵的有效行和有效列。之所以需要这两个变量,是因为我为了实现同时显示上百个字,汉字矩阵的长和宽都非常大,而有时候只希望向屏幕上投射几个字,就会有大量的位置是空的,这样在 scroll_hanzi_display 函数调用的时候,默认会认为所有行和列都是有效的,体现在 LCD 屏幕上就是展示空行和空列,即表现了长时间黑屏。

为了避免这种现象,我设置了一个逻辑来找出每次传输的一段文字对应在汉字矩阵中的有效行和有效列

if (hanzi_row + 1 > num_row){
    num_row = hanzi_row + 1;
}
if (hanzi_col + 1 > num_col){
    num_col = hanzi_col + 1;
}

如果发现第 0 行第 0 列的字,则重置 num_row 和 num_col。

经过以上的步骤后,接下来可以对汉字矩阵 BITMAP 进行修改

memcpy(&BITMAP[hanzi_row * WORD_PER_LINE * BITE_PER_WORD + hanzi_col * BITE_PER_WORD], &newValue[2], 32);

这里需要仔细计算偏置。由于 BITMAP 本质上是一个一维 uint8_t 数组,因此需要按照上文 LCD 中对 BITMAP 的规定来计算偏置,然后将读取到数据的第 2 个开始的剩下 32 字节一起写入到 BITMAP 的对应 32 个字节中,就完成了一个汉字的传输。

对于指令传输,其代码如下

以上,MCU 部分的代码基本结束了。按照以上的逻辑写入 MCU,保证接线正确,理论上已经能在 LCD 上看到输出文字,并且能使用手机等设备搜索到一个叫做 Senyao Smart Screen 的设备,并且能观察到两个特征值,也能读取到这两个特征值的取值。

在 MCU 的开发过程中,其实遇到最痛苦的问题就是编译后的代码结果本身就几乎超出 160 KB,无法被写入 FLASH 中,但又希望实现能够本地存储 100 个汉字的功能。为了能够把代码压缩进 160 KB 同时能保证功能性,不得不做大量的删改与调试,在这部分浪费了几天的时间。最后通过注释掉代码中所有的 PRINT 函数,编译器就不再编译打印所需要的底层代码。

这就带来了一个问题,如果以目前的代码来说,但凡加入一条 PRINT 语句,就会导致编译结果超出 FLASH 进而无法写入的问题。

iOS 开发

接下来是 iOS 部分。这部分的需求就是,提供一个相对比较用户友好的界面,允许用户输入文字或指令,将文字使用字模文件转换为点阵数据,并且将点阵和指令以约定好的方式编码

为了完成 iOS 开发,需要一个 Mac 的操作系统并下载 Xcode,并创建一个新项目,选定 iOS App 即可

开发 iOS 使用的语言叫做 swift,我之前虽然对这种语言并不熟悉,但是只要学习过面向对象编程的思想,只需要大致了解一下 swift 的语法即可开始开发。

我的项目大致可分为三个核心文件夹

Smart Screen 文件夹包含了项目中的绝大多数核心代码

  • Asset.xcassets:资源数据
  • Bitmap.swift:负责读取点阵文件 gbk16.bin 二进制文件,并且进行下标偏置运算,得到对应的 32 字节点阵数据
  • BLE.swift:负责与 MCU 建立 BLE 连接,通过向特征写入数据的方式进行文字传输和滚动参数控制
  • ContentView.swift:负责页面布局和各类函数的调用
  • Smart_ScreenApp.swift:主函数
  • KeyboardObserving.swift:键盘观察器,用于收起键盘

Assets.xcassets 文件夹用于存放资源,在本项目中,只有 App 的图标需要放在这里。为了方便起见,这里使用 AIGC 生成了一个图标

Resource 文件夹则存放了二进制文件 GBK16.bin,可以将任何 GBK 字符转换为点阵文件。

接下来具体谈谈各个代码的逻辑和实现的功能。

Smart_ScreenApp.swift

此文件其实就是创建项目时 IDE 自动生成的,可以理解为主函数

打开 App 后,系统的布局和逻辑就由 ContentView() 进行接管了

ContentView.swift

此函数定义了整体的布局和重要的逻辑,需要引入库和定义 MAX_ROWS 和 WORD_PER_LINE,这里的定义应与 MCU 中定义的一样

这里有一个 BitmapViewModel 类,可以将文字通过 GBK16.bin 转换为点阵代码,其类定义为

其中定义了一个函数 convertTextToBitmap,先读取 GBK16.bin 这个文件

然后用一个循环,遍历输入文字的每个字,将其转换为对应的字模文件

以及一个将颜色转换为字节数据的函数 convertColorToBytes

这里的颜色转换逻辑需要跟 MCU 部分对齐。

接下来最重要的部分就是用户界面的设计,即实现开头展示的 iOS 端 App 的效果

这里定义了一个 ContentView 结构体来完成整个页面的搭建

这里的 VStack 构建的是 App 上方的标题与图标,然后在下面加上一个文本框供用户输入希望传输的汉字

然后绘制四个上下左右的按钮,用户按下某个按钮时,触发对应的 BLE 相关代码,将指令写入 FFE2 特征

对于速度,也是同理的,创建一个文本框供用户输入文字移动的速度,按下按钮后将此值写入 FFE2 特征,这里还设置了一个判断,防止用户输入了超出 256 的速度

然后是一个颜色选择框。这里触发了一个警告,即 onChange 在 iOS 17.0 以上是不推荐使用的,但是此警告可以忽略。这里弹出了一个颜色选择界面,用户选择颜色后将颜色拆分为高低两个字节,交给 BLE 模块将颜色传输给 FFE2 特征

最后就是一个确认写入的按钮。当用户按下此按钮时,才会调用 BLE 相关函数,将文字写入 FFE1 模块。

上面用到了两部分核心代码,一个是 Bitmap 的转换,一个是 BLE 传输的相关代码,接下来先介绍 Bitmap 部分

Bitmap.swift

此文件负责的最重要的内容就是正确将汉字转为点阵数据。相关函数全部定义在 Bitmap 的类中。

第一个函数是 GBK 下标偏置计算。GBK 编码是中国国家标准 GB 2312 的扩展,用于简体中文字符的表示。所有字被编码为两个字节的数据,分别为高字节 bh 和低字节 bl,GBK 编码在高字节为 0x80 时不存在字符,因此需要去掉一条线,剩余的部分则按照 GBK 编码的顺序进行转换

然后利用 getGBKZM 函数获取结果

需要注意的是,用户可能会输入全角字符,因此还需要设计一个转换函数,将所有数字,字母和特殊符号转换为全角,才能在 GBK 字模中找到对应的文字。

BLE.swift

此文件则负责了与单片机的蓝牙通信的细节,所有函数定义在 BLEManager 这个类中。由于我们在 MCU 部分代码已经确定了设备名字为 Senyao Smart Screen,因此可以在这里直接指定需要连接的目标设备名字就是 Senyao Smart Screen。初始状态为未连接,并且持续搜索是否存在 Senyao Smart Screen。

这里还定义了 FFE2 的默认初始数据,防止修改滚动方向、速度或颜色中任意一项时,其他项的值不合理,但也被一并修改并发送给了设备。

第一个函数 centralManagerDidUpdateState 比较简单,就是打开设备的蓝牙并且开始扫描。

第二个函数 startScanning 就是对周围蓝牙设备进行扫描

第三个函数 centralManager 则对扫描到的结果进行判断,如果找到了期望的 targetDeviceName,即 Senyao Smart Screen,则认为找到了设备,并且结束蓝牙扫描

连接上蓝牙后,则判断可以准备进行数据传输。对于 BLE 协议而言,数据传输基于特征,而特征又基于服务,因此这里首先要确保发现了一个服务

接下来在服务中寻找预期的两个目标特征,分别是 FFE1(用于传输文字)和 FFE2(用于传输指令)

对于手机端来说,最重要的功能就是向这两个特征写入数据。对于 FFE1,则将之前得到的 34 字节数据直接写入即可

而对于 FFE2,存在三种不同的数据——方向,速度和颜色。为每种修改各创建一个写入函数。如果要写入方向,只需要将第 0 个元素赋值为 direction 即可

同理,如果要写入速度,则需要写入第 1 个元素

如果是要写入颜色,则分别将高低字节写入第 2 和第 3 个元素即可

至此,本项目的所有开发工作完成!所有代码均可以在我的 GitHub 中参考。


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注