功能描述:
使用官方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文件 不是源码