致谢:本文内容整理自社区开发者的两篇原创技术文章。感谢作者对先楫 SEI 外设的深入研究和无私分享,让我们得以一窥 HPM 单片机在高速 SWD 调试器领域的潜力。
原文章链接:
玩转先楫单片机之:使用HPM6P00系列的SEI外设模拟SWD协议
https://www.bilibili.com/opus/1213437323262296083
玩转先楫单片机之:继续优化SEI外设模拟的SWD协议
https://www.bilibili.com/opus/1214894163302023192
最近开始玩 HPM 单片机,原因是低价入手的几片 HPM6P41 一直吃灰。本着不能浪费的原则,研究了一下能用在什么地方。本来我对先楫的单片机不太感冒,但是在看了手册之后,对芯片里这些奇奇怪怪的外设产生了一些兴趣。

前段时间,群里开始了赛博斗蛐蛐。为了做出最强的 CMSIS-DAP,群里大佬们各显神通,甚至把 FPGA 都搬出来了。我用的是 STM32H750,靠的定时器和 SPI 模拟 SWD,每次通信都要重新配置几次,接收 ACK 后还要停下靠 CPU 做判断,速度始终快不起来。经过优化后,也才在 30M 时钟下跑出 1600K 的 RAM 读写速度。
先楫的 SEI 外设专为模拟其他通信协议而生,属于电机控制系统的一部分,主要用于模拟编码器通信协议,读写编码器位置信息,能够支持同步/异步通信,并且能够完成简单的数值匹配和程序跳转。

在先楫的手册中,对 SEI 的介绍比较少,只是简单描述了一些功能,然后给出了一些编码器协议的通讯配置实例。

SEI 有 17 个数据寄存器(DAT),每个寄存器都有位指针,可以以特定的初始比特位置、特定的字节顺序、特定的字长收发数据,非常灵活。其中:
手册中给出了 8 个指令,主要分为数据收发、跳转、等待、停止这几类,执行时按所在地址顺序依次执行。SEI 中有四个状态机,能够根据特定条件跳转,状态跳转信号可以作为触发信号输出,与其他外设精确对齐时间点。
数据收发指令可以收发 0~32 位任意长度的数据,而且可以将多次收发的数据按位拼接到一个数据寄存器中,而不会冲掉原来的数据,CRC 计算也可以连续进行。
SEI 的跳转匹配条件列表有 8 个,每个匹配条件都能设置掩码、比较上限、比较下限,如都不匹配则会跳转到最后一个。
SEI 内部的时钟信号和数据收发时机都是由一个小的计数器设置的,类似 PWM 的比较/匹配点。时钟的上升/下降沿、数据的收/发点都可以在分频值内任意设置,高速时或许可以对通信延迟进行补偿。SEI 还支持 DMA,这点没有仔细研究,不再多说。
熟知以上知识的话,并且恰巧熟悉 SWD 协议的话,就可以快乐地模拟 SWD 了。以读操作为例:
我们使用 DAT2 保存 request,DAT3 保存 data,DAT4 计算 request 的奇偶校验值,DAT5 计算 data 的奇偶校验值。SWD 是以 LSB 传输的,我们要把 DAT2~DAT5 数据格式设置为 lsb,DAT2 长度 4,DAT3 长度 32。SEI 的奇偶校验功能是异步通信使用的,同步通信时我们使用 CRC-1 计算奇偶校验值,结果长度为 1,初始值为 0,多项式为 1。
具体指令如下:
| 指令 | 操作 |
| INSTR0 | 接收 1bit 高电平,trn 信号 |
| INSTR1 | 发送 1bit 高电平,start 信号 |
| INSTR2 | 发送 4bit DAT2,同时把 CRC 值保存到 DAT4 |
| INSTR3 | 发送 1bit 奇偶校验位 |
| INSTR4 | 发送 1bit 低电平,stop 信号 |
| INSTR5 | 发送 1bit 高电平,park 信号 |
| INSTR6 | 接收 1bit 忽略,trn 信号 |
| INSTR7 | 接收 3bit ACK,保存到 CMD 寄存器 |
| INSTR8 | 根据 CMD 寄存器匹配跳转 |
| INSTR9 | 接收 32bit data,计算 CRC 保存到 DAT5 |
| INSTR10 | 接收 1bit 奇偶校验位,与 DAT5 做比较 |
| INSTR11 | 接收 1bit,trn |
| INSTR12 | halt,停止 |
| INSTR13 | 接收 32bit data,DAT0 丢弃 |
| INSTR14 | 接收 2bit 奇偶校验 + trn,DAT0 丢弃 |
| INSTR15 | halt,停止 |
指令从 INSTR1 开始执行,ACK 跳转列表如下:
| 条件 | CMD 值 | 动作 |
| OK | 0x1 | 跳转到 INSTR9,正常结束 |
| WAIT | 0x2 | 跳转到 INSTR0,重新执行 |
| FAULT | 0x4 | 跳转到 INSTR0,重新执行 |
| 大保底 | 0x7 | 跳转到 INSTR15,结束 |
上述指令仅为简化版,WAIT 和 FAULT 的处理方式可能与原程序有所不同;另外 idle cycle 也没有考虑,实际指令更复杂。
读 IDCODE:

