STM32远程烧录程序

目录

简介

不同的程序下载方式

ICP:In-Circuit Programming

ISP:In-System Programing

IAP:In-Application Programming 

BootLoader

Bootloader 是什么?

STM32的启动方式

存储器组织

存储器映像

嵌入式SRAM

嵌入式FLASH

IAP 简介

STM32启动流程

APP程序

APP 程序起始地址设置方法

中断向量表的偏移量设置方法

BootLader程序 

功能简介:

软件设计

打开stmflash.h 代码如下:

打开stmflash.c 代码如下:

打开 iap.c, 代码如下:

打开 iap.h 代码如下:  

打开 usart1.c:

最后我们看看 main 函数如下:

下载验证

第一步,烧录BootLoader程序到单片机里面

观察现象: 

第二步:先用电脑作为上位机,通过串口发送APP程序给单片机:

第三步:按下按键1将固件写入到FLASH中 

第四步:按下按键2,看看程序是否从BootLoader程序跳到APP程序

总结

ESP8266

简介

AT指令 

烧录固件

传输原理

实现流程

串口

ESP8266代码编写

现象观察

总结

简介

该篇将会从零基础开始像读者讲解怎么使用远程的方法来烧录STM32程序。我这里用的是ESP8266和STM32F407ZGT6,当然,使用其他32的芯片也是可以的,核心都是一样的。

不同的程序下载方式

目前,单片机的程序烧录方式可以分为三种:ICP,ISP,IAP

ICP:In-Circuit Programming

在电路中编程。使用厂家配套的软件或仿真器进行程序烧录,目前主流的有JTAG接口和SWD接口,常用的烧录工具为J-Link、ST-Link等。

 在程序开发阶段,通常在连接下载器的情况下直接使用编程软件进行程序下载调试。

 在MDK软件中可以选择不同的下载器。

  ISP:In-System Programing

在系统中编程。以STM32为例,其内置了一段Bootloader程序,可以通过更改BOOT引脚电平来运行这段程序,再通过ISP编程工具将程序下载进去。下载完毕之后,再更改BOOT至正常状态,使得MCU运行所下载的程序。

正点原子的STM32开发板中专门设计了一个单片机自动复位及设置Boot引脚电平状态的电路,便于程序下载。

 

IAP:In-Application Programming 

在应用中编程。IAP可以使用微控制器支持的任一种通信接口(如I/O端口、USB、CAN、UART、I2C、SPI等)下载程序或数据到FLASH中。IAP允许用户在程序运行时重新烧写FLASH中的内容。但需要注意,IAP要求至少有一部分程序(Bootloader)已经使用ICP或ISP烧到FLASH中。

无论是ICP技术还是ISP技术,都需要连接下载线,设置跳线帽等操作。一般来说,产品的电路板都会密封在外壳中,在这时若要使用ICP或ISP的方式对程序进行更新,则必然要拆装外壳,如果产品的数量比较多,将花费很多不必要的时间。

采用IAP编程技术,可以在一定程度上避免上述的情况。一般情况下,产品的外壳都会留有通信接口,若能通过这种通信方式对程序进行升级,则可以省去拆装的麻烦。在此基础上,若引入远距离或无线数据传输方案,更可以实现远程编程或无线编程

BootLoader

要学会远程烧录,首先要知道BootLoader。

Bootloader 是什么?

Bootloader是在应用程序开始前运行的一个小程序,里面可以进行一些初始化操作,升级引用程序等,在嵌入式设备中很常见。

STM32的启动方式

(1) 从地址 0x00000000 处取出栈指针 MSP 的初始值,该值就是栈顶的地址。
(2) 从地址 0x00000004 处取出程序指针 PC 的初始值,该值指向复位后应执行的第一条指令。

这两个地址处存储的值通常是在嵌入式系统或者某些操作系统启动过程中使用的关键参数,具体作用如下:

1.栈指针 MSP 的初始值:
2.栈指针 MSP(Main Stack Pointer)是处理器用来管理栈空间的指针,它指向当前栈顶的地址。
3.在许多嵌入式系统中,系统启动时会从地址 0x00000000 处读取初始的栈指针值。这个值告诉处理器当前的栈顶在哪里,即初始时可用的栈空间范围。
4.栈指针的初始值是系统启动时的重要参数,确保函数调用和中断处理等操作都能正常进行。
5.程序指针 PC 的初始值:
6.程序计数器 PC(Program Counter)是一个寄存器,存储当前正在执行的指令的地址。
7.在系统复位后,处理器需要知道从哪里开始执行代码。地址 0x00000004 处通常存储了复位后应该执行的第一条指令的地址。
8.这个值告诉处理器从哪里开始执行代码,确保系统复位后可以顺利进入正常的程序执行流程,而不是随机执行内存中的数据。

总结起来,这些地址处存储的初始值对于系统启动和复位后的正常运行至关重要。栈指针 MSP 确保了栈空间的正常分配和管理,而程序指针 PC 则确保了系统复位后可以从正确的位置开始执行程序。

 

虽然内核是固定访问 0x00000000 0x00000004 地址的,但实际上这两个地址可以被重映射到其 它地址空间。以 STM32F103 为例,根据芯片引出的 BOOT0 BOOT1 引脚的电平情况 , 这两个 地址可以被映射到内部 FLASH、内部 SRAM 以及系统存储器中 ,不同的映射配置见表 BOOT 引脚的不同设置对 0 地址的映射

存储器组织

这里以STM32F103C8T6系列来进行举例。

程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内。
数据字节以小端格式存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。

存储器映像

寄存器映像和位段不影响我们本篇使用BootLoader,所以这里就不去列举了。我们这里重点讲解嵌入式闪存(FLASH)和嵌入式(SRAM)。

嵌入式SRAM
STM32F10xxx 内置 64K 字节的静态 SRAM 。它可以以字节、半字 (16 ) 或全字 (32 ) 访问。
SRAM 的起始地址是 0x2000 0000

告诉编译器RAM的起始地址和大小。 

嵌入式FLASH

我们这里介绍一下各个STM32芯片叫法背后的区别:

 内部FLASH的构成

