STM32F051 Bootload IAP远程升级

功能描述:

使用官方STM32F0-Discovery开发板,通过板载UART直连自制4G_DTU,4G_DTU连接服务器,在服务器上设计需要升级的软件,升级程序可通过服务器上的软件加载。主要使用:STM32、串口、内部flash操作、画4G Cat.1的原理图及PCB,编写远程服务器后台程序(Java    socket)。

硬件设计:

硬件开发板资料:https://www.stmcu.org.cn/document/list/index/category-622

4G_DTU资料:后面可能会开放原理图、PCB以及使用方法。

4G_DTU购买链接:https://item.taobao.com/item.htm?id=632028079844

软件设计:

1):单片机软件:

在写单片机程序之前,我们先给单片机做一个flash区分,这样就能知道我们升级的IAP程序是在哪个区域运行了。我看了一下我的Bootload占的空间应该是12K不到,嫌大的可以删除一些程序代码,我也没有做这个精简版,有兴趣的可以尝试一下。下图是STM32F051R8T6的flash区分,讲一下bootload的设计思路。我是使用keil MDK开发,keil支持程序运行地址偏移,可以设置程序运行跳转地址,SRAM从哪里开始使用,在bootload程序里面并没有体现出来,但是在写应用程序的时候一定要区分。程序从启动文件进0x8000000开始运行,进入主函数判断是否有可升级的文件,需要升级的话就会初始化外设,比如串口IO口,延时函数。然后执行一系列的接收指令和数据的操作,在源码下载链接里面有单片机和上位机的数据协议,整理了一份。下面是主函数的代码,能看出来大致的流程,感兴趣的同仁下载源码看其他的程序吧,不一一描述了。

int main(void)
{
//	SystemInit(); //内部RC震荡
	uint16_t Cnt_200ms=0;
	uint16_t WriteBuf[1] = {0xAAAA};
	LED_Init();
//	memcpy((void*)0x20000000,(void*)0x08000000,VECTOR_SIZE);
//	SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
	//检查如果发现有可升级版本则进入升级
	if (FLASH_OB_GetRDP()!=SET)  
    { 
//        FLASH_Unlock();                          
        FLASH_OB_RDPConfig(OB_RDP_Level_1);          
    }
	
	if( *(vu16 *)FLAG_NEW_APP_ADDER == FLAG_NEW_APP_SET )
	{	
		GPIO_SetBits(GPIOC, GPIO_Pin_9);
		delay_init();
		Usart_Init(115200);
		if(FLASH_OB_GetRDP() != RESET)
		{
			FLASH_OB_RDPConfig(OB_RDP_Level_0);
		}
		//向上位机发送Bootlander程序段已准备好
		USART1SendRespondToServer(SERIAL_CODE_STM32_UPDATE_PREPAR_BOOT_OK);
		//检查上位机指令
		ReceiveCmd();
		
		//向上位机请求固件数据
		USART1SendRespondToServer(SERIAL_CODE_STM32_UPDATE_PREPAR_OK);
		//检查上位机发送的固件并写入到BackupFlash处
		ReceiveCode();
		
		//将 BackupApp 处的程序拷贝到 App处
		//注意若此处发生错误将会导致程序死亡
		Iap_BackupApp_WriteTo_App();  //

		//清除升级标志位 并 跳转到App运行
		Go_App();
	}
	else
	{
		if(((*(vu32*)(SYS_APP_ADDER + 4)) & 0xFF000000) == 0x08000000)//判断是否为0X08XXXXXX.
		{
			Iap_Load_App(SYS_APP_ADDER);
		}
	}
	TIM2_Init();
	Usart_Init(115200);
	while(1)
	{
		if(Usart1_Rx_Finish_Flag==1)
		{
			Usart1_Rx_Finish_Flag=0;
			STMFLASH_Write(FLASH_ADDR_FLAG,(uint16_t*)&WriteBuf, 1);
			SW_RESET();
		}
		if(System1Ms==1)
		{
			System1Ms=0;
			if(++Cnt_200ms>=100)
			{
				Cnt_200ms=0;					  
			}
		}
		if(system500ms==1)
		{
			system500ms=0;
			LED1_Toggle();
		}
	}
}

