查看:
30010
回复: 27 |
参赛作品《STM32/STM8离线下载器》
|
|
发表于2018-05-09 11:10:41
|
显示全部楼层
1#
电梯直达
【报名阶段需要填写的内容】 1. 参赛者姓名(必填项): 程人杰 王徽 2. 单位或学校名称(选填项): 3. 当前职务或职称(选填项): 4. 参赛作品的名字(必填项): STM32/STM8离线下载器 5. 简要陈述您的idea和作品(必填项): 当前嵌入式开发中,STM32与STM8是大多数工程师使用的主控芯片,传统的方法是通过电脑和仿真器进行程序的下载,生产上也就必须要配置有电脑,但是对于一些量产型生产,通过电脑显然合适,增加工作量, 同而且需要一定的专业知识,时把生产文件交给生产人员,非常有可能产生泄密,产生巨大的损失。而离线下载器具有体积小,价格便宜,而且可以在程序中设置生产次数,配置加密,可以有效的保护好生产资料,同时离线下载器操作方便,只要工程师在电脑上配置好后,就可以放心的交给生产人员。 6. 拟用到的立创商城在售物料(必填项): STM32,STM8,W25Q64,缓冲器,电源,电阻电容。 7. 拟用到的非立创商城物料或其它补充(必填项): TFT,外壳 8. 拟用到的EDA工具软件名称(必填项): Keil,VS2017,立创EDA设计工具 【作品正式发表(报名成功后进入设计阶段)需要填写的内容】 一、作品简介 例如,可以包括但不局限于以下内容: 1.作品的整机外观图片或焊接组装好的PCBA图片;
2.作品的研究背景、目的和功能、市场应用前景; 3.作品在创新性、趣味性、实用性甚至公益性方面,有哪些亮点可体现? 二、系统构架图 用流程图或思维导图等形式,描述您的作品的组成构架,即方案图。
三、硬件部分的描述 1.附上原理图&PCB实物图的图片或者源文件(官方建议大家尽量用源文件上传),如果是图片,请确保图片是清晰可辨的;
2.用文字把该作品的实现原理、系统的工作过程大致讲解一下。 本离线下载器,主要是基于SWD(Serial Wire Debug)串行调试接口,用于连接ARM内核的芯片,同时基于SWIM( single wire interface module )单线调试模块。 一):SWD接口,当前基于Cortex-M内核的STM32系列应用非常广泛,其中有集成了SWD调试接口,利用该调试接口可以访问芯片内部的总线上寄存器和芯片内核,当然也可以利用此方法方法进行更新Flash程序。其中SWD主要应用框架如下:通过SWD接口用于访问其中的DP端口,再通过DP端口访问其AP端口,通过AP端口再进行系统总线的访问,从而达到程序下载的目的。
图1 - SWD接口
图2:STM32调试单元 SWD具体操作请求:SWD命令的开始是由8位启动,含义如图3,起始位+AP/DP选择位+读/写位 + 两位地址位 + 奇偶检验位 + 停止位 + 固定为1的校验位
图3:SWD起始命令
图4:读写序列
图5,读写序列 二):SWIM接口,SWIM是专用于STM8系列的调试接口,仅仅用到一根线就可以完成调试与仿真,这也是由于STM8定位主要是小的单片机,和高性价比。其SWIM接口主要框图如下:
图6:SWIM调试接口框图 由于SWIM为单线模式,所以其对时间要求非常高,其时序图如下:为此在代码中特意使用了定时器才能满足这样的操作。
图7:SWIM时序图 3.注明所用到的EDA工具软件名称并附上设计链接。 本次EDA工具使用的是:立创EDA,开源的链接如下: https://lceda.cn/lengyu1226/wang1111 四、材料清单(BOM列表) 列出您这个作品所用到的主要器件(关键器件即可),比如单片机&ARM芯片、专用集成芯片(ASIC)、传感器、功能模块等。 如果所列出的芯片是来自我们立创商城上的,最好能写出该器件的商品编号或附上对应购买链接。
五、软件部分的描述(选填) 如果您的作品涉及到软件,请列出作品对应的软件工作流程图,及关键部分的例程、源码(如果您想开源的话请上传全部源码)。 上位机软件截图: 一):由于代码量巨大,贴出部分核心代码,下面为SWD控制的核心代码 /** * @B 可以理解为TurnAround * */ void SWDIO_CYCLE( void ) { if( slowModeEnable == ENABLE) { SWD_Delay(); SWD_SWDIO_PIN_OUT = 0; SWD_Delay(); SWD_SWDIO_PIN_OUT = 1; SWD_Delay(); } else { SWD_SWDIO_PIN_OUT = 0; SWD_SWDIO_PIN_OUT = 1; } } /** * @B 从AP或者DP寄存器里读出数据 * reg:显然只有4个(暂定的取值范围0-3)。 data为什么用指针,因为这样才能改变传递值,也就是指针指向的值 * 结尾后:SWCLK保持为1 */ u32 readReg( u8 APnDPReg,u8 reg, u32 *data ) { u8 i = 0; u8 cb = 0; // u8 parity; // 校验值 u8 b = 0; // 用于读ACK的位 u8 ack = 0; // ACK的值 u8 ret = SWD_ERROR_OK; *data = 0; int _APnDPReg = (int) APnDPReg; int _read = (int) 1; // 读请求值为1 u8 A2 = reg & 0x01; u8 A3 = ( reg>>1 ) & 0x01; parity = ( _APnDPReg + _read + A2 + A3 ) & 0x01; SWD_SWDIO_MODE_OUT; // 设置为输出模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_OUT; // 缓冲器设置为输出模式 { // 启动发送序列 // 发送序列的问题:发送后,可以看出,其中SWCLK保持为1: WRITE_BIT( 1 ); WRITE_BIT( _APnDPReg ); WRITE_BIT( _read ); WRITE_BIT( A2 ); WRITE_BIT( A3 ); WRITE_BIT( parity ); WRITE_BIT( 0 ); WRITE_BIT( 1 ); // SWDIO = 1, SWCLK = 0, SWCLK = 1 } { // TurnAround { SWD_SWDIO_MODE_IN; // 设置为输入模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_IN; // 缓冲器设置为输入模式 } SWCLK_CYCLE(); } { // 读ACK for( i=0;i<3;i++ ) { b = READ_BIT( ); ack |= b<<i; } // 按照我的理解,这里不是应该有一个Trn的么?但是却没有 } { // 判断ACK位 if( ack == ACK_OK ) { for( i=0;i<32;i++) { b = READ_BIT( ); *data |= b<<i; if(b) cb = !cb; // cb之前已经初始化了 } parity = READ_BIT(); // 最后再读一下校验位 // 要使系统稳定的话,处理错误至关重要 if( cb == parity ) { ret = SWD_ERROR_OK; // 系统正常 } else { ret = SWD_ERROR_PARITY; // 校验错误,检验错误就可能是位操作不对,当然这个概率是50% } } else if( ack == ACK_WAIT ) { ret = SWD_ERROR_WAIT; } else if( ack == ACK_FAULT ) { ret = SWD_ERROR_FAULT; } else { ret = SWD_ERROR_PROTOCOL; //协议出错,这个要注意了 } } { // Turnaround SWCLK_CYCLE(); } { // 进入8个Idle状态,确保传输 { SWD_SWDIO_MODE_OUT; // 设置为输入模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_OUT; // 缓冲器设置为输入模式 } for( i=0;i<8;i++ ) { WRITE_BIT(0); } } return ret; // 返回值 } /** * @B 这里有个要注意:可以忽视Ack,因为清除错误需要访问内容寄存器 * */ u32 writeReg( u8 APnDPReg,u8 reg,u32 data,u8 ignoreAck) { u8 ack = 0; u8 i; u8 parity = 0; u8 b; u8 ret = SWD_ERROR_OK; u8 _APnDPReg = APnDPReg; u8 _read = 0; u8 A2 = reg&0x1; u8 A3 = (reg>>1)&0x1; parity = ( _APnDPReg + _read +A2 + A3 )&0x1; // 计算校验值 SWD_SWDIO_MODE_OUT; // 设置为输出模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_OUT; // 缓冲器设置为输出模式 { // 启动发送序列 // 发送序列的问题:发送后,可以看出,其中SWCLK保持为1: WRITE_BIT( 1 ); WRITE_BIT( _APnDPReg ); WRITE_BIT( _read ); WRITE_BIT( A2 ); WRITE_BIT( A3 ); WRITE_BIT( parity ); WRITE_BIT( 0 ); WRITE_BIT( 1 ); // SWDIO = 1, SWCLK = 0, SWCLK = 1 (这个由主机驱动为1的!手册说是不用主机驱动,但是由于上拉总线上会为1) } { { // 发现一个特别奇葩的问题:就是如果把这一句,放到SWCLK_CYCLE();之后就会出现:ACK错误,比较费解! // 可能的原因:就是从机这个时候要驱动SWDIO,但是发现驱动不下来!! // 最好的解决办法:就是通过读DP寄存器找到这个问题! SWD_SWDIO_MODE_IN; // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_IN; } SWCLK_CYCLE(); } { { // 读ACK for( i=0;i<3;i++ ) { b = READ_BIT( ); ack |= b<<i; } // 按照我的理解,这里不是应该有一个Trn的么?但是却没有,(最后发现读是没有的人,但是写是有的!写的在下面) } } { // if( (ack == ACK_OK) || ignoreAck ) { { // 设置为输出 SWD_SWDIO_MODE_OUT; // 设置为输出模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_OUT; // 缓冲器设置为输出模式 } SWCLK_CYCLE(); // 写的时候要用Trn一次。但是读的时候没有在代码里面看到。 parity = 0; // 这个函数有问题,当我改parity的时候,出现可以读状态,说明给器件的值存在检验错误!!这个是不应该的。这个要检查! for( i=0;i<32;i++ ) { b = ( data >> i)&0x1; WRITE_BIT(b); if(b) parity = !parity; } WRITE_BIT(parity); } else if( ack == ACK_WAIT ) { ret = SWD_ERROR_WAIT; } else if( ack == ACK_FAULT ) { ret = SWD_ERROR_FAULT; } else { ret = SWD_ERROR_PROTOCOL; //协议出错,这个要注意了 } { // 进入8个Idle状态,确保传输,这个可以优化! { // 这个其实是多余的,一直是输出的,所以我都考虑这个源代码的性能了。 SWD_SWDIO_MODE_OUT; // 设置为输入模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_OUT; // 缓冲器设置为输入模式 } for( i=0;i<8;i++ ) { WRITE_BIT(0); } } } return ret; } /** * @B 上电执行JTAG转SWD的序列函数! * */ void JTAG_To_SWD_Sequence( void ) { int i; int b; SWD_SWDIO_MODE_OUT; // 设置为输出模式 // SWD_SWDIO_DIR_CTR2 = BUFFER_IC_DIR_OUT; // 缓冲器设置为输出模式 { SWD_SWDIO_PIN_OUT = 1; // 先输出为1 for( i=0;i<80;i++ ) { SWCLK_CYCLE(); } } for( i=0;i<16;i++ ) { b = ( JTAG_TO_SWD_VALUE>>i )&0x1; WRITE_BIT(b); } { SWD_SWDIO_PIN_OUT = 1; for( i=0;i<60;i++ ) { SWCLK_CYCLE(); } } { // 执行16个空闲周期 SWD_SWDIO_PIN_OUT = 0; for( i=0;i<16;i++ ) { SWCLK_CYCLE(); } } 二):下面是SWIM的核心代码
static void SWIM_Init() { if (!SWIM_Inited) { SWIM_Inited = 1; SYNCSWPWM_OUT_TIMER_INIT(0); SYNCSWPWM_PORT_OD_INIT(); //端口切换到第二功能输出口 } } static void SWIM_Fini() { SYNCSWPWM_PORT_ODPP_FINI(); SYNCSWPWM_OUT_TIMER_FINI(); SYNCSWPWM_IN_TIMER_FINI(); SWIM_Inited = 0; } static void SWIM_EnableClockInput(void) { SWIM_clock_div = 0; SYNCSWPWM_IN_TIMER_INIT(); } static uint8_t SWIM_EnterProgMode(void) { uint8_t i; uint32_t dly; SYNCSWPWM_IN_TIMER_RISE_DMA_INIT(10, SWIM_DMA_IN_Buffer); //分配DMA存储空间 SWIM_LOW(); System_Delay_us(1000); for (i = 0; i < 4; i++) { SWIM_HIGH(); System_Delay_us(500); SWIM_LOW(); System_Delay_us(500); } for (i = 0; i < 4; i++) { SWIM_HIGH(); System_Delay_us(250); SWIM_LOW(); System_Delay_us(250); } SWIM_HIGH(); dly = SWIM_MAX_DLY; SYNCSWPWM_IN_TIMER_RISE_DMA_WAIT(dly); if (!dly) { return 1; } else { return 0; } } static u8 SWIM_HW_In(u8* data, u8 bitlen) { u8 ret = 0; u32 dly; dly = SWIM_MAX_DLY; SYNCSWPWM_IN_TIMER_RISE_DMA_WAIT(dly); //先接收引导bit,目标到主机,这个位必须是1 *data = 0; if (dly && (SWIM_DMA_IN_Buffer[1] < SWIM_PULSE_Threshold)) //如果=1,低电平时间小于4个脉冲 { for (dly = 0; dly < 8; dly++) { if (SWIM_DMA_IN_Buffer[2 + dly] < SWIM_PULSE_Threshold) { *data |= 1 << (7 - dly); } } SYNCSWPWM_IN_TIMER_RISE_DMA_INIT(11, SWIM_DMA_IN_Buffer); SWIM_DMA_OUT_Buffer[0] = SWIM_PULSE_1; SWIM_DMA_OUT_Buffer[1] = 0; SYNCSWPWM_OUT_TIMER_DMA_INIT(2, SWIM_DMA_OUT_Buffer); SYNCSWPWM_OUT_TIMER_DMA_WAIT(); } else { ret = 1; } return ret; } static uint8_t SWIM_SRST(void) { return SWIM_HW_Out(SWIM_CMD_SRST, SWIM_CMD_BITLEN, SWIM_MAX_RESEND_CNT); } static uint8_t SWIM_WOTF(uint32_t addr, uint16_t len, uint8_t *data) { uint16_t processed_len; uint8_t cur_len, i; uint32_t cur_addr, addr_tmp; u8 rtv2; if ((0 == len) || ((uint8_t*)0 == data)) { return 1; } processed_len = 0; cur_addr = addr; while (processed_len < len) { if ((len - processed_len) > 255) { cur_len = 255; } else { cur_len = len - processed_len; } SET_LE_U32(&addr_tmp, cur_addr); if(SWIM_HW_Out(SWIM_CMD_WOTF, SWIM_CMD_BITLEN, SWIM_MAX_RESEND_CNT)) { return 1; } if (SWIM_HW_Out(cur_len, 8, 0)) { return 2; } rtv2=SWIM_HW_Out((addr_tmp >> 16) & 0xFF, 8, 0); //retry=0,出错后不重发 if (rtv2) { return 3; } if (SWIM_HW_Out((addr_tmp >> 8) & 0xFF, 8, 0)) { return 4; } if (SWIM_HW_Out((addr_tmp >> 0) & 0xFF, 8, 0)) { return 5; } for (i = 0; i < cur_len; i++) { if (SWIM_HW_Out(data[processed_len + i], 8, SWIM_MAX_RESEND_CNT)) { return 6; } } cur_addr += cur_len; processed_len += cur_len; } return 0; } static uint8_t SWIM_ROTF(uint32_t addr, uint16_t len, uint8_t *data) { uint16_t processed_len; uint8_t cur_len, i; uint32_t cur_addr, addr_tmp; if ((0 == len) || ((uint8_t*)0 == data)) { return 7; } processed_len = 0; cur_addr = addr; while (processed_len < len) { if ((len - processed_len) > 255) { cur_len = 255; } else { cur_len = len - processed_len; } SET_LE_U32(&addr_tmp, cur_addr); if(SWIM_HW_Out(SWIM_CMD_ROTF, SWIM_CMD_BITLEN, SWIM_MAX_RESEND_CNT)) { return 6; } if (SWIM_HW_Out(cur_len, 8, 0)) { return 5; } if (SWIM_HW_Out((addr_tmp >> 16) & 0xFF, 8, 0)) { return 4; } if (SWIM_HW_Out((addr_tmp >> 8) & 0xFF, 8, 0)) { return 3; } if (SWIM_HW_Out((addr_tmp >> 0) & 0xFF, 8, 0)) { return 2; } for (i = 0; i < cur_len; i++) { if (SWIM_HW_In(&data[processed_len + i], 8)) { return 1; } } cur_addr += cur_len; processed_len += cur_len; } return 0; } 六、作品演示 请上传您的作品的功能演示到腾讯视频,并编辑到本楼(或附上视频链接)。按要求上传视频可获得10分,具体详见活动规则。
一共录制了两个视频,第一个视频详细演示了离线下载器操作过程与说明时间较长,第二个相对简单的演示了整个操作流程。 1): http://url.cn/5ZQU77A?sf=uri 2):http://url.cn/5OXUQgP?sf=uri
七、总结 例如您在完成该作品过程中的一些体会、碰到的技术问题或调试经验、作品的未来规划,及对我们主办方的建议和意见等。
八:相关资料下载: 2:ARM Debug Interface v5 Architecture Specification.zip
|
|
发表于2021-10-07 16:23:03
|
显示全部楼层
27#
标记一下
|
|