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位,参数addr
为word地址,每次读取两个字节,返回一个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模式.