页次: 1
现在绝大部分人做调试器的方向都是错的,都是想着做低成本,而不是把东西做好。
导致现在在淘宝上买的DapLink很多都不稳定,要买多个对比实测才知道好不好用。真TM的一言难进!
这点我同意,现在的DAP调试器市场主要是两种方向,一种就是像你说的,只是简单地把USB协议栈和CMSIS-DAP或者DAPLink代码拼接起来,看着还行,实际体验并不好,总有各种小问题;另一种就是使用高端芯片做更好的功能,但是价格相对来说也比较高。
我想做的可能会更“极端”一些,对于低成本的方案,还是使用廉价MCU,但是通过重写相关的代码,实现更高的性能,解决一些潜在的问题,让低价调试器不低质;高端方案我比较想尝试基于FPGA的方案,做到更好的传输质量和更低的延迟。当然,这些只是设想,不过我也不靠这个盈利,所以会花时间来慢慢打磨,满意了再发布。
好久不见,距离上次更新又过去了近两年时间。这几年时间单片机的价格大跳水,1块钱带USB的单片机已经不再是传说,是时候重启这个项目了。
在主控芯片的选择上,当初我们基本上只有CH552能选,而现在STC8H2K08U或者CH32X033都是更好的选择,两者的散片价格都在1块钱左右。考虑到开发难度和后续的升级能力,后者都是更好的选择,因此后续我们将用RISC-V核心的CH32X033来完成——虽然和标题有些冲突,但我们的目标是花最少的钱实现同价位最高的性能。
CH32X033的纸面数据还是可以的,至少从存储器来看是管够的,这意味着我们很容易就能移植现成的CMSIS-DAP甚至DAPLink并让它跑起来,但是这样的性能可能不够极致。因此,我对这个项目的要求是:
不引用其它代码库,也就是说手写包括USB协议栈和CMSIS-DAP源码内的所有代码
对于JTAG和SWD,使用SPI进行加速,SRAM的读写速度突破300KB/s
必要时编写汇编来处理关键代码,包括IO操作和ISR等
保留后续添加新功能的能力,在Flash足够的情况下可以根据需求进行扩展
丰俭由人,BOM的成本可以压到2块钱甚至更低,也可以添加电平转换芯片等外围电路构成更完整的调试器
对于一个项目,我们当然要定个目标,我觉得目标可以分成基本目标和进阶目标,基本目标是做一个可以和DAPLink的基础功能大致对齐的调试器,进阶目标则允许和其它基于DAPLink的调试器构成差异化竞争。
基本目标:
CMSIS-DAP协议支持,包括v1(基于HID)和v2(基于WinUSB),不需要切换工作模式
SWO支持,波特率可以达到3Mbaud
异步串口支持,波特率可以达到3Mbaud
进阶目标(构思中):
加入OLED显示屏和按钮,实时显示信息并允许简单操作
WCH RISC-V调试协议支持(视协议开发情况而定,目前只有单线协议开放)
基于CMSIS-DAP的Vendor Commands实现新功能(强制调试时钟频率等)
由于是从底层搭起,想必会花费不少时间把这颗单片机摸透,不过至少SWD和JTAG的前置知识已经都在前文覆盖了,关于CMSIS-DAP协议的支持应该也会在后续提到。本贴应该会在接下来的几个月中缓慢更新,敬请期待。
大家都知道,ARM Cortex-M系列CPU中,现在最猛的是Cortex-M85,而作为第一批搭载Cortex-M85的MCU,瑞萨的RA8D1已经可以通过RT-Thread新出的Vision Board玩到了。
那么,既然是CoreMark/MHz最高的Cortex-M系列CPU,跑个分自然不在话下。这个MCU的最高主频“只有”480MHz,如果能够适当超频,是否会有更好的表现呢?
以下跑分使用以下配置:
Keil V5.39
打开Ofast和LTO
CoreMark部分运行在ITCM和DTCM
打开I$和D$
超频到720MHz
基于vision_board_blink_led工程修改,使能CoreMark,循环次数72000
结果如下,4000分整(有点巧),大概是5.56CoreMark/MHz,通过调整编译选项应该可以更快,不过这个分数已经很满意了,哈哈:cool:
@metro
那个cmsislink的xvc server能不能分享一下,另大佬怎么用python直接驱动cmsislink的引脚的,pyocd我没翻到这个功能欸
之前用CMSIS-DAP测试过,速度感觉不太理想,目前在尝试其它方案,等搞定了应该会开源的。晒下目前的硬件设计,软件部分还没开始适配:
至于驱动CMSIS-DAP,俺是直接用pyUSB发送和接收数据的,用的是CMSIS-DAP的DAP_JTAG_Sequence命令,用法还是比较简单的,可以参考官方文档:CMSIS-DAP: DAP_JTAG_Sequence
尝试测了一下CoreMark,分数还不错,基本上和STM32F407在一个等级,248MHz(数据手册中最高频率)能到630,384MHz(最高可运行频率)到了976以上,不知道这个分数是否意味着AG32VF407/AGRV2K的Flash有cache或者零等待执行。
CoreMark的工程可以在这里下载:CoreMark.zip,可以通过修改coremark.ve文件中的SYSCLK来调整时钟频率。
在更新到Supra之后,看起来支持CMSIS-DAP了,这样一来可以搓一个带下崽器的最小系统,好评。
https://whycan.com/files/members/1510/AGRV32-Supra.png
验证了一下,确实是支持的,而且OpenOCD的版本很新(版本信息是Open On-Chip Debugger 0.11.0+dev-02429-g3c36bfc (2023-02-06-17:01)),可以无bug支持CMSIS-DAP V2.1。
睡前发一下老板今天(昨天?)刚给的资料,信息量还是比较大的,特别是网盘部分,需要花点时间慢慢啃。
AG1KLPQ48.rar
AG32VF407.rar
AGRV2K.rar
某天在逛淘宝时,发现AGM店里上了个有趣的芯片:AGRV2K100(CPLD) AGM FPGA CPLD替代Altera EP1270 内嵌MCU,遂买来玩玩。接下来就发生了意想不到的一些事情:
我在早上下了一单,结果晚上到家的时候就发现快递躺着家门口了,这也太效率了。后面了解到他们一天会发两次货,并且刚好有本地仓库,所以当天就收到了,而板子都还没准备好。
收到货的时候,发现并没有收到想要的AGRV2K,反而收到了AG32VF407。后来才知道,原来AGRV32和AG32VF407是同一颗芯片,估计厂家没有重新打标,直接就推了。
发现卖家“发错”之后立刻联系卖家,结果卖家在晚上直接就打电话过来了,详细解释了丝印的问题。后面我也确认过,这两个确实是同款芯片。
来点当时刚刚收到的芯片:
metro 说:趁下班时间画了一版,大家猜猜这是什么方案😏
https://whycan.com/files/members/1510/AQUA-Lite.png这是要用PIO做USB PHY?也就是只能测FS/LS咯?
是的,先从简单的做起,熟悉下上位机。
不过这个会按照自己的想法做一些功能,具体的话可以期待一下🤭
设计不错 就是内存小 要是能跟sigrok连上 功能会更好些
其实sigrok可能未必是最好的,原因上面有总结过,主要是USB分析仪需要能分层次呈现相关信息。至于其它方案,我想到的比较好的有两种:
使用已有的开源USB分析上位机,比如上面提到的ViewSB(不过这玩意实在是太半成品了一些),或者是同一个作者正在优化的Packetry。这个比较适合实时捕获,不过功能上还比较简陋,需要优化。
将数据保存为其它工具可以打开的形式,之后可以进行离线分析。这个我能想到最好的软件是Wireshark,配合相应插件可以实现USB Class级的分析,并且有强大的功能支持(例如正则表达式筛选)。按理来说这个工作量不会很大,不过似乎没有看到过成品。
既然要做一个USB分析仪,那自然要对它的原理有一个初步的了解,只知其然不知其所以然总归是不好的嘛。
在大多数应用中,USB分析仪也就是一个特殊的逻辑分析仪,通过抓取逻辑信号的方式来获取USB传输的物理层信号,之后通过层层翻译最终得到USB协议包、USB类乃至应用层的信息。当然,相对于一般意义上的逻辑分析仪,USB分析仪有以下几点不同:
USB的传输速率和当前的USB模式有关,如果是全速(12Mbps)或者低速(1.5Mbps)的话,普通的逻辑分析仪就可以实现(事实上sigrok等逻辑分析仪上位机已经支持USB全速和低速分析了);但是,高速(480Mbps)还是比较有难度的,这个速率超过了大部分逻辑分析仪可以达到的速度。
从物理层来看,高速USB其实是个比较蛋疼的存在:一方面为了兼容性需要使用3.3V CMOS电平进行握手和一些特定状态的处理,一方面为了传输效率使用了17.78mA的差分电流源,而这两种电平需要两路驱动和接收器在不同状态下进行切换。因此,如果要抓取高速USB信号,也必须要有相应的设计才行。
相对于通用的逻辑分析仪,USB传递的数据基本上是遵循一些上层协议的(如USB类),所以分析得到的数据时最好也要整理为相应形式以呈现给开发者,否则由于USB数据量大于大部分常见协议,可能会造成分析上的困难。
除了抓取物理层信号分析的方式,其实我们还有其它方式可以分析USB,比如在计算机上使用软件(例如Bus Hound等)进行抓包。实际上,软件抓包在大多数场合都要比USB分析仪好用,毕竟在计算机上更容易实现数据过滤和分析,但是,在有些场合,USB分析仪还是无法替代的:
最典型的情况是在嵌入式开发USB主机相关应用。在嵌入式中,通常难以使用软件抓包的方式来分析,即使能够通过输出log等方式获取一些简略的信息,但终究无法反映所有的信息。
对于USB无法正常使用的场合,USB分析仪能够抓取错误的波形和数据,而软件抓包只能在驱动层进行操作,对于USB控制器自动过滤或者忽略的情况则毫无办法。一个典型的例子是,Windows在获取描述符失败后无法识别设备,因此在驱动层面无法给出具体的错误信息。
USB分析仪可以抓取更精确的信息,例如时间戳等。由于驱动层给出的信息可能不准确(比如说驱动层反馈信息的时间有延迟),这个时候就需要USB分析仪补充相关信息了。
USB分析仪可以在一些特殊情况下使用,例如系统启动时、休眠时等。
考虑到USB分析仪的使用场景,我们通常可以假定其不需要分析物理层的内容,可以从协议层开始看起。那么,一个完整的USB分析仪系统,就可以分为以下这三个部分:
USB信号采集,也就是把抓取到的物理层信号翻译为USB协议包,并且附加一些信息(如时间戳等)。
USB数据转发,一般是把采集到的USB协议包传输到上位机,当然也可以保存下来之类的。
上位机分析,也就是把USB协议包翻译成方便人类阅读的方式,从而高效的定位和分析问题。
不考虑USB物理层的原因不外乎以下几点:
这部分内容以分析信号传输质量为主,通常由专业仪器测定,一般只有IC和PCB设计时会考虑,程序猿不用头疼这个事情。
高速USB的鲁棒性其实挺强的,你想想一般情况下接个几米的线都没问题,如果不是板子画得太离谱,一般不需要往这个方面去考虑问题。
退一步说,就算高速USB出问题,一般情况下都会回退到全速,这个就真的是“拉通了就能用”的程度,以USB堪比Windows的兼容性来看,只要全速USB能工作,剩下的问题应该就容易定位了。
LUNA(现在硬件部分改名为Cynthion)就是一个完整的USB分析仪实现,也是本文的主要技术参考和灵感来源。可以从官方网站Cynthion - Great Scott Gadgets获取更多信息。
USB分析仪有一点和逻辑分析仪很像:作为调试程序时使用的工具,USB分析仪的上位机是非常重要的,一个成熟可靠的上位机才是吸引大家使用的关键。对于个人来说,开发USB分析仪上位机是一件比较庞杂的工作,好在我们可以搭上开源的快车,像Great Scott Gadgets开源的ViewSB(不得不说这个名字在汉语语境中十分微妙)和Packetry都是可以参考的对象。
综上所述,如果要自制USB分析仪,我们至少需要完成以下工作:
设计一套能够抓取USB信号,并将信号传输到上位机的硬件
为这套硬件编写必要的代码,使之可以和上位机通信
在上位机中适配新的USB分析仪
接下来的帖子也将围绕这些工作展开。
最近参加了沁恒举办的[RISC-V MCU创新应用大赛](https://www.wch.cn/RISC-V-MCU-competition),虽然很可惜没能在限定的时间内完成所有工作,不过毕竟也做了一些有意义的事情。在deadline来临之际,这里我把之前整理的一些资料和已经完成的部分分享给大家,后续有时间会尽力完成这个设计,争取把BOM成本降低到可以自制的水平,做一个有实用价值的东西出来。
考虑到参加比赛的情况,这里暂时不会把参赛用的代码直接发出来,但后续会提供一个开源版本,希望能够和大家一起讨论相关技术,一起改进现有的设计。
由于楼主工作繁忙,更新进度比较随缘,还请大家见谅。
先说结论:这样做没有语法错误,但很可能达不到楼主想要的效果,除非明确自己需要做什么,否则不建议这么写。
具体代码的编译结果可以参考这个:https://godbolt.org/z/57EfcK7eM(为了避免编译器的优化,对case值进行了少许修改)。
接下来说说C语言里面switch和case的逻辑,可以参考C语言的规范,例如C99:https://www.dii.uchile.cl/~daespino/files/Iso_C_1999_definition.pdf。
可以看到,C里面的switch的功能实际上很简单,说起来就是:
首先计算switch内的表达式的值,C要求计算的结果必须为整数(即integer)。
接下来会找到值匹配的case,如果没有匹配的值则会跳转到default(如果有),如果没有则直接跳出整个switch。由于C语言限制了case值不能重复,也不能有多个default标签,所以从switch到case/default的跳转是唯一的。
跳转到相应位置后,依次执行代码,直到遇到break提前终止或者完成整个switch。
至于case,其实case可以理解成是一种带常数表达式的label(也就是goto跳转到的地方),因此case可以出现在switch内的任何位置,但这个位置是直接跳转过去的,并不会去执行前面的语句。以楼主给的代码为例,这里if(y==1)实际上就是永远不会执行到的代码,因为上一个case结束时有break,而下一个case是在if语句里面,从执行的结果上看相当于没有这一行代码。现在,对比上面给的编译结果,应该就很容易理解为啥完全没有出现r1(第二个参数)这个寄存器了。
最后贴一个C99规范里面的例子,以及相应的说明:
In the artificial program fragment
switch (expr) { int i = 4; f(i); case 0: i = 17; /* falls through into default code */ default: printf("%d\n", i); }
the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.
顺带一提,双层沉板USB Type-A用的是这个:U224-081N-1WRZ21-S-1_(XKB Connectivity(中国星坤))U224-081N-1WRZ21-S-1中文资料_价格_PDF手册-立创电子商城,焊好后板子的厚度在1.5cm左右😎
另外,为了控制阻抗,这个板子用了4层铜,在嘉立创打样的话需要选择7628叠层。
先上链接:metro94/TinyHub: TinyHub - 4-Port USB Hub with Multi-TT
顺便贴下实拍图,可以看到真的很小,算上突出的各种接口,差不多正好是4x4 cm^2见方。
--------普通的分割线--------
那么,这次为啥画这个板子呢?简单来说就是因为便宜:虽然2块钱的Hub并不是最实惠的(SL2.1A等批量价格似乎不到1块钱?),但是带上MTT的支持的话那就不一样了。对于Single-TT和Multi-TT的区别,可以参考这篇文章:USB hub MTT STT 区别,简单来说就是对全速和低速设备的带宽有帮助。
本来对于一个Hub板子来说应该是没啥好说的,不过由于沁恒的批次有区别,这里还是详细说明下。
首先呢,在CH334的datasheet有提到,这玩意有批号的区别。对于批号倒数第五位为0的版本,由于内置的LDO最高仅支持4.5V输入,因此需要通过VBUS直接为V5供电的话,需要接一个二极管(例如1N4001)降压。在后续版本中,LDO可以支持5V直接输入,因此可以将VBUS直接接到V5上,官方建议有条件的话可以使用一个100mA的保险丝。
另外有一个坑点,这个是CH334R独有的问题。根据datasheet,CH334R应该是支持MTT的,但是我在淘宝买的散片只支持STT(批号31160HC16),这很有可能是官方初版芯片的问题。我找官方申请了一些CH334R的样片(31161HC43),发现新批次是正常的,可以识别到MTT。如果对此有疑问,可以咨询沁恒官方以获取更多消息。
总的来说,CH334系列(包括同样已经量产的CH334U)的性价比还是不错的,外围足够简单,QSOP16的封装相较于SOP16也能明显节省面积,非常适用于开发板的USB端口扩展等功能。
最后祝大家玩得愉快!
寂寞哥,上次那个CH334的hub验证成功了吗,对这个更感兴趣,可以放在全志V3s之类的板子上
暴露身份了🤣板子验证能用了,但是CH334R测试发现是STT,不是datasheet说好的MTT,怀疑可能是批次的问题,还在等官方回复,据说CH334U没这个问题。
https://www.wch.cn/bbs/thread-96354-1.html
首先感谢梦程(dreamcmi)赠送的CH340X,于是画点板子练手~
废话不多说,先上链接:metro94/TinyUSer
简单介绍下板子:超小体积,差点就可以塞进Type-C头子的那种;功能完善,CH340X新加的DTR也支持;虽然CH340已经有被CH343取代的趋势,但是偶尔用用还是不错的。至于DTR的用法,建议大家直接看datasheet,这里不再赘述。
最后晒点图,希望大家喜欢~
好消息,板子在Muse Lab上架啦~链接是https://item.taobao.com/item.htm?id=686942510375,辛苦晕哥帮忙改下标题和顶楼😘
现在推荐玩玩CH32V203G6,性能全方位优于CH552T,封装也更小(QFN28,4x4),价格的话批量也可以做到2块以内。可以参考这个帖子:CH32V203G6最小系统板正式开源,可能是最小的RISC-V全引脚开发板。
好消息,板子在Muse Lab上架啦~链接是
https://item.taobao.com/item.htm?id=686942510375
为了吸引大家点进来做一回标题党,下不为例,嘿嘿。
重要的内容放在最前面说:
CH32V203G6最小系统板开源啦,名为FlappyBoard,项目地址是https://github.com/metro94/FlappyBoard,该有的东西都有,欢迎大家围观~
说说这个板子的设计思路:
首先当然是极致体积,在保证全引脚均通过2.54mm间距排针引出的前提下,将板子体积缩小到了1.778x3.556 cm²,其中左右两排引脚的间距为600mil,正好可以适配DIP-28宽体的烧录座,也可以直插面包板。
板子当然得有各种必备的功能,包括USB-C、LED、按钮和调试接口,并且为无源晶体预留了位置。
添加了拨码开关,用于控制MCU的相关引脚是否直接接到USB-C上,解决了引脚冲突产生的问题。
丝印不仅覆盖所有引脚的名称,而且还包括阻容等器件的数值,方便手焊。
开源硬件设计,两层板布线,方便白嫖。
如果你愿意,甚至可以当钥匙扣用,哈哈哈。
这个板子可以配合MounRiver(沁恒的RISC-V单片机使用的IDE)进行开发,相关例程楼主也会持续同步到相应的GitHub中😉
话不多说,来欣赏一下渲染图和实拍图(顺便diss下嘉立创的丝印,比样板质量差多了):
原帖见Programming FTDI Devices for Vivado Hardware Manager Support,注意需要安装最新的Vivado 2022.1。
使用方法很简单,亲测可行。支持FT232H/FT2232H/FT4232H。
最后贴一下命令的使用方法:
program_ftdi
Short Description
Write/Read to FTDI EEPROM for Xilinx JTAG Tools supportSyntax:
program_ftdi {-write -ftdi=<ftdi_part> -serial=<serial_number> [-vendor=<vendor_name>][-board=<board_name>][-m=<manufacturer>][-desc=<description>] |
-write -filein=<cfg_filein> |
-read [-fileout=<cfg_fileout>]} [-help][-longhelp]Usage:
Name Description
-------------------------------------------------------------------------------------
-ftdi Specify the ftdi device to be programmed <FT232H | FT2232H | FT4232H>
-serial Serial number to be written into the EEPROM
[-vendor] Vendor information
[-board ] Name of the board being programmed
[-mfg ] Manufacturer information
[-desc ] A short description of the board
-file_in Input file with all fields to be written
[-file_out] File to which the FDI EEPROM should be read back
Description:
program_ftdi writes/reads the fdti part according to the option specified.
When called with write, either -ftdi and -serial must be used or -filein with -ftdi must be specified. The Xilinx supported configuration for the part specified using -ftdi flag is written into the EEPROM.
-vendor, --board, -m and -desc are optional arguments which can be used to specify more details to be written. All strings must be specified within inverted commas ("").
When used with read, the command reads back the content of FTDI EEPROM to standard out. if -fileout option is used, the read back information is written to the file specified.
If no arguments are passed, FTDI communication is skipped and help menu is printed.Restrictions:
Only the 1st ftdi part found will be programmed/read. If more than 1 ftdi device is detected this command will error out.Arguments:
-write - Write the FTDI EEPROM with either the options specifed with command line option of by a given input configuration file.-read - Read the contents of the FTDI device detected in chain.
-ftdi - (Required field) Specify the ftdi device to be programmed <FT232H | FT2232H | FT4232H>
This programs the ftdi part according to the supported configuration for the specified device-serial - (Required field) Serial number to be written into the EEPROM
The serial number can have digits and/or alphabets. no special characters may be used.-vendor - (Optional field) Vendor information
The vendor information should contain vendor name and other details as a string.-board - (Optional field) Name of the board being programmed
A board name can be written into FTDI using this argument.-m - (Optional field) Manufacturer information
The Manufacturer information should contain Manufacturer name and other details as a string.-desc - (Optional field) A short description of the board
-filein - (Required field) Input file with all fields to be written
If this argument is used, an input file with details about the FTDI device, serial number etc should be specified.-fileout - (Optional field) File to which the FDI EEPROM should be read back
@echo
另外,即使都是高速USB,相互之间也是有比较明显的性能差距的,这个可能和具体的实现有关,我这里不是很了解,这里有一些相关数据:https://blog.csdn.net/rui22/article/details/107814146 。DLC9和Digilent的方案的对比应该是比较多的。
(当然,最典型的反面例子就是只用CY7C68013A不用CPLD的方案,这种方案的IO速度受到CPU的限制,实际上确实不如新的USB全速方案,但是用GPIF或者Slave FIFO的方案理论上来说要好一些。)
说到底,我觉得最大的问题是高速USB对于纯下载用途而言确实应用面比较窄(原因如你所说,大容量芯片本身就是偏专业向的用途,和开源社区目前关注的方向不是很合拍),而可以发挥其优势的用途(如大容量FPGA下载,片上逻辑分析仪等)又容易受到上位机的制约(通常只支持特定型号的下载器,对开源方案不友好),估计需要多做一些工作才能达到期望的效果。
这个是之前做过的一个简单的转接板,验证过功能可用。
由于画得比较丑,就不上传到各种开源平台了,在这里随便发下,有需要的可以自取。
工程(KiCad):
DebuggerConverter-2022-06-02_213243.zip
Gerber:
DebuggerConverter-Gerber.zip
效果图(真正的转接板不需要额外的连接线.jpg):
建议参考USB-C的相关标准,里面有详细记录各种转接线和转接头的连接方式,按照这个来准没错。USB Type-C Specification Release 1.3.pdf
协议层关心的主要是所谓“数据包”的收发,也就是调试协议每次传输的最小单位。
一般而言,协议层的数据传递需要依靠收发双方各自维护一个状态机,引脚的状态变化会使得这个状态机不断更新到下一个状态,直到停留到目的状态,收发双方获取到所需的信息为止(传输错误等也可以看作是一种信息)。
需要说明的是,协议层一般只维护基本的传输功能,并不对收发的数据进行进一步分析,这些分析操作应该交由上层(数据链路层和AP层)完成。
因此,像CMSIS-DAP这样只做到协议层的调试器而言,其实只需要上位机(通常是OpenOCD等)将高级操作(例如访问寄存器等)翻译成协议层的指令,并不需要调试器“理解”指令的含义,所以CMSIS-DAP的方案可以在小Flash的MCU上实现(例如CH552),功能上并不打折扣;相对应的,CMSIS-DAP需要上位机配合才能工作,单独使用时需要手动操作AP层的寄存器,比较麻烦。
SWD
SWD作为两线协议,传输数据的过程必然需要协商好数据传输的方向,否则可能会导致信号冲突或者握手失败。好在SWD的协议并不复杂,下面我们来一窥端倪。
传输格式
SWD的数据包可以分为以下三个主要部分:
Packet request:表示调试器的命令,传输方向为从调试器到DP
Acknowledge response:表示DP对请求的响应,传输方向为从DP到调试器
Data transfer phase:表示实际传输的数据,传输方向根据命令确定
另外,在切换传输方向时,需要数个周期的时间,这个阶段被称为turnaround。一次正常的传输总会包括两次turnaround,原因是packet request和acknowledge response的传输方向总是相反的。
注:turnaround的设置位于SW-DP的DLCR寄存器中,可选值为1到4个周期,line reset后默认值为1,且可以被调试器设置。
接下来我们看各个部分的格式。
对于packet request,共有8个bit,按传输顺序分别如下(均为小端序,下同):
Start:表示开始发送,恒为0b1
APnDP:表示传输的寄存器,0b0为DP,0b1为AP
RnW:表示数据的传输方向,0b0为调试器写(DP读),0b1为调试器读(DP写)
A[2:3]:表示寄存器的索引
Parity:表示APnDP到A[3]这4个bit的偶校验结果(其实就是将这4个bit异或后得到1个bit的结果)
Stop:表示结束发送,恒为0b0
Park:将SWDIO置为高电平,以便后续turnaround保持高电平状态(通过上拉电阻)
对于Acknowledge response,共有3个bit,其中:
0b100:OK,表示读写操作正常发起
0b010:WAIT,表示当前DP正忙,需要调试器重试,或是(在重试一定次数后)使用ABORT寄存器终止传输
0b001:FAULT,表示之前的传输有误,需要调试器读取相应寄存器获取出错原因
其它:表示可能出现其它错误,包括协议错误(protocol error)或是与DP断开连接等,由于packet request阶段结束时已经将SWDIO设为高电平,因此最有可能出现的是0b111,表示DP没有驱动SWDIO
对于Data transfer phase,共有33个bit,传输方向由Packet request中的RnW确定,其中:
前32个bit表示传输的数据
最后1个bit表示前32个bit的偶校验结果
从上面可以看出,一个数据包的长度为(Read) 8 + Trn + 3 + 33 + Trn = (Write) 8 + Trn + 3 + Trn + 33 = 44 + 2 * Trn,其中Trn表示turnaround的时钟周期数。
读写方式和错误处理
在明确传输格式后,接下来就可以让调试器和DP开始沟通了。不过,一次完整的传输需要考虑的事情可不少:
DP和AP寄存器的读写方式有所不同。由于AP寄存器的访问需要时间,因此相对于DP寄存器而言等待时间可能更长,并且可能出现的状况也更多。
在acknowledge response返回不同值的时候,需要有不同的处理方法。成功的时候自然是最好,如果遇到了其它状况,也应该妥善处理,以保证调试器和DP能够正常工作。
我们先来说写寄存器的情况。在成功写寄存器时,波形图如图B4-1所示。
注:在ADIv6中,SWDIO的波形均被描述为上升沿时改变,这对调试器而言实际上不够好,见物理层部分的相应描述。
可以看到,在acknowledge response阶段,DP通过返回OK表明自己有能力接收新的数据。至于此次传输的数据是否被DP成功接收,则需要等待下一次传输的acknowledge response返回,其中:
若写的是DP寄存器,则通常可以直接获知是否成功传输,也就是说会在下一次传输时返回OK/FAULT或其它错误
若写的是AP寄存器,则由于可能需要等待上次传输完毕,可能需要等待一段时间才能获知传输是否成功,也就是说下一次传输时可能会返回WAIT,直到上次传输完毕
注:写AP寄存器在下一次传输返回OK并不代表已经成功写入到AP部分的相应寄存器,而只是表示已经写入到写缓冲(write buffering)区域。如果写操作进入到写缓冲但最终被丢弃,将会通过CTRL/STAT.WDATAERR标志位来表示。这一部分内容可以参考B4.2.7的SW-DP write buffering部分。
接下来是读寄存器的情况。在成功读寄存器时,波形图如图B4-2所示。
这里需要区分读DP寄存器和读AP寄存器的情况了。对于读DP寄存器,这种情况比较简单,只需要一次传输就能获取到acknowledge response和所需的状态;与之相对应的是,读AP寄存器需要考虑AP寄存器的延迟,需要两次传输才能获取到实际的数据。
读AP寄存器的流程如下:
第一次传输,需要在packet request指明传输的AP寄存器;在acknowledge response阶段,DP通过返回OK表明自己有能力处理此次请求;此时传输的数据内容无意义,应当丢弃。
第二次传输,需要在packet request指明读取DP寄存器中的RDBUFF寄存器;在acknowledge response阶段,DP通过返回OK表明自己已经从AP获取到新的数据并准备好回传给调试器;此时传输的数据即为所需的内容。如果数据没有准备好,DP会返回WAIT,需要重复第二次传输直到DP返回OK为止。
可以看到,读AP寄存器实际上需要分为先提交后接收的两个步骤。当然,如果需要多次读取AP寄存器,则提交和接收可以在同一次传输中交替完成(在ADIv6中被称为流水线化),即:
首先发起第1个AP寄存器的读取,并且丢弃此时返回的数据。
接下来发起第2个AP寄存器的读取,等待acknowledge response返回OK,此时返回的数据即为第1个AP寄存器的结果。
重复2的步骤,每次发起第n个寄存器的读取时,都会返回第(n-1)个寄存器的结果。
在所有AP寄存器均发起读取后,需要再额外发起一次传输,读取DP寄存器中的RDBUFF寄存器,以获取最后一次传输的结果。
到这里为止,我们就把成功传输时的处理方式介绍完了。但是,调试器的传输过程不可能做到尽善尽美,那么在遇到意外状况时,应该怎么处理呢?这里我们按照错误类型进行介绍:
WAIT:一般表示上一次读写AP寄存器的操作还未结束,这种情况下常规操作是继续发起相同的传输,直到响应OK为止;当然也可以通过ABORT寄存器终止传输,这个会在后续数据链路层中介绍。
FAULT:技术上说,返回FAULT表示的是CTRL/STAT寄存器有sticky flag被设为0b1,而这通常表示DP发现了一些错误,包括写寄存器时data packet phase的校验位出错等,感兴趣的读者可以参考数据链路层部分。
Protocol error:包含了一系列不符合传输协议的错误,例如packet request中的校验位出错、Stop和Park出错、不支持的turnaround周期数等。
大家可能会有些疑惑,为啥还没有看到错误发生时的波形图呢?这是因为,WAIT和FAULT时的波形与overrun detection有关。Overrun detection是DP的一个特性,针对调试器在高可靠性、高延迟、高吞吐量的场合使用,可以有效加快处理效率。当然,我们这里主要说明的还是打开和关闭overrun detection的不同之处。
注:针对overrun detection的特性,我这边举一个可能实用的例子。
对于FTDI这种只使用物理层的调试器,输出信号通常是bit-bang或类似的方案,这需要上位机发出一系列指令来模拟SWD协议。一个可能的使用方式是这样的(假设是写寄存器):
USB发送一个命令,写入8个bit,也就是packet request部分
USB发送一个命令,读取5个bit,包括了两个turnaround和acknowledge response部分
USB发送一个命令,写入33个bit,也就是data transfer phase部分
可以发现,在overrun detection关闭前,是否执行3依赖于2的结果,而相关的结果并不能在物理层中得到反馈,必须要回传到上位机,并由上位机来决定下一步的处理方式。这样的一大缺点是,2和3之间必然会有很大延迟(如果使用interrupt transfer,延迟通常是ms级的),这会严重制约SWD的实际读写效率。
对于这个问题,打开overrun detection就能得到有效缓解,原因是overrun detection打开后data transfer phase就是一直存在的,并不受到acknowledge response返回结果的影响,上位机完全可以在发起一系列传输后通过检查CTRL/STAT寄存器获取传输结果,也就是说上位机可以尽可能多的填充传输队列,从而提高传输效率。
当然,这么做也有一个显而易见的缺点:如果传输不稳定或成功率过低,则每次发起一系列传输后DP大概率会进入到需要从错误中恢复的状态,这也会导致效率下降。因此,overrun detection只适合在高可靠性、高延迟、高吞吐量的场合使用。
在关闭overrun detection时,如果DP返回WAIT或FAULT,则跳过接下来的data transfer phase,直接开始下次传输,如下图B4-3和B4-4所示。
但是,如果打开overrun detection,不论DP返回WAIT还是FAULT,接下来的data transfer phase都和成功传输一样进行,只是传输的数据无效,如图B4-6和B-7所示。
最后说说Protocol error。一般来说,protocol error的错误是比较严重的,通常意味着信号传输不稳定,导致调试器或DP读取到了错误的结果。这种情况下,DP的策略是比较保守的,一般是直接终止所有传输(除特定寄存器外),调试器需要尽快发起line reset以重置DP状态;因此,DP将会在检测到错误后释放对SWDIO的驱动,在调试器一端通常会读到0b111的响应,如下图B4-5所示。
对于protocol error,详细的处理方式可以参考ADIv6中的B4.2.5,这里不再赘述。
其它细节
从上面我们知道,SW-DP是有状态的,读写结果会和之前的传输有关,因此恢复到初始状态显得非常重要。
在ADIv6中,SWD有个名为line reset的特性,就是在必要的时候对SW-DP进行重置,以保证SW-DP能工作在确定的状态,这个特性会在以下场景使用:
SW-DP初始化:在首次连接到SW-DP时,需要在传输前先来一发line reset,以保证SW-DP不会受到上电时不定态或已传输数据的影响。
切换DP:无论是从JTAG-DP切换到SW-DP,还是从其它SW-DP切换过来,也都需要一次line reset。
错误恢复:前面提到的protocol error需要通过一次line reset才能恢复。
注意到line reset只重置SW-DP部分寄存器(DLCR和SELECT.DPBANKSEL),不影响SW-DP的其它寄存器、JTAG-DP、SWJ-DP和所有AP。
那么,line reset要怎么做到呢?其实很简单,就是传输以下序列即可:
首先,给至少50个周期的SWDIO高电平,此时SW-DP可以识别到line reset。
紧接着给至少2个周期的SWDIO低电平,让SW-DP做好准备。
最后正常传输即可。
Line reset的波形图如图B4-8所示。
可以看到,2的话并不影响结果,这实际上是SW-DP的一个特性:空闲周期(idle cycles)。回想起每次传输的Start信号为0b1,如果在此时传输的是0b0,则传输不会开始,SW-DP可以维持在空闲状态,等待SWDIO传输0b1后传输才正式开始。
注:CMSIS-DAP中,可以定义每次传输后附加的空闲周期数,这部分的内容并不需要修改DP和AP寄存器。合理使用空闲周期可以减少DP响应WAIT的次数,从而减少实际的传输时间。
JTAG-DP
对于JTAG,想必大家都不陌生(陌生的建议先看看IEEE Std 1149.1,逃),在其它器件上已经使用过。由于JTAG的引脚都是单向传输的(到了1149.7才有两线调试的方案,才会涉及到引脚传输方向切换),并且JTAG在协议层本质上就是一个状态机(见图B3-2),因此说明起来会比较容易。这里就尽量简明扼要地描述其中需要关注的部分。
传输格式
JTAG-DP和普通的JTAG状态机的用法基本一致,都是分为指令寄存器(Instruction Register,即IR)和数据寄存器(Data Register,即DR)。对于JTAG而言,一般是有一个IR和多个DR,其中:
IR用于选择和控制JTAG扫描链,位数是固定的
DR用于交换暴露在JTAG扫描链的信息,具体的信息一般由IR决定,位数是可变的
因此,一次传输需要先选择IR再向DR写数据同时读返回结果。具体的传输过程在B3.2.3中已有详细描述,这里不再赘述。
读写方式和错误处理
从ADIv6的表B3-3中,我们可以获知,标准IR指令如下:
ABORT
DPACC
APACC
IDCODE
BYPASS
其中,IDCODE和BYPASS是JTAG标准要求的部分,DPACC和APACC基本对应SW-DP中的DP和AP寄存器,而ABORT则是单独列出。后面主要提到DPACC和APACC部分。
注:
IEEE Std 1149.1要求IR全1时对应BYPASS指令。
IEEE Std 1149.1要求Test-Logic-Reset状态下IR对应的是IDCODE或BYPASS,JTAG-DP使用的是IDCODE。
DPACC和APACC的操作如B3.4.3的图所示。可以看到,这两个DR都是35位,数据结构如下:
对于TDO这一端,JTAG依次输出ACK[0:2]和ReadResult[0:31]
对于TDI这一端,JTAG依次输入RnW、A[2:3]和DATAIN[0:31]
相对于SW-DP,由于JTAG-DP的Capture-DR在Update-DR之前执行,而JTAG-DP的acknowledge response和ReadResult在Capture-DR阶段确定、packet request和DATAIN在Update-DR阶段生效,因此当前传输的数据包需要在下一次传输时给出。当然,和SW-DP中读AP寄存器类似,JTAG-DP也可以通过流水线执行的方式“重叠”这些命令,执行过程如下:
首先发起第1个寄存器的传输,并且丢弃此时返回的acknowledge response和ReadResult。
接下来发起第2个寄存器的传输,并且从acknowledge response的结果获知传输是否成功,如果第1个寄存器对应的传输是读取,则还需同时读取ReadResult并作为第1个寄存器的读取结果,否则直接丢弃。
重复2的步骤,每次发起第n个寄存器的传输时,都会返回第(n-1)个寄存器的acknowledge response,并根据需要判断是否需要读取ReadResult。
在所有寄存器均发起传输后,需要再额外发起一次传输,读取DPACC中的RDBUFF寄存器,以获取最后一次传输的acknowledge response。如果第n个寄存器对应的传输是读取,则还需同时读取ReadResult并作为第1个寄存器的读取结果,否则直接丢弃。
这里有一些需要注意的细节:
由于JTAG-DP对所有指令的写入和读取都是流水线化的,因此要确保此次传输被成功接受,就必须发起下一条指令来读取结果。可以通过读取DPACC寄存器的RDBUFF寄存器来获取acknowledge response,这是因为读取RDBUFF寄存器总是不会产生其它副作用。
当返回的结果为WAIT,或者有sticky flag为0b1时,此次传输将被丢弃,因此可以仅通过读取TDO输出的前3位(即ACK[0:2])来判断上一次传输是否成功进行,若非成功进行则可以直接结束传输。
JTAG-DP返回的ReadResult仅在上一个传输访问的是DPACC、APACC和BYPASS这三个DR时才有意义,因此不能在执行DPACC和APACC的传输过程中切换到其它DR。
注:注意第二点原理和SW-DP的overrun detection不同,JTAG可以在传输会被丢弃的情况读取任意位数(前提是不会影响扫描链上的其它设备,这可以通过将其它设备的IR均设为BYPASS来做到),这可以减少传输时间(在WAIT状态时尤其有用)。
另外,JTAG-DP在协议版本不同的时候acknowledge response的定义会有所不同,见表B3-6。在v0中,OK和FAULT合并为一个,即OK/FAULT,对应的ACK[2:0]为0b010,是否传输成功需要读取CTRL/STAT寄存器才能获知;而v1中,又将OK和FAULT分离。
其它细节
在Capture-IR阶段,JTAG扫描链会写入0b0001或0b00000001(取决于IR长度为4位或8位),并且最靠近TDO的是0b1,这个可以在后续更新IR时被移出,用来检查扫描链的连接是否正常。
注:根据IEEE Std 1149.1,JTAG扫描链需要保证在Capture-IR阶段写入到扫描链中最靠近TDO的2个bit为0b01,JTAG-DP显然符合此要求。
和SW-DP类似,JTAG-DP也有自己的idle cycles,不过这一定义借用了JTAG状态机中的Run-Test/Idle状态。除了和SW-DP类似用于减少实际执行时间的作用外,如果host需要在传输结束后停止TCK,需要保证在Update-DR后至少在Run-Test/Idle状态转8个周期,以保证传输结束(当然如果继续使用JTAG或者不需要最后一次传输结果时则不受此影响)。
注:一般来说,在最后一次传输发起后(无论读写),调试器会至少再发送一次读RDBUFF寄存器请求,确认传输是否成功进行,因此这个设定对正确进行的读写操作没有影响。
JTAG-DP和SW-DP的一大不同是没有line reset,这意味着JTAG-DP不能(其实也不需要)将传输恢复到初始状态。从ADIv6中,我们知道,JTAG-DP仅能在上电复位时重置DP寄存器,但是JTAG-DP不需要像SW-DP那样设置turnaround,因此初始化对于JTAG-DP来说只需要重新设置一遍寄存器就好,不会和当前的状态冲突。
SWJ-DP
在协议层,SWJ-DP主要做的事情就是处理SW-DP和JTAG-DP之间的切换。根据SWD协议版本不同,SWJ-DP最多有两套切换方式,分别是:
SWD和JTAG之间的切换
休眠状态和相关操作
接下来详细介绍这两种切换方式。
SWD和JTAG之间的切换
这种方式多用于SWD协议v1,v2也可以支持但被废弃(deprecated)。
先贴个状态转移图,见图B5-1。
从图上可以看出:
SWJ-DP的初始状态为JTAG-Sel TLR,也就是JTAG状态机中的Test-Logic-Reset状态
SWD和JTAG之间的切换需要在特定的状态完成,成功的话会切换到相应状态,失败的话则会保持在原有状态
当然,实际应用中,我们只需要使用JTAG-to-SWD和SWD-to-JTAG这两种序列就可以了。这部分是ARM规定好的,因此没啥好说的,照做就是了。
JTAG-to-SWD的引脚时序如图B5-2所示。
注:和正常的line reset不同,在JTAG-to-SWD序列中,最后不允许将SWDIOTMS拉低两个周期。
SWD-to-JTAG的引脚时序如图B5-3所示。
注:和正常的line reset不同,在SWD-to-JTAG序列中,在至少50个周期的SWDIOTMS高电平后,不存在将SWDIOTMS拉低两个周期。
休眠状态和相关操作
SWD协议v2引入了休眠状态(dormant state),所有的切换操作都需要途径dormant state状态进行,这有助于目标设备使用不同的混合协议。
原话如下:Using dormant state allows the target to be placed into a quiescent mode, allowing devices to inter-operate with other devices implementing other protocols. Those other protocols must also implement a quiescent state, with a mechanism for entering and leaving that state that is compatible, but not necessarily compliant, with the SWJ-DP and SW-DP protocols.
在引入休眠状态后,SWJ-DP的协议选择如图B5-4所示,注意到前面提到的JTAG-to-SWD和SWD-to-JTAG已被废弃。
对于SW-DP,相关状态转移图中JTAG相关状态被移除,且初始状态即为dormant state,如图B5-5所示。
JTAG-to-DS涉及到ZBS(zero-bit-DR-scan)序列,并没有统一的引脚时序,其中一个推荐的引脚时序如图B5-7。需要注意的是,这里需要事先将IR切换到IDCODE或BYPASS,否则行为无法预测。对ZBS有兴趣的同学可以参考B5.3.2。
SWD-to-DS则是固定序列,引脚时序如图B5-8所示。
从dormant state离开时,根据activation code确定下一个使用的协议,相关序列被称为Selection Alert sequence(这里不知道怎么翻译比较好),如图B5-9所示。Activation code的取值如表B5-2所示。
距离上次更新已经过了14个月了,还好最近又想起来了,闲话不多说,让我们继续之前的内容吧。
说到ARM的调试协议,必然绕不开ADI(ARM Debug Interface)相关的规范了。目前,ARM在新型号CPU上主要使用ADIv5和ADIv6这两个规范,两者之间互不兼容(主要是AP接口的区别),因此调试器这边需要分别支持。
根据规范,ADIv6(当然v5也是)由低到高可以分为以下层次:
物理层(Physical Layer):定义各种物理信号,包括SWD和JTAG
协议层(Protocal Layer):定义数据包的格式,维护SWD和JTAG的状态机
数据链路层(Data Link Layer):定义如何访问DP寄存器和AP寄存器,实现基本的调试功能
AP层(AP Layer):定义如何访问SoC上的一个或多个子系统
从这个角度来说,市面上已有的调试器方案可以按照使用的最高层次分为以下几类:
只使用物理层:FTDI家的所有方案,与操作GPIO无异,突出一个“又不是不能用”,效率通常最低,当然优势是比较灵活,只要改改上位机代码理论上就能支持所有协议。当然,除了大家熟知的bit-bang之外,也有像MPSSE这种效率比较高的方案,不过依然需要上位机赋予其“灵魂”。
支持协议层:ARM自家的CMSIS-DAP,为协议层访问做了一些优化,DAP_Transfer和DAP_TransferBlock命令了解一下?不过更高级的操作(例如访问SoC上的内存地址)单靠调试器本身做不来,需要上位机下发一系列命令来完成,没有上位机的配合基本只能歇逼。
支持所有层次:J-Link等商用调试器,为了功能和效率,通常在调试器上集成了各种高级操作,访问个内存地址之类的不在话下,当然调试器的实现上会比较复杂。开源的方案可以参考Black Magic Probe。
接下来我们按照由低到高的顺序介绍每个层次的相关规范。
物理层关心的部分如下:
信号名称及必要性
部分信号是可选的,例如JTAG中的TRST
信号的敏感沿
当前的调试接口都是带同步时钟的,需要定义信号在何时被采样
信号的传输方向
主要包括调试器到DP、DP到调试器和双向这三种情况,其中双向传输需要在协议层约定好传输方向的切换
信号的其它物理层需求
举个栗子,SWD要求给SWDIO一个上拉电阻,以保持该信号在未被驱动时的状态确定
SW-DP
SW-DP共有两个必需的引脚,分别是SWCLK和SWDIO,分别表示时钟和数据。
SWCLK没啥好说的,唯一需要说明的是该信号可以是门控时钟,这意味着SWCLK可以在不需要传输数据的时候停止翻转,这样可以节省功耗,并简化调试器的逻辑。
SWDIO是双向信号,这意味着信号的传输方向需要调试协议规定好,通过状态机的方式实现信号方向切换。对于调试器而言,通常需要额外输出SWDIO_TXEN信号(CMSIS-DAP中定义,表示当前调试器是否驱动SWDIO信号),以控制电平转换电路的工作方向,但这一信号并不包括在SW-DP内。
在SW-DP中,规定了DP在SWCLK信号上升沿的时候采样或切换数据(取决于当前状态是输入或输出)。对于调试器而言,这意味着需要在SWCLK信号下降沿的时候采样或切换数据(假设占空比是50%)。
另外,SW-DP要求对SWDIO信号上拉100 kΩ的电阻,以保持该信号在未被驱动时的状态确定。
JTAG-DP
JTAG-DP使用的引脚和普通的JTAG区别不大,最大的区别是修改了引脚名称。可以参考IEEE Std 1149.1获取更多信息。
JTAG信号和SPI Mode 0或3类似,都是在时钟信号下降沿改变数据、上升沿采样数据。
ADIv6并未对JTAG引脚的外部连接进行更多说明,不过根据IEEE Std 1149.1,还是建议在设备端对TMS和TDI等引脚上拉,以满足稳定性的需求。
SWJ-DP
前面已经介绍了SW-DP和JTAG-DP,那么这个SWJ-DP又是何物呢?看名字就知道,这个其实就是Serial Wire + J(TAG)的组合。
实际上,SWJ-DP并不负责具体的DP功能,其主要作用就是将SW-DP和JTAG-DP使用的引脚整合在一起,并且允许使用特定的序列切换实际使用的DP,这样便可使用相同的引脚和连接器定义(例如Coresight 10),节省引脚数量。
SWJ-DP定义的引脚名称如下表所示。需要说明的是,SWJ-DP可以只拉出SW-DP相关的引脚,并可在使用SW-DP时重新定义TDI、TDO和TRSTn这几个引脚的功能,当然在这种情况下就只能使用SWD进行调试了。
这个好,同理 高速DAP 也是可以的了
可以的,顺着Xilinx / XilinxVirtualCable的代码思路,用Python写了个XVC server to CMSIS-DAP,可以正常工作,不过目前性能有些感人,还需要再优化下才能见人。
免责声明:本文所记载的内容仅涉及技术科普和探讨,与具体代码和实现方式无关,作者不会对破解等敏感问题进行回应,请悉知。
IP核是在电路设计中不可或缺的组成部分。通常,IP核可以从FPGA厂商或专门的提供商获得。为了保护知识产权,这部分代码需要进行保护。
正如所猜测的那样,IP核的加解密工作是有专门的标准进行规范的,目的应该是保证各个EDA工具可以正确处理相关代码。这个标准也就是IEEE Std 1735,可以从这里获得标准的PDF文档。
总的来说,IP核的加解密算法和大家常用的算法其实区别不大,都是综合利用了非对称加密的安全性与对称加密的效率,是比较成熟的方案,可以很方面地使用市面上已有的加密库(比如OpenSSL cyrpto library,即libcrpyto)进行加解密。具体来说:
非对称加密可以通过对公钥和私钥的区分来实现很高的安全性。EDA工具厂商可以将公钥分发给需要加密的第三方厂商,而将私钥通过各种手段隐藏在自身代码中(或者通过远程服务器进行解密),从而降低了密钥流出的风险。
对称加密则用于对代码主体的加密。考虑到EDA工具对IP核的处理速度,这里可以使用更加高效的对称加密,典型的算法是AES。考虑到对称加密的加解密密钥是一致的,可以事先使用非对称加密对密钥进行处理,进一步提升安全性。
当然,以上提到的算法只是IP核加解密的一个重要环节,标准只能解决文本级别的加解密,目的主要是避免相关代码被直接暴露。这里还可以使用其它技术手段(如代码混淆)进行更深层次的加密,这对于隐藏具体实现方式和避免逆向设计来说更为有效。
以下文章主要探讨V1(也就是等级1)的加解密,V2可以参考上面给出的标准文档。
在加密IP核的时候,我们需要准备好以下内容(session key的定义可以见后文):
首先最重要的当然是RTL代码,目前在标准层面支持Verilog HDL和VHDL。
非对称加密(用于加密session key)和对称加密(用于加密代码块)的算法需要给出,目前常用的分别是RSA(推荐大于等于2048位)和AES(128位或256位,CBC)。
非对称加密使用的公钥,这部分通常由EDA工具或者代码加密工具提供。在加密时,可以同时使用多个公钥,工具将会为每个公钥生成对应的session key,这些公钥可以使用keyowner等字段区分。
其实还有个不大明显的部分,就是如何从session key中得到对称加密使用的密钥。为了增强安全性,我们可以通过加盐等方式让session key和AES的密钥有一个特定的生成关系。我们定义相关的处理函数为f: x ↦ y,其中函数f并不要求可逆。
其中,session key是一个重要的部分,它可以保证对每个IP核都生成一个专门的密钥,这可以获得以下好处:
每个IP核使用的session key都可以不同,从而提升了加密的有效性。
不同的RSA公钥可以用来加密同一个session key并获得不同的结果,可以避免对同一个代码块进行重复多次加密,节省了文件的体积。
加密流程如下:
确定需要加密的文本部分,并将其填充到AES要求的整数倍字节(例如128位)。这里使用的填充算法应该是PKCS#7 padding,也是OpenSSL等工具默认的填充算法。
生成session key,并使用函数f将其处理为AES使用的密钥。为了保证AES密钥的强度,session key最好使用较长的位数,并且可以使用加盐等方式进一步提升强度。
代码加密工具读取RSA公钥,对session key进行加密,使用Base64编码以将二进制映射到ASCII字符,并将其写入到key_block中。
代码加密工具读取AES密钥,对代码主体进行加密,使用Base64编码以将二进制映射到ASCII字符,并将其写入到data_block中。
可以发现,上述流程中生成的key_block和data_block是最重要的部分,分别记录了加密后的session key和代码块,而这些部分将会成为解密的关键。
在解密IP核的时候,我们需要准备好以下内容:
key_block,记录了加密后的session key。
data_block,记录了加密后的代码主体。
RSA的私钥,这个是整个环节中隐蔽性最高的部分,一般EDA工具都会将其写入到代码的特定位置,使用时经过特殊处理获取,防止被直接泄露。
在加密流程中定义的处理函数f。
解密流程如下:
分别读取key_block和data_block,并且使用Base64解码获取实际的二进制字符。
使用RSA私钥解密key_block,获得session key。
使用函数f将session key处理为AES使用的密钥。
使用AES解密data_block,并根据padding的数值去除padding部分,得到实际的代码块内容。
通过以上流程,我们就能从加密的IP核获得解密后的代码。在这其中,最重要的就是RSA和私钥和处理函数f,这两个部分需要在EDA工具中被重点保护。
可以看到,IP核的加解密过程其实并不神秘,其安全性主要依赖于对RSA私钥和处理函数f的保护程度。当然,V2规范还提到了多种加密方式,这些方式也离不开非对称加密和对称加密的组合,有兴趣的读者可以自行研究。
metro 说:之前在外网上看到SMT2的示意图,不过没试过,不知道是否可用。
https://whycan.cn/files/members/1510/JTAG-SMT2.png大佬,您这个图是从哪里看到的?可否给个连接?
多谢!
https://electronix.ru/forum/index.php?app=forums&module=forums&controller=topic&id=114633&page=5
看到楼主的另一个回复了。有篇论文给出了几个IC的型号和一个稍作修改后的原理图,但没有直接给出SMT3-NC的原理图,可以参考一下。An Example of PCB Reverse Engineering – Reconstruction of Digilent JTAG SMT3 Sche.pdf
省流小助手:
GitHub - metro94/RV-DAP-Plus
######## 我是假装成注释的分割线 ########
简单来说就是在Sipeed RV-Debugger Plus(MCU是博流智能的BL702)上移植好了CMSIS-DAP。有一说一,USB协议栈写得不错,虽然不支持Microsoft OS 2.0 Descriptors有点小遗憾,不过自己魔改了SDK还是支持上了。
目前这个版本是基于CMSIS-DAP V2.1(也就是Bulk Transfer的版本)的代码移植的,支持SWD/JTAG的调试,但不支持虚拟串口和SWO。有兴趣的坑友可以自行移植串口部分。
固件和使用方法已经在GitHub的Releases里面有了,这里不再赘述,祝大家玩得开心。😋
Sipeed官方有下载链接的,地址是http://dl.sipeed.com/shareURL/MaixII/MaixII-A/HDK/2_Schematic
这个帖子是我开的,当时二楼的回复是兼容ARM的SWD,后面他们自己删了😅
接下来是尝试连接BL702的JTAG。虽然BL702的JTAG可以映射到任意引脚(经典IOMUX),但在上电时默认有4个引脚是使能为JTAG功能,因此调试时最好还是使用这几个引脚比较合适。
经过一番研究(还跑去官方论坛确认了一下😅),连接方式如下:
GPIO0 -> TMS
GPIO1 -> TDI
GPIO2 -> TCK
GPIO9 -> TDO
需要注意的是,RV-Debugger Plus的默认固件将这几个引脚复用了,因此需要在上电时按住Boot按键进入ISP模式才行(此时设备管理器可以发现一个虚拟串口设备);另外,由于RV-Debugger Plus将GPIO9用作LED,因此需要断开链接并且手动飞线。
首先用J-Link连接。J-Link更新到最新版本后是支持SiFive E24的,因此使用起来很方便,直接可以识别。
接下来使用OpenOCD搭配FT232H连接。最新的OpenOCD v0.11.0支持RISC-V,因此只需要为SiFive E24添加配置文件即可,当然你也可以使用SDK里面的配置。这里我贴一份从sifive-e31arty.cfg改过来的配置,使用方法见注释:
#
# Be sure you include the speed and interface before this file
# Example:
# -c "adapter speed 5000" -f "interface/ftdi/olimex-arm-usb-tiny-h.cfg" -f "board/sifive-e31arty.cfg"
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x20000E05
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME
$_TARGETNAME.0 configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1
# flash bank spi0 fespi 0x40000000 0 0 0 $_TARGETNAME.0 0x20004000
init
# if {[ info exists pulse_srst]} {
# ftdi_set_signal nSRST 0
# ftdi_set_signal nSRST z
# }
halt
# flash protect 0 64 last off
echo "Ready for Remote Connections"
经过一番操作,OpenOCD也能正确连接上BL702了:
最后提一下,在SDK里面是有SVD文件的,虽然只有BL602,估计还需要等待进一步更新。
整理一下目前找到的资料:
Sipeed提供的资料(包括原理图、BSP和已经开发好的固件):sipeed/RV-Debugger-BL702
Bouffalo的GitLab:Bouffalo Lab
Bouffalo的Gitee:博流智能科技(南京)有限公司
官方文档:BL MCU SDK 开发指南
开发者社区(包括文档、论坛等):博流智能开发者社区
SiFive E24的相关文档:
最近Sipeed上线了一款调试器RV-Debugger Plus,这个调试器是模拟FT2232D,本身没啥好玩的;不过呢,这个调试器使用的MCU比较有意思,是博流智能的BL702,支持二次开发并且提供SDK,值得玩玩。
先列一下我觉得比较有趣的配置:
RISC-V(准确说是SiFive E24,四舍五入算是年轻人的第一个SiFive),指令集为RV32IMAFC,支持FPU(单精度),可以跑到144MHz
132KB RAM+192KB ROM,并且手头这个版本SiP了512KB的Flash,在QFN32的封装下算是不错了
2.4G,支持BLE 5.0和Zigbee 3.0,集成balun和PA/LNA
USB 2.0 FullSpeed Device
集成了DC-DC和LDO,只需要5V和3.3V两路供电即可
另外还有SiP PSRAM,EMAC和DVP的选项,但是和BL702无缘
拍一张图康康:
奇怪,我按楼主说的搜了一下ESP8266 D1 mini和ESP-01下载器,发现都是正确的脚位(5/6)。
http://www.weiguo.com.tw/d1-mini-esp8266mod.html
https://item.taobao.com/item.htm?id=623312189879
@Blueskull
Quartus默认不支持FTDI的芯片,不过我在网上看到一个号称可以通过替换dll支持的方法,链接如下:https://mil.ufl.edu/3701/docs/quartus/quartus18.1_installation.pdf。
手头没有Altera的FPGA板子(严格来说有但是板载了下载器233)所以没法测试,可以试一下好不好用。
ft232如何作为intel fpga的下载器比如ep4c系列
Quartus默认不支持FTDI的芯片,不过我在网上看到一个号称可以通过替换dll支持的方法,链接如下:https://mil.ufl.edu/3701/docs/quartus/quartus18.1_installation.pdf。
手头没有Altera的FPGA板子(严格来说有但是板载了下载器233)所以没法测试,可以试一下好不好用。
最近买了块Xilinx Kria KV260,发现底板板载的调试器是FT4232H,感觉有点意思,因此花了点时间把EEPROM的内容(俗称固件)dump出来并做了少许修改。在用于Xilinx调试器时,FT4232H的ChannelA是JTAG,ChannelB和ChannelC可用于串口,ChannelD可以忽略,因此是1xJTAG+2xUART,看起来性价比相对FT2232H做成的调试器(Digilent JTAG-SMT3-NC)更高。
亲测可用于Xilinx Vivado烧录任意器件,不过手头只有一个Memblaze(XC7K325T)可供测试,欢迎大家自行测试后反馈结果。如果需要原理图,可以参考KV260底板的原理图,Xilinx官网搜索XTP682就有。
晒张图证明可用:
最后贴一下工程和编译好的二进制文件:
FT4232H EEPROM Modify.zip
ARM调试接口(即ADI,ARM Debug Interface),定义了基于ARM的SoC中所有调试组件的功能;而我们常说的DAP(即Debug Access Port),可以认为是基于ADI标准的一个实现。
对于DAP,我们又可以进一步分为两个部分:
Debug Port:面向调试器的接口,主要用于接收来自调试器的请求,并将Access Port的数据和响应传回到调试器。
Access Port:面向SoC内各个组件的接口,主要用于将Debug Port传来的请求转换成对SoC内对应组件的传输操作,并且将结果返回到Debug Port。
DAP的基本架构如下图所示。
可以发现,对于一个部署了ADI的系统来说,通常会存在一个DP和若干个AP,并且DP通过选择AP的方式一次只和一个AP进行交互。一方面,DP与(外置的)调试器相连,并根据调试器的协议进行数据交换;另一方面,AP和系统中的某个组件(一般是总线之类的组件)相连,可以将DP传来的请求进一步翻译成系统可以处理的操作(一般是寄存器或内存的读写操作)。
下图展示了一种包含了DPv0 JTAG-DP和通用AP的示意图,可以一窥DAP的架构。
常见的DP包括:
JTAG-DP:使用了JTAG接口,一种四线(另有可选的TRST,加上之后有五线)的全双工同步协议。
SW-DP:使用了SWD接口,一种两线的半双工同步协议。
SWJ-DP:同时兼容JTAG-DP和SW-DP接口,并且包含了JTAG/SWD双协议切换的一种特殊协议。
常见的AP包括:
MEM-AP:包括一大类调试组件的AP,特点是通过内存映射(Memory-mapped)的方式进行访问,覆盖了SoC的各种功能。
JTAG-AP:一个面向传统JTAG接口的AP,可以外接系统中已经存在的包含JTAG接口的非ARM组件。
MEM-AP的一个实现如下图所示。可以看到,MEM-AP可以通过ROM Table获取其它调试组件的定义情况,并且通过访问特定的调试组件以实现特定的调试功能。
对调试架构感兴趣的同学,可以阅读ADIv5标准的Chapter A1部分;对各个AP和CoreSight各个调试组件感兴趣的同学,可以阅读ADIv5标准的Part C以及CoreSight的相关文档。在下文中,我们主要介绍DP,因为这与我们的调试器设计密切相关。
本作品采用知识共享署名 4.0 国际许可协议进行许可。
如标题所述,TinyDAP是一个在兼容8051的单片机上实现的CMSIS-DAP调试器,不仅可以用于调试和烧录ARM Cortex-M系列单片机,配合第三方工具还能支持RISC-V等其它单片机和SoC。可以说,CMSIS-DAP是最适合实现的开源调试器协议(个人认为没有之一),而TinyDAP的目标就是做CMSIS-DAP的最小实现方案(同样没有之一!)。
TinyDAP在软件和硬件两个层面都将做到极致:
在软件上,通过汇编级别的优化,将CMSIS-DAP协议的几乎所有功能一一实现在小于16KB的Flash中,可以兼容JTAG和SWD的调试线协议,还能通过支持CMSIS-DAP v2达到不输于闭源调试器(如ST-Link/V2)的速度
在硬件上,可以运行在CH551/552等超低价格(1.x RMB)的单片机上,芯片占用的面积可以低至4.9x3.0 mm²(CH552E),预计成品体积不大于STLINK-V3MINI,还能直接在开发板上板载。
当然,截止至今日,TinyDAP还在初步开发阶段,不过前期的验证工作已经结束(参见帖子:晒一下CH558跑的CMSIS-DAP v2调试器,下载速度可以达到70KB/s(Flash)和300KB/s(SRAM)),可以证明这个目标是能够实现的(无非就是多掉几根头发而已)。
除此之外,TinyDAP还将依赖于以下工作:
利用SPI等外设加快调试速度。由于SWD和JTAG都是属于类SPI协议(串行输入/输出,并且有独立时钟引脚),因此可以通过SPI等外设加快数据传输,从而加快总体的调试速度。
对CMSIS-DAP的算法进行深度优化。通过阅读代码发现,当前的CMSIS-DAP代码控制调试线时的算法在有些情况下不够高效,可能会导致性能降低。在CMSIS-DAP的代码之外,作者还会参考ARM的其它文档(见下文),重新整理CMSIS-DAP的算法,进一步提升调试的性能。
参考DAPLink的代码,实现更多定制的功能。DAPLink也是基于CMSIS-DAP协议的实现,在完全开源的基础上做出了虚拟串口、拖拽烧录等实用功能。在存储空间和外设允许的前提下,作者也会在项目后期尝试将这些功能加入到TinyDAP中。
由于作者已经工作(四舍五入996的那种),所以这个项目必然不会很快完成(否则也不会从去年鸽到现在),还请大家耐心等待。当然,由于TinyDAP是一个比较随性的项目,所以也不会给自己设定各种时间上的目标,但会借此机会尝试用文字等方式将自己的想法表述出来供大家参考,因此本帖会尽可能地持续更新(可以想起来的时候常来看看hhh)。
本文的目标是记录开发的过程,分享一些开发过程中遇到的问题和解决方法,也欢迎和大家一同探讨各种相关话题。
本文将会按照以下顺序编写文档:
ARM调试接口
CMSIS-DAP协议
USB设备的协议栈(包括HID和WinUSB等)
WCH CH54x/CH55x
TinyDAP的实现细节
参考代码以ARM官方维护的代码为主,包括:
CMSIS-DAP:ARM基于CMSIS-DAP协议的一个简单实现,集成在CMSIS Version 5中。这一版本的CMSIS-DAP使用RTX v5 RTOS和Keil提供的USB设备协议栈与驱动,其中后者似乎并不开源。
DAPLink:ARM基于CMSIS-DAP协议的另一个实现,加入了虚拟串口、拖拽烧录等实用功能,由ARM Mbed维护。这一版本的CMSIS-DAP使用RTX v4 RTOS和开源的USB设备协议栈与驱动,代码完全开源并且可以方便地添加新设备的支持。DAPLink的主要缺点是编译后二进制文件太大(完整配置超出STM32F103C8T6的Flash空间,只能上CBT6)。
DesignStart:ARM的一个项目,提供了Cortex-M0和M3的RTL代码(经过混淆处理),在实现调试接口时作为参考。
参考文档则是覆盖了上面所说的所有内容,包括:
CMSIS-DAP:ARM官方的CMSIS-DAP文档。CMSIS-DAP包括协议和实现两部分,这个文档主要覆盖了协议部分,重要性无需多言。
ARM Debug Interface Architecture Specification:ARM调试接口的文档,版本是ADIv5.2。需要注意的是,该文档的最新版本是ADIv6.0,但当前主流的ARM单片机似乎并不使用v6.0,所以v5.2已经够用(v5.x加入了很多实用功能)。
USB相关的各个标准,包括(除了最后一项外都可以在USB官网找到):
USB Specification 2.0:USB 2.0的主标准。
与BOS(Binary Device Object Store)描述符相关的补充标准:于Wireless USB Specification 1.0引入,在USB 3.0时被写入主标准中,主要用于实现WinUSB、WebUSB等功能。
USB Device Class Definition for Human Interface Devices (HID):HID类的标准,主要用于实现CMSIS-DAP v1的免驱动安装。
Microsoft OS 2.0 Descriptors Specification:MS OS描述符标准,主要用于实现CMSIS-DAP v2的免驱动安装(通过WinUSB),这里我们使用的是2.0的标准,可以兼容Windows 8.1和10。
8051的指令集和单片机的数据手册及相关文档。
2021/3/7:开坑,更新简介部分.
JTAG线也不是很多,自己杜邦线连一下也不麻烦。
我这边ARM的器件比较全,M0 M3 M4 M23都有,GD32的ARM芯片几乎所有型号都有。
RISC-V芯片比如VF103价格没有优势的前提下,实在找不到考虑它的理由。
调试方面,我基本只用USB和UART,其实SWD都很少用。metro 说:echo 说:所有IO都引出了呀。
话说JTAG现在还有人用么?我这里无论板子还是调试器,JTAG都拿掉了,只留SWD,管脚少就是好。不是的,GD32VF103(注意不是GD32F103)是RISC-V的CPU,不支持SWD,只支持四线的JTAG协议,所以要调试的话需要从两个地方都接出来,不是很方便。
至于CH32V103,沁恒实现了和SWD相似的RVSWD(只是引脚相同,协议应该不兼容),所以倒是不像GD32VF103那样有这个问题。不过看起来CH32V103在协议没公开之前只能用他家的调试器了。
话说GD32VF103的接口定义可以参考Sipeed Longan RV,两面共引出2x4p的2.54mm接口,不过接口稳定性堪忧,我的那块板子就被我把焊盘弄掉了
是的,现在新出的单片机很多都不支持JTAG,没记错的话GD32F系列也是全员不支持JTAG。RISC-V可能是个问题,因为没有占优势的两线协议,很多厂商还在用着四线JTAG。
至于价格,我记得CH32V103应该还不错(某宝上小于5块?),只不过去年才发布,用的人还不多。
所有IO都引出了呀。
话说JTAG现在还有人用么?我这里无论板子还是调试器,JTAG都拿掉了,只留SWD,管脚少就是好。metro 说:感觉应该也兼容CH32V103?另外GD32VF103的调试口是JTAG,好像不是很方便,可以考虑引出JTAG的所有接口。
不是的,GD32VF103(注意不是GD32F103)是RISC-V的CPU,不支持SWD,只支持四线的JTAG协议,所以要调试的话需要从两个地方都接出来,不是很方便。
至于CH32V103,沁恒实现了和SWD相似的RVSWD(只是引脚相同,协议应该不兼容),所以倒是不像GD32VF103那样有这个问题。不过看起来CH32V103在协议没公开之前只能用他家的调试器了。
话说GD32VF103的接口定义可以参考Sipeed Longan RV,两面共引出2x4p的2.54mm接口,不过接口稳定性堪忧,我的那块板子就被我把焊盘弄掉了
话不多说,先看效果:
两个屏幕都是ST7789,8位8080接口的屏幕,分辨率分别是240x240和240*280,刷新率拉到99Hz依然非常流畅(更高的刷新率似乎无法正常初始化,有些遗憾)。
为了同时驱动两个屏幕,这里让每个LCD都有一个对应的CPU进行刷新,并且由同一个PIO的不同状态机来驱动总线,两路总线可以同时传递不同的数据。状态机的频率被设置为5分频,因此总线上的WR信号的频率可达12.5MHz,可以满足99Hz刷新率的需求。
可以看到,树莓派Pico的PIO还是非常强大的,这里相当于只使用了状态机总数的四分之一(主要是引脚数量限制了发挥233),还是具有一定可玩性的。
Swd有专利吗 专利号是多少
搜了一下,能看到的最早专利似乎是这个:Communication Interface for Diagnostic Circuits of An Integrated Circuit,2023年8月到期,不过不清楚是否还存在后面改进的版本。
在中国的专利号是CN100487472C。
Pico镇楼
相信这个小玩意大家都知道了,双核Cortex-M0+,时钟频率133MHz(实际上保底可以超频到250MHz+),自带PIO、SIO等一系列有趣的外设,并且拥有2MB SPI Flash(自带16KB的cache)+264KB SRAM的内存.
那么问题来了,已知:
之前我在Nios II上移植过NES的模拟器,可以跑到20 fps左右(不包含索引色处理的部分,声音暂时也没做),而且当时没有用上汇编优化,(不排除超频的前提下)RP2040的性能应该足以模拟NES了。
RP2040有两个核心,可以像NES一样,一个模拟2A03(CPU+APU),另一个模拟PPU,两者可以同时工作。
RP2040的片上内存达到了264KB,不考虑显示部分的话应该够用了,而且显示部分可以由一个核心直接渲染,不需要把一整帧渲染后的RGB图像存储下来,大大节约了空间。
RP2040支持XIP,可以把Flash直接映射到内存空间,最大支持16MB,足以放下所有的NES游戏并且直接寻址,有cache的话速度应该还不错。
RP2040的PIO可以用来处理输出并且自带FIFO,根据别人的demo来看直接输出VGA应该没啥问题。
RP2040的SIO中的Interpolator应该很适合用来处理音频数据。
感觉经过以上分析,似乎在Pico(RP2040)上跑个NES模拟器是可行的?欢迎大家讨论。
George Hilliard(就是那个做 F1c100s 电子名片的)在博客上发表了 Mastering Embedded Linux 一系列的文章。我看着写的很不错,就把它翻译了过来,也作为一个英语翻译的练习吧。翻译的不好还请见谅。
- 精通嵌入式 Linux 第一章:概述
- 精通嵌入式 Linux 第二章:硬件
- 精通嵌入式 Linux 第三章:Buildroot
- 精通嵌入式 Linux 第四章:添加功能新的第五章还没翻译,等我有空再继续翻吧。
一直在等他关于F1C200s解码的文章,哈哈。
我的是287,也在吃灰,i.mx28x系列有主线linux吗
主线也就是mainline,是由Linus Torvalds本人维护的Linux内核,该内核将会作为其它发行者使用的基础。见https://www.kernel.org/doc/html/latest/process/1.Intro.html#the-importance-of-getting-code-into-the-mainline。
按我的理解,楼主的需求不是使用单一的Linux内核在不同CPU上执行不同的程序(即使CPU占用的资源是互相独立的,但依然是同一个内核调配的),而是在不同CPU上使用不同的Linux内核或者裸机和RTOS(此时CPU之间不受同一个内核控制)。
楼主的这个需求可以使用AMP(Asymmetric Multi Processing)实现。Linux基本上都是实现为SMP(Symmetric Multi Processing),如果要用AMP需要使用另外的框架,比如说https://github.com/OpenAMP/open-amp。
在FPGA等地方经常使用到异构计算,所以在这些平台上AMP还是常见的,可以参考Xilinx的相关文档。
不能打电话,只能上网,能不能收短信没测试,估计不行,无需实名认证,上电即开始激活,有效期貌似一年。
应该可以收发短信,官方提供了demo,见Luat_4G_RDA_8910/script_LuaTask/demo/sms/。
看到了
V3S 比超频到700M的F1C200S快了3倍X86锐龙2700(虚拟机里)跑 比V3S 快了70倍
看来架构很重要啊 频率次之
有没有STM32H7系列的测试数据啊?
参考List of ARM microarchitectures(需fq
F1C100s用的是ARM926EJ-S,大致是1.1 DMIPS/MHz,V3(Cortex-A7)是1.9 DMIPS/MHz,而Cortex-M系列最高端的Cortex-M7(对应STM32F7/H7系列)则达到了2.14 DMIPS/MHz。
如果 8N04 的 EH 不足以提供足够的驱动能力,那可以直接从天线上取电,一般读写器辐射的能力都有富余。
另外,还可以考虑用双界面安全芯片,比如 thd89, sle78 带有 spi master,可以驱动 e-paper。
微雪做了个完全靠NFC驱动的墨水屏,可以参考解决方案:
http://www.waveshare.net/shop/7.5inch-NFC-e-Paper-Eval-Kit.htm
Traceback (most recent call last):
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 223, in get_interface_and_endpoint
return self._ep_info[endpoint_address]
KeyError: 2During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\Administrator\Desktop\ch55x_dumper-master\ch55x_dumper-master\dump.py", line 9, in <module>
dev.write(0x02, bytearray([0xA7, 0, 0, 0x1F, 0]))
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 940, in write
intf, ep = self._ctx.setup_request(self, endpoint)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 102, in wrapper
return f(self, *args, **kwargs)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 215, in setup_request
intf, ep = self.get_interface_and_endpoint(device, endpoint_address)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 102, in wrapper
return f(self, *args, **kwargs)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 225, in get_interface_and_endpoint
for intf in self.get_active_configuration(device):
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 102, in wrapper
return f(self, *args, **kwargs)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 236, in get_active_configuration
self.managed_open()
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 102, in wrapper
return f(self, *args, **kwargs)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\core.py", line 120, in managed_open
self.handle = self.backend.open_device(self.dev)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\backend\libusb1.py", line 786, in open_device
return _DeviceHandle(dev)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\backend\libusb1.py", line 643, in __init__
_check(_lib.libusb_open(self.devid, byref(self.handle)))
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\lib\site-packages\usb\backend\libusb1.py", line 593, in _check
raise NotImplementedError(_strerror(ret))
NotImplementedError: Operation not supported or unimplemented on this platform
可能是驱动问题。试试用最新版的Zadig安装WinUSB的驱动。
的确有可能,可是这边显示的是44.1kHz诶?
难道是驱动的问题吗?
http://ys-k.ys168.com/613493438/w732J3L2587N4lPnfgk/3.png
不好说,可以先播放其它采样率(比如32/48 kHz)的音频试试,看是不是还有相同问题。
歪个楼,现在通过CDC类实现串口的话要控制RTS/CTS信号似乎会比较困难,所以用作这类下载器的USB转串口芯片还是适合专用驱动的芯片。
见:http://www.wch.cn/bbs/thread-69122-1.html#2
我当时(2008年)参加的是辽宁赛区,C语言组,我们训练用的是Windows+Dev-C,实际比赛用的环境是Linux+gcc -std=gnu89。
我那一届没有大数运算,我记得是一个动态规划,一个排序,还有一个类似排序+查找,最后一道题我没有做到就交卷了。
08年还没开始学编程,层主可以说是前辈了,哈哈。
其实初中的时候只是比较简单地学习了编程知识,当时的学校在这方面也并不强势,虽然过了初赛,但是复赛的题目没怎么联系过,所以名次比较一般。
高中的时候,刚好赶上11年赛制改革,由一天4题变成两天3题,也是几家欢喜几家愁。福建这边的竞争算是比较激烈的,我查了一下一等奖分数线是385。
大学之后就没有继续参加相关比赛的兴趣了。不过偶尔还是会参加学校为非计算机专业举办的编程大赛,除此之外再无交集。
92年的,听过Delphi,做奥赛的时候用过Lazarus。我们小学教过QBASIC,之后自学的VB。
上初中开始学C,基本上就是核心功能用C写,UI用VB写。
后来上大学了开始接触wxWidgets,之后就是C/C++混合了。
再之后接触测试仪器自动化,因为库都是.NET的,又搞起了C#。
但是现在用的最多的肯定还是C,偶尔需要跑个脚本或者玩玩大数据也会用Python。
准确说Delphi用的是Pascal语言。我也是因为OI开始学编程的,当时大家都用Pascal入门,后来才转到C和C++。之后对编程语言的学习就是纯粹的兴趣了,不过学艺不精,用的最趁手的还是那几个常用的语言,可以再算上Verilog等HDL。
注意:这是一个专用于VexRiscv的OpenOCD,支持使用了VexRiscv作为CPU的SoC(例如Murax、SaxonSoC等)。其并不支持其它RISC-V的CPU(反之亦然,主要是因为VexRiscv使用了非标准的调试协议)。
OpenOCD的Git库地址:SpinalHDL/openocd_riscv: Spen's Official OpenOCD Mirror
VexRiscv的Git库地址:SpinalHDL/VexRiscv: A FPGA friendly 32 bit RISC-V CPU implementation
说明:
编译时打开了CMSIS-DAP(仅v1)、J-Link和FTDI的支持,但受条件限制仅测试过CMSIS-DAP。
目前测试发现JTAG TCP不可使用(对应VexRiscv的仿真时同步调试功能),提示Error: TRST/SRST error,原因未知。
使用mingw32编译,理论上可用于32位、64位系统。
下载地址:openocd-vexriscv.zip
https://whycan.cn/files/members/3946/QQ图片20200428202619.jpg
和供应商反复沟通, 终于定位到问题了,
把 set_gamma( ) 这个函数屏蔽起来就可以了.
可是问题来了, 这个 gamma 参数用来干嘛的呢?
可以参考这里:色彩校正中的 gamma 值是什么?
简单来说,Gamma值用于在离散的亮度值和实际的光强之间进行映射,以保证中间值就是人眼中的“中灰”,如果该值不合适就会造成暗部/亮部不均衡。
晕哥很早就推荐过了,哈哈。https://whycan.com/viewtopic.php?id=2168
情况越来越不明了了,我现在越来越怀疑是不是存在越界或者其他什么问题了。添加了如下测试点
static inline unsigned __get_cpsr(void) { unsigned long retval; asm volatile (" mrs %0, cpsr" : "=r" (retval) : ); return retval; } static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) { struct nand_device *nand = spinand_to_nand(spinand); if (spinand->eccinfo.get_status) return spinand->eccinfo.get_status(spinand, status); switch (status & STATUS_ECC_MASK) { case STATUS_ECC_NO_BITFLIPS: printf("cpsr -> 0x%x\n", __get_cpsr()); return 0; case STATUS_ECC_HAS_BITFLIPS: /* * We have no way to know exactly how many bitflips have been * fixed, so let's return the maximum possible value so that * wear-leveling layers move the data immediately. */ return nand->eccreq.strength; case STATUS_ECC_UNCOR_ERROR: return -EBADMSG; default: break; } return -EINVAL; }
验证在spinand_init函数下添加TEST点,返回地址确实最低位为1,测试函数代码段OK。
noinline static void TEST(void) { unsigned int tfunc = (unsigned int) __builtin_return_address(0); if (gd->flags & GD_FLG_RELOC) tfunc -= gd->reloc_off; printf("ret <- 0x%x\n", tfunc); } static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) { struct nand_device *nand = spinand_to_nand(spinand); TEST(); if (spinand->eccinfo.get_status) return spinand->eccinfo.get_status(spinand, status); switch (status & STATUS_ECC_MASK) { case STATUS_ECC_NO_BITFLIPS: return 0; case STATUS_ECC_HAS_BITFLIPS: /* * We have no way to know exactly how many bitflips have been * fixed, so let's return the maximum possible value so that * wear-leveling layers move the data immediately. */ return nand->eccreq.strength; case STATUS_ECC_UNCOR_ERROR: return -EBADMSG; default: break; } return -EINVAL; }
当添加到前面的测试点位置时,迷惑的事情又出现了,打印并未出现。确认TEST点(81716c1a)在故障(81716c7c)位置之前。
奇奇怪怪的事情真奇妙,lr的地址显示他都在thumb模式下。而TEST点打印未出现,我怀疑堆栈可能已经GG,但是uboot下并未有
栈回溯功能,还是我不知道?U-Boot 2018.11 (Apr 26 2020 - 18:24:48 +0800) Allwinner Technology CPU: Allwinner F Series (SUNIV) Model: f1c200s DRAM: 64 MiB MMC: SUNXI SD/MMC: 0 Setting up a 480x272 lcd console (overscan 0x0) In: serial@1c25400 Out: serial@1c25400 Err: serial@1c25400 Hit any key to stop autoboot: 0 ret <- 0x817172c9 Reading 6291456 byte(s) (3072 page(s)) at offset 0x001c0000 undefined instruction pc : [<82fa9c90>] lr : [<82fa9c7d>] reloc pc : [<81716c90>] lr : [<81716c7d>] sp : 82e709f0 ip : fffdcc80 fp : 00000000 r10: deadbeef r9 : 82e72ec8 r8 : 82f932a0 r7 : 00000000 r6 : 00000000 r5 : 00000002 r4 : 82e748d0 r3 : 00000000 r2 : 00000002 r1 : 82e752e0 r0 : 82e752e0 Flags: Nzcv IRQs off FIQs off Mode SVC_32 Code: 29104019 2920d008 2900d00b 0038d106 (bc04b024) Resetting CPU ... resetting ...
感觉在迷茫的路上越来越迷茫了,也可能是我并不能让编译器生成跳转为blx的调用,大神还有什么招数没有哦?
U-Boot 2018.11 (Apr 26 2020 - 18:25:27 +0800) Allwinner Technology CPU: Allwinner F Series (SUNIV) Model: f1c200s DRAM: 64 MiB MMC: SUNXI SD/MMC: 0 Setting up a 480x272 lcd console (overscan 0x0) In: serial@1c25400 Out: serial@1c25400 Err: serial@1c25400 Hit any key to stop autoboot: 0 Reading 6291456 byte(s) (3072 page(s)) at offset 0x001c0000 undefined instruction pc : [<82fa9c7c>] lr : [<82fa9c7f>] reloc pc : [<81716c7c>] lr : [<81716c7f>] sp : 82e709f0 ip : fffdcc88 fp : 00000000 r10: deadbeef r9 : 82e72ec8 r8 : 82f932a0 r7 : 00000000 r6 : 00000000 r5 : 00000002 r4 : 82e748d0 r3 : 00000000 r2 : 00000002 r1 : 82e752e0 r0 : 82e752e0 Flags: nZCv IRQs off FIQs off Mode SVC_32 Code: 18129309 9210414b e7af9311 f00c18c9 (e7c5feb9) Resetting CPU ... resetting ... 81716c70: 414b adcs r3, r1 81716c72: 9210 str r2, [sp, #64] ; 0x40 81716c74: 9311 str r3, [sp, #68] ; 0x44 81716c76: e7af b.n 81716bd8 <spinand_read_page+0xec> memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs, 81716c78: 18c9 adds r1, r1, r3 81716c7a: f00c feb9 bl 817239f0 <__memcpy_from_thumb> 81716c7e: e7c5 b.n 81716c0c <spinand_read_page+0x120> <-----------------------lr地址 switch (status & STATUS_ECC_MASK) { 81716c80: 2130 movs r1, #48 ; 0x30 81716c82: 4029 ands r1, r5 81716c84: 2910 cmp r1, #16 81716c86: d008 beq.n 81716c9a <spinand_read_page+0x1 81716c02: 000a movs r2, r1 81716c04: 0001 movs r1, r0 81716c06: 9806 ldr r0, [sp, #24] 81716c08: f7fe f840 bl 81714c8c <mtd_ooblayout_get_databytes> if (!ecc_enabled) 81716c0c: 9b07 ldr r3, [sp, #28] 81716c0e: 2b00 cmp r3, #0 81716c10: d03e beq.n 81716c90 <spinand_read_page+0x1a4> return spinand_check_ecc_status(spinand, status); 81716c12: aa08 add r2, sp, #32 81716c14: 230d movs r3, #13 81716c16: 189b adds r3, r3, r2 81716c18: 781d ldrb r5, [r3, #0] TEST(); 81716c1a: f7ff fdff bl 8171681c <TEST> <----------------------------------------- if (spinand->eccinfo.get_status) 81716c1e: 6e23 ldr r3, [r4, #96] ; 0x60 81716c20: 2b00 cmp r3, #0 81716c22: d02d beq.n 81716c80 <spinand_read_page+0x194> return spinand->eccinfo.get_status(spinand, status); 81716c24: 0029 movs r1, r5 81716c26: 0020 movs r0, r4 81716c28: 4798 blx r3 81716c2a: 0007 movs r7, r0 81716c2c: e030 b.n 81716c90 <spinand_read_page+0x1a4> void *buf = NULL; 81716c2e: 9703 str r7, [sp, #12] unsigned int nbytes = 0; 81716c30: 003e movs r6, r7 81716c32: e7ac b.n 81716b8e <spinand_read_page+0xa2>
感觉只是读代码的话确实很难发现错误。U-Boot要分析栈帧的话确实比较麻烦,需要手动导出栈的内容并自己分析。通过修改interrupts.c应该可以实现栈内容导出的功能,之后再自行分析或是使用类似于u-boot 实现 backtrace的方式来分析。
当然,如果要快速找到问题的话,调试依旧是必不可少的,如果手头有任何调试器或是可以用来刷调试器固件的开发板(比如说STM32F103),完全可以考虑使用OpenOCD连接调试器,再通过gdb连接OpenOCD进行调试,这个过程应该不麻烦的。
谢谢大神的分析指点,我又仔细回顾了一下当前的现象:
1. CPU的状态目前看起来确实是不正确的,但是是不是我觉得不能确定,不是很熟悉uboot的那段汇编,但是CPRS应该不能直接访问把。
2. 从现象上来看,问题现场不好确定是不是这个位置,有可能是其他代码块引发,然后飞到这个位置上来的,但从lr的寄存器上看,返回地址又是正确的
3. 尝试了下加入故障点,发现问题发生在spinand_check_ecc_status函数返回,mrs指令在thumb下不可用,求大神支个招。
4. 手上没有调速器,这个坑爹问题验证不了,害....static inline unsigned __get_cpsr(void) { unsigned long retval; asm volatile (" mrs %0, cpsr" : "=r" (retval) : ); return retval; } static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) { struct nand_device *nand = spinand_to_nand(spinand); if (spinand->eccinfo.get_status) return spinand->eccinfo.get_status(spinand, status); switch (status & STATUS_ECC_MASK) { case STATUS_ECC_NO_BITFLIPS: printf("cpsr -> 0x%x\n", __get_cpsr()); return 0; case STATUS_ECC_HAS_BITFLIPS: /* * We have no way to know exactly how many bitflips have been * fixed, so let's return the maximum possible value so that * wear-leveling layers move the data immediately. */ return nand->eccreq.strength; case STATUS_ECC_UNCOR_ERROR: return -EBADMSG; default: break; } return -EINVAL; }
确实有可能是其它代码块引发的问题,不过U-Boot已经执行到这个地方了,我觉得还是先在附近查找问题比较好。
CPSR(准确说是SPSR,因为是表示异常触发时记录的CPSR寄存器内容)是在vectors.S的get_bad_stack宏中存储的,这边的逻辑应该没有错,反映的就是异常发生时(而非进入异常后)的CPSR寄存器状态。
在Thumb状态中,确实没有什么比较好的办法来获取CPSR寄存器的内容,不过我觉得可以写个函数尝试输出函数内lr寄存器(记录返回地址)的内容,如果该函数正确执行的话,输出的lr寄存器的最低位应当是1(表示Thumb状态)。为了保存T状态位,这个函数必须使用blx调用,而blx接受的参数是寄存器而非常量offset,我认为通过函数指针访问函数的方式应该会编译成带blx跳转的形式(只要没被编译器优化),可以试试。
看了异常附近的指令,函数内bx/blx指令只有一条,结合代码来看,应该是在core.c中的spinand_read_page函数返回处,而函数中blx指令对应的应该是同一文件中spinand_check_ecc_status函数使用的get_status函数指针。我检查了一下函数指针的地址,应该没有问题。
重新整理一下问题,我觉得是这样的:
首先,异常内容是未定义指令,但当前出错的指令实际上是ARMv5T兼容的,因此和架构没有关系。
其次,看U-Boot的提示,当前应该处在ARM状态而非Thumb状态,这与预期不符(可以通过前面说的(T)提示和Code的指令长度发现CPU将这些指令当成了32位宽的ARM指令),这应该是问题的原因。
不过,就目前的已有信息,还是很难看出是哪个地方引发了错误的状态切换,毕竟代码能够跑到这里来,侧面说明了这个错误的状态切换很可能是在异常附近发生的。事实上,前面几条指令在ARM状态下也能执行(非未定义指令),只是执行结果是错误的而已。
要想快速排查出错误原因,应该还是要上调试器比较靠谱,在异常附近加个断点,看看在异常附近的状态切换是否符合要求。
另外补充一下,除了bl/blx指令之外,pop指令也有改变执行状态的能力,前提是pop指令的操作对象包括pc。
我大概梳理了一下,还是有几个迷惑点:
1. uboot代码未使用到Thumb和Arm的指令混编,不应该出现ARM状态
2. 该代码处是一函数调用,但由于优化并未产生调用,附近跳转也没有异常
3. 从code位置二进制上看,代码二进制并未被修改,即不应该是内存越界问题
4. 大概去看了下ThumbV1和ThumbV2没有发现在ADD上有什么差异,不知道是不是理解问题
我觉得不是Thumb的问题,指令都是正确的,并且异常附近的Thumb指令都是从ARMv4T就支持的。
ARM和Thumb的状态切换是需要显式调用bx或blx指令,之后通过识别pc寄存器的最后一位来判断跳转后进入何种模式(0是ARM,1是Thumb)。所以问题可能是因为在其它地方调用bx或blx指令时错误地进入到了Thumb模式(汇编没写好时有这个可能性),但是在执行上面的指令时可能还是正常的(指令虽然乱套了但并没有引发异常),直到出现未定义指令异常。
U-Boot应该有开关可以控制是否使用Thumb模式(SYS_THUMB_BUILD),不过我觉得这个开关应该是正确打开的,否则不应该执行到这里才报错。更有可能的情况还是上面那种。如果不介意的话,可以把编译好后的文件发上来看看。
看了一下U-Boot关于异常处理部分的代码:interrupts.c,感觉在异常发生处时处于ARM状态而非Thumb状态(Thumb状态会有(T)的提示),所以感觉可能是你的代码从其它地方跳转到此处时没有正确切换状态所致。可以检查一下到附近位置的跳转指令。
static_cast是C++风格的强制类型转换,相比C多了个指针类型的编译期检查,运行期应该没有区别。见What is the difference between static_cast<> and C style casting?
至于目标类型,是在QComboBox类内的函数的指针类型。由于类内非静态函数隐式传递了this指针,因此不能直接使用函数本身的类型(此时应当对应void (*)(int)),而是要加上类限定符。实际指向的函数应该具有void QComboBox::xxx(int)的形式。
riscv应该是个配置射频的小mcu 。类似stm008的用法
不是很赞同。RISC-V没有和RF模块放到同一个部分(CP应该是Co-Processor,协处理器),而是在AP(应该是Application Processor,应用处理器),说明RISC-V应该和RF没有关系。事实上射频相关的协议应该也不算太简单,STM8这种级别的单片机应该是搞不定的;根据经验,像蓝牙、WiFi之类的协处理器至少是Cortex-M0+级别,复杂如4G用上Cortex-A/R也算正常。
至于此处的RISC-V,目前看来很少有厂商将其用于高性能领域(毕竟涉及到OS特性部分的支持还不够成熟),所以更有可能是作为PMU控制SoC的电源或唤醒等服务,或是更进一步的低功耗专用处理器(类似于ESP32的ULP,实际上ESP32-S2也用上了RISC-V)。当然以上都只是猜测,实际用途只能等待厂商公布更多技术细节了。
我记得我在18年暑假的时候就在立创买过CH55x,结果今年才拿出来玩,有些惭愧。
根据之前的一些经验CH558T USB功能实现+性能测试,除了一些小bug和EP数量不大够用之外,其实还是相当够用的,之前用不到4K的代码空间跑CMSIS-DAP就是一个例子。
另外同意楼上观点,SDCC挺好用的,代替Keil完全没有问题,不过51下的编译器似乎优化方面比较一般,不少地方需要自己写汇编以确保性能,好在51的指令集并不算复杂。
从邮件列表里面找到了一个可能是这个公司名称来历的地方:[Arm-netbook] A20 and A10 NAND
看起来是先有代码上的修改,之后有好事者注册了这个域名并放上了相反的内容。
原文如下:
2013/5/31 Tom Cubie <mr.hipboi at gmail.com>:
>
> On May 30, 2013 3:11 AM, "luke.leighton" <luke.leighton at gmail.com> wrote:
>>
>> i've started a comparison of function names and IO base addresses (nm
>> modules/nand/libnand) and have found that the function names are
>> exactly the same, as well as the I/O base for the A20 NAND driver is
>> exactly the same as that of the A10. i would hazard a very rough
>> guess therefore that the hardware of the A20 NAND is identical to that
>> of the A10.
>>
>> "the usual" fucking around with the header files however has been done
>> [by that manager that we know orders allwinner software team members
>> to alter the copyright messages]. in the A10 3.4 source code
>> arch/arm/plat-sunxi/include/plat/platform.h we have a correct
>> Copyright message, 2007-2012, Copyright Allwinner Ltd, and in the A20
>> 3.3 source code arch/arm/mach-sun7i/include/mach/platform.h we have
>> Copyright 2010-2015, Copyright Reuuimlla Technology Co., Ltd.
>> <www.reuuimllatech.com>
>>
>> i note according to whois queries that the domain name
>> reuuimllatech.com is not registered. does anyone have £15 to spare
>> that they'd like to throw at a domain registrar, perchance?
>>
>
> sorry, i invented the word reuuimllatech. my stupid manager asked me to
> remove all allwinner word from source code. so i replaced all allwinner with
> reuuimlla, the reverse of allwinner.ROFLMAO. Brilliant. I wondered where such a name came from. You didn't
only reverse you tilted a few letters.BTW. I know it's a thin line. But if the copyright holder is
non-existent, then there is no copyright right?Another questionable thought. What about registering "Reuuimlla
Technology Co., Ltd." at the Chinese authorities (chamber of
commerce) and claim copyright as stated in the code....Corporate legal thinking yugh.
Rules and laws never hinder those who which to harm. Those who want
live in harmony are always limited by the rules to prevent harm.The problem is choice and choice is probably the solution.
>
>> anyway, i'm going to try an experiment - compile up an A20 kernel,
>> replicate the stuff i did earlier with A10 (fex loader) and then try
>> compiling the standard A10 3.4 nand kernel in.
>>
>> l.
>>
>> _______________________________________________
>> arm-netbook mailing list arm-netbook at lists.phcomp.co.uk
>> http://lists.phcomp.co.uk/mailman/listinfo/arm-netbook
>> Send large attachments to arm-netbook at files.phcomp.co.uk
>
>
> _______________________________________________
> arm-netbook mailing list arm-netbook at lists.phcomp.co.uk
> http://lists.phcomp.co.uk/mailman/listinfo/arm-netbook
> Send large attachments to arm-netbook at files.phcomp.co.uk
去linux-sunxi上找了一下,果然有相关代码,不过似乎没有被合并到主线上:add gpio & dma driver to sun7i · linux-sunxi/linux-sunxi@715dc19
猜一下可能的原因。
首先还是有可能会出现与每个核相关的功耗,比如LDO(用于控制单核电压)之类的,这个还是要看架构。
其次,90mW可能是最低配置下的结果。像Cortex-A系列的应用处理器,至少cache容量是可调的,这也会显著影响功耗。
最后,频率和功耗肯定是有关的。我们知道在活动时动态功耗通常是占主要地位,而对于单个寄存器来说P=αCV^2f,其中C是电容,V是电压,f是时钟频率,α是翻转概率。虽然P和f是线性关系,但是在频率提升时通常也要适当提升电压,因此功耗上升的速度要快于频率的上升速度。
其实可以看一下桌面级CPU的功耗曲线,每提升一定频率消耗的功耗会越来越多,这个是必然的。
顺便贴一下可能的信息来源:New ARM Cortex-A35 Processor Extends the ARMv8-A Architecture Deeper Into Mobile and Embedded Markets
请问下有参考的网址或者是你有教程不?
这个不需要什么教程,只需和普通RGB屏一样调好参数,接上转接板就能用。当然事先需要看一下引脚定义是否一致。
可以参考这个帖子:https://whycan.com/viewtopic.php?id=3267
找了半天,只找到一个8p的:AYF330835,楼主看看能不能用。
con_cn_y3bw.pdf
清华和中科大的镜像站确实好用,不过和1楼给的不是同一种东西,后者是Git源。
最近随手翻阅kernel.org时发现了这么一个消息:Git mirror available in Beijing。
于是上去看了一下,原来是Code Aurora提供的服务。比较有趣的是,不仅是kernel,很多大家常用的开源项目Git源也有,比如说i.MX相关的各种源。
试着下了个uboot-imx,速度还行,稳定在大概400kB/s,泪目。
网址如下:Code Aurora git repositories,逃(
建议楼主先看一下GIC的一些基本概念。我觉得这篇文章可以一看:linux kernel的中断子系统之(七):GIC代码分析。
更新:找到了U2 Basic的升级方法,不过手头没有硬件,需要大家自行验证。
地址:Превращаем DSLogic U2Basic в DSLogic Plus,理论上可以套用我的方法不使用编程器。
1.现在stm32大概有那些型号支持这个?
2.自己移植到一个新的MCU上 工作量大致能估计么
1. 可以说基本全部支持,并且部分开发板直接提供编译好的文件,见MicroPython - Python for microcontrollers;对于其它使用STM32的板子,源代码自带HAL库,只需根据需求添加板级描述文件即可,见micropython/micropython。
2. 需要适配不同组件对应的驱动,并且根据需要裁剪MicroPython支持的功能,如果MCU已有SDK的话我想应该不会很麻烦。可以参考micropython/ports/minimal/
搜了整个uboot目录,没有找到nuc970_config文件。
试着把configs目录下的nuc970_defconfig改名,再make nuc970_config,提示找不到nuc970_defconfig。
好奇,这是怎么对应上去的?
这是在Makefile中通过匹配命令中的%_defconfig和%_config实现的。
可以参考这里:scripts/kconfig/Makefile · master · U-Boot / U-Boot,第127到132行。
比如CH552的自带BOOT在0x3800上的能换下来嘛?
另外GCC下,ROM_CFG_ADDR这个寄存器是16位的,要关闭No_Boot_Load?
感觉有点坑哇。呵呵
我没试过,但是1楼那个帖子的老外说是可以的。
通过No_Boot_Load关闭bootloader,那样的话bootloader提供的ISP下载就直接废了,除非你自己的程序可以修改自己,否则就只能用他那个不公开的专用编程器才能下载。
所以我现在打算自己写个bootloader把它替换掉,通过附加额外的限制使得固件不能随意烧入(比如密码验证),这样的话只要bootloader本身和原有的用户程序足够安全,就不存在导出固件的方法了。
当然理论上还有其它修补办法,比如修改原来的bootloader之类,但我觉得不如另起炉灶,毕竟原来的ISP工具也不见得多好用。
谷歌的 “chacha20加密算法” 符合你需求。
感谢推荐。简单看了一下,对代码和内存的要求确实都不算大(当然还是要比XXTEA多一些),不过好不好用还是要编码试试看才知道。
贴个标准地址:ChaCha20 and Poly1305 for IETF Protocols
直接弄一块芯片,写一个程序和pc通讯,用UINT8C指针赋值为bootloader所在的地址就能轻而易举的导出bootloader
是的,我就是写了一个简单的程序读出bootloader并分析的,参考的是FX2的方式。由于bootloader不设防,所以导出的过程很简单。
不过这篇文章是从bootloader导出用户区的代码,即使用户区的代码禁止拷贝程序,因为管不到bootloader,所以可以绕过用户程序直接导出Flash上的所有代码,包括bootloader和用户代码。这恐怕不是WCH设计bootloader时的本意(他们提到新版本bootloader修复了1.0版本的漏洞,但漏洞依然存在)。
我觉得大家在讨论前还是应该关注一下具体的业务范围会比较合适,毕竟隔行如隔山嘛。
举个例子,对于医疗器械类的应用,我觉得很多情况下H7这种级别的单片机会比同等性能(当然价格就差不少了)的应用处理器(Application Processor,例如Cortex-A系列)更适合,因为H7在这种应用场景下有以下优势:
需要对产品细节进行严格控制。比如中断周期数,波形的时序要求,软件功能的自定义,这些要求肯定不是Linux内核自带的通用驱动可以完全满足的,需要高度定制化。
对外设的需求比CPU的计算能力更加重要。应用处理器一般不会带非常厉害的外设,有些需求肯定难以达到,比如自带高性能ADC等,H7提供的外设(特别是模拟部分)可以超越大部分应用处理器。
本身是垄断型产品,价格利润高,处理器的价格差距实际上不重要,而且高端单片机实际上可以增加破解难度(安全措施强,而且破解的人少,破解价格自然就高)。这些产品通常都不是通过走量的形式来获取利润的,所以国产芯片厂商在这方面说实话没啥优势(举个例子,大家可以了解目前国产FPGA)。
设计上的其它考量,比如温度、ESD等,另外芯片数量太多一般不是什么好事。
当然原因很多,这里不再列出。大家看看H7目前主要的应用范围就清楚了:示波器、医疗器械、工控设备等等。这些都是高附加值的产品,完全撑得起H7在硬件成本和软件成本上的支出。
所以,这里会得到一个可能反直觉的结论:高端单片机有时候不是小厂商的最佳选择,反而有能力的大厂或是专精于某一领域的厂商才能玩得转这些高端单片机。如果是走量的、靠降低价格获取更多利润的产品,由于本身的需求量更大,像Linux这样的坑基本都被踩过一遍了,所以选用Linux方案的综合开发成本(包括软硬件)会更低。这实际上还是和需求紧密相关的。
61美分?他买的这么便宜么?
我也对这点感到好奇,毕竟打开某宝也找不到这个价格的F1C200s。
顺带一提,我翻了一下博客,发现博主就是之前搞信用卡上跑Linux的老哥:My Business Card Runs Linux。在这个帖子里面写了F1C100s是$1.42。
其他国外大佬能获取更多资料。
The F1C100s needs some additional work to enable its Cedar peripheral. I’ve asked Allwinner for more info about the F1C100s’s video stack, and I plan to continue working on it in the meantime as well. All this means that you could be playing 1080P HD video on Linux on a 61-cent chip. Absolutely crazy.
我也看到这个了,不过博客似乎还没有相关更新,坐等。
简单说一下背景,目前计划在CH558上实现一个bootloader,并且计划加上固件加密功能。
初步想法是实现一个简单的对称加密算法,密钥存储在单片机上,bootloader从USB接收加密后的程序,并在本地解密之后写入到Flash中(这一步可以添加校验以确保固件来源可靠)。只要在bootloader部分不提供密钥区的访问,这样应该是安全的。
那么问题来了,现在需要在单片机上实现一个简单的加密算法(准确说只包括解密部分),我的要求如下:
最重要的是空间限制,由于bootloader区域只有2 KB,因此涉及到挂表的算法基本上不是很现实,但多轮加密只要不大复杂应该可行。空间复杂度不要超过O(n)的级别。
时间方面比较宽松,只要不是很慢就可以了(非对称算法似乎不可行?),毕竟Flash的擦除和写入本身也不是很快。
支持细粒度的加密,目测加密块大小在8到1024 Bytes之间,再大就会分成多个block分开加密了。
安全性要足够(暂时不考虑侧信道攻击之类的话题),对密钥格式之类的没有多大要求,只要可以存在单片机上即可。对于bootloader应用,似乎对称加密就可以接受,不需要考虑网络传输的问题。
对于上述要求,我看了一下已有的算法,感觉TEA(以及变种XXTEA)似乎是个可行的选择,不过也有对特定条件下加密强度的担忧(见cryptanalysis - Is TEA considered secure? - Cryptography Stack Exchange)。
大家对这方面有没有什么想法?欢迎交流。
免责声明:
下文中的所有内容仅供学习和研究之用,对由此造成的各种问题概不负责,如有侵权请删除本帖,谢谢。
今天在沁恒官网论坛看到这个帖子:关于单片机里面的程序的安全性,颇感兴趣,遂亲自验证了一下,发现确实可行,于是发帖分享下经验。
帖子里面说的GitHub地址应该是这个:MarsTechHAN/ch552tool,结合导出的bootloader分析,发现验证固件的命令有漏洞,虽然数据需要由PC端发出且一次至少需验证8个字节(此时理论的可能性达到2^64种,无法枚举),并且设备只会返回是否校验正确,但由于该命令不限制范围和字节对齐,因此可以利用已知字节+1个未知字节的方式,遍历该未知字节的值直到校验成功为止(理论可能性为256种,可以接受)。正好0xFC00开始的部分flash内容恒为0xFF(这部分内容由bootloader管理),因此可以利用0xFC00开始的已知字节,倒推出前面所有未知字节。
我写了个程序:metro94/ch55x_dumper,导出的时间大概是5分多钟(4KB左右的代码量),相对来说比较慢,毕竟需要花时间猜。
最后说一句,由于bootloader存在漏洞,因此打算商用的小伙伴们建议替换或直接关掉这个bootloader(通过修改No_Boot_Load的方式)。由于官方工具的IAP实际上会在进入bootloader后才调用,因此也不保险。目测bootloader区域没有额外锁定,应该可以在运行主程序时进行替换。
伸手党厚颜无耻在线求教程或链接.
QEMU/debootstrap approach
其他发行版同理,只是将镜像地址换一下。
啥时候上市?
软件仿真的结果是对的,download到芯片里面跑,就不正确了。
使用strncmp,就没有问题。像下面这样写:
printf("strncmp=0x%bx\n", strncmp("123", "123", 3));
printf("strncmp=0x%bx\n", strncmp("1234", "123", 4));
printf("strncmp=0x%bx\n", strncmp("123", "1234", 4));
printf("strncmp=0x%bx\n", strncmp("1233", "1234", 4));
神奇,要不自己写个strcmp?
char mystrcmp(const char *str1, const char *str2)
{
while (*str1 || *str2) {
if (*str1 < *str2)
return -1;
else if (*str1 > *str2)
return 1;
++str1;
++str2;
}
return 0;
}
楼主的代码用了%bx,应该是Keil C51编译的吧?
我在本地编译并仿真了一下,结果是正确的。测试代码如下:
#include <stdio.h>
#include <string.h>
sfr SCON = 0x98;
sbit TI = SCON^1;
void main(void)
{
TI = 1;
printf("strcmp=0x%bx\n", strcmp("123", "123"));
printf("strcmp=0x%bx\n", strcmp("1234", "123"));
printf("strcmp=0x%bx\n", strcmp("123", "1234"));
printf("strcmp=0x%bx\n", strcmp("1233", "1234"));
for (;;);
}
输出结果如下:
strcmp=0x0
strcmp=0x1
strcmp=0xff
strcmp=0xff
楼主把反汇编的结果放上来看看?
下面解释一下为什么上述操作可以修改固件。其实这是因为Cypress FX2系列USB单片机内置了Firmware Load请求。该命令属于Vendor Request,并且是在硬件层面就实现的功能,因此就算固件没有进行相关编码也可以用。(所以某种程度上来说FX2没办法加密固件,毕竟可以绕过固件直接读写RAM的内容。)
可以看到,FX2支持Firmware Download(从PC到FX2)和Firmware Upload(从FX2到PC)。特别地,Firmware Download的过程需要在Reset状态下完成(毕竟固件都要被覆盖了)。所以下载固件时的正确操作如下:
使用Firmware Download往CPUCS中的8051RES位写1,以复位FX2的CPU(USB部分不受影响)。这等价于往0xE600寄存器里写1。
使用Firmware Download往RAM区域(0x0-0x4000)写代码。由于FX2的代码和数据共用一条总线,因此向RAM写入的数据可被执行。另外,写入的时候似乎有大小限制,每次最多可以写0x1000字节。
使用Firmware Download往CPUCS中的8051RES位写0,以取消对FX2 CPU的复位。这等价于往0xE600寄存器里写0。
当然,上面说的是下载固件到RAM,要下载到EEPROM就会麻烦些。由于Firmware Load不能直接操作EEPROM,因此需要借助下载的固件来实现这个功能(类似于bootloader)。另外,EEPROM的固件并不是源程序,而是经过了打包,这在参考手册中被称作是C2 Format,打包后的文件后缀是.iic格式。C2 Format的结构如下图所示。
言归正传,这个bootloader要怎么做呢?在FX2的官方例程中,我发现了一个名为Vend_Ax的程序,该程序就能完美实现我需要的功能。具体代码见这里:Vend_ax.zip。
因此,下载到EEPROM的过程如下:
使用上述步骤上传编译好的Vend_Ax.bin文件。注意这里不该使用.iic文件,因为Vend_Ax是直接上传到RAM中执行的。
在上传固件并取消复位后,在不进行USB重新枚举的情况下,我们按照Vend_Ax记录的文件内容进行操作,做法是使用对应的Vendor Request发起Control Transfer
在成功上传固件后,重新插拔设备,此时将使用新固件启动。
上面就是烧写固件所需的全部操作。由于DSLogic使用了WinUSB,因此上述过程可以直接用libusb或是基于此的PyUSB完成。
免责声明:
下文中的所有内容仅供学习和研究之用,对由此造成的损坏、保修等问题概不负责,如有侵权请删除本帖,谢谢。
DSLogic是一个便携式的逻辑分析仪,功能够用,价格还算便宜。DSLogic分为Basic和Plus两个版本(现在似乎有新版了),两者在硬件上仅有SDRAM缓冲和屏蔽线的区别。通过一些硬件上的改造,可以将Basic升级为Plus。
注:下文内容在我的Basic上验证成功,对于新版U2 Basic是否修改硬件并不清楚,如有成功经验欢迎分享。
首先,还是要将SDRAM焊到板子上。官方用的SDRAM似乎有多个版本(例如Alliance AS4C16M16SA-7TCN,见DreamSourceLab DSLogic Plus),我自己用的是H57V2562GTR-75C,已经正常使用一年半了,应该133 MHz及以上频率、16-bit的SDRAM都可以用。
这里我们需要Plus对应的固件,好在DSView是开源软件,我们可以在GitHub上找到历史版本:DSLogicPlus.fw,下载下来备用。
接下来就该烧写固件了。我把相关操作都写好代码放到GitHub上了,地址在metro94/fx2_tools。需要安装Python 3.x及PyUSB。
现在,可以根据需要选择以下操作:
临时修改固件:
只需使用firmware_run.py即可,执行命令python firmware_run.py DSLogicPlus.fw,等待写入完成即可。之后,硬件应该自动重新枚举,可以在DSView中看到设备名称已经变为DSLogic Plus。由于固件存储在RAM上,重启后会丢失。
永久修改固件:
这一操作需要将固件烧录到EEPROM上。首先,由于EEPROM的固件存储格式是封装过的(FX2的特性),因此需要将DSLogicPlus.fw文件封装为iic格式(C2 format),firmware_mkimage.py可以用来做这个事情。命令如下:python firmware_mkimage.py DSLogicPlus.fw DSLogicPlus.iic
由于EEPROM的WC引脚默认接高电平,此时忽略写入,因此需要临时将WC引脚接地,如下图所示。
(小提示:可以直接用信号线中的黑色线(地线),接到引脚后另一头通过测试夹短接采样端口。)
之后,运行命令python firmware_download.py DSLogicPlus.iic。由于EEPROM的写入较慢,需要多等待一段时间,直到执行完成,之后根据提示重新插拔即可。
经过上述步骤,应该就可以搞定升级过程了。后面会解释这样做的原理。
最后说一下SWD的时序问题以及优化方案吧。
众所周知,SWD是简单的两线协议,是ARM为Cortex-M单片机提出的优化方案。事实上,SWD的优势不仅是占用引脚少,而且传输的指令更加简单,相对而言提升了传输效率。
SWD的波形如下图所示。
可以看到,SWD协议每次传输的数据包的位宽是相对固定的。对于Successful/OK的情况,可以分为以下6个阶段(空闲状态是SWCLK输出高电平,SWDIO输出高电平):
传输8-bit的command。实际上,有效数据是4个bit,Start用于指示开始,Parity用来提供4个bit的奇偶校验,Stop用于指示结束,而Park用于将引脚拉高。
第一个Turnaround,调试器切换到输入态,SWDIO暂时维持高电平。Turnaround的长度为1到4个bit,可以通过DLCR寄存器修改。
设备返回3-bit的response。一般的状态包括OK,FAULT,WAIT和Protocol Error。
对于Write而言,第4步需要切换到输出态,因此需要第二个Turnaround;对于Read而言,第4步用于输入32-bit数据及1-bit校验位。
对于Write而言,第5步用于输出32-bit数据及1-bit校验位;对于Read而言,第5步需要切换到输出态,因此需要第二个Turnaround。
第6步用于输出IDLE(如果有),简单来说就是在SWDIO输出若干个周期的低电平(直到下一个高电平时才开始下一个传输)。
这里我们先不探讨各种技术细节,可以看到SWD中8-bit的command和32-bit的data是比较规整的,而其它则比较零散。由于CH55x的SPI只能支持按字节传输,因此也只能对这两个部分优化(但是占比可以达到(8+32)/(8+5+32+1)≈87%,还是比较多的)。
当然,上文中针对的是SWD部分。由于SWJ接口(SWD和JTAG的共用接口)规定,TCK和SWCLK同一引脚,TMS和SWDIO统一引脚,并且不能修改(协议切换的逻辑只能在这两个引脚上进行),因此对于CH55x这种只有一个SPI和一组复用引脚的情况,必然只能选择其中一个协议应用SPI优化。
当然,JTAG也可以应用SPI优化,但是涉及到IR/DR切换等问题会比较复杂,等以后做到了再分享吧。
推荐购买DSLogic Basic版本 299,可以自己焊接上SDRAM升级成Plus版本(直接买Plus版本是499),软件基于开源的sigrok定制的。支持协议很丰富,有一百多种,应该是目前市面上最多的,也可以自己写解码器解析协议,不过缺点就是协议解析可能比较慢(用python实现的协议解析)。
这个是几个中科大的人出来搞的,记得在kickstart众筹了十几万美金吧,我个人还是很喜欢这个LA的,做工好,上位机也比较漂亮,而且还是开源的。
我就是这么搞的,哈哈。从硬件上来说两个版本只差了一个SDRAM,而且FPGA的bitstream是通过USB下载的,所以焊上SDRAM并替换CY7C68013A的固件之后可以正常使用,完全没区别。
接下来说说优化思路。
首先还是得从CH558的架构说起(CH552等基本同理)。由于51的RAM有限(最大256Bytes),所以USB只能从XRAM读写数据,而众所周知51对XRAM的访问比较麻烦,只能通过DPTR寄存器进行,因此虽然访问速度很快(movx相关的指令都是单周期),但是寻址很麻烦。另外,涉及到数据搬移的部分也会消耗一定时间。
为了解决这个问题,CH55x系列单片机都支持双DPTR指针和自增指针。但是很遗憾,包括Keil(准确说是C51)和SDCC在内的编译器似乎都不支持CH55x上的这两个特性。因此,这部分代码需要按照实际执行的逻辑自己编写代码,这也是要用汇编进行优化的原因之一(另外的原因是可以在固定寄存器上传递跨函数的参数,类似于U-boot的gd指针)。
因此,结合CMSIS-DAP的特点,在我的代码里面,使用DPTR0存储Command指针,DPTR1存储Response指针。由于CH55x支持0xA5指令,这一指令等效于__asm__ ("movx a, @dptr1\ninc dptr1"),因此我们默认使用DPTR0指针。这样,我们就能使用一个周期的时间完成读取Command(通过movx a, @dptr)和写入Repsonse(通过.db 0xa5),读写效率得到了很大提高。
另外是数据搬移的问题。CMSIS-DAP可以通过设置DAP_PACKET_COUNT的方式来提升缓冲深度,可以通过提交多条指令的方式加速处理速度。如果不在这一部分进行优化,则会花费不少时间在USB DMA地址和DAP FIFO之间搬移数据。为此,我使用了一种看似麻烦的方式,就是设置USB DMA为单缓冲,并且每读一个包就切换到下一个FIFO地址(需要处理空满情况)。考虑到全速USB的Bulk最大包大小为64Bytes,这里我设置DAP_PACKET_COUNT为4,并且确保Command和Response的FIFO对齐到整256Bytes空间。这样一来,DMA的地址可以通过对低8位的部分每次累加64得到,并且在溢出时相当于循环FIFO。由于不涉及到数据搬移,因此USB中断处理流程大大加快,并且不会干扰DAP部分的执行。
顺带一提,在我测试时CH558的USB_DMA寄存器(只读,用于表示当前DMA的传输地址)似乎有问题(似乎CH552并没有这个寄存器),会在运行一段时间后出现未知的错误结果,原因不明。在我改用端点对应的UEPx_DMA寄存器之后,问题解决。
刚想起来上次找最便宜的高速USB的MCU的原来是您啊,我也是看了您的那个帖子才做了个505尝试了下
然后就掉坑里了~~~
顺手记录了下505的调试过程。
https://kmcu.gitee.io/2020/02/21/NUC505/
哈哈哈,这个MCU据说坑比较多,之前我也买了块板子打算玩的,结果压箱底了,现在也暂时回不去,打算以后研究下UAC之类的。
上文提到了,CMSIS-DAP是基于命令式的处理,调试器从PC接收指令(Command/Request),在处理结束后调试器整理好结果(Response)发送回PC。由于没有Outstanding(但是可以有内部缓冲),因此指令是顺序执行的,类似于FIFO的架构。因此,CMSIS-DAP的固件内部是一个大的循环,PC端发送的数据会先置于一个FIFO中,待DAP_Thread函数读取、处理指令后,将会写回到另一个FIFO中,等待发送回PC。因此,Command/Request和Response各有一个FIFO,DAP_Thread只处理CMSIS-DAP协议本身,USB部分需要另行处理。
此时看CMSIS-DAP的代码结构(见CMSIS-DAP),就会非常清晰:
DAP.c就是DAP的核心部分,用于对指令进行解析和处理。
SW_DP.c和JTAG_DP.c用于兼容不同协议的DP(Debug Port)。
SWO.c用于SWO的部分。
DAP_vendor.c是自定义的DAP指令,可以根据需求添加。
在官方实现中,如果要移植CMSIS-DAP,只需要添加兼容CMSIS-Driver接口的驱动,并且在target或board级进行一些设定(例如I/O端口,特定的重置序列等)就可以用了,还是很方便的。
下面简单介绍一下CMSIS-DAP的特点。
CMSIS-DAP是ARM提的一套调试器协议(作为具体实现之一的DAPLink可能知名度更高?)。CMSIS-DAP的特点是融合了通用调试器(如基于FTDI的方案)和专用调试器(如ST-Link)的优势,即可单独控制输入输出波形,又对Cortex-M的高频操作封装成了固定指令。因此,CMSIS-DAP不仅通用性非常好(理论上可以兼容所有JTAG接口,当然还有自家的SWD接口),而且针对ARM设备可以达到不错的速度(前提是使用V2版本)。
上图是基于CMSIS-DAP的调试架构。CMSIS-DAP本质上是一套基于PC发送命令并进行处理的中间人,本身并没有决定何时执行哪些命令的功能,这可以简化调试器软件的设计。当然,需要集成相关功能的话也没有问题,例如DAPLink也是利用了这套框架搭了一个基于USB MSC的下载器。
另外,CMSIS-DAP的固件版本包括V1和V2,两者的主要区别在于V1用的是HID,而V2改成了Bulk传输。HID的好处是免驱,但由于HID类的限制每1ms只能传输一条指令,实际上大大限制了传输速度。V2版本则利用WinUSB实现了免驱(对Windows的版本有要求),传输速度也不再受到HID类的限制。(这里要感谢@le062分享的经验,说明了V2版本中USB速度不再是瓶颈,全速USB也是够用的,因此才有了这个想法。)
对V2版本USB部分的详细描述可以查看Configure USB Peripheral。
最近一段时间在鼓捣CMSIS-DAP在CH558的移植。现在终于搞定SWD部分了,分享一下结果。
对于CMSIS-DAP,ARM官方的代码是针对32位环境优化的,对于51这种8位单片机就会导致效率很低。因此,我重写了CMSIS-DAP代码(核心部分的代码基本都是汇编写的),将其移植到CH558上,并且使用SPI进行优化(同时依然可以使用GPIO模拟引脚)。
下图是我在STM32F407上的测试结果,使用IAR下载程序(顺便一提,为了提升下载速度,我将Flash的并行编程宽度改成了32位)。
上图是Flash的测试结果,下载速度70KB/s,验证速度315KB/s。
上图是SRAM的测试结果,下载速度305KB/s。
可以看到,即使是CH558这种级别的单片机也可以跑到很高的速度,完全可以满足使用要求。实际上,我的代码大小仅为3.69KB,继续添加功能的空间还是充足的,目测把CMSIS-DAP的所有功能都做上去不成问题,甚至可以考虑做个兼容V1和V2的版本(使用Vendor Command切换版本,兼容一些不支持V2的软件)。
下一步我打算将代码移植到CH552上,毕竟CH552的性价比更高,而且需要修改的部分很少。虽然CH552的时钟频率较低(3.3V下只能跑到16MHz),但是应该对性能的影响不是特别严重(SWD接口带宽还有一定余量),值得尝试。
考虑到JTAG部分以及一些相对次要重要的功能(例如SWO和充话费送的串口)还没完成,所以暂时不放出源代码,待修改完成后再发布,敬请期待。
下文会谈一些目前的优化手段,欢迎大家交流。
union它不香吗
用匿名union可以用多种方式访问同一地址,形式也很简洁。像USB Request就很适合用这种表达方式。
typedef __packed struct _REQUEST_TYPE {
U8 Recipient : 5; /* D4..0: Recipient */
U8 Type : 2; /* D6..5: Type */
U8 Dir : 1; /* D7: Data Phase Txsfer Direction */
} REQUEST_TYPE;
typedef __packed struct _USB_SETUP_PACKET {
REQUEST_TYPE bmRequestType; /* bmRequestType */
U8 bRequest; /* bRequest */
__packed union {
U16 wValue; /* wValue */
__packed struct {
U8 wValueL;
U8 wValueH;
};
};
__packed union {
U16 wIndex; /* wIndex */
__packed struct {
U8 wIndexL;
U8 wIndexH;
};
};
U16 wLength; /* wLength */
} USB_SETUP_PACKET;
STM32的调试口写的是兼容5V的
1.能读到,这个可能和GPIO的结构有关
2.感觉51速度慢,就没有加延时,强制快速就行了今天加了USB转串口CDC功能,代码中肯定还有需要改进的地方,我只是简单的实现一下功能!
设备管理器为什么只显示USB 串行设备呢,不能改名字吗?@metro
https://whycan.cn/files/members/390/TIM截图20200223170326.png
CDC类的默认串口驱动好像就是这样的,没看过可以指定名称的方式,应该是需要自己写驱动才行。
读了一下楼主的代码,有几个疑问想请教一下楼主:
1. SW_DP.c中,原版代码是有PIN_SWDIO_OUT_ENABLE这一个切换SWDIO的功能的宏的,我看在楼主的代码里面并没有使用,而是直接将这个IO设置为1。请问IO在PP模式下可以直接这样读取到SWDIO的值吗?
2. DAP.c的DAP_SWJ_Clock函数中,原版代码是有设置快速和慢速的区分,而楼主代码里面则是强制设置为快速,并取消掉所有的IO延时。请问这样在实际使用中是否有发现问题?
关于时钟频率的问题,CH552在3.3V下只能跑到16 MHz,就算是用上SPI最高也就是8 MHz,用IO模拟最高只能到(16/7) MHz(读1位需要7个周期),而且是汇编情况下的理想值,实际上应该是到不了的。在这种情况下,如果要新增一个TransferSlow,就会显著增加代码体积。我估计根本的解决方法应该还是要用汇编重写这部分代码,目前就在做相关工作,不过DAP_SWD_Transfer函数确实比较复杂,需要花点时间重写。
那个开源的用GD32做的有没有卖的
有个楼主放出了GitHub链接,里面有淘宝链接,可以看看(利益无关)
https://github.com/vllogic/vllink_lite
这个是程序问题,获取描述长度大于255了,我只看了低字节,导致回应错误不能识别!
再次感谢@metro大神!
DAP-20200222-免驱动.7zhttps://whycan.cn/files/members/390/TIM截图20200222150610.png
客气了,希望早日见到成品,哈哈。
现在我在重写DAP部分的核心代码,估计短时间内搞不定了,到时候也可以在你的板子上验证一下效果。
@metro 求助
#if (WINUSB == 1) UINT8C DevDesc[] = { 0x12, 0x01, 0x10, 0x02, 0x00, 0x00, 0x00, THIS_ENDP0_SIZE, 0x28, 0x0D, 0x04, 0x02, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01 }; UINT8C CfgDesc[] = { 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0xfa, //配置描述符 0x09, 0x04, 0x00, 0x00, 0x02, 0xff, 0x00, 0x00, 0x04, //接口描述符 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00, //端点描述符 0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x00 }; #else #Err no USB interface define #endif /*字符串描述符 略*/ // 语言描述符 UINT8C MyLangDescr[] = {0x04, 0x03, 0x09, 0x04}; // 厂家信息: KEI - Tools By ARM UINT8C MyManuInfo[] = {0x28, 0x03, 0x4B, 0x00, 0x45, 0x00, 0x49, 0x00, 0x4C, 0x00, 0x20, 0x00, 0x2D, 0x00, 0x20, 0x00, 0x54, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x20, 0x00, 0x42, 0x00, 0x79, 0x00, 0x20, 0x00, 0x41, 0x00, 0x52, 0x00, 0x4D, 0x00}; // 产品信息: DAP-Link-II UINT8C MyProdInfo[] = {0x18, 0x03, 0x44, 0x00, 0x41, 0x00, 0x50, 0x00, 0x2D, 0x00, 0x4C, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x6B, 0x00, 0x2D, 0x00, 0x49, 0x00, 0x49, 0x00}; // 序列号: 0001A0000000 UINT8C MySerNumber[] = {0x1A, 0x03, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x41, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00}; // 接口: CMSIS-DAP UINT8C MyInterface[] = {0x14, 0x03, 0x43, 0x00, 0x4d, 0x00, 0x53, 0x00, 0x49, 0x00, 0x53, 0x00, 0x2d, 0x00, 0x44, 0x00, 0x41, 0x00, 0x50, 0x00}; UINT8C USB_BOSDescriptor[0x21] = { 0x05, 0x0F, 0x21, 0x00, 0x01, 0x1C, 0x10, 0x05, 0x00, 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, 0x00, 0x00, 0x03, 0x06, 0xAA, 0x00, 0x20, 0x00 }; UINT8C WINUSB_Descriptor[0xAA] = { 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0xAA, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x14, 0x00, 0x03, 0x00, 0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x04, 0x00, 0x07, 0x00, 0x2A, 0x00, 0x44, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x49, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x47, 0x00, 0x55, 0x00, 0x49, 0x00, 0x44, 0x00, 0x73, 0x00, 0x00, 0x00, 0x50, 0x00, 0x7B, 0x00, 0x43, 0x00, 0x44, 0x00, 0x42, 0x00, 0x33, 0x00, 0x42, 0x00, 0x35, 0x00, 0x41, 0x00, 0x44, 0x00, 0x2D, 0x00, 0x32, 0x00, 0x39, 0x00, 0x33, 0x00, 0x42, 0x00, 0x2D, 0x00, 0x34, 0x00, 0x36, 0x00, 0x36, 0x00, 0x33, 0x00, 0x2D, 0x00, 0x41, 0x00, 0x41, 0x00, 0x33, 0x00, 0x36, 0x00, 0x2D, 0x00, 0x31, 0x00, 0x41, 0x00, 0x41, 0x00, 0x45, 0x00, 0x34, 0x00, 0x36, 0x00, 0x34, 0x00, 0x36, 0x00, 0x33, 0x00, 0x37, 0x00, 0x37, 0x00, 0x36, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00 }; void DeviceInterrupt(void) interrupt INT_NO_USB using 1 //USB中断服务程序,使用寄存器组1 { UINT8 len; if (UIF_TRANSFER) //USB传输完成标志 { switch (USB_INT_ST & (MASK_UIS_TOKEN | MASK_UIS_ENDP)) { case UIS_TOKEN_IN | 2: //endpoint 2# 端点批量上传 UEP2_T_LEN = 0; //预使用发送长度一定要清空 Endp2Busy = 0; UEP2_CTRL = UEP2_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_NAK; //默认应答NAK break; case UIS_TOKEN_OUT | 2: //endpoint 2# 端点批量下传 if (U_TOG_OK) // 不同步的数据包将丢弃 { len = USB_RX_LEN; memcpy(Ep2DataO[Ep2Oi++], Ep2BufferO, len); //待优化 if (Ep2Oi >= DAP_PACKET_COUNT) Ep2Oi = 0; } break; case UIS_TOKEN_SETUP | 0: //SETUP事务 len = USB_RX_LEN; if (len == (sizeof(USB_SETUP_REQ))) { SetupLen = UsbSetupBuf->wLengthL; len = 0; // 默认为成功并且上传0长度 SetupReq = UsbSetupBuf->bRequest; if ((UsbSetupBuf->bRequestType & USB_REQ_TYP_MASK) != USB_REQ_TYP_STANDARD) /*HID类命令*/ { if ((UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE) { switch (SetupReq) { case 0x20: //GetReport if (UsbSetupBuf->wIndexL == 0x07) { pDescr = WINUSB_Descriptor; //把设备描述符送到要发送的缓冲区 len = sizeof(WINUSB_Descriptor); } break; default: len = 0xFF; /*命令不支持*/ break; } } if (SetupLen > len) { SetupLen = len; //限制总长度 } len = SetupLen >= THIS_ENDP0_SIZE ? THIS_ENDP0_SIZE : SetupLen; //本次传输长度 memcpy(Ep0Buffer, pDescr, len); //加载上传数据 SetupLen -= len; pDescr += len; } else //标准请求 { switch (SetupReq) //请求码 { case USB_GET_DESCRIPTOR: switch (UsbSetupBuf->wValueH) { case 1: //设备描述符 pDescr = DevDesc; //把设备描述符送到要发送的缓冲区 len = sizeof(DevDesc); break; case 2: //配置描述符 pDescr = CfgDesc; //把设备描述符送到要发送的缓冲区 len = sizeof(CfgDesc); break; case 3: // 字符串描述符 switch (UsbSetupBuf->wValueL) { case 0: pDescr = (PUINT8)(&MyLangDescr[0]); len = sizeof(MyLangDescr); break; case 1: pDescr = (PUINT8)(&MyManuInfo[0]); len = sizeof(MyManuInfo); break; case 2: pDescr = (PUINT8)(&MyProdInfo[0]); len = sizeof(MyProdInfo); break; case 3: pDescr = (PUINT8)(&MySerNumber[0]); len = sizeof(MySerNumber); break; case 4: pDescr = (PUINT8)(&MyInterface[0]); len = sizeof(MyInterface); break; default: len = 0xFF; // 不支持的字符串描述符 break; } break; case 15: pDescr = (PUINT8)(&USB_BOSDescriptor[0]); len = sizeof(USB_BOSDescriptor); break; default: len = 0xff; //不支持的命令或者出错 break; } if (SetupLen > len) { SetupLen = len; //限制总长度 } len = SetupLen >= THIS_ENDP0_SIZE ? THIS_ENDP0_SIZE : SetupLen; //本次传输长度 memcpy(Ep0Buffer, pDescr, len); //加载上传数据 SetupLen -= len; pDescr += len; break; case USB_SET_ADDRESS: SetupLen = UsbSetupBuf->wValueL; //暂存USB设备地址 break; case USB_GET_CONFIGURATION: Ep0Buffer[0] = UsbConfig; if (SetupLen >= 1) { len = 1; } break; case USB_SET_CONFIGURATION: UsbConfig = UsbSetupBuf->wValueL; if (UsbConfig) { Ready = 1; //set config命令一般代表usb枚举完成的标志 } break; case 0x0A: break; case USB_CLEAR_FEATURE: //Clear Feature if ((UsbSetupBuf->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) // 端点 { switch (UsbSetupBuf->wIndexL) { case 0x82: UEP2_CTRL = UEP2_CTRL & ~(bUEP_T_TOG | MASK_UEP_T_RES) | UEP_T_RES_NAK; break; case 0x81: UEP1_CTRL = UEP1_CTRL & ~(bUEP_T_TOG | MASK_UEP_T_RES) | UEP_T_RES_NAK; break; case 0x02: UEP2_CTRL = UEP2_CTRL & ~(bUEP_R_TOG | MASK_UEP_R_RES) | UEP_R_RES_ACK; break; default: len = 0xFF; // 不支持的端点 break; } } else { len = 0xFF; // 不是端点不支持 } break; case USB_SET_FEATURE: /* Set Feature */ if ((UsbSetupBuf->bRequestType & 0x1F) == 0x00) /* 设置设备 */ { if ((((UINT16)UsbSetupBuf->wValueH << 8) | UsbSetupBuf->wValueL) == 0x01) { if (CfgDesc[7] & 0x20) { /* 设置唤醒使能标志 */ } else { len = 0xFF; /* 操作失败 */ } } else { len = 0xFF; /* 操作失败 */ } } else if ((UsbSetupBuf->bRequestType & 0x1F) == 0x02) /* 设置端点 */ { if ((((UINT16)UsbSetupBuf->wValueH << 8) | UsbSetupBuf->wValueL) == 0x00) { switch (((UINT16)UsbSetupBuf->wIndexH << 8) | UsbSetupBuf->wIndexL) { case 0x82: UEP2_CTRL = UEP2_CTRL & (~bUEP_T_TOG) | UEP_T_RES_STALL; /* 设置端点2 IN STALL */ break; case 0x02: UEP2_CTRL = UEP2_CTRL & (~bUEP_R_TOG) | UEP_R_RES_STALL; /* 设置端点2 OUT Stall */ break; case 0x81: UEP1_CTRL = UEP1_CTRL & (~bUEP_T_TOG) | UEP_T_RES_STALL; /* 设置端点1 IN STALL */ break; default: len = 0xFF; /* 操作失败 */ break; } } else { len = 0xFF; /* 操作失败 */ } } else { len = 0xFF; /* 操作失败 */ } break; case USB_GET_STATUS: Ep0Buffer[0] = 0x00; Ep0Buffer[1] = 0x00; if (SetupLen >= 2) { len = 2; } else { len = SetupLen; } break; default: len = 0xff; //操作失败 break; } } } else { len = 0xff; //包长度错误 } if (len == 0xff) { SetupReq = 0xFF; UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL; //STALL } else if (len <= THIS_ENDP0_SIZE) //上传数据或者状态阶段返回0长度包 { UEP0_T_LEN = len; UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK; //默认数据包是DATA1,返回应答ACK } else { UEP0_T_LEN = 0; //虽然尚未到状态阶段,但是提前预置上传0长度数据包以防主机提前进入状态阶段 UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK; //默认数据包是DATA1,返回应答ACK } break; case UIS_TOKEN_IN | 0: //endpoint0 IN switch (SetupReq) { case USB_GET_DESCRIPTOR: case 0x20: len = SetupLen >= THIS_ENDP0_SIZE ? THIS_ENDP0_SIZE : SetupLen; //本次传输长度 memcpy(Ep0Buffer, pDescr, len); //加载上传数据 SetupLen -= len; pDescr += len; UEP0_T_LEN = len; UEP0_CTRL ^= bUEP_T_TOG; //同步标志位翻转 break; case USB_SET_ADDRESS: USB_DEV_AD = USB_DEV_AD & bUDA_GP_BIT | SetupLen; UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; break; default: UEP0_T_LEN = 0; //状态阶段完成中断或者是强制上传0长度数据包结束控制传输 UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; break; } break; case UIS_TOKEN_OUT | 0: // endpoint0 OUT len = USB_RX_LEN; if (SetupReq == 0x09) { if (Ep0Buffer[0]) { } else if (Ep0Buffer[0] == 0) { } } UEP0_CTRL ^= bUEP_R_TOG; //同步标志位翻转 break; default: break; } UIF_TRANSFER = 0; //写0清空中断 } if (UIF_BUS_RST) //设备模式USB总线复位中断 { UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK; UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK; UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_NAK; USB_DEV_AD = 0x00; UIF_SUSPEND = 0; UIF_TRANSFER = 0; Endp2Busy = 0; UIF_BUS_RST = 0; //清中断标志 } if (UIF_SUSPEND) //USB总线挂起/唤醒完成 { UIF_SUSPEND = 0; if (USB_MIS_ST & bUMS_SUSPEND) //挂起 { } } else { //意外的中断,不可能发生的情况 USB_INT_FG = 0xFF; //清中断标志 } }
代码应该没有问题呀,咋提示没有兼容驱动
https://whycan.cn/files/members/390/TIM%E6%88%AA%E5%9B%BE20200222112207_20200222-1123.png
这个代码是从DAPLink来的吧?我遇到过类似的问题,结果发现是WinUSB描述符里面有个function subset header(对应于0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0xA0, 0x00那段),但是在只有一个function的时候不应该添加这个,否则就会报找不到驱动的错误,删除掉相关选项(顺便修改BOS和WinUSB描述符里面的对应长度)就可以用了。
@metro 厉害,USB我不太懂,弄这个小板子就是想学学USB
@metro有没有学USB的相关资料,今天晚上加上去试试
可以看看我发过的博文:Category: USB | MetroCores
不过一些比较新的东西我还没整理上去,一般我也是看看网上的博客或者直接啃标准
不太懂,需要手动装驱动,我只是代码搬运工,哈哈,大家一起改
找个可以自动装驱动的DAP-link看一下枚举过程,改一下枚举就行了吧
那我贴一下我的这部分代码吧,献丑了。
WinUSB我是实现的MS 2.0描述符,需要改bcdUSB为0210,之后需要响应Get Descriptor里面的BOS Descriptor,之后还需要响应一个Vendor Request,说起来麻烦但其实蛮简单的,参考开源版本的DAPLink就可以实现。
下面是BOS Descriptor的内容,其中USB_DESCRIPTOR_TYPE_BOS为15,USBD_WINUSB_VENDOR_CODE就是Vendor Request的bRequest的值。USB 2.0 Extension是我为了通过USB测试工具加上去的。
static __code uint8_t BOSDescriptor[] = {
5, // bLength
USB_DESCRIPTOR_TYPE_BOS,
_WBVAL(40), // wTotalLength
2, // bNumDeviceCaps
// USB 2.0 Extension
7, // bLength
USB_DESCRIPTOR_TYPE_DEVICE_CAPABILITY,
USB_DEVICE_CAPABILITY_TYPE_USB_20_EXTENSION,
0x00, 0x00, 0x00, 0x00, // bmAttributes
// WinUSB
28, // bLength
USB_DESCRIPTOR_TYPE_DEVICE_CAPABILITY, // bDescriptorType
USB_DEVICE_CAPABILITY_TYPE_PLATFORM, // bDevCapabilityType
0x00, // bReserved
0xDF, 0x60, 0xDD, 0xD8,
0x89, 0x45, 0xC7, 0x4C,
0x9C, 0xD2, 0x65, 0x9D,
0x9E, 0x64, 0x8A, 0x9F, // PlatformCapabilityUUID
// CapabilityData (Descriptor Set Information)
0x00, 0x00, 0x03, 0x06, // dwWindowsVersion
_WBVAL(162), // wDescriptorSetTotalLength
USBD_WINUSB_VENDOR_CODE, // bVendorCode
0 // bAltEnumCode
};
之后是WinUSB描述符,这个描述符就是在USBD_WINUSB_VENDOR_CODE对应的Vendor Request返回的东西。
static __code uint8_t WinUSBDescriptorSet[] = {
// set header
_WBVAL(10), // wLength
_WBVAL(MS_OS_20_SET_HEADER_DESCRIPTOR),
0x00, 0x00, 0x03, 0x06, // dwWindowsVersion
_WBVAL(162), // wTotalLength
// compatible ID descriptor
_WBVAL(20), // wLength
_WBVAL(MS_OS_20_FEATURE_COMPATBLE_ID),
'W', 'I', 'N', 'U', 'S', 'B', 0, 0, // CompatibleID
0, 0, 0, 0, 0, 0, 0, 0, // SubCompatibleID
// registry property
_WBVAL(132), // wLength
_WBVAL(MS_OS_20_FEATURE_REG_PROPERTY),
_WBVAL(MS_OS_20_PROPERTY_DATA_TYPE_REG_MULTI_SZ), // wPropertyDataType
_WBVAL(42), // wPropertyNameLength
'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0,
'I', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0,
'G', 0, 'U', 0, 'I', 0, 'D', 0, 's', 0, 0, 0, // PropertyName
_WBVAL(80), // wPropertyDataLength
'{', 0,
'C', 0, 'D', 0, 'B', 0, '3', 0, 'B', 0, '5', 0, 'A', 0, 'D', 0, '-', 0,
'2', 0, '9', 0, '3', 0, 'B', 0, '-', 0,
'4', 0, '6', 0, '6', 0, '3', 0, '-', 0,
'A', 0, 'A', 0, '3', 0, '6', 0, '-', 0,
'1', 0, 'A', 0, 'A', 0, 'E', 0, '4', 0, '6', 0, '4', 0, '6', 0, '3', 0, '7', 0, '7', 0, '6', 0,
'}', 0, 0, 0, 0, 0
};
是U-boot驱动的问题,实际上在config里面配置PWM引脚是无效的,而第一版Tiny200的背光默认关闭,所以就啥都没有了。
解决方法可以看我在这个帖子的回复: https://whycan.cn/t_3295.html#p28639
以前试过,不过现在有CMSIS-DAP v2协议,全速BULK也够了,换成GD32F350搞,这个免晶振,封装更小,很适合板上集成,代码: https://github.com/vllogic/vllink_lite
可以的,可能也很难找到更便宜的单片机了(CH55x跨平台不好搞,不过有空的话可以试试?
NUC505 应该是最佳。不过它的USB有两个坑,而且无法规避:
1. 如果存在两个相同端点号的IN OUT端点,同时收发时会出现数据错误。
2. 如果用到了同步端点,需根据勘误表用特定逻辑进行处理。综上,没事先去看看勘误表,不能头铁硬上。
我看过勘误表,确实如你所说,NUC505的USBD有两个主要问题,端点号这个算是硬件设计缺陷,包括USBD DMA在内的功能都只考虑端点而没有定义传输方向,因此基本上只能改端点号解决(当然EP0不会有问题,本身特殊处理过了);另外的问题主要是CPU、USBD DMA和USBD控制器(也就是勘误表里提到的USB Host)在同时访问Buffer时会出错(可能是Arbiter出问题了?),这个就按照勘误表提供的解决方案来做应该就没问题了。虽然暂时不打算用Isochronous Transfer,不过将来如果玩到UAC了还是可以考虑一下。
话说我记得层主好像研究过CMSIS-DAP,不知道有没有兴趣把这货移植到NUC505呢?不管是价格还是封装感觉比SAM3U2C都更有优势啊。
目前正在寻找支持高速USB(速度480 Mb/s)的单片机,希望电路面积尽可能小,最好是单芯片方案,要求如下:
内置高速USB的PHY,支持Device模式即可,不需要外接USB3300等芯片。
单芯片方案,不需要外接Flash即可工作(排除F1C100s这样的芯片)。
对存储空间的需求不大,目测128KB ROM/32KB RAM已经够用了。
对其它外设基本没有要求,只要有常用的GPIO/I2S/SPI/UART就可以了。
对引脚数量要求不高(10个引脚以内),封装越小越好(优先考虑QFP/QFN封装)。
芯片最好有通用的开发平台,例如Cortex-M系列。
在满足上述条件下,价格越低越好。
目前找到最合适的方案是新唐的NUC505系列,型号是NUC505YLA2A,是QFN48封装,价格在14元以内。不知道有没有更好的选择呢?
https://whycan.cn/files/members/457/usbAn.png
我也做了一个,把usb换成了typeC。
这个可以有,不知道硬件性能如何?那块SDRAM不知道是否用得上
谢谢回复。SD1的WP本来就是关闭的。
今天看了下面这个帖子,还是不知道改怎么做。
https://forums.xilinx.com/t5/Processor-System-Design/Zynq-SDIO-via-EMIO/td-p/327479
还好现在这个问题已经绕过了,不会block下一步,可以慢慢想办法。
昨天有其他人反应了相同的问题,这可能确实是PetaLinux的bug。可能暂时也没有什么好的解决方法,只能像USB部分一样手动修改吧。
开搞OpenVizsla usb 协议分析仪 (amoBBS 阿莫电子论坛)
似乎阿莫上已经有人在搞了,不过软件可能还不够完善(对于协议分析仪而言软件才是重头戏),暂时观望一下。
官网地址:OpenVizsla
瞄了一下方案,成本确实不高:USB PHY (USB3343) + FPGA (Xilinx Spartan 6 LX9) + FTDI (FT2232H),用PHY做sniffer确实挺有意思。
顺手贴一下原理图:ov_3_2_design.pdf
不知道有没有玩过的同学分享一下?
自己找到办法了。这个问题比较典型,网上资料也比较少,贴出来可能其他人会有用。我自己折腾了好几天。
主要参考了这个帖子:http://www.zedboard.org/content/ultrazed-iocc-sd-card-ro简单说一下原因。SD卡是有写保护的,但是TF卡是没有写保护的。根据AR#61064 (https://www.xilinx.com/support/answers/61064.html),硬件设计的时候应该把SDIO的WP脚拉低,但是EdgeBoard并没有这么做。
解决办法是在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi文件中增加以下内容:
&sdhci1 {
status = "okay";
max-frequency = <50000000>;
no-1-8-v; /* for 1.0 silicon */
disable-wp;
};
感谢分享,不过我还没试过把rootfs放在SD卡。。
不过如果我没记错的话,应该是可以在Vivado关闭WP的,不需要在设备树进行额外操作。所以要么是原本的Vivado工程有问题,要么就是PetaLinux的bug了。
最后说说CH558的USB缓冲机制。
经过测试发现,CH558实现的USB缓冲机制是最怂的方案:不管是否开了双缓冲,也不管发送/接收是否做了并行处理(也就是一边CPU复制一边USB传输),只要设置了回复NAK(不管是通过bUC_INT_BUSY还是手动修改UEPn_CTRL),且在数据传输阶段(即Data Phase)时当前中断仍处于活跃状态(可能更准确的说法是UIS_TRANSFER为1?不是很清楚内部机制),那么当前的Transaction就会自动回复NAK表示暂缓传输。
换句话说,所有基于ISR内复制的方案,单端点的最高速度都只能达到理论值的一半左右(一半时间用于回复NAK,当然不影响多个端点同时传输的情形)。(当然关掉bUC_INT_BUSY手动切换,在ISR中保证数据传输时不会出现冲突也是可行的,不过这样一来逻辑就复杂了,不如移到外面。)
好消息是我们总能保证数据是正确的,理论上不会出现新数据覆盖旧数据的情形。
不过,上面的结论从另一个方面证实了,只要ISR中的处理速度够快(大概100个周期还是够的?),把数据搬运的过程移到ISR之外,那么还是可以达到最高速度的。这也在某种程度上说明了双缓冲的优势是有局限性的,既然可以通过ISR中快速切换缓冲区的方式获得更大的缓冲区,那为什么还要用自带的双缓冲呢?因此,除非想好了确实有必要使用双缓冲,否则以CH558的硬件设计来看并没有很大必要。(猜测多缓冲可能更适合可以提前submit的架构,类似CY7C68013A?)
(只要处理速度够快,NAK就追不上我,单缓冲也一样)
昨天太晚了,起床继续。
接下来说一说一个很坑的点:UIS_TRANSFER和U_IS_NAK。
在datasheet中,只提到了UIS_TRANSFER表示传输完成,而U_IS_NAK表示接收到NAK。但是,众所周知,Host并不会发送NAK,所以这里应该是用设备发送NAK更为准确?另外,这个传输完成到底是什么意思?NAK和STALL算传输完成吗?无响应呢?
带着这些问题,我测试了一下各种搭配,结果令人震惊(夸张了哈):
先说U_IS_NAK,很明显是在发送NAK的时候会置1,这倒没啥好惊讶的。当然,要实现这个功能,就应该事先置bUIE_DEV_NAK为1。
比较令人困惑的是UIS_TRANSFER。在bUIE_DEV_NAK为0时,只有接收/发送ACK的时候置1,NAK和STALL没有反应(没有测试无响应,猜测和STALL一致);但是当bUIE_DEV_NAK为1时,如果接收/发送NAK,UIS_TRANSFER也会置1(STALL仍然没反应)。这不得不说是比较有趣的行为。
另外,在bUIE_DEV_NAK为1,且对应端点的bUEP_AUTO_TOG为1时,如果使用设备的接收功能(对应Bulk OUT)会出现奇怪的bug,表现是U_IS_NAK没有正确设置,且自动切换TOG的功能错误(反应就是U_TOG_OK的值错误),如下图所示。但是,只要上述两个flag的任意一个为0(不使能NAK中断,或是手动切换TOG),则功能又恢复正常。而且,这个bug并不影响Bulk IN,或是没有发送NAK的情形。这一点也是令人困惑。
鉴于以上原因,在使用CH558的USB功能时,我建议:
不要使用U_IS_NAK,也就是置bUIE_DEV_NAK为0。一般来说,对于NAK我们也不需要进行特殊的操作,而是让它重新传输一次就行了。
在不打开bUIE_DEV_NAK的时候,自动切换TOG还是好用的,当然手动切换也并不麻烦,只需要异或一下TOG就好了。
由于STALL信号不触发中断,因此对于Control Transfer而言,不能在Token为OUT(Host到Device方向)的时候设置Device为回复STALL,否则无法正确接收到下一个Setup包。正确的选择是,在需要表明Request Error的情况下,对于Control Read应当在Data Stage阶段回复STALL,而对于Control Write则只能在Status Stage阶段回复STALL。也就是说,只能令UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_STALL,而不能同时使用UEP_R_RES_STALL。
另外,在UEP0_CTRL的STALL设置优先级高于bUC_INT_BUSY发出的NAK,也就是说即使bUC_INT_BUSY为1,在ISR中仍然会优先发送STALL信号。由于STALL信号仅需发送一个周期(下一个周期应当恢复到Setup包),而Setup包本身的处理逻辑又十分复杂,在发送STALL表明Request Error之后(典型例子是全速设备的Device_Qualifier),如果接收到Setup包(对于SETUP Token正确回复ACK)并且按原有路径在ISR中处理Setup包,则会因为当前Setup包的处理时间过长,从而对于当前的Request错误地回复STALL(也就是说在ISR中已经经历了Setup和Data两个Stage),造成逻辑错误。对此,解决方案是在接收到Setup包时第一步先将UEP0_CTRL的STALL去掉(见case UIS_TOKEN_SETUP下一行),之后正常处理便是(bUC_INT_BUSY在此时会正确地回复NAK)。
下文将会按顺序说一下调试CH558T时踩到的坑。
首先,时钟和休眠之类的倒还好(除了一开始忘了SAFE_MOD这回事儿懵逼了半天)。为了支持USB Suspend,需要在WAKE_CTRL开启USB唤醒,不过可以不用在中断里面开启Suspend,在我的代码里面是直接在loop部分判断bUMS_SUSPEND了,这样的好处是保证在当前不活动的情况下再进入休眠模式,不会影响当前的工作。
USB SIE的重置是一个小坑。原本我是在USB接收到Reset中断时对SIE进行重置(USB_CTRL = bUC_RESET_SIE | bUC_CLR_ALL,当然还有相关的初始化),但是发现这样处理之后SIE就会进入无响应状态,可能是因为重置需要一定时间。将重置改成只在Initialize阶段进行就解决了。
大概解释一下程序的功能吧。
如上文所述,这是个USB性能测试程序。匹配的上位机程序实际上是来自于Cypress FX2(也就是大名鼎鼎的CY7C68013A的所属系列),名为CyStreamer。该程序有配套的CY7C68013A固件,如果买了相关开发板的可以直接烧录并测试。
本身测试程序的功能比较简单,就是扫描除了EP0外的可用Endpoints(包括Altenate Settings),并且通过模拟传输的方式测试速度(包括IN和OUT);另外,还通过P2.5(TNOW)输出当前USB的状态,在调用USBD_Initialize和USBD_ISR时将会置高以表示处于活动状态。该程序并没有包括回环测试(loopback),我个人认为loopback更适合用CDC类作为虚拟串口的方式实现,而这在官方demo中已经实现了。
当然,为了使用Cypress的程序,这里需要把VID和PID改一改,这样才能用上官方的驱动,从而可被CyUSB.dll调用。其它描述符也尽可能照着官方固件的改了。
写这个程序的目的呢,除了实现USB协议之外,其实也有探究CH55x的USB实现的想法。所以,这里设置了4套端点(IN和OUT都有),分别对应以下几种情况:
EP1 IN/OUT:双缓冲,ISR中无复制
EP2 IN/OUT:双缓冲,ISR中有复制(使用memcpy,下同)
EP3 IN/OUT:单缓冲,ISR中无复制
EP4 IN/OUT:单缓冲,ISR中有复制
首先说说双缓冲机制。很明显,这是为了提升传输速度用的:毕竟这种级别的单片机主频不太高且执行周期长,移动速度未必比USB FS本身的读写快多少,如果没有缓冲的话,复制就要占据大部分时间,从而在复制完成前没办法准备下一阶段的传输,这通常意味着下一次传输通常只能NAK。对于没有PING机制的全速设备而言,Bulk OUT尤其是问题(毕竟需要传输一整个Packet后才能答复NAK)。双缓冲机制可以通过允许提前准备下一阶段的传输,从而比较有效地解决这个问题。
那么,在单缓冲的情况下,要实现数据的搬移,就不得不和ISR扯上关系了——要么直接在ISR中复制数据,要么直接在ISR中置为NAK,等到正常执行的阶段准备好下一阶段的传输之后再置为ACK,而后者肯定比前者更慢。因此,EP1和EP4应该是最为常用的两种情况,而EP2和EP3则是作为对照组存在。
当然,如果ISR的处理速度够快(通常中断都是在本次传输结束后开始的,因此对应的是本次传输结束到下次传输开始,当然理论上下次传输的Token Phase时也是来得及操作的),其实还有一种方案:自己维护缓冲区(通常是环形缓冲),端点缓冲只使用单缓冲,并且在ISR中切换当前缓冲。下面的测试将会看到,即使是CH558这种级别的单片机,也是完全来得及的。
下面分享一下代码。由于实现了比较完整的协议栈(虽然没有Class,不过Request基本都实现了),所以代码体积略大,不过编译后也不到2K。
代码经过官方工具USB3CV的验证(Test All Pass),所以大概率不会有很大问题,就当献丑了。
源程序是在Keil编译的,后面有时间再尝试一下SDCC。头文件CH558.H需要自备。
#include "../CH558.H"
#include <string.h>
#define bMaxPacketSize0 64
#define wMaxPacketSize 64
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define HI(x) ((x) >> 8)
#define LO(x) ((x) & 0xFF)
#define WBVAL(x) LO(x), HI(x)
#define UIS_EP_IN(x) (UIS_TOKEN_IN | (x))
#define UIS_EP_OUT(x) (UIS_TOKEN_OUT | (x))
#define EP_IN(x) (USB_ENDP_DIR_MASK | (x))
#define EP_OUT(x) (x)
UINT8C DeviceDescriptor[] = {
18, // bLength
1, // bDescriptorType, DEVICE
WBVAL(0x0200), // bcdUSB
0x00, // bDeviceClass
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
bMaxPacketSize0, // bMaxPacketSize0
WBVAL(0x04B4), // idVendor
WBVAL(0x1003), // idProduct
WBVAL(0x0000), // bcdDevice
1, // iManufacturer
2, // iProduct
0, // iSerialNumber
1 // bNumConfigurations
};
UINT8C ConfigurationDescriptor[] = {
9, // bLength
2, // bDescriptorType, CONFIGURATION
WBVAL(74), // wTotalLength
1, // bNumInterfaces
1, // bConfigurationValue
0, // iConfiguration
0x80, // bmAttributes
250, // bMaxPower
// Interface 0
9, // bLength
4, // bDescriptorType, INTERFACE
0, // bInterfaceNumber
0, // bAlternateSetting
8, // bNumEndpoints
0xFF, // bInterfaceClass
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// Endpoint 1 IN
7, // bLength
5, // bDescriptorType, ENDPOINT
0x81, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 1 OUT
7, // bLength
5, // bDescriptorType, ENDPOINT
0x01, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 2 IN
7, // bLength
5, // bDescriptorType, ENDPOINT
0x82, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 2 OUT
7, // bLength
5, // bDescriptorType, ENDPOINT
0x02, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 3 IN
7, // bLength
5, // bDescriptorType, ENDPOINT
0x83, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 3 OUT
7, // bLength
5, // bDescriptorType, ENDPOINT
0x03, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 4 IN
7, // bLength
5, // bDescriptorType, ENDPOINT
0x84, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0, // bInterval
// Endpoint 4 OUT
7, // bLength
5, // bDescriptorType, ENDPOINT
0x04, // bEndpointAddress
0x02, // bmAttributes
WBVAL(wMaxPacketSize), // wMaxPacketSize
0 // bInterval
};
UINT8C LanguageString[] = {
4, // bLength
3, // bDescriptorType, STRING
WBVAL(0x0409)
};
UINT8C ManufacturerString[] = {
9, // bLength
3, // bDescriptorType, STRING
'C', 'y', 'p', 'r', 'e', 's', 's'
};
UINT8C ProductString[] = {
11, // bLength
3, // bDescriptorType, STRING
'C', 'Y', '-', 'S', 't', 'r', 'e', 'a', 'm'
};
PUINT8C StringDescriptors[] = {
LanguageString,
ManufacturerString,
ProductString
};
#define STR_DESC_NUM (sizeof(StringDescriptors) / sizeof(PUINT8C))
static UINT8XV EP0Buf[64] _at_ 0x0;
static UINT8XV EP4Buf[2][64] _at_ 0x40;
static UINT8XV EP1Buf[4][64] _at_ 0x100;
static UINT8XV EP2Buf[4][64] _at_ 0x200;
static UINT8XV EP3Buf[2][64] _at_ 0x300;
static UINT8XV unused[64] _at_ 0x400;
struct {
UINT8 req;
UINT16 len;
PUINT8C desc;
} data setup;
#define SetupPacket ((const USB_SETUP_REQ xdata *)EP0Buf)
#define GET_CONFIGURED() (USB_DEV_AD & bUDA_GP_BIT)
#define SET_CONFIGURED() (USB_DEV_AD |= bUDA_GP_BIT)
#define CLR_CONFIGURED() (USB_DEV_AD &= ~bUDA_GP_BIT)
void Clock_Initialize(void)
{
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
PLL_CFG = 7 << 5 | 28 << 0;
CLOCK_CFG = bOSC_EN_INT | 6 << 0;
SLEEP_CTRL = bSLP_OFF_USB | bSLP_OFF_ADC | bSLP_OFF_UART1 | bSLP_OFF_SPI0 | bSLP_OFF_TMR3 | bSLP_OFF_LED;
++SAFE_MOD;
}
void Port_Initialize(void)
{
PORT_CFG &= ~bP2_OC;
TNOW = 0;
P2_PU &= ~bTNOW;
P2_DIR |= bTNOW;
}
void USBD_Initialize(void)
{
TNOW = 1;
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
SLEEP_CTRL &= ~bSLP_OFF_USB;
WAKE_CTRL |= bWAK_BY_USB;
++SAFE_MOD;
USB_CTRL = bUC_RESET_SIE | bUC_CLR_ALL;
USB_CTRL = bUC_DEV_PU_EN | bUC_INT_BUSY | bUC_DMA_EN;
UDEV_CTRL = bUD_DP_PD_DIS | bUD_DM_PD_DIS | bUD_PORT_EN;
UEP0_DMA = (UINT16)EP0Buf;
UEP1_DMA = (UINT16)EP1Buf;
UEP2_DMA = (UINT16)EP2Buf;
UEP3_DMA = (UINT16)EP3Buf;
// UEP4_DMA = UEP0_DMA + 64
UEP4_1_MOD = bUEP1_RX_EN | bUEP1_TX_EN | bUEP1_BUF_MOD | bUEP4_RX_EN | bUEP4_TX_EN;
UEP2_3_MOD = bUEP3_RX_EN | bUEP3_TX_EN | bUEP2_RX_EN | bUEP2_TX_EN | bUEP2_BUF_MOD;
USB_INT_EN = bUIE_TRANSFER | bUIE_BUS_RST;
USB_INT_FG = 0xFF;
IE_USB = 1;
TNOW = 0;
}
void USBD_ISR(void) interrupt INT_NO_USB using 1
{
TNOW = 1;
if (UIF_TRANSFER) {
if (U_TOG_OK)
switch (USB_INT_ST & (MASK_UIS_TOKEN | MASK_UIS_ENDP)) {
case UIS_EP_IN(1):
break;
case UIS_EP_OUT(1):
break;
case UIS_EP_IN(2):
memcpy((PUINT8XV)USB_DMA, unused, wMaxPacketSize);
break;
case UIS_EP_OUT(2):
memcpy(unused, (PUINT8XV)USB_DMA, USB_RX_LEN);
break;
case UIS_EP_IN(3):
break;
case UIS_EP_OUT(3):
break;
case UIS_EP_IN(4):
memcpy((PUINT8XV)USB_DMA, unused, wMaxPacketSize);
UEP4_CTRL ^= bUEP_T_TOG;
break;
case UIS_EP_OUT(4):
memcpy(unused, (PUINT8XV)USB_DMA, USB_RX_LEN);
UEP4_CTRL ^= bUEP_R_TOG;
break;
case UIS_EP_IN(0):
if ((setup.req & 0x80) == USB_REQ_TYP_IN) {
if ((setup.req & 0x7F) == USB_GET_DESCRIPTOR) {
UEP0_T_LEN = (UINT8)MIN(setup.len, bMaxPacketSize0);
memcpy(EP0Buf, setup.desc, UEP0_T_LEN);
setup.desc += UEP0_T_LEN;
setup.len -= UEP0_T_LEN;
UEP0_CTRL ^= bUEP_T_TOG;
}
} else {
// Status Stage finished
if ((setup.req & 0x7F) == USB_SET_ADDRESS)
USB_DEV_AD = *(UINT8D *)&setup.len;
setup.req = 0xFF;
UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
}
break;
case UIS_EP_OUT(0):
if ((setup.req & 0x80) == USB_REQ_TYP_OUT) {
// Not possible unless SET_DESCRIPTOR
UEP0_CTRL ^= bUEP_R_TOG;
} else {
// Status Stage finished
setup.req = 0xFF;
UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
}
break;
case UIS_TOKEN_SETUP:
// Avoid STALL because of slow Setup Packet handling
UEP0_CTRL = UEP0_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_NAK;
setup.req = SetupPacket->bRequestType & 0x80 | SetupPacket->bRequest;
switch (SetupPacket->bRequest) {
case USB_GET_STATUS:
if ((SetupPacket->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_DEVICE ||
(SetupPacket->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_INTERF && SetupPacket->wIndexL == 0) {
EP0Buf[0] = 0x0;
} else if ((SetupPacket->bRequestType & USB_REQ_RECIP_MASK) == USB_REQ_RECIP_ENDP) {
if (!GET_CONFIGURED()) {
if (SetupPacket->wIndexL == 0)
EP0Buf[0] = 0x0;
else
goto REQUEST_ERROR;
} else {
switch (SetupPacket->wIndexL) {
case EP_IN(0):
case EP_OUT(0):
EP0Buf[0] = 0x0;
break;
case EP_IN(1):
EP0Buf[0] = !!((UEP1_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
break;
case EP_OUT(1):
EP0Buf[0] = !!((UEP1_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
break;
case EP_IN(2):
EP0Buf[0] = !!((UEP2_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
break;
case EP_OUT(2):
EP0Buf[0] = !!((UEP2_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
break;
case EP_IN(3):
EP0Buf[0] = !!((UEP3_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
break;
case EP_OUT(3):
EP0Buf[0] = !!((UEP3_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
break;
case EP_IN(4):
EP0Buf[0] = !!((UEP4_CTRL & MASK_UEP_T_RES) == UEP_T_RES_STALL);
break;
case EP_OUT(4):
EP0Buf[0] = !!((UEP4_CTRL & MASK_UEP_R_RES) == UEP_R_RES_STALL);
break;
default:
goto REQUEST_ERROR;
}
}
} else {
goto REQUEST_ERROR;
}
EP0Buf[1] = 0x0;
UEP0_T_LEN = 2;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case USB_CLEAR_FEATURE:
switch (SetupPacket->wValueL) {
case 0: // ENDPOINT_HALT
if (!GET_CONFIGURED())
goto REQUEST_ERROR;
switch (SetupPacket->wIndexL) {
case EP_IN(1):
UEP1_CTRL = UEP1_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
break;
case EP_OUT(1):
UEP1_CTRL = UEP1_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
break;
case EP_IN(2):
UEP2_CTRL = UEP2_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
break;
case EP_OUT(2):
UEP2_CTRL = UEP2_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
break;
case EP_IN(3):
UEP3_CTRL = UEP3_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
break;
case EP_OUT(3):
UEP3_CTRL = UEP3_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
break;
case EP_IN(4):
UEP4_CTRL = UEP4_CTRL & ~(MASK_UEP_T_RES | bUEP_T_TOG) | UEP_T_RES_ACK;
break;
case EP_OUT(4):
UEP4_CTRL = UEP4_CTRL & ~(MASK_UEP_R_RES | bUEP_R_TOG) | UEP_R_RES_ACK;
break;
default:
goto REQUEST_ERROR;
}
UEP0_T_LEN = 0;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case 1: // DEVICE_REMOTE_WAKEUP
case 2: // TEST_MODE
default:
goto REQUEST_ERROR;
}
break;
case USB_SET_FEATURE:
switch (SetupPacket->wValueL) {
case 0: // ENDPOINT_HALT
if (!GET_CONFIGURED())
goto REQUEST_ERROR;
switch (SetupPacket->wIndexL) {
case EP_IN(1):
UEP1_CTRL = UEP1_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
break;
case EP_OUT(1):
UEP1_CTRL = UEP1_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
break;
case EP_IN(2):
UEP2_CTRL = UEP2_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
break;
case EP_OUT(2):
UEP2_CTRL = UEP2_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
break;
case EP_IN(3):
UEP3_CTRL = UEP3_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
break;
case EP_OUT(3):
UEP3_CTRL = UEP3_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
break;
case EP_IN(4):
UEP4_CTRL = UEP4_CTRL & ~MASK_UEP_T_RES | UEP_T_RES_STALL;
break;
case EP_OUT(4):
UEP4_CTRL = UEP4_CTRL & ~MASK_UEP_R_RES | UEP_R_RES_STALL;
break;
default:
goto REQUEST_ERROR;
}
UEP0_T_LEN = 0;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case 1: // DEVICE_REMOTE_WAKEUP
case 2: // TEST_MODE
default:
goto REQUEST_ERROR;
}
break;
case USB_SET_ADDRESS:
// USB Address should be changed after handshake packet, so temporarily kept in len
*(UINT8D *)&setup.len = SetupPacket->wValueL;
UEP0_T_LEN = 0;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case USB_GET_DESCRIPTOR:
setup.len = (UINT16)SetupPacket->wLengthL << 0 | (UINT16)SetupPacket->wLengthH << 8;
switch (SetupPacket->wValueH) {
case USB_DESCR_TYP_DEVICE:
if (SetupPacket->wValueL > 0)
goto REQUEST_ERROR;
setup.desc = DeviceDescriptor;
if (setup.desc[0] < setup.len)
setup.len = setup.desc[0];
break;
case USB_DESCR_TYP_CONFIG:
if (SetupPacket->wValueL > 0)
goto REQUEST_ERROR;
setup.desc = ConfigurationDescriptor;
// Assuming wTotalLength < 256
if (setup.desc[2] < setup.len)
setup.len = setup.desc[2];
break;
case USB_DESCR_TYP_STRING:
if (SetupPacket->wValueL >= STR_DESC_NUM)
goto REQUEST_ERROR;
setup.desc = StringDescriptors[SetupPacket->wValueL];
if (setup.desc[0] < setup.len)
setup.len = setup.desc[0];
break;
case USB_DESCR_TYP_INTERF:
case USB_DESCR_TYP_ENDP:
case USB_DESCR_TYP_QUALIF:
case USB_DESCR_TYP_SPEED:
default:
goto REQUEST_ERROR;
}
UEP0_T_LEN = (UINT8)MIN(setup.len, bMaxPacketSize0);
memcpy(EP0Buf, setup.desc, UEP0_T_LEN);
setup.desc += UEP0_T_LEN;
setup.len -= UEP0_T_LEN;
UEP0_CTRL = bUEP_R_TOG | bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case USB_GET_CONFIGURATION:
EP0Buf[0] = !!GET_CONFIGURED();
UEP0_T_LEN = 1;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case USB_SET_CONFIGURATION:
switch (SetupPacket->wValueL) {
case 0:
CLR_CONFIGURED();
UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
UEP3_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
UEP4_CTRL = UEP_R_RES_STALL | UEP_T_RES_STALL;
break;
case 1:
SET_CONFIGURED();
UEP1_T_LEN = wMaxPacketSize;
UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
UEP2_T_LEN = wMaxPacketSize;
UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
UEP3_T_LEN = wMaxPacketSize;
UEP3_CTRL = bUEP_AUTO_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
UEP4_T_LEN = wMaxPacketSize;
UEP4_CTRL = UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
default:
goto REQUEST_ERROR;
}
UEP0_T_LEN = 0;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case USB_GET_INTERFACE:
if (!GET_CONFIGURED() || SetupPacket->wIndexL != 0)
goto REQUEST_ERROR;
EP0Buf[0] = 0;
UEP0_T_LEN = 1;
UEP0_CTRL = bUEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
break;
case USB_SET_DESCRIPTOR:
// Unsupported, STALL at Status Stage
UEP0_CTRL = bUEP_R_TOG | UEP_R_RES_ACK | UEP_T_RES_STALL;
break;
case USB_SET_INTERFACE: // STALL to use default alt settings
case USB_SYNCH_FRAME:
default:
REQUEST_ERROR:
UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_STALL;
}
}
}
if (UIF_BUS_RST) {
UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
UEP1_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
UEP2_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
UEP3_CTRL = bUEP_AUTO_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
UEP4_CTRL = UEP_R_RES_STALL | UEP_T_RES_STALL;
USB_DEV_AD = 0x00;
}
USB_INT_FG = 0xFF;
TNOW = 0;
}
void main(void)
{
Clock_Initialize();
Port_Initialize();
USBD_Initialize();
EA = 1;
for (;;) {
if (USB_MIS_ST & bUMS_SUSPEND)
PCON |= PD;
}
}
先晒一个调试组合,逻辑分析仪+CH558T最小系统板,随身携带不碍事儿~
有一说一,对于USB协议的分析真的是需要专门的工具(特别是不带驱动的裸机开发),全速设备尚且可以拿普通的逻辑分析仪凑合一下,高速以上的设备还是需要更专业的协议分析仪,可以少走很多弯路(很多底层的错误是没办法在软件层面发现的)。
另外,CH55x芯片虽然性价比高,但是调试一直是个老大难问题,不仅没有专用的调试接口(基本靠GPIO,串口偶尔能用,ISD51和USB八字不合就不要想了),而且低端的CH551/2/4还有烧写次数的限制(200次,所以这也就是我买CH558的原因,能折腾),烧写还非常麻烦(我是基本靠USB,必须按着按键上电才行,通电时重启都不好使)。为此,我DIY了一根带开关的USB信号线(某宝有这玩意,但是快递已经基本停业了),勉强还能用吧。后面考虑一下用单片机做个简单的烧录器,通过按键时间长短决定是否进入bootloader。
由于众所周知的原因,这几天宅在家里闲来无事(毕业论文:?那我呢),刚好这次回家带了个CH558T开发板(主要是体积小方便带),于是乎研究了一下USB功能。
刚好一直以来对USB协议挺感兴趣,所以这次没有参考官方demo,而是照着datasheet自己敲了个USB协议栈,调试了两天终于能用了,来坑网分享一下源代码核相关的调试经验。
目前实现的功能是对多种不同的传输方式组合进行速度测试,在保证性能的同时发现一些datasheet没有提及的问题,需要配合上位机进行测试。
当然,鼓捣这玩意儿还是有目的的,不过现在先卖个关子(否则到时咕掉了就不好意思了)。目前看来,CH552的资源和性能可能稍微有些不够用,CH558挺好的,虽然调试确实有点头疼
可以参考这篇文章:Raspberry Pi 4-pole Audio/Video Jack。
看起来应该是把mic的接口改成了Composite Video,也就是标准的4段设计。不过,由于TRRS接口的定义本就没有统一(有所谓的国标和美标之分),而且也不是标准接口,所以用的确实也比较少。
已上车,买了一块,好像有硬件问题,板子无法启动:o
尝试按照说明文档烧写固件,mfgtool停在 Jumping to OS image.
Debug输出:
U-Boot 2009.08-00683-gb8f6a79-dirty (Mar 10 2014 - 11:12:01)CPU: Freescale i.MX6 family TO1.2 at 792 MHz
Thermal sensor with ratio = 186
Temperature: 31 C, calibration data 0x59a51d5f
mx6q pll1: 792MHz
mx6q pll2: 528MHz
mx6q pll3: 480MHz
mx6q pll8: 50MHz
ipg clock : 66000000Hz
ipg per clock : 66000000Hz
uart clock : 80000000Hz
cspi clock : 60000000Hz
ahb clock : 132000000Hz
axi clock : 198000000Hz
emi_slow clock: 99000000Hz
ddr clock : 396000000Hz
usdhc1 clock : 198000000Hz
usdhc2 clock : 198000000Hz
usdhc3 clock : 198000000Hz
usdhc4 clock : 198000000Hz
nfc clock : 24000000Hz
Board: i.MX6DL/Solo-RIoTboard: 0x61012 [POR ]
Boot Device: MMC
I2C: ready
DRAM: 1 GBhttps://whycan.cn/files/members/1651/Snipaste_2020-01-14_11-41-47.png
https://whycan.cn/files/members/1651/Snipaste_2020-01-14_11-41-30.png尝试了tf卡和emmc,都是不能启动,估计是翻车了。。。
实话说我还没测过官方的镜像。。不过mfgtool是自带配置文件的,可能是配置与实际不符。可以试试我推荐的WinCE能不能用,不行的话估计就真的是硬件问题了。
cmsis-dap慢的主要原因是V1版的HID协议,而非USB Full Speed。下图是全速CMSIS-DAP v2(spi时序优化)的flash下载速度和sram读速度,分别是82KB和305KB。
https://whycan.cn/files/members/1446/TIM%E6%88%AA%E5%9B%BE20200110104059.png我个人分析认为,当前低成本调试器方案应该选用3-5元成本的全速MCU,批量包括外壳的成本甚至可以控制到10元以内,而速度完全可以媲美j-link v8/v9。
感谢分享。确实USB HID的传输效率不高,V2换成了Bulk Transfer,应该会好不少。另外,由于IO频率限制,高速USB的提升也不是很大。
之前曾经设想过使用CH55x系列单片机做个支持CMSIS-DAP V2的调试器,不过咕咕咕了。。之后有时间的话可能试着写一下。
我编译了两个支持cmsis-dap v2的openocd执行文件:https://github.com/vllogic/openocd_cmsis-dap_v2/releases
cmsis-dap v2对于ARM芯片效果很好,对于riscv或者esp32效果不佳,不过这个锅应该扣给openocd
感谢分享!
根据之前的经验,OpenOCD的速度不行,一方面是没有利用好CMSIS-DAP的Atomic Commands(一次性处理多条指令),配合USB 2.0 FS的传输频率(最高1 kHz),一次只能处理一次寄存器读写,速度自然十分感人;另一方面则是无效操作过多,有一些错误或者多余的指令,使得传输效率远低于J-Link。当然这也和CMSIS-DAP本身就是适配ARM处理器有关,不过感觉还有很大的优化空间。
使用NXP LPC8N04官方开发板的demo的方法我已近试过了,
LPC8N04没有把从天线收集到的能源(Energy harvesting)输出的专用口,不过从datasheet上看可以使用IO口(PIO0_3, PIO0_7, PIO0_10 and PIO0_11)给外部设备供电:
PIO0 current source mode
PIO0_3, PIO0_7, PIO0_10 and PIO0_11 are high-source pads that can deliver up to
20 mA to the load. These PIO pins can be set to either digital mode or analog current sink
mode. In digital mode, the output voltage of the pad switches between VSS and VDD. In
analog current drive mode, the output current sink switches between the values set by the
ILO and IHI bits. The maximum pad voltage is limited to 5 V.以上来自LPC8N04 datasheet,一个IO口最多可以输出20mA,如果是3.3V的话(为甚么是3.3呢),可以输出最大3.3*0.02=0.066=66mW,
我随便找个墨水瓶的参数
工作电压: 3.3V
通信接口: 3-wire SPI、4-wire SPI
外形尺寸: 90.1mm × 77.0mm × 1.18mm
显示尺寸: 84.8mm × 63.6mm
点 距: 0.212 × 0.212
分 辨 率: 400 × 300
显示颜色: 红、黑、白
灰度等级: 2
全局刷新: 15s
刷新功耗: 26.4mW(typ.)
待机功耗: <0.017mW
可视角度: >170°
来自:http://www.waveshare.net/shop/4.2inch-e-Paper-B.htm
66mW > 26.4mW,从这参数来看不用外部电源,是可以使用LPC8N04做一个通过NFC修改墨水瓶的设备,我实际测试结果是NXP LPC8N04官方开发板的demo 输出的电压只有1.8V(使用小米note3测试)驱动不起来,原因可能是我的手机输出功率问题,也可能是这块板子的天线没做好
https://whycan.cn/files/members/930/_20200106182305.jpg
我翻了一下User Manual,似乎NFC的输出电压就是1.8 V(没有明说,只有下面一张图,注意VNFC的值):
然后IO Pad是接到VDDE上的,而VDDE直接和VDD_ALON相连:
所以在被动模式下最大电压应该是1.8 V了,应该不是其它方面的问题。
问题解决了,是HDMI线造成的,重新换了根接上就正常了
https://whycan.cn/files/members/2544/QQ%E5%9B%BE%E7%89%8720200101193559.jpg
可能是因为线芯数量不够,需要准备一根19pin全引出的mini HDMI线。
最近想测试一下买的触摸屏是否可以正常使用,正巧从箱底翻出了吃灰多年的pyboard(其实是使用STM32F407的国产版,不过没区别啦),配合VSCode + Pymakr,很容易就驱动起来了。
贴个测试图和代码:
import struct
import time
from pyb import Pin, ExtInt, Switch
from machine import I2C
from micropython import schedule
import micropython
micropython.alloc_emergency_exception_buf(1000)
class GT911:
def __init__(self, i2c, rst_label, int_label):
self.i2c = i2c
self.rst_pin = Pin(rst_label, Pin.OUT_PP)
self.int_pin = Pin(int_label, Pin.OUT_PP)
# Assert reset
self.rst_pin.low()
self.int_pin.low()
time.sleep_ms(10)
# Deassert reset
self.rst_pin.high()
# Wait until reset done
time.sleep_ms(5)
# Release INT pin for interrupt
self.int_pin = Pin(int_label, Pin.PULL_UP)
self.gt911_addr = 0x5D
def read_reg(self, reg_addr, nbytes):
return self.i2c.readfrom_mem(self.gt911_addr, reg_addr, nbytes, addrsize = 16)
def write_reg(self, reg_addr, data):
self.i2c.writeto_mem(self.gt911_addr, reg_addr, data, addrsize = 16)
def get_touch_point(self, id):
info = self.i2c.readfrom_mem(self.gt911_addr, 0x8148 + id * 0x8, 0x8, addrsize = 16)
return tuple([struct.unpack('<h', info[0:2])[0], struct.unpack('<h', info[2:4])[0], struct.unpack('<h', info[4:6])[0], int(info[7])])
def register_callback(self, callback):
self.extint = ExtInt(self.int_pin, ExtInt.IRQ_FALLING, Pin.PULL_UP, callback)
gt911 = GT911(I2C(1, freq = 100000), 'X12', 'X11')
def gt911_isr(_):
# Get status register
sta_reg = gt911.read_reg(0x814E, 1)[0]
if sta_reg & 0x80 == 0:
return
# Get number of touch points
npoints = sta_reg & 0xF
# Display touch points
print('\r', end = '')
for id in range(npoints):
info = gt911.get_touch_point(id + 1)
print('(%4d, %3d) ' % (info[0], info[1]), end = '')
# Fill blank to flush display
for id in range(5 - npoints):
print(' ', end = '')
# Clear interrupt
gt911.write_reg(0x814E, bytearray([0]))
print('Press USR button to stop')
# Attach ISR
gt911.register_callback(lambda e: schedule(gt911_isr, e))
while Switch().value() == False:
pass
# Detach ISR
gt911.register_callback(None)
又尝试了一下QSPI Flash的编译,又遇到了一些问题,不过最后还是解决了。
根据PetaLinux工具文档,QSPI Flash的编译需要修改以下配置(还是运行petalinux-config进行修改):
Subsystem AUTO Hardware Settings - Advanced Bootable Images Storage Settings,选择JFFS2,并且进行相关配置(主要是把各种device都改成primary flash)。
Subsystem AUTO Hardware Settings - Flash Settings,修改分区表信息,需要在boot、bootenv、kernel之后再建立一个jffs2分区,并分配合适的空间。
Image Packaging Configuration,确保Root filesystem type为JFFS2,并且调整jffs2 erase block size(本例为64 KiB)。
在修改并重新编译后,将BOOT.bin、image.ub和rootfs.jffs2文件按照分区表拼接在一起,并且通过SD卡写入到QSPI Flash中。
但是,重启之后发现,在Linux启动的时候,将会弹出大量的jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at ...的错误。
搜索了一下,发现是因为内核默认使用了4K大小的erase sector,但当前的QSPI Flash不支持,因此出现此错误。
知道了问题,解决方法就简单了。运行petalinux-config -c kernel进行内核配置,进入到Device Driver - Memory Technology Device (MTD) support - SPI-NOR device support,关闭Use small 4096 B erase sectors即可。
最后同样上传一下编译好的flash镜像:flash.zip
上传一个自己编译好的版本,只添加了一些基本的接口,欢迎大家测试
EdgeBoard_20191228-1853.zip
最后提一下遇到的问题。
第一次移植的时候,我发现USB功能无法正常使用,表现是找不到USB控制器,并且对于U-boot和Linux都是如此。
之后搜索了一下,发现问题是没有正确配置USB所致。USB驱动需要指定dual role的具体使用方式,但是默认的设备树没有配置。其实在U-boot启动时也会报错找不到dr_mode。
解决方案是修改设备树。打开项目文件夹下的project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi文件,在末尾添加以下代码即可:
&dwc3_0 {
dr_mode = "host";
};
之后重新执行petalinux-build和petalinux-package,即可正常使用USB。
接下来是编译PetaLinux。
首先是新建工程,在目标文件夹下运行:
petalinux-create --type project --template zynqMP --name demo
这样就会在当前文件夹下新建一个名为demo的文件夹。之后项目的默认名称均为demo。
在开始下一步操作之前,需要切换到项目文件夹:
cd demo
接下来需要从工程中导入配置文件。在最新的Vivado/Vitis中,导出的配置文件已经从HDF变更为XSA。可以在Vivado中通过File - Export - Export Hardware导出XSA文件。之后在PetaLinux中导入XSA文件:
petalinux-config --get-hw-description=<PATH-TO-HDF/XSA DIRECTORY>
稍等片刻之后,将会弹出menuconfig,这里可以简单地修改配置。特别需要注意的是串口的选择,我们这里需要选择ps_uart_1,以对应板子上的USB UART接口。
在配置完毕后,开始正式编译文件:
petalinux-build
之后等待编译完成即可。在我的笔记本(i5 8300H)上需要10分钟左右。
最后,如果需要在SD卡上执行,需要打包BOOT.bin文件:
petalinux-package --boot --format BIN --fsbl images/linux/zynqmp_fsbl.elf --u-boot --fpga <BITSTREAM-PATH>
之后将images/linux文件夹下的BOOT.bin和image.ub文件复制到SD卡的第一个分区(需要格式化为FAT 16/32),之后将拨码开关调整为SD1,插入SD卡启动即可。
如果需要JTAG启动,则需要将拨码开关调整为JTAG,上电并连接好调试器后执行下面命令即可:
petalinux-boot --jtag --uboot --kernel --fpga
JTAG启动的时间比较长,需要耐心等待。
近来尝试了一下Xilinx的PetaLinux,感觉十分好用,不到半个小时就可以生成带rootfs的所有文件(包括PMUFW、ATF、bitstream、U-boot、Linux kernel和busybox)并打包,不过在其中也遇到了一些问题,故在此记录移植过程。
本次移植使用的PetaLinux版本是2019.2,软件和相关文档的地址见PetaLinux工具,下文基本是按照文档所述方法进行的。
本次移植是在EdgeBoard Lite FX3开发板上实现的,见收到EdgeBoard Lite了,简单地开个箱。
我的屏也拆下顺利点亮,可惜触屏拆碎了。
https://whycan.cn/files/members/1401/IMG_LVDS.jpg
看起来LVDS是直接连到HDMI的……这个转接板感觉很适合给RIoTboard用。
资料全吗?能用来学习LINUX吗?
这个板子的资料可以分为三种:
第一方:NXP提供的硬件资料肯定是非常全的,这点比国产芯片通常要好不少,目前到Android P/Linux 5.x都有更新,不需要担心。
第二方:Embest提供的资料确实多年不更新了,像楼上朋友所说,目前只到Android 4.x/Linux 3.x,聊胜于无吧。这点肯定比不上官方开发板(例如Sabre-SD)。
第三方:这个板子在社区似乎还是比较火的,上文列出的Element14讨论区就是其中之一,目前也有一些第三方镜像或资料(例如上文提到的GuruCE),毕竟是官方认证的第三方开发板嘛。
贴一下板子大概的元件,看得清型号的我就懒得标了。背面有一个4T245被我标成了TXS0102,不过应该不碍事。
这里我比较好奇的是USB部分的配置。看起来USB 2.0先后接了一个PHY和一个Hub后分别给了一个USB 3.0和一个USB 2.0,然而USB 3.0是直接接到Zynq的(Zynq Ultrascale+自带PS-GTR可以直接操作SSTX/SSRX,但2.0部分的DP/DM需要接PHY),这样做真的没问题吗?
另外,板子上的USB1T20也比较迷(丝印是仙童的PG2SB,Google了一下直接给了USB1T20的datasheet,引脚定义看起来似乎没问题),这是一个USB 2.0 FS/LS的transceiver(收发器),看起来意义不明,有谁可以解释一下吗?
可以参考知乎上的这个问题:memcpy比循环赋值快吗?为什么?,实际上现在编译器已经可以自动加上多种优化,当然优化的效果还得看平台和编译器的支持程度。
对于所谓“跨界处理器”不成功的原因,我也来提两句自己的看法。
目前这些“跨界处理器”的卖点,主要体现在以下几个方面:
高性能,或者更准确地说是高能效比。目前Cortex-M7可以达到2.14 DMIPS/MHz,甚至比Cortex-A7/R5还要更强,再加上FPU之类的特性,性能确实不输同频ARM9/Cortex-A7。
集成众多高端外设,包括高精度定时器、高精度ADC等等,这也是传统的SoC所不具备的。
单片机内部通常集成了大容量的SRAM和Flash,对于不使用操作系统、没有图形界面的应用通常是绰绰有余。
更加强调安全性,自带各种安全组件支持。
中断和执行的延迟低于Cortex-A系列处理器,在时序紧张的场合可以更加精确地控制时间。
相对更低的价格(同品牌内部对比),以及更加简单的硬件设计。
有这么多的优点,为什么“跨界处理器”并不成功呢?我觉得问题主要出在芯片本身的定位上,和宣发、配套支持等也有很大关系:
上面列举的优点都是相对的。有些优点是不需要的,或者是可以弥补的,比如说效率不够主频来凑,或者干脆做SiP集成DDR之类的,其实在大部分使用场景都是够用。
从绝对成本而言并没有优势。从硬件成本来说,虽然单片机通常都自带了电源管理,但是一些像DRAM之类布局麻烦的信号反倒更加麻烦,远不如SiP的方案,而且更重要的是芯片本身的价格已经被国产芯片压得足够低;从软件成本来说,其实单片机的软件编写更加麻烦,这主要还是Linux的功劳,通过封装驱动的方式使得硬件调用更加简单,像i.MX RT这种照搬硬件架构的做法必然要付出更大代价。
我觉得最为核心的问题是,“跨界处理器”的设计思路实际上是和主流需求背道而驰的。“跨界处理器”并不适合通用设计的场合,恰恰相反,它最适合对功能和设计高度定制化的场合。举个例子,对于HMI,为什么少有使用单片机的方案呢?因为基于Linux的方案具有移植简单、可复制性强、开发效率高的优势,而这对HMI这种对开发人员的技术要求不高、对成本控制和开发效率而言更加看重的应用场合是一击致命的痛点,所以上述场合就很适合像F1C100s这样的芯片。
那么“跨界处理器”的真正优势在哪里呢?我觉得是对综合性能(特别是外设性能)要求极高,且希望开发出专用产品的场合。这种场景下使用的方案必然是经过密集优化,是通用方案无法满足的情形。像SDR、调试工具、医疗器械等。在这种情况下,自然不需要考虑软件开发成本的问题——市面上没有通用方案,只能自己来搞,况且像USB这种外设的使用往往是独占式的,不需要做通用方案的话,往往并不复杂。如果宣传“跨界处理器”可以解决一切问题,那只能说是南辕北辙。
总而言之,我赞同“没有真正的垃圾芯片,只有不适合和不够便宜的芯片”的说法,其实这些“跨界处理器”在某些场合还是有用的,但必然不是通用和主流的方案。
这个LVDS接头那个转接器是什么啊,x宝上找没找到。。。。。
额,这个实际上是板子上的私有定义,只有官方的配件是支持的。我看了一下,采用了同样接口的迅为开发板的定义并不相同,所以应该没有通用的定义吧。
所以我直接去淘宝买了免焊接的HDMI插头(mini HDMI买不到),链接见https://detail.tmall.com/item.htm?id=520762230985。之后还打算用他家的HDMI面板直接把屏幕装到壳子里,这样用起来更方便一些。
RGB2LVDS转接板哪里有,来个连接看看?
我用的是Sipeed那里买的,和https://whycan.cn/t_3267.html这里是同款。
另外,LVDS转接板(其实是液晶+背光驱动)是在https://item.taobao.com/item.htm?id=573147381149这里买的。
这板子这个价格是挺香的,那个破风扇配上百度的贴纸挺有内味的,不过没看见JTAG口引哪去了
ZYNQ MP的PS-GTR是挺坑爹的,说是俩USB3.0+PCIE X4+2line DP,datasheet拉下来一看全复用的
很好奇楼主为什么这么纠结于USB3.0,数据量不是完全跑满加个HUB不就够了,PCIE这种东西才好玩不是,连个NVME之类的
JTAG就是FPC 32p旁边那个2.54mm 6p接口。
主要还是觉得USB 3.0的通用性比较好,而且这个控制器只有6in6out共12个ep,担心hub接太多不是很够用。。另外确实不大了解PCIe,不过这么一说,接支持SATA/NVME的SSD的话,NVME应该要远好于SATA吧。。不知道x1的兼容性会不会有影响。
非广告,只是水个积分
1K不到的Zynq Ultrascale+开发板,同芯片的Ultra96某宝价2400,相比之下还是很香的(虽然资源引出不够多
虽然FPGA本身资源不是很多(也就5个矿板的样子),不过搭配的外设还是挺丰富的,4xCortex-A53+2xCortex-R5+Mail400MP2+PCIe Gen2 x1+mini DP+USB 3.0+GbE,已经可以玩得很开心了(
看起来还附送AI计算功能,感觉不亏(
比较大的问题可能是文档不会很全,但是既然是MPSoC,有个JTAG外加XDC应该就足够了吧。。总之有意思
下面贴个图和文档。
EdgeBoard_lite_ug.pdf
贴一下patch内容。其实很简单,就是把ARCH_SUNXI的select DM_GPIO去掉,顺便修一下相关的编译错误。
另外理论上应该可以保留DM_GPIO,用设备树的方式配置,有兴趣的同学可以试一下
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 918424a..8f68be1 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -697,7 +697,6 @@ config ARCH_SUNXI
select CMD_USB if DISTRO_DEFAULTS
select DM
select DM_ETH
- select DM_GPIO
select DM_KEYBOARD
select DM_SERIAL
select DM_USB if DISTRO_DEFAULTS
diff --git a/drivers/spi/sun6i_spi.c b/drivers/spi/sun6i_spi.c
index 750c4d8..dabf661 100644
--- a/drivers/spi/sun6i_spi.c
+++ b/drivers/spi/sun6i_spi.c
@@ -10,9 +10,7 @@
*/
#include <common.h>
-#ifdef CONFIG_DM_GPIO
#include <asm/gpio.h>
-#endif
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <dm.h>
@@ -103,10 +101,10 @@ static int sunxi_spi_cs_activate(struct udevice *dev, unsigned cs)
struct sunxi_spi_reg *spi = (struct sunxi_spi_reg *)plat->base;
struct sunxi_spi_privdata *priv = dev_get_priv(bus);
int ret = 0;
-
+#if 0
debug("%s (%s): cs %d cs_gpios_num %d cs_gpios %p\n",
dev->name, __func__, cs, plat->cs_gpios_num, plat->cs_gpios);
-
+#endif
/* If it's too soon to do another transaction, wait... */
if (plat->deactivate_delay_us && priv->last_transaction_us) {
ulong delay_us;
增加了这几个后,log显示“Setting up a 800x480 lcd console (overscan 0x0)“,驱动加载成功
CONFIG_VIDEO_SUNXI=y
CONFIG_VIDEO=y
CONFIG_CFB_CONSOLE=y
CONFIG_VIDEO_LCD_MODE="x:800,y:480,depth:18,pclk_khz:33000,le:16,ri:209,up:22,lo:22,hs:30,vs:1,sync:0,vmode:0"
CONFIG_VIDEO_LCD_BL_PWM_ACTIVE_LOW=n
CONFIG_VIDEO_LCD_BL_PWM="PE6"不过,背光和显示还是不工作,跟踪背光PIN的设置,发现找PE6的时候,没找到相应管脚,pin为-22,不是正值。。。
看来dts或什么地方还缺少GPIO的配置。。。
我遇到过这个问题,发现是因为默认开启了DM_GPIO,在这种情况下会从设备树中搜索指定名称的引脚,所以会返回-EINVAL。
一个解决方案是关闭DM_GPIO,不过ARCH_SUNXI默认select了DM_GPIO,所以需要改一下Kconfig,我记得还要改SPI的驱动才能编译通过。改完之后就能用了。
在开始玩Linux/Android之前,先来个有趣的东东:WinCE。之前没玩过WinCE,正好有人移植好了i.MX6的BSP,并且提供了prebuilt的固件(包括WEC7和WEC2013),可以简单地玩一玩。下载地址:iMX6 BSP | GuruCE。
可以看到,GuruCE为RIoTboard提供了prebuilt的固件,并且允许用户在上面做一些开发(需要安装M$的开发套件),包括OpenGL-ES和OpenVG。我懒得在上面编译并运行程序了,所以试着跑了一下他的demo,效果还不错的样子,低分辨率下非常流畅,1080p就有点吃力了。跑了一会儿就发现发热量还是比较感人的。
接下来是吐槽时间,说说这个板子设计的槽点。
最为生草的还是板上自带的OpenSDA。看起来OpenSDA可以提供一个调试接口和一个虚拟串口,可以对SoC进行全方位的调试,但是!这个OpenSDA!不!能!用!直接上电一点反应都没有!这是为什么呢?用J-Link读了一下U23 (MK20DX128VFM5)的Flash,发现Flash全是FF,根本就没有烧录固件进去,这就是个白板!
但是问题不仅限于此,看了原理图就会发现,这货实际上压根没有连接JTAG,而是接到了SD/SPI Flash上,也就是说OpenSDA的设计本意是用来直接向SD/SPI Flash写入数据的,而不是用于调试功能,所以不能使用OpenSDA对CPU进行调试。当然,理论上来说虚拟串口还是能用的,但是实际使用的串口基本上是网口旁边的J18,而J18接的是UART2,OpenSDA接的是UART1,要用的话还得改参数,所以结论就是,这个自带的OpenSDA就是个废物。
另外,虽然板上自带了比较丰富的视频输入/输出,但是也是一言难尽。比如说并行输入只有8位,并行输出只有20位,MIPI-DSI不存在,LVDS只引出了1-ch 6-bit。说到LVDS,板上提供的接口是自定义的mini HDMI,看了一下接口定义似乎和市面上的类似方案不兼容(iTop-4418/6818也用mini HDMI传输LVDS),而且看样子背光电路也要自己搞,工作量+1。不过好在HDMI+LVDS也还是够用的,可以玩一玩双屏异显了。
最后就是供电。供电接口还是上古时期的DC这个就不吐槽了,毕竟这板子也有些年头了;不过只有DC和GPIO扩展接口可以给板子供电也是很难受了。看原理图可以发现D48可以用来从OpenSDA向板子供电(虽然本意似乎并非如此),但是鉴于OpenSDA是残废状态,还是暂时用DC供电吧。
另外,板子上似乎有大量的ESD二极管用来保护GPIO等接口,但都没有焊上,估计是成本考虑吧。
接下来大概看一下板子的配置。
i.MX 6Solo:1*Cortex-A9@1 GHz
存储器:DDR3 1GB + eMMC 4GB + SD (J6) + TF (J7)
供电:DC5.5*2.5 (J1)或GPIO扩展接口 (J13)
视频:输入包括MIPI-CSI 2-lane (J8) + Parallel 8-bit (J9),输出包括LVDS 1-ch 6-bit (J2) + HDMI v1.4 (J3) + Parallel 20-bit (J13)
音频:Audio Codec是SGTL5000,支持3.5mm In (J4) + 3.5mm Out (J5)
网络:PHY是AR8035,支持RGMII (J15)
USB:USB 2.0 OTG (J11) + USB 2.0 Hub (4-port)
调试:JTAG (J10) + OpenSDA (J14)
先贴一下已有资料的链接吧:
开发板中文官网:RIoTboard开发板|基于飞思卡尔i.MX 6Solo多媒体处理器设计的物联网解决方案|Cortex-A9架构-深圳英蓓特科技
开发板英文官方资源:Dropbox - SVN3636 - 简化您的生活(需翻墙)
element14上的资源/讨论区:RIoTboard | element14
i.MX 6Solo官方资源:i.MX 6Solo Applications Processors应用处理器 | Arm® Cortex®-A9单核,主频为1GHz | NXP
话说最近在闲鱼看到出全新i.MX 6Solo的开发板,是Embest的RIoTboard,价格大概110包邮,有完整的BSP,主线U-boot/Linux/Android支持,不知道性价比如何?
大概列一下参数:
单核Cortex-A9,1GHz,32KB I/D L1,512KB Unified L2
1GB DDR3,4GB eMMC
GPU+VPU,支持2D/3D加速,支持H.263/H.264/MJPEG编解码
两路USB,一路接4口HUB,另一路支持OTG
视频输入支持Parallel/CSI,视频输出支持HDMI/LVDS/RGB
两路SD/TF接口
一路千兆以太网
外置低功耗Audio Codec
板载OpenSDA调试器
@metro 你这个烧的是什么系统?debian?有固件吗?
是这个固件:https://whycan.cn/t_2689.html,不过这个固件是电容屏的,电阻屏的应该需要另外编译内核,等有时间再研究
这个SDIO要自己编译出DLL,然后放到协议分析的目录中去。
我以前编译过,因为要修改一下SDIO时钟边沿。后面直接使用梦源的逻辑分析仪来处理了。
另外, sigrok的PulseView 连接 我的 Saleae 以及 梦源逻辑分析仪 都没能成功。
我用手头的DSLogic Plus试了一下,参照安装教程https://sigrok.org/wiki/DreamSourceLab_DSLogic,Ubuntu 18.04下可以正常使用。不过需要注意的是需要使用0.9.7版本的固件,新版本似乎不能用。
这种FT2232能用吗,或者有推荐的某宝链接没?
https://whycan.cn/files/members/390/O1CN018IJai42GL8Hi3xAV0_3301708998.jpeg
应该可以,我用的就是同一家的FT232H
这里简要分析一下OpenOCD的操作指令。
上图是未解码前的原始JTAG数据。可以发现,每次访问寄存器时JTAG分别都需要三次操作,分别是写IR(切换DTM寄存器)、写DR(发起传输)、读或写DR(验证传输正确性,读操作时同时获取结果)这三部分。在绝大部分情况下,我们只需要访问dtmcs寄存器,因此写IR这步通常是多余的;另外,在连续读取数据等情况下,没有必要每次专门发起一次传输来验证传输的正确性,而是可以先发起下次传输,视返回值确定上次传输是否成功。这里可以节省2/3的传输。
上图是简单解码后的传输内容。通过研究可以发现,在每次单步调试时,OpenOCD都需要完成暂存寄存器、写入算法(通常是单步调试)、执行、还原寄存器等操作。暂且不提这些操作的必要性(显然可以通过记录数据是否已改变的方式减小需经过调试器访问的数据量),上图中还可以看到这里执行了许多明显错误的指令(可以看到Debug Module报错了)——更可怕的是,这段代码足足重复了34次(仅CSR地址略有不同)。不是很清楚为何OpenOCD会做这些事情,可能需要研究一下OpenOCD的代码了。
我抓了一组使用OpenOCD的JTAG波形,并且进行了适当解码。大家可以看看OpenOCD的调试指令有何问题。CMSIS-DAP.zip
为了探究各个调试器调试/下载速度差异的根源,这里尝试使用逻辑分析仪抓取JTAG波形的方式进行分析。
此处的比较对象包括J-Link(直连)、J-Link(OpenOCD)、CMSIS-DAP(OpenOCD)。JTAG时钟速度为1 MHz,不过直连时的速度似乎是4 MHz,这个对分析没有影响。
J-Link(直连):
J-Link(OpenOCD):
CMSIS-DAP(OpenOCD):
可以发现:
1. 单步执行的速度大致为J-Link直连 (5 ms) < J-Link+OpenOCD (450 ms) < CMSIS-DAP+OpenOCD (3050 ms),可以发现速度之间的差异是巨大的。
2. 对比J-Link+OpenOCD和CMSIS-DAP+OpenOCD,这两种方案传输的数据量是差不多的,主要区别在于指令之间的间隔长度。
3. 造成J-Link直连和通过OpenOCD这两种情况速度差异的因素,主要是OpenOCD的处理序列较长,这个问题我们在后文分析。另外还有一个原因是OpenOCD不支持将调试指令队列化处理,使得每次USB传输时只能访问一个寄存器,调试器在大部分时间是空闲状态,效率很低且调整JTAG时钟无法改善。
4. 由于2中提到的调试指令处理速度,所有使用OpenOCD的方案的瓶颈都在于USB传输频率。我们知道,USB 2.0 HS的最大传输频率为8 kHz(每秒可以传输8000个subframe),而CMSIS-DAP通常是HID设备(官方OpenOCD只支持这种方案),而HID的传输频率实际上与报文的interval相关,这就造成了至少8倍的时间差异。不巧的是,在时间增加数倍之后,调试速度已经影响到了正常执行,变得无法忍受。
5. 此处还测试了J-Link+GDB Server方案,发现JTAG波形与直连时没有太大区别,猜测主要的差异在于GDB Server引入的延迟。
因此,在OpenOCD没有大的改进之前,建议尽可能使用USB 2.0 HS接口的调试器(FTDI的高速IC方案不错,FT232H/FT2232H都可以),或是直接使用J-Link等自带调试软件的方案。
测试方法:比较各个调试器单步执行(step over)的执行速度
测试工具:SEGGER Embedded Studio for RISC-V(集成开发环境) + Sipeed GD32VF103最小系统板
测试对象:以下各种调试器/工具的组合。
J-Link V10 EDU(正版,目前V10明确支持RISC-V,其它版本未知),可选直接连接、GDB Server和OpenOCD
FT232H开发板(应该可以代表FTDI支持USB HS芯片的性能,包括FT2232H/FT4232H),仅OpenOCD
LPC-Link2,刷了CMSIS-DAP固件(包括LPCScrypt提供的版本和CMSIS-DAP自带版本),仅OpenOCD
淘宝常见的STM32F103C8T6最小系统板,刷了CMSIS-DAP固件,仅OpenOCD
测试结果:
J-Link (直连) > J-Link (GDB Server) > J-Link (OpenOCD) ≈ FT232H >> STM32F103C8T6 with CMSIS-DAP
由于OpenOCD不支持非基于HID的CMSIS-DAP(针对USB HS可使用Bulk传输,速度更快),LPC-Link2退出了群聊。
结论:
1. J-Link对RISC-V的支持挺不错的,调试速度和其它平台没啥区别,现在唯一遇到的问题是不支持显示自定义CSR,对于调试基于Bumblebee内核的GD32VF103(特别是ECLIC相关的部分)而言颇为不便。
2. OpenOCD对调试器的支持面很广(除了使用Bulk传输的CMSIS-DAP,但这个比较小众),不过调试速度一般,显然存在瓶颈。目前看来OpenOCD可以支持自定义CSR,但还需要IDE的支持才能方便地显示。
3. CMSIS-DAP的调试速度简直是弟中弟,大于5秒/指令(注意不是代码是指令)的速度基本无法忍受。
注:STM32F103C8T6的CMSIS-DAP固件见RadioOperator/STM32F103C8T6_CMSIS-DAP_SWO,调试Cortex-M单片机的时候速度比较正常,应该不是固件本身的问题。
影响编码效率的参数有很多,主频可能是其中一个不很重要的因素,考虑到编码图像基本上都在进行重复性很高的工作,因此编码器的处理能力和体系结构对部分函数运行效率的关系很大,这也就是硬件编码器可以在低于CPU的频率下实现更高编码效率的主要原因。
类似于这样的编码应用,瓶颈往往在于一些经常被调用的、计算量比较大的函数,比如说预测时计算cost、变换和量化之类,这些函数通常有大量的低精度乘法、乘加运算等操作,在这些情况下一般要使用SIMD来优化执行的效率。我记得ARMv5好像没有SIMD指令?楼主可以试着比较一下ffmpeg中的ff_simple_idct_arm(ARM9使用)和ff_simple_idct_neon(Cortex-A9等使用),就可以发现代码之间的差异了。我猜把ffmpeg搬到同主频的Cortex-A9上应该会快不少。
另外可能需要考虑的因素在于存储设备的读写能力,毕竟一帧VGA大小的yuv420p已经有640*480*1.5=450KB了,而SD卡在嵌入式设备的性能可能有限(特别是在电压是3.3V时),所以也不能指望编码速度能达到什么程度。如果要测试软件的编码能力,建议还是采用摄像头等输入源,这样就不会受到存储设备的瓶颈限制。
Sipeed已经有相关的文档和工具可供下载,据称下周会上架开发板。http://dl.sipeed.com/LONGAN/Nano/
@metro 请参考最新烧录器"HC-PM51 V5.0.11.0"里面"help.pdf"文档的Page28
[HC-PM51 V5.0.11.0]下载地址
搞定了,原来我下的是5.0.10.0的HC-PM51,没有找到相关内容 多谢!
收到开发板了,试着用PWM做了个blink,没有问题。
#include "HC89S003F4.h"
#define LED_PERIOD 250
void pwm3_isr(void) interrupt PWM_VECTOR
{
static unsigned char idata next = 0;
static unsigned char idata dir = 0;
PWM3D = next;
if (next == 0) {
next = 1;
dir = 0;
} else if (next == LED_PERIOD) {
next = LED_PERIOD - 1;
dir = 1;
} else {
if (dir == 0)
++next;
else
--next;
}
PWM3C &= ~bmPWM3IF;
}
int main(void)
{
WDTCCR = 0x00;
CLKSWR = 0x51;
CLKDIV = 0x01;
PWM3P = LED_PERIOD;
PWM3D = 0;
PWM3C = bmPWM3EN | bmPWM3IE | bmPWM3OEN | bmPWM3S | 0x7;
PWM3_MAP = 0x00;
P0M0 = 0x3 << 4 | 0x8 << 0;
EA = 1;
for (;;)
PCON = bmIDL;
}
不过现在有个小问题,开发板上电之后MCU默认是不上电的,且只有在下载和调试时才上电。虽然可以选择下载后上电,但似乎找不到开发板上电时MCU也跟着上电的选项。有解决方法吗?
周末花了点时间尝试走了一遍板子的开发流程,目前移植的U-Boot已经能用了,但是NAND和网络暂时还不可用。NAND的主要问题是SPL下的驱动不完善(看来没人在Zynq上用NAND启动233),网络的主要问题是还没搞定EMIO的初始化(不过应该加载.bit文件后就能用了?不确定驱动对百兆网络支持如何)。后面要是解决了这些bug就把U-Boot发上来
这里记录一下中间遇到的一些问题。
入门的话,我觉得EBAZ4205 ZYNQ 7Z010 裸机程序NAND固化 JTAG调试方法这篇说得最好,按照他的做法一次就能过了。唯一想吐槽的是NAND,我从Winbond官网找到了对应的datasheet,结果一通设置之后竟然读不出NAND的信息。。反而是博客里面看起来不靠谱的这个设置是可用的,不知道是什么原因。
在熟悉了开发的流程之后,如果之后不需要从SDK生成和编译代码(比如说编译U-Boot),那么比较建议使用XSCT,这是Vivado自带的命令行下的工具,比SDK好用多了。执行的流程大概是这样的:
connect # 连接到开发板
target 2 # 指定调试目标为2,即双核中的#0核
# 初始化Zynq,如果需要初始化外设(例如SDRAM)则需要执行,如果只是要加载到OCM则可以不用执行
source xxx/ps7_init.tcl # 加载tcl文件,一般位于项目目录的yyy.sdk/zzz_wrapper/文件夹中
ps7_init # 执行初始化流程,可能需要一点时间
# 加载elf文件,由于elf文件自带执行地址,因此不需要指定载入地址,且会自动设置pc到开始位置
dow xxx.elf
# 也可以加载普通文件(包括二进制文件),但是需要指定载入地址,且需要加上-data
dow -data xxx.yyy addr1
rwr pc addr2 # 设置pc到开始位置
# 加载bitstream
fpga -file xxx.bit # 加载bitstream,一般位于项目目录的yyy.sdk/zzz_wrapper/文件夹中
# 开始执行
con
以上方法试验过,可以启动U-Boot的SPL和本体(本体可以初始化后直接丢到SDRAM执行,挺方便的),但是自己编译的lwip Echo Server似乎用不了(理论上需要分别加载bitstream和elf),可以启动但提示初始化失败,不知道是什么原因。
另外顺便吐槽一下Create Boot Image功能,在指定elf文件的时候一切正常,但在指定bin文件的时候并不会计算大小,而是直接置零,按照定义置零表示XIP,因此在SD卡上就不能正常启动了 。还有就是建议使用最新的Vivado和Zynq TRM,否则对于Boot Header的定义会有出入。。
这是我在网上找到的一些Digilent JTAG调试器的资料,现在想做一个支持宽电压的FT232H调试器,需要实现SRST引脚,不知道有没有dalao有兴趣?没画过PCB
http://www.digilent.com.cn/community/616.html 这里面有个网盘地址有Digilent调试器的量产工具,亲测可用(使用淘宝买的FT232H开发板)(低调低调
搞了个FT232H的JTAG调试线
这是原作者的pcb/固件烧录等资料
https://whycan.cn/files/members/884/Xilinx_JTAG-SMT2.rar
这是我改小的SCH/PCB/BOM文件,用AD可以打开
https://whycan.cn/files/members/884/FT232H.7z
在vivado中成功识别
https://whycan.cn/files/members/884/TIM20190513110450.png
实物图 盗用群里大佬的图了
https://whycan.cn/files/members/884/TIM20190513110330.jpg
https://whycan.cn/files/members/884/TIM20190513110413.jpg
https://whycan.cn/files/members/884/TIM20190513110430.jpg
支持micro或者typec
232h封装支持lqfp或者qfn
配置芯片封装支持sop8或者sto23-6
好评,不过好像没看到引出SRST引脚?这个引脚可以用来重置Zynq,调试的时候应该方便一些
这类题目可以先考虑交点的数值。设所有点横向顺序为a到h。
首先可以确定,第一行中间的点有三条线,则必有三组数满足x+y=14-b,枚举后可以发现只有5和7满足要求。
进一步可以验证b=5时三组数分别是(1,8),(2,7),(3,6),g为4。
b=7时三组数分别是(1,6),(2,5),(3,4),g为8。
之后考虑剩下的三个交点。把所有可能的情况加起来,可以得到
(a+b+c)+(b+d+f)+(b+e+h)+(a+d+g)+(c+e+g)
=(a+b+c+d+e+f+g+h)+(a+2*b+c+d+e+g)
=(1+2+3+4+5+6+7+8)+(a+b+c)+(b+e)+(d+e)
=36+14+(b+e)+(d+e)=14*5=70
因此(b+e)+(d+e)=20,代入可以求出所有解。
页次: 1