Python网络编程-远程更新STM32APP程序

目标:设计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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值