查看: 9149|回复: 4

[教程] [群150178878]Arduino通过SWD协议烧写nRF51822笔记

  [复制链接]

8

主题

0

好友

104

积分

童生

Rank: 2

该用户从未签到

发表于 2014-5-6 15:43:40 |显示全部楼层
本帖最后由 wkong501 于 2015-1-18 15:20 编辑

以下是提供一个思路,论坛的编辑器很差,全乱了,图片也丢了,先放出来等有空整理好了发到我的博客去,源码参考mbed写的CMSIS-DAP源码,里面包含了详细的SWD协议。
[size=13.913043975830078px]E-Mail:xxxxxx Creation Date:2014.2.28  Updated:2014.3.2

一、任务要求:[size=13.913043975830078px]  1.通过SWD协议将Firmware烧入nRF51822, 实现Firmware Update.
  2.要实现对nRF51822 Flash的读,写,擦除,以及寄存器的访问.
  3.必须使用Arduino, 需要注意nRF51822芯片规定swd接口的操作速度需要125KHz以上.故需要使用arduino寄存器编程.更新2014.3.4:IO速度无需达到125KHz也能实现SWD协议操作,不确定官方文档要求的125khz的频率是用在起其实的50时钟的捕捉上还是全程速度。

二、介绍及设计思路
  1.硬件使用Arduino UNO, 先测试寄存器操作方式下UNO引脚的翻转速度,测得UNO IO翻转速度可以达到几百KHz。更新2014.3.4:如上述,速度不影响。
  2.源码包括:Arduino驱动程序,SWD library for arduino(dapswd.cpp, dapswd.h)
  3.了解Serial Wire Debug接口的寄存器组,分为DP和AP两大寄存器组,DP是基础操作的寄存器间接管理AP寄存器组,AP是和芯片内部打交道的寄存器组包含目标寄存器TAR(addr:0x04)、读写寄存器DRW(addr:0x0c)等等。DP寄存器数量比较少有7个,相同地址的读和写操作对象是不同的。AP的寄存器比较多,AP寄存器组分成16个bank, 每个bank只有4个寄存器.每个寄存器都有一个对应的地址(如下图).比如IDR寄存器0xFC, 即位于AP的bank0xf中地址为0x0c的寄存器.
下图是两个寄存器组的常用寄存器:
file:///C:/Users/yuting/AppData/Local/Temp/Wiz/f270e55f-2835-4b56-b202-b60f13a05b13_128_files/9792057.png
file:///C:/Users/yuting/AppData/Local/Temp/Wiz/f270e55f-2835-4b56-b202-b60f13a05b13_128_files/9855207.png
由上图可知DP和AP寄存器的寄存器都是4字节对齐,且最低位的两个bit都为0。两寄存器组的地址有相同,如何区分命令是写DP还是写AP?下面来看看SWD协议写成功操作的时序就知道了。(参考文档 ARM Debug Interface v5 Architecture Specifation)
Successful write operation (OK response):
file:///C:/Users/yuting/AppData/Local/Temp/Wiz/f270e55f-2835-4b56-b202-b60f13a05b13_128_files/10123544.png
写时序分析:
第一个byte是SWD协议任何操作都需要搭配的起始字节,start=1;APnDP:若操作对象是AP则为1,DP则为0;RnW:若为Read操作则为1,Write操作则为0;A[2:3]:2bits,是AP或DP寄存器地址的第2,3位(任何寄存器最低2bit都为0);Parity:根据APnDP、RnW、A[2:3]这4位出现的1的个数,若为奇数个则为1,偶数个则为0;(编程时移位相加取最低位就能得到) Stop=0;Park:此时host不驱动总线,由于swdio硬件内部上拉,target读取到此位为1;Trn:一个时钟;
随后从target返回一个ACK应答信号(3bits),ACK=0x01(上图5-1001):OK Response;ACK=0x02(010):WAIT Response;ACK=0x04(001):FAULT Response;如果出现ACK=0或者ACK=0xff,那么可能是初始化协议错误,也可能是接线问题,看看clk和io有没有接反。
接着Host向Target写入32bits数据注意后面还得跟一位Parity。若为读操作返回的parity后面还得跟一位Trn。(详见文档的Figure 5-2)