WAIT 自动重试:

多次重试:

在 30M 时钟下跑出了约 1700KB 左右的速度,但还是不出意料地遇到了一些问题。在经过评论区疑似 HPM 人士的大佬指点后,我重新优化了程序,解决了大部分问题。

上回说到,SEI 程序中接收到了芯片回应的 ACK[0:3] 值,存储在 DAT_CMD 寄存器中,用于程序的条件跳转。但是在 SEI 的程序结束后,我们还是需要读取到 ACK 值返回给上层的函数作判断,否则在调试时会出现很多意外的错误。
上次发文时,我误以为 ACK 的值存储在 DAT_1 寄存器,读取到了错误的结果。虽然官方在手册把 DAT_CMD 排列在 DAT_1 的位置,它在 SEI 程序中的定义值也与 DAT_1 一致:



但是实际上,DAT_CMD 寄存器是每个控制器独有的,只有通过 CTRL[x].CMD 才能正确读取。
SEI 外设的时钟频率与 AHB 同频,默认值也是最大值为 200MHz。


在官方库中,第一个跳变点(即下降沿)在周期的 1/4 处,第二个跳变点(即上升沿)在 3/4 处。

理论上,SWD 协议中,芯片端时序只对上升沿敏感,所有数据的收发都在上升沿完成,芯片会在绿色标记处发送数据,我们在蓝色或紫色处都可以接收数据。但在实际测试中(例如 30MHz 下),蓝色标记和紫色标记处都收不到数据,需要把采样点向后继续移动几个采样点才能接收到数据。

在高频下,时钟分频值很小,如果要提高延迟周期数,只能将下降沿(p0)和上升沿(p1)同时前移,在上升沿后留出足够空间。所以我们在 20M 以上频率时,将 AHB 超频到 300M(小超个 50%),并按照下图的方法计算采样点,成功将 SWD 跑到 30MHz。

最后,我们选取多点进行测速,使用群友的 openocd 测速脚本,多次结果取平均,得到如下的折线图:

相比于低频,高频下速度提升并不成比例,最终呈现的曲线略凸。
HPM 的片内 FLASH 为 QSPI 形式,虽然每个内核有着 32K + 32K 的巨大 cache,但运行速度仍不够极限。所以我们修改链接脚本,使用 ram.icf,由此测试最高速度(但实际上此时的变量默认存放在 AXI_RAM,虽然有 Cache 加持,应该还是比 DLM 稍慢一点)。
最终在 30MHz 下达到了:
写入速度:1820 KB/s读取速度:1650 KB/s
距离理论速度 30/45×32/8=2.6667 MB/s30/45×32/8=2.6667 MB/s 仍有较大差距。这其中还有 USB 应答延时的开销不可忽略——每次 SWD 读写事务之间,上位机(OpenOCD)通过 USB 下发命令、等待响应都需要额外的往返时间,这部分延迟在高速通信中占比相当可观,也是制约实际速度逼近理论极限的重要因素。

SEI 外设功能强大,能够模拟各种同步/异步协议。虽然不是专门为高速通信设计的,但在我们的调教下,最终成功实现了 30MHz 频率下的通信。
我们仍然对 SEI 所知甚少,手册中还有很多寄存器没有介绍。HPM6P00 系列的 FGPIO 更加强大,600M 主频下 FGPIO 的翻转速度能够达到 300M。使用 FGPIO 模拟 SWD/JTAG 协议有望实现更快的速度以及SEI模拟JTAG时序,这将是下一步探索的方向。
全部0条评论
快来发表一下你的评论吧 !