STM32 的内部 FLASH 包含 主存储器、系统存储器以及选项字节区域 ,它们的地址分布及大小见表STM32 大容量产品内部 FLASH 的构成 (在《 STM32 参考手册》中没有关于其内部 FLASH
说明,需要了解这些内容时,要查阅《 STM32F10x 闪存编程参考手册》)。

 各个存储区域的说明如下:

主存储:
一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序空间,芯片型号说明中的 256K FLASH 512K FLASH 都是指这个区域的大小。
主存储器分为 256 页,每页大小为 2KB ,共 512KB 。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。
注意上表中的主存储器是本实验板使用的 STM32ZET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。
系统存储区
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码 ,它负责实现串口和 USB以及CAN ISP 烧录功能。
选项字节
选项字节用于 配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能 ,这部分共 16字节。可以通过修改 FLASH 的选项控制寄存器修改。

IAP 简介

前面是进行远程烧录的基础知识讲解,我们本篇的重点是IAP。
IAP In Application Programming )即在应用编程, IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。 这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
1)检查是否需要对第二部分代码进行更新
2)如果不需要更新则转到 4)
3)执行更新操作
4)跳转到第二部分代码执行
        第一部分代码必须通过其它手段,如 JTAG ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。
        我们将第一个项目代码称之为Bootloader程序,第二个项目代码称之为APP程序 ,他们存放在 STM32 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader ,紧跟其后的就是APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的,本章我们只讨论一个APP 程序的情况)。这样我们就是要实现 2 个程序: Bootloader APP
STM32 的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行,本章, 我们将制作两个 APP ,一个用于 FLASH 运行,一个用于 SRAM 运行。

STM32启动流程

我们先来看看 STM32 正常的程序运行流程(FLASH启动):

 STM32 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外 STM32 是基于 Cortex-M3 内核的微控制器,其内部通过一张“中断向量表” 来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。

        在图中, STM32 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时 STM32 强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。
当加入 IAP 程序之后,程序运行流程如图所示:

         在图所示流程中,STM32 复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP main 函数,如图标号①所示,此部分同图 52.1.1 一样;在执行完IAP以后(即将新的APP代码写入STM32的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,并且注意到此时  STM32  的FLASH在不同位置上,共有两个中断向量表。

         在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址为 0X08000004 中断向量表处,而不是新程序的中断向量表 ,如图标号④所示; 程序再根据我们设 置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中 ,如图标号⑤所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。
通过以上两个过程的分析,我们知道 IAP 程序必须满足两个要求:
1) 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始;
2) 必须将新程序的中断向量表相应的移动,移动的偏移量为 x;
本篇,我们有 2 APP 程序,一个为 FLASH APP ,程序在 FLASH 中运行,另外一个SRAM APP ,程序运行在 SRAM 中,图 虽然是针对 FLASH APP 来说的,但是在SRAM 里面运行的过程和 FLASH 基本一致,只是需要设置向量表的地址为 SRAM 的地址。

APP程序

APP 程序起始地址设置方法

FLASH APP 的起始地址设置:
随便打开一个之前的实例工程,点击 Options for Target Target 选项卡,如图 所示:

        默认的条件下, 图中 IROM1 的起始地址(Start)一般为 0X08000000 ,大小( Size) 0X80000 ,即从 0X08000000 开始的 1024 K 空间为我们的程序存储(因为我们 STM32F407ZGT6的 FLASH大小是 1024K )。而图中,我们设置起始地址( Start )为 0X08010000 ,即偏移量为 0X10000 (64K字节),因而,留给 APP 用的 FLASH 空间只有 0X100000-0X10000=0XF0000(960K 字节)。设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。
         这里的 64K 字节,需要大家根据 Bootloader 程序大小进行选择 ,比如我们本章的 Bootloader程序为 20K 左右,理论上我们只需要确保 APP 起始地址在 Bootloader 之后,并且 偏移量为0X200的倍数 即可(相关知识,请参考:http://www.openedv.com/posts/list/392.htm )。这里我们选择 64K(0X10000 )字节,留了一些余量,方便 Bootloader 以后的升级修改。

SRAM APP,那么起始地址设置如图所示:
这里我们将 IROM1 的起始地址(Start)定义为:0X20001000,大小为 0XC000(48K 字节),
即从地址 0X20000000 偏移 0X1000 开始,存放 APP 代码。因为整个 STM32F103ZET6 SRAM 大 小 为 64K 字 节 , 所 以 IRAM1 SRAM ) 的 起 始 地 址 变 为 0X2000D000( 0x20001000+0xC000=0X2000D000 ), 大 小 只 有 0X3000 12K 字 节 )。 这 样,,整 个STM32F103ZET6 的 SRAM 分配情况为:最开始的 4K Bootloader 程序使用,随后的 48K 存放APP 程序,最后 12K ,用作 APP 程序的内存。这个分配关系大家可以根据自己的实际情况修改,不一定和我们这里的设置一模一样,不过也需要注意,保证偏移量为 0X200 的倍数(我们这里为 0X1000 )。
后续我的代码会只有FLASH的APP,因为在实际应用当中,由于FLASH的容量大于SRAM,APP程序通常还是存在FLASH中,所以我们不去讨论SRAM,当然,机制都是一样的,学会FLASH的APP,自然而然的也能弄出存在SRAM的APP。

中断向量表的偏移量设置方法

在STM32微控制器中,启动时的初始化顺序通常如下:

1.复位向量:


2.当STM32微控制器上电或者复位时,会首先跳转到复位向量所指向的地址。复位向量通常指向微控制器内部的复位处理程序(Reset Handler)。


3.复位处理程序(Reset Handler):


4.复位处理程序是一个特殊的中断服务程序(ISR),它负责执行一系列的系统初始化操作,包括:
5.初始化堆栈指针(Stack Pointer)。
6.初始化数据段(Data Segment)和BSS段(未初始化数据段)。
7.调用 SystemInit() 函数进行系统级初始化。


8.SystemInit() 函数:


9.SystemInit() 函数是在复位处理程序中被调用的,用于执行系统级的初始化。这个函数通常包括设置系统时钟、初始化外设等操作。它的目的是在启动时将系统配置到一个合适的状态,以便后续的应用程序执行。