至此我们已经学会访问DP及AP寄存器组的任何地址的寄存器。
  4.SWD协议的操作步骤:
    (1)初始化步骤:
      a)初始化IO口,SWCLK和SWIO设置为输出模式,发送line reset操作:保持SWIO=1,发送至少150个时钟以保证至少有50个时钟被nRF51捕捉到。
      b)发送jtag to swd命令,0xE7 0x79两个byte(在DAP-Lite官方文档中可以查询到命令值),随后再发送一次line reset操作
      c)发送一次Bus Idle信号:保持SWIO=0,发送8个时钟(不能低于2个)。
      d)读取Chip ID,从而完成初始化操作。记住,此时你只能访问DP寄存器组,AP寄存器组还无法访问。
    (2)解锁AP
      a)开启debug port及其时钟, 这个是DP寄存器组中的地址为0x04的CTRL/STAT寄存器的第28位和第30位(成功后,29位和31位被置1)
      b)接下来要访问AP寄存器组中的IDR寄存器(addr=0xFC)实现解锁AP的目的.详细步骤:写AP SELECT寄存器来选择访问IDR寄存器所处于的bank地址,这个是DP寄存器组中的地址为0x08的AP SELECT寄存器.写入的数据为0x000000F0, APSEL区域的值有4个, 一般我们访问MEM-AP,要将APSEL=0X00表示AHB访问.其他的取值,0x01,0x02,0x03,要看ARM文档说明.写完SELECT寄存器后在写读AP IDR寄存器的命令,AP寄存器的读操作总共需要两次,第一次是dummy read, 第二次读取到的才是正确的数据。或者第二次可以读RDBUFF寄存器,原文如下:
On a SW-DP, performing a read of the Read Buffer captures data from the access port,
presented as the result of a previous read, without initiating a new access port
transaction. This means that reading the ReadBuffer returns the result of the last access
port read access, without generating a new AP access.

      c)写入数据后使用SWD读操作命令, 读取出IDR寄存器值, 这步要注意, 如果读不出来请多尝试几次。

      d)配置CSW寄存器。
    (3)操作MCU的寄存器以及读写flash
      d)至此,我们已经成功做好了准备工作, 接下来就能根据目标芯片文档说明随意访问芯片内部寄存器.记住,不是你想访问就能访问,不同的芯片有不同的说明, 另外, 倘若芯片处于工作状态要使cpu 处于halt状态, 这个得看 arm v6, v7构架手册中的寄存器。
      e)随后,我们要对arm芯片内核的几个关键寄存器进行读写操作,如上述,通过寄存器配置让CPU处于halt状态,关闭MPU,以及其他操作,对于nRF51822,请阅读Nordic官方文档:Download process white paper》,里面有详细的寄存器操作流程图。
      f)擦除,读写Flash,完成。
以下是google上的参考资料:
参考资料原文:(这个是Cortex M3内核的操作)
1.The Host needs to switch the target from JTAG to SWD mode by clocking 0xE79E onto SWDCLK/SWDIO
2.SWD connection sequence- clock out more than 50 binary 1s
3.Must read the Debug Port IDCODE register (address 0)
4.Turn on Debug Port by settings bits 28 and 30 at DP address 4
5.Write AP select (debug port address 8) to 0xF0 (to prep for AP read of 0xFC)
6.Unlock Access Port by reading AP ID register (AP address 0xFC)




三、进度

[size=13.913043975830078px]  1.在github上找到mbed写的CMSIS-DAP源码,里面包含了详细的SWD协议,深入研究中...  2014.2.28
[size=13.913043975830078px]  2.已完成nRF51822 Flash简单的按字烧写。2014.3.4
[size=13.913043975830078px]  3.继续更新SWD协议:                   2014.3.9
[size=14.074073791503906px]    发现问题:写了新的驱动程序,之前是将flash用软件擦除直接尝试写数据到flash,现在加入halt CPU操作,erase CPU操作。根据nRF51822手册,擦写flash时必须关闭flash的MPU保护。这一步swd_write_chip_reg(NRF_MPU_DISABLEDEBUG, 0x01)操作之后如果不延时会发生错误!更新:后面发现每次对MCU的寄存器操作以后都得需要一个延时,否则会出现错误。
[size=14.074073791503906px]    解决:a)延时1ms(已采用);b)查找协议资料,查看目标mcu寄存器读写成功是否会影响到swd寄存器的变化,找出那个寄存器。
[size=14.074073791503906px]    Tip:每次写MCU寄存器都加个1ms延时,如果是flash操作,要延时更长。猜测:MCU操作内部寄存器需要时间。
[size=14.074073791503906px]

[size=14.074073791503906px][size=13.913043975830078px]四、问答
[size=13.913043975830078px]  1.学习SWD协议需要哪些文档?