我再贴一下应用程序的main函数和keil设置。可以看出应用程序中SRAM地址也有偏移  主要是复制中断向量表。应用程序也许遵循协议,接收到程序升级指令,写flash升级标志,然后软重启。

#include "stm32f0xx.h"
#include "led.h"
#include "time.h"
#include "Time1_PWM.h"
#include "ADC1.h"
#include "Usart1.h"
#include <string.h>
#include <stdio.h>
#include "flash.h"

#define VECTOR_SIZE  0xC0

#define  SW_RESET()	       NVIC_SystemReset() 

#define FLASH_ADDR_FLAG           0x08003400     



int main(void)
{
//	uint32_t i = 0;
//	SystemInit(); //ÄÚ²¿RCÕ
	uint16_t Cnt_200ms=0;
	uint16_t WriteBuf[1] = {0xAAAA};
	memcpy((void*)0x20000000,(void*)0x08003800,VECTOR_SIZE);
	SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
	if (FLASH_OB_GetRDP()!=SET)  
    { 
//        FLASH_Unlock();                          
        FLASH_OB_RDPConfig(OB_RDP_Level_1);          
    }
	TIM2_Init();
//	Adc_Init();
	LED_Init();
//	Realy_Init();
	GPIO_ResetBits(GPIOB, GPIO_Pin_4);
//	TIM1_PWM_Init();
	Uart1_Init();; 
	Usart1_Printf(Send_char,8);
//	Send_char[0] = 0XAA;
//	Send_char[1] = 0X55;
	while(1)
	{
		if(System1Ms==1)
		{
			System1Ms=0;
			if(++Cnt_200ms>=100)
			{
				Cnt_200ms=0;
				LED1_Toggle();
			}
		}
		if(Usart1_Rx_Finish_Flag==1)
		{
			Usart1_Rx_Finish_Flag=0;
			FLASH_WriteNWord((uint16_t*)&WriteBuf, FLASH_ADDR_FLAG, 1);
			SW_RESET();
		}
//		if(system500ms==1)
//		{
//			system500ms=0;
//			LED1_Toggle();
//		}
	}
}

2)服务器程序

用Java写了一个GUI界面,丑不丑的能看就行。。。主要使用SWT和Socket,也是初学的时候写的,肯定会有不足,懒得改了。数据处理的代码贴出来,界面的程序链接中下载。

package Net.Port;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import com.ibm.icu.util.BytesTrie.Entry;

import comm.Port.SerialListner;
import comm.Port.SerialTool;
import comm.Port.SerialUI;


public class ServerDataDeal {

//	private static int port_num = 0;
	// 存储所有注册的客户端
//	private static byte[] pushHead = new byte[2];
//	private static byte[] exitbyte = new byte[8];
	private static byte[] bytes = new byte[16];
	public static byte[] data_bytes = new byte[1031];
	
//	private static volatile Map<Socket, String> clientMap = new ConcurrentHashMap<Socket, String>();
	private static ExecutorService executorService;
	private static ServerSocket serverSocket;
	private static byte SumNumber;
	public static volatile int UpdataFlag=0;
	public static int Page_Cont=0;	//接收包计数
	public static volatile int Page_Val=0;	//总包数
	public static byte UpdataLength=0;

	// 具体处理与每个客户端通信的内部类
	private static class ExecuteClient implements Runnable {
		private Socket client;
//		private String exitDriveName; // 设备退出变量
//		private String[] exitDriveName01; // 设备退出变量数组
//		private int exitDriveNum; // 需要移除的设备计数

		public ExecuteClient(Socket client) {
			this.client = client;
		}