总结:
在STM32微控制器启动过程中,首先会跳转到复位向量指向的复位处理程序(Reset Handler)。复位处理程序会在其内部调用 SystemInit() 函数来进行系统级的初始化。因此,先调用的是复位处理程序,然后在复位处理程序内部调用 SystemInit() 函数。

systemInit 函数不仅初始化时钟系统,systemInit 还完成了中断向量表的设置,我们可以打开 systemInit 函数,看看函数体的结尾处有这几行代码:
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
/* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 
/* Vector Table Relocation in Internal FLASH. */
#endif

SRAM_BASE是STM32中SRAM的基地址,FLASH_BASE是STM32中FLASH的基地址这段代码的作用是根据宏 VECT_TAB_SRAM 的定义,动态地选择将向量表重定位到内部的SRAM或FLASH存储器中。通过这种方式,可以在不同的嵌入式系统配置中灵活地设置向量表的位置,以适应特定的硬件设计和需求。

从代码可以理解 , VTOR 寄 存 器 存 放 的 是 中 断 向 量 表 的 起 始 地 址 。VECT_TAB_SRAM是没有定义 , 所以执行 SCB->VTOR = FLASH_BASE|VECT_TAB_OFFSET; 对于 FLASH APP ,我们设置为 FLASH_BASE+ 偏移量 0x10000, 所以我们可以在 FLASH APP的 main函数最开头处添加如下代码实现中断向量表的起始地址的重设:
SCB->VTOR = FLASH_BASE | 0x10000;

以上是 FLASH APP 的情况,当使用SRAM APP的时候,我们设置起始地址为:

SRAM_bASE+0x1000, 同样的方法,我们在 SRAM APP main 函数最开始处,添加下面代码:
SCB->VTOR = SRAM_BASE | 0x1000;

这样,我们就完成了中断向量表偏移量的设置。

        通过以上两个步骤的设置,我们就可以生成 APP 程序了,只要 APP 程序的 FLASH SRAM大小不超过我们的设置即可。

不过 MDK 默认生成的文件是.hex 文件,并不方便我们用作IAP更新,我们希望生成的文件是.bin 文件,这样可以方便进行 IAP 升级。bin文件和hex文件最大的区别就是hex文件会附带下载的地址,而bin文件则没有。

         这里我们通过 MDK 自带的格式转换工具 fromelf.exe,来实现.axf 文件到.bin 文件的转换。该工具在 MDK 的安装目录\ARM\BIN40 文件夹里面。

本章,我们通过在 MDK 点击 Options for TargetUser选项卡,在After Build/Rebuild栏, 勾选 Run #1,并写入:D:\tools\mdk5.14\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\RTC.bin ..\OBJ\RTC.axf。如图所示:

通过这一步设置,我们就可以在 MDK 编译成功之后,调用 fromelf.exe,在得到.bin 文件之后,我们只需要将这个 bin 文件传送给单片机,即可执行 IAP 升级。
我们这里看一下APP程序的现象:

BootLader程序 

功能简介:

我们先用电脑作为上位机,通过串口来对STM32进行APP程序的下载。

本章实验( Bootloader 部分)功能简介:开机的时候先显示提示信息,然后等待串口输入接收APP 程序(无校验,一次性接收),在串口接收到 APP 程序之后,即可执行 IAP。我们通过KEY0,当key0按下之后,将串口接收到的 APP 程序存放到 STM32 FLASH,之后在按下KEY1既可以执行这个 FLASH APP 程序,通过 KEY2 按键,可以手动清除串口接收到的 APP程序, DS0 用于指示程序运行状态。

本实验用到的资源如下:
1、3个按键。
2、串口。
3、TFTLCD模块。
4、 指示灯 DS0。
这些用到的硬件,我们会在程序中用函数封装好,这里就不再介绍了。

软件设计

我自己写好了程序,如果读者需要的话,可以在评论区或者私聊我,我看到之后,会把工程分享出去。

由于我们要擦除和写入FLASH,我们要封装好写入FLASH的函数,我们IAP组下,添加了stmflash.c以及头文件stmflash.h。