[size=13.913043975830078px]    你需要阅读的文档(全部都在官网下载, ARM关于SWD文档的资料比较分散,一定要收集全资料再学习)
      a)IHI0031A_ARM_DEBUG_INTERFACE_V5.pdf(详细介绍JTAG SWD协议, 各种基础操作操作时序,需详看)
      b)CoreSight DAP-Lite Technical reference manual.pdf(简要介绍, 包含一些重要的知识点)
      c)serial_wire_debug.pdf(只用于介绍如何读取IDCODE, 第一步的时候可以参照)
      d)DDI0314H_coresight_components_trm.pdf(很大的一个文件, 我还没看, 内容很丰富, 应该很有料)
      e)GitHub上有mbed为kl25写的swd程序,必须要看,很有参考价值,https://github.com/mbedmicro/CMSIS-DAP

[size=13.913043975830078px]  2.每次读写操作需要什么特殊的间隔操作吗?
[size=13.913043975830078px]    需要. 我们需要发送一个idle信号, 以保持和芯片同步. IDLE信号是IO=0, swdclk发送几个时钟信号.(不管8个还是200个,都行,貌似不能低于2个.更新:在mbed的CMSIS-DAP中他们用8个时钟with SWIO=LOW)
[size=13.913043975830078px]  3.怎样知道自己对AP,DP寄存器操作成功了?
[size=13.913043975830078px]    两种方式:
[size=13.913043975830078px]    (1)ACK应答信号直接告诉你当前操作是否成功.
[size=13.913043975830078px]    (2)DP的CTRL/START寄存器中的WDATAERR和READOK预示着对AP寄存器的读写操作是否成功.(仅仅是对AP)
[size=13.913043975830078px]  4.若芯片处于工作状态如何Halt CPU?
[size=13.913043975830078px]    先记录一下我做这一步时的工作步骤:
[size=13.913043975830078px]    (1)在此之前我已经能够读写处于非工作状态的芯片的Flash,现在先做好前面的工作,开启debug时钟,解锁寄存器之类的.
[size=13.913043975830078px]    (2)使CPU停止工作,需要查看ARM V6构架手册(针对M0),对于一个处于非工作状态的MCU,我先读取了一下这三个寄存器的值DHCSR=0x03080000, AIRCR=0xFA050000, DEMCR=0.随后下载程序到nrf51822中,再次读取这三个寄存器数据.照样能读出,于是开始写入三个寄存器值来停止CPU.按顺序写DHCSR=0xA05F0003, DEMCR=0x01, AIRCR=0xFA050004;然后再擦除芯片.工作中的CPU成功被停止并擦除数据.(测试过程中发现, 即使不停止MCU, 第一次擦写会出错, 但是第二次擦写居然成功了, 或许是我的操作不够严谨,在这里姑且提一下,就是说遇到卡住的地方要重复尝试,后来在mbed开放的源码中也发现相同的代码,他们在读写ap,dp寄存器时设定了10次的失败尝试.)
[size=13.913043975830078px]以下是google上的参考资料:
[size=13.913043975830078px]参考资料原文:(这个是Cortex M3内核的操作)
The process is as follows:
1. Write 0xA05F0003 to DHCSR. This will halt the core.
2. Write 1 to bit VC_CORERESET in DEMCR. This will enable halt-on-reset
3. Write 0xFA050004 to AIRCR. This will reset the core.
Now the CPU will be halted on the first instruction and all peripherals and registers (except for the debug registers) will have their reset value.