		@Override
		public void run() {
			try {
				InputStream ins = client.getInputStream();
				while (true) {
					byte[] bys = new byte[1024];
					int len = ins.read(bys);
					String strFormClient = new String(bys, 0, len);
					System.out.println(strFormClient);
					// 解析数据
					if ((bys[0] == 0x55) && (bys[1] == (-86)) && (bys[2] == 0x01)) { 
						try {
							switch (bys[4]) {
								case 0: {
									UpdataLength=(byte)bys[3];
									UpdataFlag=1;
								}
								break;
								case 1: {
									UpdataLength=(byte)bys[3];
									UpdataFlag=2;
								}
								break;
								case 2: {
//									UpdataLength=(byte)data[3];
//									Page_Cont++;
									UpdataFlag=2;
								}
								break;
								default: {
									UpdataFlag=0;
								}
									break;
							}
							// 更新界面Label值

						} catch (ArrayIndexOutOfBoundsException e) {
							MessageDialog.openInformation(new Shell(), "Error", "数据解析过程出错,更新界面数据失败!请检查设备或程序!");
							System.exit(0);
						}
					}
					else
					{
						Display.getDefault().syncExec(() -> {
							DataViewUINet.Debugtext.append(strFormClient+"\n");
						});
					}
					
				}
			} catch (Exception e) {
//				System.err.println("服务器通信异常,错误" + e);
				MessageDialog.openInformation(new Shell(), "Error", "服务器通信异常,错误");
			}
		}
	
	}