打开stmflash.h 代码如下:
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "main.h"   
#include "common.h"//FLASH扇区的地址
#define FLASH_Sector_0 		0
#define FLASH_Sector_1		1
#define FLASH_Sector_2		2
#define FLASH_Sector_3		3
#define FLASH_Sector_4		4
#define FLASH_Sector_5		5
#define FLASH_Sector_6		6
#define FLASH_Sector_7		7
#define FLASH_Sector_8		8
#define FLASH_Sector_9		9
#define FLASH_Sector_10 	10
#define FLASH_Sector_11 	11//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 	//STM32 FLASH的起始地址//FLASH 扇区的起始地址
#define ADDR_FLASH_SECTOR_0     ((u32)0x08000000) 	//扇区0起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1     ((u32)0x08004000) 	//扇区1起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2     ((u32)0x08008000) 	//扇区2起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3     ((u32)0x0800C000) 	//扇区3起始地址, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4     ((u32)0x08010000) 	//扇区4起始地址, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5     ((u32)0x08020000) 	//扇区5起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6     ((u32)0x08040000) 	//扇区6起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7     ((u32)0x08060000) 	//扇区7起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_8     ((u32)0x08080000) 	//扇区8起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9     ((u32)0x080A0000) 	//扇区9起始地址, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10    ((u32)0x080C0000) 	//扇区10起始地址,128 Kbytes  
#define ADDR_FLASH_SECTOR_11    ((u32)0x080E0000) 	//扇区11起始地址,128 Kbytes  u32 STMFLASH_ReadWord(u32 faddr);		  	//读出字  
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite);		//从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead);   		//从指定地址开始读出指定长度的数据#endif
打开stmflash.c 代码如下:
#include "stmflash.h"
#include "common.h"
#include "usart1.h" 
#include "stm32f4xx_hal_flash_ex.h"
#include "stm32f4xx_hal.h"uint32_t STM32_FLASH_GetSector(uint32_t Address)
{uint32_t sector = 0;if ((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)){sector = FLASH_SECTOR_0;}else if ((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)){sector = FLASH_SECTOR_1;}else if ((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)){sector = FLASH_SECTOR_2;}else if ((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)){sector = FLASH_SECTOR_3;}else if ((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)){sector = FLASH_SECTOR_4;}else if ((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)){sector = FLASH_SECTOR_5;}else if ((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)){sector = FLASH_SECTOR_6;}else if ((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7)){sector = FLASH_SECTOR_7;}else if ((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8)){sector = FLASH_SECTOR_8;}else if ((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9)){sector = FLASH_SECTOR_9;}else if ((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10)){sector = FLASH_SECTOR_10;}else{sector = FLASH_SECTOR_11;}return sector;
}//读取指定地址的半字(16位数据) 
//faddr:读地址 
//返回值:对应数据.
u32 STMFLASH_ReadWord(u32 faddr)
{return *(vu32*)faddr; 
}  //获取某个地址所在的flash扇区
//addr:flash地址
//返回值:0~11,即addr所在的扇区
uint16_t STMFLASH_GetFlashSector(u32 addr)
{if(addr<ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_Sector_10; return FLASH_Sector_11;	
}//从指定地址开始写入指定长度的数据
//特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数
//         写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
//         写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
//         没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写. 
//该函数对OTP区域也有效!可以用来写OTP区!
//OTP区域地址范围:0X1FFF7800~0X1FFF7A0F
//WriteAddr:起始地址(此地址必须为4的倍数!!)
//pBuffer:数据指针
//NumToWrite:字(32位)数(就是要写入的32位数据的个数.) void STMFLASH_Write(uint32_t WriteAddr, uint32_t *pBuffer, uint32_t NumToWrite)	
{ HAL_StatusTypeDef status = HAL_OK;uint32_t addrx = 0;uint32_t endaddr = 0;	if (WriteAddr < FLASH_BASE || WriteAddr % 4 != 0) return;	// 非法地址HAL_FLASH_Unlock();	// 解锁闪存__HAL_FLASH_DATA_CACHE_DISABLE();	// 闪存擦除期间必须禁止数据缓存addrx = WriteAddr;				// 写入的起始地址endaddr = WriteAddr + NumToWrite * 4;	// 写入的结束地址if (addrx < 0x1FFF0000)	// 只有主存储区,才需要执行擦除操作{while (addrx < endaddr)	// 扫清一切障碍.(对非FFFFFFFF的地方,先擦除){if (*(volatile uint32_t*)addrx != 0xFFFFFFFF)	// 有非0xFFFFFFFF的地方,要擦除这个扇区{   uint32_t sectorError = 0;FLASH_EraseInitTypeDef eraseInitStruct;eraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;eraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;eraseInitStruct.Sector = STM32_FLASH_GetSector(addrx);eraseInitStruct.NbSectors = 1;status = HAL_FLASHEx_Erase(&eraseInitStruct, &sectorError); // 擦除操作if (status != HAL_OK) break;	// 发生错误了}else {addrx += 4;}} }if (status == HAL_OK){while (WriteAddr < endaddr)	// 写数据{status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr, *pBuffer); // 写入数据if (status != HAL_OK){ break;	// 写入异常}WriteAddr += 4;pBuffer++;} }__HAL_FLASH_DATA_CACHE_ENABLE();	// 闪存擦除结束,开启数据缓存HAL_FLASH_Lock();	// 上锁闪存
}//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToRead:字(4位)数
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)   	
{u32 i;for(i=0;i<NumToRead;i++){pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.ReadAddr+=4;//偏移4个字节.	}
}
打开本实验工程,可以看到我们增加了 IAP 组,在组下面添加了 iap.c 文件以及其头文件isp.h。

 打开 iap.c, 代码如下:
#include "usart1.h"
#include "stmflash.h"
#include "iap.h"
#include "main.h"iapfun jump2app; 
u32 iapbuf[512];   
//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{u16 t;u16 i=0;u32 temp;u32 fwaddr=appxaddr;//当前写入的地址u8 *dfu=appbuf;for(t=0;t<appsize;t+=4){						    temp=(u32)dfu[3]<<24;temp+=(u32)dfu[2]<<16;temp+=(u32)dfu[1]<<8;temp+=(u32)dfu[0];dfu+=4;//偏移4个字节iapbuf[i++]=temp;	    if(i==512){i=0;STMFLASH_Write(fwaddr,iapbuf,512);	fwaddr+=2048;//偏移2048  16=2*8.所以要乘以2.}}if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  
}//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.{ jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)jump2app();									//跳转到APP.}
}		 

总体代码还是非常简单的,这里需要注意的是:

void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize) ;

        当 iapbuf 数组填满时,即存储了1024个16位数据时,执行以下操作:

  • 调用 STMFLASH_Write 函数,将 iapbuf 中的512个32位数据写入Flash存储器中,从 fwaddr 开始的位置。
  • fwaddr 递增2048,因为每次写入512个32位数据,占用了2048字节的Flash空间。

这段代码实现了一个简单的IAP函数,用于将应用程序的二进制数据写入STM32的Flash存储器中。它通过循环遍历应用程序数据,并将其暂存在 iapbuf 数组中,每次达到512个数据时就将其写入Flash。最后,处理剩余的数据并确保所有数据都被写入到目标Flash地址中,以实现固件更新或应用程序下载的功能。 

void iap_load_app(u32 appxaddr) ;

