查看: 30000
回复: 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



七、总结

例如您在完成该作品过程中的一些体会、碰到的技术问题或调试经验、作品的未来规划,及对我们主办方的建议和意见等。



  1. 在进行开发的过程中使用了立创EDA一共设计了三款PCB,第一个因为外形不够,第二个测试过程中发现几处不合理,经过三次的更改最终成型。
  2. 需要支持更多的芯片类型,当前下载器可以支持STM32与STM8系列,但是为了日后能够推广,需要支持更多的芯片,这样一个下载器在手上,可以进行多种类型的芯片的下载,而不用多套设备
  3. SWD/SWIM调试阶段比较难懂,尤其是对于协议部分,但是如何仔细阅读,深刻理解其中的含义,最后发现也比较简单,所以遇到开发的难题时候,要迎难而上。



八:相关资料下载:

1:串口通信协议.zip 

2:ARM Debug Interface v5 Architecture Specification.zip 

3:CD00226555.zip 

4:en.CD00173911.pdf.zip 

5:en.CD00191343.pdf.zip 



主题

回复
发表于2019-12-18 22:26:36   |  显示全部楼层
25#
这是我一直想做,但没做出来的!

主题

回复
  • 温馨提示: 标题不合格、重复发帖、发布广告贴,将会被删除帖子或禁止发言。 详情请参考: 社区发帖规则
  • 您当前输入了 0 个文字。还可以输入 8000 个文字。 已添加复制上传图片功能,该功能目前仅支持chrome和火狐

禁言/删除

X
请选择禁言时长:
是否清除头像:
禁言/删除备注:
昵 称:
 
温馨提示:昵称只能设置一次,设置后无法修改。
只支持中文、英文和数字。

举报

X
请选择举报类型:
请输入详细内容:

顶部