乐趣区

LarduinoISP-for-LGT8FX8D-SWD通信协议源码简析

LGT8FX8D/ P 系列的 CPU 可以指令级兼容 avr 芯片, 引脚定义也相近. 将 avr 的程序移植到 LGT8FX8D/ P 只需作少量的修改, 而且增强了一些性能, 价格却更低, 性价比高.
要将程序写入空片, 其 flash 烧写方式与 avr 并不一样, 需要专门的调试下载器. 使用说明
在 LarduinoISP for LGT8FX8D 公开了份代码, 其中实现了通过 SWD 接口实现 LGT8FX8D 的读写. 我们通过阅读这份代码来看看通过 SWD 通信方式来实现 flash 烧写的过程.

SWD 通信硬件要求

需使用的引脚:

引脚 传输方向 描述
SWD 输入与输出 用作传输数据比特, 双向
SWC 输出 用作时钟信号
RST 输出 SWD 模式时 RST 拉低

SWD对应 PB5,SWC对应 PB4,RST对应 PB2

#define SWDIF_PIN        PINB
#define SWDIF_DIR        DDRB
#define SWDIF_PORT        PORTB
#define SWDIF_CLK        (1 << 5)    // PB5
#define SWDIF_DAT          (1 << 4)    // PB4
#define SWDIF_RSTN        (1 << 2)    // PB2
SWD 通信时序分析

所有时序的实现, 由 CPU 操作 IO 口完成.

通信速率

在一个通信时钟周期会调用二次 SWD_Delay() 即 $12*2$ 个 NOP 指令, 再加上设置 SWC SWD 电平的指令, 总约需 30 个指令周期, 在 16MHz 的系统时钟下, 通信速率可达 500Kbps. 如果一个位输出中有更多的逻辑操作, 则通信频率会更低一些.

#define SWD_Delay()    do {\
    NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); \
    NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); \
} while(0);
        // 一个时钟周期的模拟
        SWC_CLR();
        SWD_Delay();
        SWD_CLR();
        SWD_Delay();
        SWC_SET();
        SWD_Delay();
通信时序

数据通信由 起始位 SWD_CLR开始, 然后是输出一个字节是由一串 8 比特数据一个结束位 SWD_SET 组成, 如有多个字节输出, 则中间结束位为SWD_CLR, 最后的结束位为SWD_SET. 每个字节的先输出最低位, 再到最高位的顺序输出, 即LSB.

双向引脚 SWD 输出数据时, 在时钟 SWC 低电平时改变 SWD 输出数据, 写入目标芯片会在 SWC 的上升沿检测 SWD 数据.

void SWD_WriteByte(uint8_t start, uint8_t data, uint8_t stop)
{
    volatile uint8_t cnt;
    
    if(start) {SWC_CLR();
        SWD_Delay();
        SWD_CLR();
        SWD_Delay();
        SWC_SET();
        SWD_Delay();}
    
    // send data
    for(cnt = 0; cnt < 8; cnt++)
    {SWC_CLR();
        if(data & 0x1) SWD_SET();
        else SWD_CLR();
        SWD_Delay();
        data >>= 1;
        SWC_SET();
        SWD_Delay();}
    
    SWC_CLR();
    if(stop) SWD_SET();
    else SWD_CLR();

    SWD_Delay();
    SWC_SET();
    SWD_Delay();}

双向引脚 SWD 输入数据时, 在时钟 SWC 高电平时设为输入并上拉, 由写入目标芯片控制 SWD, 在SWC 的下降沿检测 SWD 数据.

uint8_t SWD_ReadByte(uint8_t start, uint8_t stop)
{
    volatile uint8_t cnt;
    volatile uint8_t bRes = 0;
    
    if(start)
    {SWC_CLR();
        SWD_CLR();
        SWD_Delay();
        SWC_SET();
        SWD_Delay();}
    
    SWD_IND();
    //SWD_Delay();
    for(cnt = 0; cnt < 8; cnt++)
    {
        bRes >>= 1;
        SWC_CLR();
        SWD_Delay();
        if(SWDIF_PIN & SWDIF_DAT)
            bRes |= 0x80;

        SWC_SET();
        SWD_Delay();}
    
    SWD_OUD();
    
    SWC_CLR();
    if(stop) SWD_SET();
    else SWD_CLR();

    SWD_Delay();
    SWC_SET();
    SWD_Delay();
    
    return bRes;
}

一些操作需要通过一些时序才能完成, 故有些需加上 SWD_Idle 等待.

void SWD_Idle(uint8_t cnt)
{
    volatile uint8_t i;

    SWD_SET();
    
    for(i = 0; i < cnt; i++)
    {SWC_CLR();
        SWD_Delay();
        SWC_SET();
        SWD_Delay();}
}
Flash 写入相关
  • 读取 SWD ID

SWD_ReadSWDID()

  • Unlock()操作, 因芯片的保护机制, 掉电再上电后, 是不能通过 SWD 接口来读取 Flash 中的数据的, 此 Unlock 操作相当于对芯片进行全芯片擦除, 之后所读数据全部为 0xff, 并允许写入操作.
uint8_t SWD_UnLock()
{
...
    SWD_UnLock0();
    SWD_EEE_UnlockTiming();
    SWD_UnLock1();
    delayus(100);
    SWD_UnLock0();
    delayus(100);
    SWD_UnLock1();    
...
}
  • 读取 Read(), 因 flash 为 16 位, 参数addrword地址, 每次读取两个字节, 返回一个 word 数据

uint16_t SWD_EEE_Read(uint16_t addr)
{
    volatile uint8_t hbyte, lbyte;
    
    SWD_EEE_CSEQ(0x00, addr);
    SWD_EEE_CSEQ(0xa0, addr);
    
    SWD_WriteByte(1, 0xaa, 1);
    lbyte = SWD_ReadByte(1, 0);
    hbyte = SWD_ReadByte(0, 1);
    SWD_Idle(10);
    SWD_EEE_CSEQ(0x00, addr);

    return (hbyte << 8) | lbyte;
}
  • 写入 write(), 因 flash 为 16 位, 参数addr 为 word 地址, 每次写入两个字节, 即一个 word 数据
void SWD_EEE_Write(uint16_t data, uint16_t addr)
{
    volatile uint8_t ib;
    volatile uint8_t timout = 0x1f;
    
    SWD_EEE_CSEQ(0x00, addr);
    SWD_EEE_DSEQ(data);
    SWD_EEE_CSEQ(0x04, addr);
    SWD_EEE_CSEQ(0x84, addr);
    SWD_EEE_CSEQ(0x02, addr);
    
    do {delayus(50);
        ib = SWD_EEE_GetBusy();
        --timout;
    } while(ib == 1 && timout > 0);
    
    SWD_EEE_CSEQ(0x00, addr);
}

以上 16 位地址和数据输入输出均为低字节优先, 然后再高字节, 即 little-endian 模式.

退出移动版