[size=13.913043975830078px]  5.SWD烧写nrf51822时钟有没有要求?
[size=13.913043975830078px]    官方文档说SWD协议的时钟必须在125K以上,但实际操作发现,不需到达这个速度也能操作。上电空闲的时候置SWCLK和SWIO为高电平(不确定是否一定要)。
[size=13.913043975830078px]  6.如何从JTAG转换到SWD?
[size=13.913043975830078px]    1. Send more than 50 SWCLKTCKcycles with SWDIOTMS=1. This ensures that both SWD and JTAG are in their reset states
[size=13.913043975830078px]    2. Send the 16-bit JTAG-to-SWD select sequence on SWDIOTMS
[size=13.913043975830078px]    3. Send more than 50 SWCLKTCKcycles with SWDIOTMS=1. This ensures that if SWJ-DP was already in SWD mode, before sending the select sequence, the SWD goes to line reset.
[size=13.913043975830078px]    4. Perform a READID to validate that SWJ-DP has switched to SWD operation. The 16-bit JTAG-to-SWD select sequence is defined to be 0b0111100111100111, MSB first. This can be represented as 16'h79E7if transmitted MSB first or 16'hE79E if transmitted LSB first.
[size=13.913043975830078px]
[size=13.913043975830078px]This sequence has been chosen to ensurethat the SWJ-DP switches to using SWD
[size=13.913043975830078px]whether it was previously expecting JTAG or SWD. As long as the 50 SWDIOTMS=
[size=13.913043975830078px]sequence is sent first, the JTAG-to-SWD select sequence is benign to SW-DP, and is
[size=13.913043975830078px]also benign to SWD and JTAG protocols used in the SWJ-DP, and any other TAP
[size=13.913043975830078px]controllers that might be connected to SWDIOTMS.
[size=13.913043975830078px]  7.若芯片处于低功耗睡眠模式也能进行SWD协议操作吗?
[size=13.913043975830078px]    可以,如果设备处于system off状态要特殊处理,详细要看nRF51的文档Download process white paper》以及reference manul。
[size=13.913043975830078px]  8.DP寄存器组中的CTRL/STAT寄存器中的WDATAERR,READOK位如何使用?
[size=13.913043975830078px]    (1)WDATAERR: (复位时为0)写数据错误发生时,该位被置1, 错误包括 a)数据奇偶校验出错. b)数据被DP丢弃不提交给AP(暂时还不理解). 该位只能通过对WDERRCLR位(AP ABORT寄存器中)写1才能清除.
[size=13.913043975830078px]    (2)READOK: (复位时为0)该位仅仅在读AP寄存器或RDBUFF寄存器成功时才被置1. 再详细点说,当我们需要读一个AP寄存器, 数据还未被准备好时, ACK可能回复一个WAIT response,此时READOK=0, 如果回复一个OK response, 那么READOK=1;
[size=13.913043975830078px]扩展: 针对这一特点我们可以利用READOK位来判断我们将要读的AP寄存器是操作完成.如果我们发现READOK=1, 那么read操作完成,host可以使用一个RESEND请求来获得read的结果(暂时不理解RESEND怎么使用).如果我们发现READOK=0, 那么read操作失败, host必须重新尝试对AP或RDBUFF的读请求.
[size=13.913043975830078px]注意: 对DP寄存器访问(除了RDBUFF)都不会影响READOK标志位.
[size=13.913043975830078px]  9.请介绍DP寄存器组中的CTRLSEL寄存器。
[size=13.913043975830078px]    CTRLSEL: 0:表示当前选中的是CTRL/STAT寄存器. 1:表示当前选中WCR寄存器
[size=13.913043975830078px]  10.如何读一个AP寄存器?
[size=13.913043975830078px]    目前为止,发现IDR和CSW,写好SELECT寄存器后,后面需要连续两次读操作,记住要读2遍!  若函数是写SELECT后马上读1次, 该函数即使执行两次也读不出来.必须一次写SELECT 后面跟两次读操作. 更新2014-3-4:并非要2遍,只是当我们读取到数据有误时,继续尝试。mbed的驱动中,基础函数关于ap,dp的读写都有10次的retry。
[size=13.913043975830078px]  11.关于dummy read的问题。
[size=13.913043975830078px]    Note thatwhen reading the AP IDR register, a dummy read is performed first, followed by reading the actual valuefrom the DP RDBUFF register.
[size=13.913043975830078px]

[size=13.913043975830078px]Therefore when executing a read operation, a 2nd dummy read must be done to get the results. If executing a sequence of reads, only one dummy read is needed to get the first result. There is no need for a dummy read between every read in
a sequence.

[size=13.913043975830078px]  12.DPSECLECT 只用来选择Bank吗?
[size=13.913043975830078px]    APBSEL、APBANKSEL、DPBANKSEL都很重要
[size=13.913043975830078px]Tips(杂):
[size=13.913043975830078px]  1.CSW中的DbgSwEnable不要开启。
[size=13.913043975830078px]  2.每一次读写muc内部寄存器后应该加一个延时。

[size=13.913043975830078px]五、版本修改记录
[size=13.913043975830078px]SWD Burn nRF51822 v1.0.0 2014-3-4
[size=13.913043975830078px]1.完成基础驱动的读写,如读写DP,AP寄存器。
[size=13.913043975830078px]2.实现按字读写flash,只完成初步工作,后续可以加入对flash按block擦写。