这段代码的作用是从指定的应用程序起始地址 appxaddr 处跳转执行应用程序代码。让我们逐行分析:
函数定义和参数
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{


1.iap_load_app 函数用于加载并执行位于 appxaddr 处的应用程序。
2.appxaddr 是用户代码的起始,即应用程序的入口点。

检查栈顶地址是否合法
    if(((*(vu32*)appxaddr) &amp; 0x2FFE0000) == 0x20000000)


3.这里通过 (*(vu32*)appxaddr) 来读取 appxaddr 地址处的数据,然后检查其高位地址是否符合特定的合法栈顶地址的格式。
4.0x2FFE0000 是一个标准的栈顶地址的检查模式,用来确保 appxaddr 指向的位置是有效的。

设置函数指针和堆栈指针
    {
        jump2app = (iapfun) * (vu32 *)(appxaddr + 4);
        MSR_MSP(*(vu32 *)appxaddr);

}
5.如果栈顶地址合法,则执行以下操作:
6.jump2app = (iapfun) * (vu32 *)(appxaddr + 4);:从 appxaddr + 4 处读取一个 vu32 类型的值,这个值是应用程序的入口地址(复位地址)。然后将其转换为函数指针类型 iapfun,即 jump2app 现在指向应用程序的入口函数。
7.MSR_MSP(*(vu32 *)appxaddr);:用 appxaddr 处的值来初始化堆栈指针。在 ARM Cortex-M 微控制器中,初始化堆栈指针是通过 MSR_MSP 指令来实现的,它将 appxaddr 地址处的值作为新的主堆栈指针值。

执行应用程序跳转
        jump2app();    // 跳转到应用程序入口点
    }
}
8.最后,调用 jump2app() 函数指针,实际上是跳转到应用程序的入口点,开始执行应用程序代码。

总结
这段代码实现了一个简单的应用程序加载函数 iap_load_app,它检查给定的应用程序起始地址的栈顶地址是否有效,然后设置函数指针和堆栈指针,并最终通过函数指针调用实现了应用程序的跳转和执行。这种方式通常用于实现固件升级或者在运行时加载并执行新的应用程序。
 

该文件总共只有2 个函数,其中, iap_write_appbin 函数用于将存放在串口接收 buf 里面的APP 程序写入到 FLASH iap_load_app 函数,则用于跳转到 APP 程序运行,其参数 appxaddr为 APP 程序的起始地址,程序先判断栈顶地址是否合法,在得到合法的栈顶地址后,通过MSR_MSP 函数(该函数在 sys.c 文件)设置栈顶地址,最后通过一个虚拟的函数( jump2app) 跳转到 APP 程序执行代码,实现 IAP APP 的跳转。

打开 iap.h 代码如下:  
#ifndef __IAP_H__
#define __IAP_H__
#include "common.h"  
typedef  void (*iapfun)(void);				//定义一个函数类型的参数.#define FLASH_APP1_ADDR		0x08010000  	//第一个应用程序起始地址(存放在FLASH)//保留0X08000000~0X0800FFFF的空间为IAP使用void iap_load_app(u32 appxaddr);			//执行flash里面的app程序
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen);	//在指定地址开始,写入bin
#endif
这部分代码比较简单。本章,我们是通过串口接收 APP 程序的,我们将 usart1.c usart1.h做了稍微修改,在 usart.h 中,我们定义 USART_REC_LEN 55K 字节,也就是串口最大一次可以接收55K 字节的数据,这也是本 Bootloader 程序所能接收的最大 APP 程序大小。然后新增一个USART_RX_CNT 的变量,用于记录接收到的文件大小,而 USART_RX_STA 不再使用。
打开 usart1.c:
//iap//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.    
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       	//接收状态标记	  
u16 USART_RX_CNT=0;			//接收的字节数	
u8 aRxBuffer[RXBUFFERSIZE]; //HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄/****************************************************************************
* 名    称: void HAL_UART_MspInit(UART_HandleTypeDef *huart)
* 功    能:UART底层初始化,时钟使能,引脚配置,中断配置
* 入口参数:huart:串口句柄
* 返回参数:无
* 说    明:此函数会被HAL_UART_Init()调用 
****************************************************************************/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{//GPIO端口设置GPIO_InitTypeDef GPIO_Initure;if(huart->Instance==USART1)//如果是串口1,进行串口1 MSP初始化{__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟__HAL_RCC_USART1_CLK_ENABLE();			//使能USART1时钟GPIO_Initure.Pin=GPIO_PIN_9;			//PA9GPIO_Initure.Mode=GPIO_MODE_AF_PP;		//复用推挽输出GPIO_Initure.Pull=GPIO_PULLUP;			//上拉GPIO_Initure.Speed=GPIO_SPEED_FAST;		//高速GPIO_Initure.Alternate=GPIO_AF7_USART1;	//复用为USART1HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA9GPIO_Initure.Pin=GPIO_PIN_10;			//PA10HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA10HAL_NVIC_EnableIRQ(USART1_IRQn);				//使能USART1中断通道HAL_NVIC_SetPriority(USART1_IRQn,3,3);			//抢占优先级3,子优先级3	}
}/****************************************************************************
* 名    称: void uart1_init(u32 bound)
* 功    能:USART1初始化
* 入口参数:bound:波特率
* 返回参数:无
* 说    明: 
****************************************************************************/
void uart1_init(u32 bound)
{   //UART 初始化设置UART1_Handler.Instance=USART1;					    //USART1UART1_Handler.Init.BaudRate=bound;				    //波特率UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B;   //字长为8位数据格式UART1_Handler.Init.StopBits=UART_STOPBITS_1;	    //一个停止位UART1_Handler.Init.Parity=UART_PARITY_NONE;		    //无奇偶校验位UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;   //无硬件流控UART1_Handler.Init.Mode=UART_MODE_TX_RX;		    //收发模式HAL_UART_Init(&UART1_Handler);					    //HAL_UART_Init()会使能UART1HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}/*因printf()之类的函数,使用了半主机模式。使用标准库会导致程序无法运行,以下是解决方法:使用微库,因为使用微库的话,不会使用半主机模式. 请在工程属性的“Target“-》”Code Generation“中勾选”Use MicroLIB“这样以后就可以使用printf,sprintf函数了*/ 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   USART1->DR = (u8) ch;      return ch;
}//串口1发送一个字符
void uart1SendChar(u8 ch)
{      while((USART1->SR&0x40)==0);  USART1->DR = (u8) ch;      
}/****************************************************************************
* 名    称: void uart1SendChars(u8 *str, u16 strlen)
* 功    能:串口1发送一字符串
* 入口参数:*str:发送的字符串strlen:字符串长度
* 返回参数:无
* 说    明: 
****************************************************************************/
void uart1SendChars(u8 *str, u16 strlen)
{ u16 k= 0 ; do { uart1SendChar(*(str + k)); k++; }   //循环发送,直到发送完毕   while (k < strlen); 
} 
可以看到我们修改 HAL_UART_RxCpltCallback部分代码如下:

 这里,我们指定 USART_RX_BUF 的地址是从 0X20001000 开始,这里的0x20001000这个地址其实十分的巧妙,细心发现,其实可以当SRAM的启动地址,这样子,只需直接跳转,甚至不需要在调用写FLASH函数来对FLASH进行擦写。然后在 USART1_IRQHandler 函数里面,将串口发送过来的数据,全部接收到 USART_RX_BUF,并通过 USART_RX_CNT 计数。代码比较简单,我们就不多说了。

 最后我们看看 main 函数如下:

      

