读取请求
读取请求与写入请求非常相似。只有两个重要的区别:
◾RW_ID 字节的最高有效位设置为 1;
◾主盘从 SWire 数据线读取数据,而不是在 RW_ID 字节之后发送数据;
再次以下面的例子为例。为了让事情更有趣,在这个例子中,主盘从从盘中读取两个字节:
使用SWire读取数据的示例
发送RW_ID字节后,主盘会发送一个低级别的单位。从盘以 8 位数据和 1 个低级别单位响应。主盘可以通过写入一个低级别单元来请求更多的数据,否则主盘发送END字节(0xff),传输结束。
让我们放大读取请求期间多个字节的传输,就在上面的 RW_ID 字节之后:
放大多字节读取请求
在本例中,主盘从从盘的地址 0x007e 读取值 0x5316。
我们刚刚读取的地址0x007e实际上是TLSR82xx芯片中的一个特殊寄存器,它持有“芯片ID”。对于我们的TLSR8232,芯片ID是0x5316。
你可以在 get_soc_id.sal 中找到整个带注释的逻辑分析器捕获,它包括读取和写入请求。
在上面的示例中,我们看到主盘和从盘都在同一条总线上读写,他们必须理解彼此的信息。一个关键设置是两个设备传输数据的速度。
下面的示例与上述地址 0x007e 的读取请求相同:
主机和从盘之间速度不匹配的示例
看看主盘发送它的RW_ID字节后发生了什么,从盘开始响应,但速度明显降低。我们可以看到这些位是在比以前从主盘发送的更宽的窗口中编码的。还要注意,即使整个传输都失败了,从盘响应的开始似乎很有希望。它以0x16开始,这是预期的“芯片ID”的第一个字节。
这种速度不匹配是个问题,它打破了主盘必须协调的问题。
坏处一:要读取数据,从盘的速度必须与主盘的速度兼容,否则主盘无法协调整个操作。我相信我们可以找到一个解决方案来调整这个速度并让主盘根据从盘的速度调整它的速度,但目前还没有完成。
好处:写入数据似乎是一种对协调不那么敏感的操作。正如我们上面提到的,从盘似乎已经能够正确理解从主盘发送的字节(拼写“地址 0x007e 的读取请求”),即使从盘本身配置错误,速度较慢。
关于速度的最后一个问题是,我们可以通过写入从盘的一个特殊寄存器来配置它的速度。如上所述,我们看到了关于地址 0xb2 处的寄存器的一些说明:
从盘的 SWire 速度控制寄存器
简而言之,我们可以通过写入它的 0xb2 内存地址来调整从盘的 SWire 速度。
另一方面,我们还需要设置主盘的SWire速度。
有效的策略是将主盘的 SWire 速度固定在一个合理的值,并为从盘尝试一些可能的速度。这正是我们的 Python 脚本所做的:
无效的 CPU 状态危险
另一个棘手的问题是有时从盘的 CPU 似乎不响应 SWire 请求,我还没有找到确切的原因,但我的猜测是当从盘处于某种省电模式或禁用中断时,SWire 不起作用。
实际上,这意味着根据目标设备上运行的程序,可能很难启动 SWire 交换。为了克服这个问题,pvvx 的策略是:
◾重置设备(通过将其 RST 引脚拉低);
◾当 RST 引脚被拉高时,开始用“CPU 停止”SWire 命令轰炸目标设备;
此处的目标是在CPU重置时使其处于良好状态,以免应用程序太过混乱。
尽早停止 CPU 的技巧
对于特别好奇的读者,“CPU停止SWire命令”是0x05对地址0x0602的一个简单的写入请求,该地址对应于控制 CPU 状态的特殊寄存器。
获取 RST 引脚
上面的 RST 技巧非常有效,唯一的缺点是,如果你查看 M6 板,RST 引脚在任何焊盘中都没有断开。
获取 RST 引脚
在数据表中,我们可以看到 TLSR8232 RST 位于引脚 26 上。在 M6 板上,该引脚直接连接到一个微型电容器,如上图所示。这是一项棘手的焊接工作,但它是可以做一个预先镀锡的金属丝和一点焊剂。
替代技巧——不需要 RST 焊接
只需在没有 RST 引脚的情况下尝试即可,另外你可以尝试在“CPU 停止”轰炸正在进行时手动重启目标板,你可以尝试调整代码以增加这个时间窗口。
读取 SoC ID
有了到目前为止所介绍的内容,我们就可以了解一个真实的场景了。使用tlsr82-debugger-client.py Python脚本读取目标设备的内存:
在后台,此调用执行了我们之前介绍的一些步骤:
1.通过拉低 RST 重置目标板;
2.通过在 RST 拉高的同时将许多“CPU 停止”值写入目标板的 CPU 控制寄存器来轰击目标板;
3.设置主盘的 SWire 速度;
4.迭代目标板的可能 SWire 速度,直到找到合适的速度;
5.向地址 0x007e 发出 2 字节读取请求;
读取和写入内部闪存
我们的目标之一是转储目标板的固件,它被存储在板子的内部闪存中。虽然 SWire 协议的细节不公开,但Telink确实为 TLSR8232 SoC 提供了 SDK。此时,ble_sdk_hawk/drivers/5316/flash.c 中有一个有趣的文件,其中包含芯片读取和写入其内部闪存的代码:
我们可以看到,与内部闪存交互归结为写入目标板的 SPI 控制寄存器(地址为 0x0d)和读取/写入 SPI 数据寄存器(0x0c),以及操作SPI芯片选择逻辑级别。
由于我们知道如何通过 SWire 与目标板的内存地址进行交互,因此我们可以在 Python 脚本中实现完全相同的操作,以读取和写入 SPI 控制和数据寄存器(分别为 0x0d 和 0x0c)为目标。这正是我所做的。例如,查看 write_flash 函数:
read_flash 函数的工作原理类似
转储固件
有了通过 SWire 读取目标板内部闪存的能力,我们现在可以转储 M6 的固件:
你可以在项目的存储库中的 dumped/flash.bin 下找到原始转储。
SDK,编译器和 Docker 镜像
我们的第一个主要目标是转储固件,第一步是获得TLSR8232的SDK和编译器。
该SDK可以在Telink的网站上找到,我使用的那个列在“Bluetooth LE Generic”部分。拆开 SDK 发现它与Telink自己的 IDE 集成在一起,该 IDE 基于 Eclipse IDE,似乎仅适用于 Windows。这很好,但我希望能够更轻松地创建单个 Docker 文件,其中包含编译 TLSR8232 程序所需的所有环境。
通过谷歌搜索,我找到了Ai-Thinker-Open/Telink_825X_SDK存储库。它包含一个SDK的Telink芯片,它指的是一个Linux tc32工具链,这正是我们需要在Docker下运行它。我使用了tc32工具链和TLSR8232 BLE SDK,并设置了一个Dockerfile,使编译自定义代码更简单。
有了它,我们可以简单地启动Docker容器并输入make来编译我们的代码,可以通过这样做来构建blinky二进制文件:
Blinky
我将一个红色 LED 连接到 TX 焊盘并开始让它闪烁。
blinky 的示例代码可以在 GitHub 存储库中的 example-programs/blinky 下找到。这是它的 main() 函数的全部内容:
正如我们在 Docker 映像中设置 SDK 和工具链的所有艰苦工作一样,编译它是轻而易举的,正如我们在上一节中看到的那样。我们只需要使用我们的 Docker 文件,将 example-programs/ 存储库目录挂载到 /app 并在我们要构建的示例上输入 make :
在 M6 板上刻录已编译的固件是使用我们可信赖的 Python 脚本完成的:
命令执行完毕后,M6 板应立即执行其操作,详细视频点击这里。
电容式按钮
M6 中的触摸板没有直接连接到 TLSR SoC,而是通过板上的驱动器 IC。我怀疑该 IC 负责管理触摸感应电路并将干净的数字信号传输到 SoC,但我无法轻松识别神秘的 IC。
为了找出相应的 SoC 按钮引脚,我使用了二进制搜索方法。我首先确定了所有尚未使用的 GPIO 引脚,并将它们全部设置为输入。然后我遍历它们并检查当我触摸按钮时它们中的任何一个是否改变了状态。如果发生这种情况,我会切换 LED。然后我将测试中的 GPIO 引脚分成两组,并对该组重复该过程。原来我们的按钮状态可以从 GPIO_PC2 引脚读取。运行示例程序/按钮固件的结果的视频点击这里。
显示
下一个目标是在显示器上绘制一些东西,第一个任务是识别硬件。经过大量的搜索和猜测,我在全球速卖通上发现了完全相同的显示。
它是一个 13 针、160x80 像素的彩色 SPI TFT 显示器。有点奇怪的是,数据线被称为 SDA 和 SCL(在 I²C 设备中经常看到)。我相信它们实际上是伪装的 MOSI 和 SCLK。
显示连接器上覆盖的引脚标签
它使用 ST7735 驱动程序 (PDF) 将像素推送到屏幕,这是个好消息,因为该驱动程序在彩色显示器中相对流行。它在许多制造商友好的产品中都有特色,并得到 Adafruit 的 ST7735 库的支持。虽然 Adafruit 的库建立在 Arduino 抽象之上,而我们离这一点还很远,但事实证明它是一个很好的参考。
接下来的任务是确定显示器连接到哪些 SoC 引脚:
要在显示器上绘制单个像素,我们需要执行以下操作:
1.通过驱动它的 LED 阴极(LEDK 引脚)来打开显示器的背光;
2.拉高 RST;
3.使用目标板上的引脚 SDA 和 SCL 设置 SPI;
4.向ST7735驱动发送一堆命令,包括:
◾退出睡眠模式;
◾设置颜色格式(这里我使用 RGB565,每像素 16 位);
◾设置显示器的物理尺寸;
◾打开显示器;
5.发送命令来定义绘图区域;
6.为单个像素发送 16 位颜色;
把所有的细节都弄对并不是一件容易的事,大多数时候感觉就像在黑盒子里工作,没有反馈,错误可能出现在任何地方,从显示标识到引脚映射,再到程序逻辑,再到固件刻录错误。
示例程序/显示在屏幕中间绘制了一些颜色方块:
绘制文本
既然我们知道如何在显示器上绘制单个像素,那么绘制文本就归结为找出每个字符应该绘制哪个像素,位图字体非常适合这个要求。在这些例子中,每个字符只是一个位数组,其中0代表背景,1代表我们需要绘制的像素。
让我们从 Adafruit GFX 库中借用 Picopixel 位图字体。
举个例子,如果我们稍微挖掘一下,我们会发现字母 A 是 3 像素宽 x 5 像素高,并且被编码在两个字节 0x57 0xda 中。我们首先将这些字节展开为它们的二进制表示:0x57 0xda = > 0101011111011010
我们知道这个特定的字符有 3 个像素宽,所以我们将这个位序列放入行中,每次放置3个位:
就像魔术一样,如果我们绘制 1 位,我们会看到字母“A”出现:
我在示例程序/文本中实现了这个想法,很高兴注意到该算法可以很好地推广用于放大文本。我们可以将一组 2x2、3x3 或 4x4 像素作为目标,就好像它们是单个超像素一样。具体过程请点击这里。
为了开始使用 BLE,我将 TLSR8232 设置为外设模式并定义了一个 BLE 特性,该特性在写入时切换 LED。在示例程序/ble-services 中,我将 LED 连接到 TX 焊盘,具体过程请点击这里。
在之前的视频中,我使用 nRF connect iOS 应用程序连接到 M6 板并与我在固件中定义的 BLE 服务进行交互。
本文翻译自:https://rbaron.net/blog/2021/07/06/Reverse-engineering-the-M6-smart-fitness-band.html如若转载,请注明原文地址