[size=13.913043975830078px]SWD Burn nRF51822 v1.0.1 2014-3-10
1.加入flash erase,flash write,halt cpu 函数,初步完善swd驱动
2.flash write依然是按地址写入一个byte,后续可以加入对flash按blo,优化速度擦写

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.0 2014-3-11
[size=13.913043975830078px]1.已成功烧录程序并成功运行。
[size=13.913043975830078px]2.在驱动程序的某些地方加入延时函数,解决烧写卡死的问题。
[size=13.913043975830078px]3.arduino烧写程序修改完成,注意,烧写flash时要记得烧写softdevice末尾UICR的信息。
[size=13.913043975830078px]4.速度未优化,目前烧写速度是19K/m,通过提高IO速度,减少烧写flash函数的延时时间可提高速度。
[size=13.913043975830078px]5.未加入verify,未加入程序烧写完毕后reset and run的驱动函数

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.1 2014-3-11
[size=13.913043975830078px]1.加入read block函数,删除不必要的写CSW的代码。
[size=13.913043975830078px]2.更新swd_init函数,CSW寄存器的操作在这里操作一次,以后不再修改。

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.2. 2014-3-11
1.减少延时时间加快烧写速度


[size=13.913043975830078px]SWD Burn nRF51822 v1.1.3. 2014-3-11
1.加入Verify
2.烧写速度为95K/140s


[size=13.913043975830078px]SWD Burn nRF51822 v1.1.4. 2014-3-12 (稳定)
[size=13.913043975830078px]1.在swd_init函数中对abort寄存器操作后加入5ms延时,提高稳定性。
[size=13.913043975830078px]2.已验证cpu halt函数成功停止了工作中的nRF51822。
[size=13.913043975830078px]

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.5 2014-3-12 (快速版)
1.删除swd_burst_write_flash函数中检测NRF_NVMC_READY=0x01的代码(有风险),改用1ms延时替代显著提高速度。95kb/85s
[size=13.913043975830078px]

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.6
[size=13.913043975830078px]1.调整代码结构,删除没用的代码,通过修改dap.h中的宏定义,可以适配任何MCU
[size=13.913043975830078px]

[size=13.913043975830078px]

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.7 (增加mBed KL25做烧录器)
[size=13.913043975830078px]1.增加mBed KL25做烧录器
[size=13.913043975830078px]

[size=13.913043975830078px]SWD Burn nRF51822 v1.1.8 (使用USB 做串口烧录)
[size=13.913043975830078px]1.修改command line代码,以前使用的send buffer函数在mbed的usb串口上会出错!
[size=13.913043975830078px]2.使用USB做串口烧录flash


回复

使用道具 举报

14

主题

3

好友

3174

积分

状元

Rank: 6Rank: 6

  • TA的每日心情
    擦汗
    2016-11-17 08:28
  • 签到天数: 638 天

    [LV.9]以坛为家II

    发表于 2014-5-7 08:08:34 |显示全部楼层
    不错啊,支持一下。
    回复

    使用道具 举报

    8

    主题

    2

    好友

    402

    积分

    秀才

    Rank: 3Rank: 3

  • TA的每日心情
    奋斗
    2014-11-26 16:58
  • 签到天数: 34 天

    [LV.5]常住居民I

    发表于 2014-5-7 15:18:00 |显示全部楼层
    请问GY 你的博客地址是多少,想和你交流 nordic51
    回复

    使用道具 举报

    8

    主题

    0

    好友

    104

    积分

    童生

    Rank: 2

    该用户从未签到

    发表于 2014-5-7 17:48:06 |显示全部楼层
    本帖最后由 wkong501 于 2014-7-15 11:47 编辑
    Depth 发表于 2014-5-7 15:18
    请问GY 你的博客地址是多少,想和你交流 nordic51

    发邮件给我吧,我的博客现在空荡荡的,我所有的笔记都存放在为知笔记里,邮箱:yuting0501@gmail.com
    回复

    使用道具 举报

    0

    主题

    0

    好友

    3

    积分

    白丁

    Rank: 1

    该用户从未签到

    发表于 2016-4-12 14:32:44 |显示全部楼层
    楼主你好,请问SWD协议怎么按字烧写到FLASH??FLASH只能是半字16位操作,我试着用swd协议写进去,按照闪存编程的操作流程,但是读的时候ACK出错,读出来是0x00000000。楼主能指导一下吗?谢谢了
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    关闭

    站长推荐上一条 /6 下一条


    手机版|爱板网 |网站地图  

    GMT+8, 2016-12-6 08:54 , Processed in 0.165954 second(s), 12 queries , Memcache On.

    苏公网安备 32059002001056号

    Powered by Discuz!

    回顶部