#include "main.h"
#include "gpio.h"#include "common.h"
#include "lcd.h"
#include "key.h"
#include "led.h"
#include "usart1.h"
#include "iap.h"
#include "stmflash.h"void SystemClock_Config(void);int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();u8 t;u16 oldcount=0;				//老的串口接收数据值u16 applenth=0;				//接收到的app代码长度u8 clearflag=0;  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);//设置NVIC中断分组2:2位抢占优先级,2位响应优先级delay_init();     //延时函数初始化uart1_init(9600);	    //串口初始化波特率为9600LED_Init();				//LED初始化KEY_Init();       //按键初始化LCD_Init();           //初始化LCD FSMC接口和显示驱动BRUSH_COLOR=RED;      //设置画笔颜色为红色LCD_DisplayString(10,10,16,"KEY_UP:Copy APP2FLASH");	LCD_DisplayString(10,80,16,"KEY2:Erase SRAM APP");LCD_DisplayString(10,150,16,"KEY1:Run FLASH APP");LCD_DisplayString(10,220,16,"KEY0:Run SRAM APP");	while (1){if(USART_RX_CNT){HAL_Delay(1);if(oldcount==USART_RX_CNT)//新周期内,没有收到任何数据,认为本次数据接收完成.{applenth=USART_RX_CNT;oldcount=0;USART_RX_CNT=0;printf("用户程序接收完成!\r\n");printf("代码长度:%dBytes\r\n",applenth);}else oldcount=USART_RX_CNT;			}t++;delay_ms(10);if(t==30){LED0=!LED0;t=0;if(clearflag){clearflag--;if(clearflag==0){}//LCD_Fill(30,210,240,210+16,WHITE);//清除显示}}	  	 key_scan(0);if(keydown_data==KEY0_DATA){if(applenth){printf("开始更新固件...\r\n");	//LCD_ShowString(30,210,200,16,16,"Copying APP2FLASH...");if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.{	 iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   //LCD_ShowString(30,210,200,16,16,"Copy APP Successed!!");printf("固件更新完成!\r\n");	}else {//LCD_ShowString(30,210,200,16,16,"Illegal FLASH APP!  ");	   printf("非FLASH应用程序!\r\n");}}else {printf("没有可以更新的固件!\r\n");//LCD_ShowString(30,210,200,16,16,"No APP!");}clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示									 }if(keydown_data==KEY2_DATA){if(applenth){																	 printf("固件清除完成!\r\n");    //LCD_ShowString(30,210,200,16,16,"APP Erase Successed!");applenth=0;}else {printf("没有可以清除的固件!\r\n");//LCD_ShowString(30,210,200,16,16,"No APP!");}clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示									 }if(keydown_data==KEY1_DATA){printf("开始执行FLASH用户代码!!\r\n");if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.{	 iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码}else {printf("非FLASH应用程序,无法执行!\r\n");//LCD_ShowString(30,210,200,16,16,"Illegal FLASH APP!");	   }									 clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示	  }if(keydown_data==KEY3_DATA) }				   }
}

   该段代码,实现了串口数据处理,以及 IAP 更新和跳转等各项操作Bootloader 程序就设计完成了,但是一般要求 bootloader 程序越小越好(给 APP 省空间),所以,本章我们把一些不需要用到的.c 文件全部去掉。

下载验证

第一步,烧录BootLoader程序到单片机里面

观察现象: 

第二步:先用电脑作为上位机,通过串口发送APP程序给单片机:

 

第三步:按下按键1将固件写入到FLASH中 

第四步:按下按键2,看看程序是否从BootLoader程序跳到APP程序

 

 总结

 可以看到,现象成功,证明代码没有问题,成功将串口接收到的bin文件烧录到FLASH里面,并且成功从BootLoader程序跳到APP程序。

ESP8266

简介

ESP8266 是一款低成本、高性能的Wi-Fi模块,由乐鑫(Espressif Systems)开发。它集成了处理器和Wi-Fi模块,广泛应用于物联网设备、智能家居、传感器网络等领域。主要特点包括:

1.处理器: ESP8266集成了Tensilica L106 32位处理器,时钟频率为80MHz或者160MHz。
2.Wi-Fi功能: 支持802.11 b/g/n协议,可以作为Wi-Fi客户端或者热点模式运行,支持TCP/IP协议栈,可以连接到互联网或者本地网络。
3.低功耗: 在待机模式下,功耗非常低,适合电池供电的应用场景。
4.丰富的GPIO: ESP8266具有多个通用IO口,可以连接外部设备,如传感器、执行器等。
5.易于开发: 提供了丰富的开发工具和资源,支持多种编程语言和开发环境,如Arduino IDE、MicroPython等。

6.ESP8266和STM32通常在物联网应用中合作,ESP8266负责Wi-Fi连接和数据传输,而STM32负责处理和控制设备的各种功能。这种分工使得系统既能保持低功耗和高效率,又能满足复杂的物联网应用需求。

前面我们用电脑作为上位机,通过串口将APP程序传给STM32,这里,我们通过ESP8266,通过远程传输,将APP程序传到STM32里面。

AT指令 

我们烧录ESP8266的官方固件,就可以通过AT指令去控制ESP8266。

烧录固件

我们去下载官方的烧录软件

选择合适的固件(去找你买ESP8266的厂家要) 

选中合适的COM号,然后点击START,就可以开始烧录官方的固件。

 传输原理

我们简单看一下使用说明,可以发现有很多种传输模式,我们这里用TCP传输举例子。

单连接TCP Client 

