目标:设计STM32的BootLoader程序,实现STM32远程更新APP程序。
工具:STM32ZET6和SIM800c模块(客户端),一个可以上网的SIM卡,一个有外网的电脑(服务器端)。
思路:stm32通过SIM800c的GPRS通信模块可以与服务器进行通信,服务器端发送APP程序,STM32接收并写入特定地址的flash中,接收完毕后在flash中固定位置处运行,即可实现APP程序的远程更新。服务器和STM32通信可通过简单的SOCKET进行。首先需要了解一些基础知识。
TCP/UDP协议
在了解socket通信之前,需要了解一下什么是TCP协议。TCP/IP事实上是一些协议(protocols)的合集。当前大多数使用中的通信都是使用TCP协议。为了实现共享,TCP是通过把需要发送的数据流分解为很多小信息包在Internet上传输的,而这些信息包到了接收者的地方会再次合成在一起。通过分解成小的信息包,其他程序的信息包也可以同时被传送。
TCP需要远程识别机器,每台机器都有一个唯一的IP的地址,通过端口号可以知道是这台电脑的哪个程序在发送和接收信息包。所以每个TCP连接的端点是由一个IP地址和一个端口号来唯一标识的。
TCP是一个可靠的协议,除非整个网络出了问题,数据将完好的正确的传输到另一端。
此外还有UDP也被广泛的使用,经常被用来从一个系统向其他系统传送非常短的消息。只能保证接收到的数据是完整的,UDP比TCP低级,即不能保证数据是否真的能被接收到,也不能保证数据是不是只接收一次,还不能保证接收到的信息次数是否和发送时候的一致,但是优点是,由于它不能提供上面的那些保证,所以比TCP低级,TCP建立和关闭连接要花费时间,而UDP对连接没有什么概念,不存在花费时间建立和关闭连接的问题。
对于我们的问题,需要可靠的数据传输,而且需要传输大量的数据,所以需要选择TCP协议。
Python socket通信
在网络编程中的一个基本组件就是套接字(socket)。套接字基本上是两个端点的程序之间的“信息通信”。在Python中的大多数网络编程都隐藏了socket模块的基本细节,不直接和套接字交互。
套接字包括服务器套接字和客户机套接字。对于我们的问题,首先需要建立服务器套接字,直接运用Python的socket模块。
服务器端程序
思路是这样的:
1.首先运用socket模块中的socket类实例化一个socket对象,设置通信类型为IPv4,协议家族为TCP(socket类示例化默认参数即为IPv4和TCP);
2.设置socket选项(如果没有这个选项,当用ctrl+c终止该服务时,需要等上一段时间才能使用同一个端口号,有的时候确实要等很久);
3.然后使用blind方法,再调用listen方法去监听某个特定的地址
以上是最简单的服务器流程,对于我们的服务器,需要读取APP文件,即后缀为.bin的文件,一般文件的大小为几k~几百k,需要将文件的内容分段发送,否则一次发送大量文件会出现丢包的情况,比如我将文件分为若干个1024个字符,每次发送1024个字符,字符前面加上特定的序列表示此次发送的是文件中的那一段,在发送完成后,需要一个接收函数,接收stm32端返回的序列,如果1s内没有接收到返回,则重新发送这段序列的内容,直到接收到返回的序列,代表发送完成。
具体的程序如下:
import socket
import os
import time
import math
sk = socket.socket()
#sk.settimeout(1)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#当程序终止后,端口释放
sk.bind(("182.92.108.54",9995))
sk.listen(5)
waitTime = 1 #接收等待时间为1s,可以根据自己的要求来设定
while True:
print("wait for connecting... ... ....")
conn,address = sk.accept()
print("connected!")
#获取文件大小
dataSize = os.stat("RTCNew.bin").st_size
#conn.sendall(str(dataSize))
print("Data Size:%d" % dataSize)
sendSizeOnce = 1024
sendOnce = []
#time.sleep(5)
#发送包的格式为:序号+“;”+数据,便于客户端知道传送的是哪个数据段
sendOnce = str(0)+";"+str(dataSize)
print("send line:0 ")
#先将文件大小包发送至客户端
while True:
try:
print("try send data to client")
conn.sendall(sendOnce)
time.sleep(waitTime)
recvData = conn.recv(1024,socket.MSG_DONTWAIT)#如果没有接收到数据,则会触发错误,直接进入except中
print("%s" %recvData)
if recvData == '0':#如果满足条件才退出
print("recvDataFromClientSuccess:%s,complete data sending" %recvData)
break
except:
print("Not data received by client,send again... ...")
#将文件拆分为若干个1024个字节再发送
with open("RTCNew.bin","rb") as f:
allData = f.read()
dataLength = 0
numSend = int(math.ceil(dataSize/float(sendSizeOnce)))
for lineIndex in range(numSend):
if lineIndex != numSend:
sendOnce = str(lineIndex+1) + ";"+allData[lineIndex * sendSizeOnce:(lineIndex+1) * sendSizeOnce];
else:
sendOnce = str(lineIndex+1) + ";"+allData[lineIndex * sendSizeOnce:dataSize];
dataLength += len(sendOnce) - len(str(lineIndex+1)) - 1
print("send line:%d data Length:%d Complete: %dpercent" %(lineIndex+1,dataLength,dataLength * 100/dataSize))
#sendOnce = []
while True:
try:
conn.sendall(sendOnce)
time.sleep(waitTime)
recvData = conn.recv(1024,socket.MSG_DONTWAIT)
if recvData == str(lineIndex+1):
print("recvDataFromClientSuccess:%s,complete data sending" %recvData)
break
except:
print("Not data received by client,send again... ...")
客户端
思路是这样的:
1.STM32连接SIM800C模块,需要插入一个能上网的SIM卡;
2.通过TCP连接服务器端;
3.接收到数据,将序号立刻返回至服务器端;
4.将APP数据合并为16位数据,写入flash特定地址中;
5.重复3,4步直到所有数据接收完成;
6.从flash特定地址运行flash中的程序。
具体的实现方法可以参考这篇文章以及正点原子的《STM32F1开发指南(精英版)-库函数版本_V1.0》有关IAP的实验和《AN1604B ATK-SIM800C GSM/GPRS 模块使用说明》.
STM32核心代码如下:
if(USART3_RX_STA&0X8000)//接收到一次数据
{
USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0; //添加结束位
p2=(u8*)strstr((const char*)USART3_RX_BUF,"+IPD");//用p2指针变量指向+IPD出现的位置的首地址
if(p2)
{
//将USART3_RX_BUF拆分获得数据包的其他信息
p4=(u8*)strstr((const char*)p2,",");
p3=(u8*)strstr((const char*)p2,":");
p2=(u8*)strstr((const char*)p2,";");
p2[0]=0;//
p3[0]=0;
sequence = atoi((char*)(p3+1));//序列号
//序号为0时为文件的大小
if(sequence == 0)
{
appLengthRecv = atoi((char*)(p2+1));
printf("recv data length:%d\r\n",appLengthRecv);
}
if(sequence >= 1 && sequence < 10) appLengthOnceRecv = atoi((char*)(p4+1)) - 2;
if(sequence >= 10 && sequence < 100) appLengthOnceRecv = atoi((char*)(p4+1)) - 3;
if(sequence >= 100) appLengthOnceRecv = atoi((char*)(p4+1)) - 4;
printf("序号:%d len:%d\r\n",sequence,appLengthOnceRecv);
if(sequence >= 1)
{
for(t=0;t<appLengthOnceRecv;t+=2)
{
//变为二字节
temp=(u16)p2[2+t]<<8;
temp+=(u16)p2[1+t];
recvBuf[iIndex++]=temp;
}
fwaddr=FLASH_APP1_ADDR + 1024 * (sequence -1);//偏移量
STMFLASH_Write(fwaddr,recvBuf,iIndex);
sumOnceRecv+=appLengthOnceRecv;
iIndex = 0;
}
USART3_RX_STA=0;
//将序列返回至服务器端
sim800c_send_cmd("AT+CIPSEND",">",500); //·¢ËÍÊý¾Ý
u3_printf((char*)(p3+1));
if(sim800c_send_cmd((u8*)0X1A,"SEND OK",1000)==0);//printf("Send!\r\n");
if(sumOnceRecv == appLengthRecv)
{
printf("写入flash完成\r\n");
break;
}
USART3_RX_STA=0;
}
USART3_RX_STA=0;
}
参考文献:
JohnGoerzen. Python网络编程基础[M]. 电子工业出版社, 2007.