	// 具体处理与每个客户端通信的内部类
	private static class DealChatGUI extends Thread {
		@Override
		public void run() {
			DataViewUINet window = new DataViewUINet();
			window.open();
		}
	}
	public static void main(String[] args) throws Exception {
		executorService = Executors.newFixedThreadPool(200);
//		executorService.submit(new DealChatGUI());
		new DealChatGUI().start();
		serverSocket = null;
		int[] port_num = { 0 };
		int total_con = 0;
		try {
			while (true) {
				Thread.sleep(200);
				if (DataViewUINet.Connect_Flag != 1)
					continue;
				Display.getDefault().syncExec(() -> {
					port_num[0] = Integer.parseInt(DataViewUINet.textID.getText());
				});
				if (serverSocket != null) {
					try {
						serverSocket.close();
						serverSocket = null;
					} catch (Exception e) {
						// TODO: handle exception
					}
				}
				serverSocket = new ServerSocket(port_num[0]);
				System.out.println("等待客户端连接。。\n");
				Display.getDefault().syncExec(() -> {
					DataViewUINet.text_Msg.append("等待客户端连接...\n");
				});
				Socket client = serverSocket.accept();
				System.out.println(
						"有新的客户端连接," + "IP为:" + client.getInetAddress() + "  端口号为:" + client.getPort() + "\n");
				Display.getDefault().syncExec(() -> {
					DataViewUINet.text_Msg.append("有新的客户端连接," + "IP为:" + client.getInetAddress() + "  端口号为:"
							+ client.getPort() + "\n");
				});
				executorService.submit(new ExecuteClient(client));
				while (total_con < 20) {
					Thread.sleep(200);
					if (DataViewUINet.data_UpdataFlag == 1) {
						try {
							OutputStream out = client.getOutputStream();
							bytes[0] = (byte) 0x55;
							bytes[1] = (byte) 0xAA;
							bytes[2] = 0x01;
							data_bytes[0] = (byte) 0x55;
							data_bytes[1] = (byte) 0xAA;
							data_bytes[2] = 0x01;
							switch (UpdataFlag) {
								case 0: {
									UpdataFlag = 10;
									bytes[3] = 0x10;
									bytes[4] = 0x00;
									bytes[5] = 0x00;
									bytes[6] = 0x00;
									bytes[7] = 0x00;
									bytes[8] = 0x00;
									bytes[9] = 0x00;
									bytes[10] = 0x00;
									bytes[11] = 0x00;
									bytes[12] = 0x00;
									bytes[13] = 0x00;
									bytes[14] = 0x00;
									for (int x = 0; x < 15; x++) {
										SumNumber = (byte) (SumNumber + bytes[x]);
									}
									bytes[15] = (byte) (0 - SumNumber);
									out.write(bytes);
									out.flush();
									SumNumber = 0;
	
								}
								break;
								case 1: {
									UpdataFlag = 10;
									bytes[3] = UpdataLength;
									bytes[4] = 0x01;
									bytes[5] = (byte) ((DataViewUINet.filelength / 1024) + 1);
									bytes[6] = 0x00;
									bytes[7] = 0x00;
									bytes[8] = 0x00;
									bytes[9] = 0x00;
									bytes[10] = 0x00;
									bytes[11] = 0x00;
									bytes[12] = 0x00;
									bytes[13] = 0x00;
									bytes[14] = 0x00;
									for (int x = 0; x < 15; x++) {
										SumNumber = (byte) (SumNumber + bytes[x]);
									}
									bytes[15] = (byte) (0 - SumNumber);
									out.write(bytes);
									out.flush();
									SumNumber = 0;
								}
									break;
								case 2: {
//									if (SerialListner.Page_Cont == 0) {
//										dataall_bytes = Bsubcontent.getBytes();
//									}
									if (Page_Cont < (Page_Val - 1)) {
										UpdataFlag = 10;
										data_bytes[3] = (byte) Page_Cont;
										data_bytes[4] = 0x02;
										data_bytes[5] = (byte) 0xff;
										for (int x = 0; x < 1024; x++) {
											data_bytes[6 + x] = (byte) DataViewUINet.dataall_bytes[x + Page_Cont * 1024];
										}
										for (int y = 0; y < 1031; y++) {
											SumNumber = (byte) (SumNumber + data_bytes[y]);
										}
										data_bytes[1030] = (byte) (0 - SumNumber);
//										SerialTool.sendToPort(SerialUI.getSerialPort(), data_bytes);
										out.write(data_bytes);
										out.flush();
//										System.out.println(data_bytes.length + "..." + Arrays.toString(data_bytes));
										SumNumber = 0;
										for (int x = 0; x < 1024; x++) {
											data_bytes[6 + x] = 0;
										}
										Page_Cont++;
										Display.getDefault().syncExec(() -> {
											DataViewUINet.progressBar.setSelection((int)(100/(Page_Val-1)*Page_Cont)); //DataViewUI.progressBar.getSelection() + 
										});
//										
										
									} else {

//										byte[] dataall_bytes = Bsubcontent.getBytes();
										UpdataFlag = 10;
										data_bytes[3] = (byte) Page_Cont;
										data_bytes[4] = 0x02;
										data_bytes[5] = (byte) 0xff;
										for (int x = 0; x < ((DataViewUINet.filelength) - ((Page_Cont) * 1024)); x++) {
											data_bytes[6 + x] = (byte) DataViewUINet.dataall_bytes[x + Page_Cont * 1024];
										}
										for (int y = 0; y < 1031; y++) {
											SumNumber = (byte) (SumNumber + data_bytes[y]);
										}
										data_bytes[1030] = (byte) (0 - SumNumber);
//										SerialTool.sendToPort(SerialUI.getSerialPort(), data_bytes);
										out.write(data_bytes);
										out.flush();
//										System.out.println(data_bytes.length + "..." + Arrays.toString(data_bytes));
										SumNumber = 0;
//										SerialListner.Page_Cont = 0;
										DataViewUINet.data_UpdataFlag = 0;
										
										Display.getDefault().syncExec(() -> {
											DataViewUINet.progressBar.setSelection((int)(100/(Page_Val-2)*Page_Cont));
//											if(DataViewUI.progressBar.getSelection()==100) {
												MessageDialog.openInformation(new Shell(), "Success", "File Updata Successful");
//											}
										});
									}

								}
									break;

								default: {

								}
									break;
								}

						} catch (IOException e) {
							MessageDialog.openInformation(new Shell(), "Error", "私发信息错误。");
						}
					}
//					try {
//						System.out.println("等待客户端连接。。\n");
//						Display.getDefault().syncExec(() -> {
//							DataViewUI.text_Msg.append("等待客户端连接...\n");
//						});
//						Socket client = serverSocket.accept();
//						System.out.println(
//								"有新的客户端连接," + "IP为:" + client.getInetAddress() + "  端口号为:" + client.getPort() + "\n");
//						Display.getDefault().syncExec(() -> {
//							DataViewUI.text_Msg.append("有新的客户端连接," + "IP为:" + client.getInetAddress() + "  端口号为:"
//									+ client.getPort() + "\n");
//						});
//						executorService.submit(new ExecuteClient(client));
//					} catch (IOException e) {
//						// TODO 自动生成的 catch 块
//						e.printStackTrace();
//						break;
//					}

				}
//				serverSocket.close();
//				
			}
		} finally {
			executorService.shutdown();
		}
	}
}

调试结果:

总结:

博客就只是分享了原理图和部分程序,一些注释也不全。有需要的可以下载源码。

单片机源码链接:单片机源码

Java源码链接:.class文件 不是源码

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值