这里有一点需要注意,当esp8266接收到服务器的信息之后,他会通过串口将收到的信息发送回上位机,我们要注意的是,他会回多一个:/r/n+IPD,n: 这不是我们需要的,我们要的是APP1程序的bin文件,所以,我们要把这个进行代码上的移位,不把他擦写到flash里面。

这样子,FLASH的0x08010000开始存放的,都是APP1的程序。 

实现流程

前面用串口模拟远程烧录只是为了证明我们BOOTLOADER程序可以成功实现,现在我们需要在原来的基础上,用8266远程烧录,现在我们所说的一切,都是基于STM32角度的,我们编写的代码全是STM32.

串口

我们这里需要用两个串口,一个串口(usart2)负责STM32和ESP8266通信,一个串口负责和电脑通信(usart1)。

因为这里是用串口2和8266通信,所以意味着,APP1的程序文件,是用串口二接收的,所以要改到前面的串口代码,把串口2收到的bin文件存放在[u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.]里面,具体代码,交给读者自行编写,因为不是很难。

串口二和串口一的基本逻辑是一样的,写法几乎一致,只是句柄、缓存数组和一些定义会有所区别,不过注意,我们这里的printf用的是串口一,因为我们要打印消息给电脑,来知道程序运行状况。 

ESP8266代码编写

我们要编写STM32发送AT指令给ESP8266。

#include "ESP8266.h"//发送命令给ESP8266
void ESP8266_SendCommand(const char* command)
{HAL_UART_Transmit(&UART2_Handler, (uint8_t *)command, strlen(command), 10000);
}//连接wifi
void ESP8266_ConnectWiFi(const char* ssid, const char* pass) 
{char cmd[100];// 发送命令:AT+CWJAP="YourWiFiSSID","YourWiFiPassword"sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pass);ESP8266_SendCommand(cmd);  
}//连接TCP服务器
void ESP8266_ConnectTCPServer(const char* ServerIP, int ServerPort)
{// 定义一个缓冲区用于存储命令字符串char cmd[100];// 发送连接到TCP服务器的命令// 例如:AT+CIPSTART="TCP","192.168.1.100",80sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", ServerIP, ServerPort);ESP8266_SendCommand(cmd);	
}//发送数据到TCP服务器
void ESP8266_SendToTCPServer(const char* txData)
{// 发送命令:AT+CIPSEND=<length>char cmd[100];sprintf(cmd, "AT+CIPSEND=%d\r\n", strlen(txData));ESP8266_SendCommand(cmd);HAL_Delay(100);// 发送数据ESP8266_SendCommand(txData);	
}
//关闭TCP服务
void ESP8266_CLOSETCP(void)
{ESP8266_SendCommand("AT+CIPCLOSE\r\n");
}void ESP9266_Init(void)
{//延迟十秒,因为复位的时候,esp8266会发一点没用的东西,我们选择忽视//切记,要把串口二收到的垃圾清空HAL_Delay(10000);//设置工作模式ESP8266_SendCommand("AT+CWMODE=3\r\n");HAL_Delay(10000);//连接wifiESP8266_ConnectWiFi("chenjiajun2","12345678");HAL_Delay(20000);//连接TCP服务器ESP8266_ConnectTCPServer("10.201.150.216",8080);HAL_Delay(10000);//清掉串口2收到的东西memset(receive2_str, 0, sizeof(receive2_str));//清空接收到的数据;uart2_byte_count=0;}

 我写的代码其实很不好!!!因为我用了延迟来无视8266发回给我的信息,正确的是,应该去判断8266回我的信息,然后再根据这个,去决定之后怎么发送指令。

现象观察

我们接好线,8266的串口接到STM32的串口2,STM32的串口一和电脑连接(ch340)。

我们复位STM32,通过串口观察串口一和串口二。可以发现,ESP8266成功连接到TCP服务器。

我们将APP1的bin文件,通过服务器发送给ESP8266。  

然后串口一打印,接收到程序文件。

我们摁下按键,将程序擦写到FLASH里面,然后我们在摁下按键,跳转到APP程序里面看一下。 

实验成功,成功实现远程烧录。 

总结

        至此,STM32成功实现了远程烧录,这其中主要知识点就是STM32的启动机制、BootLoader程序、ESP8266基本的AT指令使用,和部分外设(串口、按键、FSMC驱动TFT LCD)等......,我自己去研究这个远程烧录的时候,也学了很多东西,因为遇到了不少bug,但是遇到一个问题就去解决一个问题,我们就能不断的进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/865304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不同行业如何选择适合自己行业的项目管理工具?

在当今的信息化时代&#xff0c;项目管理软件已成为各行各业不可或缺的工具。然而&#xff0c;由于各行业具有不同的特点和需求&#xff0c;因此选择合适的项目管理软件成为了一个重要问题。本文将探讨不同行业在选择项目管理软件时需要考虑的因素&#xff0c;希望能帮助大家更…

vue实现一个简单的审批绘制功能

1、vue代码 <div class"approval"><div class"approval_ul" v-for"(item,key) in approvalList" :key"key"><div><el-radio-group v-model"item.jointlySign"><el-radio label"1">…

【超全详解】耳机怎么清理?手把手教你清洁各种耳机!

平时听歌、听书、打电话耳机往往有时候几个小时都戴着&#xff0c;时间久了难免会堆积污垢堵孔。这可能就会造成耳机左右音量不一致、声音小、降噪效果变差、嗡嗡声等问题&#xff0c; 而且耳机用久了如果不及时清理&#xff0c;可能导致耳朵感染细菌&#xff0c;严重的话会影响…

亚马逊测评策略全攻略:详析各方案优势与局限,你精通了吗?

亚马逊测评&#xff0c;一个绕不开的话题。不管是对于新手卖家还是资深卖家来说&#xff0c;它都是提升产品销量和排名的有效手段之一。接下来&#xff0c;我将为大家详细解析亚马逊测评的各种方式和注意事项。 一、精准筛选真人测评资源 在寻找真人测评资源时&#xff0c;许多…

难道 Java 已经过时了?

当一门技术已经存在许多年了&#xff0c;它可能会失去竞争力&#xff0c;而后黯然退场&#xff0c;默默地离开&#xff0c;这对大部分的人来说就已经算是过时了。 Java 于 1995 年正式上线&#xff0c;至今已经走过了 27 个年头&#xff0c;在众多编程技术里算是年龄比较大的语…

数据结构----栈和队列之队列的实现

目录 1.基本概况 2.队列组成 3.队列的实现 &#xff08;1&#xff09;队列的初始化 &#xff08;2&#xff09;队列的销毁 &#xff08;3&#xff09;队列的尾插 &#xff08;4&#xff09;队列的头删 &#xff08;5&#xff09;队列的判空 &#xff08;6&#xff09;队…

外挂级OCR神器:免费文档解析、表格识别、手写识别、古籍识别、PDF转Word

智能文档解析&#xff1a;大模型友好的文档解析工具 PDF转Markdown 支持将任意格式的文件&#xff08;图片、PDF、Doc&#xff0f;Docx、网页等&#xff09;解析为Markdown或Json格式&#xff0c;以对LLM友好的方式呈现。 更高速度&#xff1a;100页PDF最快1.5s完成解析 更大…

SAR目标检测

Multi-Stage with Filter Augmentation 多阶段滤波器增强(MSFA) 对SAR合成孔径雷达目标检测性能的改善 MSFA ON SAR 传统方法: 预训练:传统方法开始于在通用数据集上预训练一个基础模型。 微调:这个预训练的模型会被微调以适应特定的SAR图像&#xff0c;试图缩小域间的差距 …

【JAVA多线程】JDK中的各种锁,看这一篇就够了

目录 1.概论 1.1.实现锁的要素 1.2.阻塞队列 1.3.Lock接口和Sync类 2.各种锁 2.1.互斥锁 2.1.1.概论 2.1.2.源码 1.lock() 2.unlock() 2.2.读写锁 2.3.Condition 2.3.1.概论 2.3.2.底层实现 1.概论 1.1.实现锁的要素 JAVA中的锁都是可重入的锁&#xff0c;因为…

苹果再出新招:macOS15 Beta2预览版更新,居然还有iPhone镜像功能

在数字化时代&#xff0c;操作系统的更新迭代是技术进步的显著标志。苹果公司以其一贯的创新精神&#xff0c;不断推动着个人计算体验的边界。 2024年6月25日&#xff0c;苹果公司向Mac电脑用户推出了macOS 15开发者预览版Beta 2更新&#xff0c;这不仅是对macOS系统的一次重大…

【探索Linux】P.36(传输层 —— TCP协议段格式)

阅读导航 引言一、TCP段的基本格式二、控制位详细介绍三、16位接收窗口大小⭕窗口大小的作用⭕窗口大小的限制⭕窗口缩放选项⭕窗口大小的更新⭕窗口大小与拥塞控制 四、紧急指针温馨提示 引言 在上一篇文章中&#xff0c;我们深入探讨了一种无连接的UDP协议&#xff0c;它以其…

14-14 商业领域的人工智能革命

在商业技术领域&#xff0c;对话式人工智能已获得广泛认可和使用&#xff0c;产生了重大而直接的影响。GPT-2 和 GPT-3 等大型语言模型一直是该领域的基础&#xff0c;但它们的高级继任者将对话界面推向了新的高度。这些较新的模型不仅仅是处理输入&#xff1b;它们旨在完美地集…

RK3568驱动指南|第十六篇 SPI-第188章 mcp2515驱动编写:复位函数

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

[数据结构] --- 树

1 树的基本概念 1.1 树的定义 树是n(n>0)个结点的有限集。当 n 0 时&#xff0c;称为空树。在任意一棵树非空树中应满足&#xff1a; (1) 有且仅有一个特定的称为根 (root) 的结点&#xff1b; (2) 当 n > 1 时&#xff0c;其余结点可分为m(m>0)个互不相交的有限集…

GDB 远程调试简介

文章目录 1. 前言2. GDB 远程调试2.1 准备工作2.1.1 准备 客户端 gdb 程序2.1.2 准备 服务端 gdbserver2.1.3 准备 被调试程序 2.2 调试2.2.1 通过网络远程调试2.2.1.1 通过 gdbserver 直接启动程序调试2.2.1.2 通过 gdbserver 挂接到已运行程序调试 2.2.2 通过串口远程调试2.2…

如何快速申请免费SSL证书,实现网站HTTPS安全传输

随着互联网技术的飞速发展&#xff0c;网络安全已成为不可忽视的重要议题。HTTPS协议&#xff0c;作为HTTP协议的安全版本&#xff0c;通过SSL协议加密客户端与服务器之间的数据传输&#xff0c;从而保障信息在传输过程中的安全性。对于网站运营者而言&#xff0c;为网站部署SS…

违规停放智能监测摄像机

对于现代城市管理来说&#xff0c;违规停放智能监测摄像机正逐渐成为解决交通拥堵和城市管理难题的重要工具。这类摄像机通过先进的视觉识别和数据分析技术&#xff0c;有效监控和管理道路上的车辆停放行为&#xff0c;对提升城市交通运行效率和改善市民出行环境具有显著的意义…

三代测序PacBioONT reads过滤和修剪-Chooper

chopper简介 chopper是NanoFilt和NanoLyse的Rust语言版本&#xff0c;适用于长reads测序&#xff08;如PacBio和纳米孔测序ONT&#xff09;的过滤和修剪fastq文件。 chopper相对于python编写的NanoFilt和NanoLyse&#xff0c;运行输出相同结果的时间更短&#xff0c;且NanoFi…

思维,CF 739A - Alyona and mex

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 739A - Alyona and mex 二、解题报告 1、思路分析 我们考虑区间mex运算的值最大也就是区间长度&#xff0c;所以我们最大值的上界就是所有区间中的最小长度&#xff0c;假如记为mi 我们一定可以构造出答案…

zabbix 配置钉钉告警

1.申请一个钉钉企业版 2.群内申请一个机器人 下载电脑版钉钉&#xff0c;登录后&#xff0c;在要接收群消息的群里&#xff0c;点击右上角设置图标&#xff0c;下滑找到机器人&#xff0c;添加一个机器人&#xff0c;保存机器人的webhook地址 保存这里的加签字符串 保存这里的…