当前位置: 首页 > article >正文

STM32----IAP远程升级

一、概述:

IAP,全称是“In-Application Programming”,中文解释为“在程序中编程”。IAP是一种对通过微控制器的对外接口(如USART,IIC,CAN,USB,以太网接口甚至是无线射频通道)对正在运行程序的微控制器进行内部程序的更新的技术(注意这完全有别于ICP或者ISP技术)。

ICP(In-Circuit Programming)技术即通过在线仿真器对单片机进行程序烧写。

ISP技术则是通过单片机内置的bootloader程序引导的烧写技术。

无论是ICP技术还是ISP技术,都需要有机械性的操作如连接下载线,设置跳线帽等。若产品的电路板已经层层密封在外壳中,要对其进行程序更新无疑困难重重,若产品安装于狭窄空间等难以触及的地方,更是一场灾难。

1、核心

实现IAP技术的核心是一段预先烧写在单片机内部的IAP程序。这段程序主要负责与外部的上位机软件进行握手同步,然后将通过外设通信接口将来自于上位机软件的程序数据接收后写入单片机内部指定的闪存区域,然后再跳转执行新写入的程序,最终就达到了程序更新的目的。

2、简述

在STM32微控制器上实现IAP程序之前首先要回顾一下STM32的内部闪存组织架构和其启动过程。STM32的内部闪存地址起始于0x8000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动。而这张“中断向量表”的起始地址是0x8000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。最后还需要知道关键的一点,通过修改STM32工程的链接脚本可以修改程序文件写入闪存的起始地址。

                                                                        图1
对图1解读如下:
1、 STM32复位后,会从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序,如图1中标号1所示。
2、 复位中断服务程序执行的最终结果是跳转至C程序的main函数,如图1中标号2所示,而main函数应该是一个死循环,是一个永不返回的函数。
3、 在main函数执行的过程中,发生了一个中断请求,此时STM32的硬件机制会将PC指针强制指回中断向量表处,如图1中标号3所示。
4、 根据中断源进入相应的中断服务程序,如图1中标号5所示。
5、 中断服务程序执行完毕后,程序再度返回至main函数中执行,如图1中标号6所示。

 若在STM32中加入了IAP程序,则情况会如图2所示。

                                                                                图2 

对图2的解读如下:
1、 STM32复位后,从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序,随后跳转至IAP程序的main函数,如图2中标号1、2所示。这个过程和图1相应部分是一致的。
2、 执行完IAP过程后(STM32内部多出了新写入的程序,图2中以灰色底纹方格表示,地址始于0x8000004+N+M)跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,其过程如图2的标号3所示。新程序的main函数应该也具有永不返回的特性。同时应该注意在STM32的内部存储空间在不同的位置上出现了2个中断向量表。
3、 在新程序main函数执行的过程中,一个中断请求来临,PC指针仍会回转至地址为0x8000004中断向量表处,而并不是新程序的中断向量表,如图2中标号5所示。注意到这是由STM32的硬件机制决定的。
4、 根据中断源跳转至对应的中断服务,如图2中标号6所示。注意此时是跳转至了新程序的中断服务程序中。
5、 中断服务执行完毕后,返回main函数。如图2中标号8所示。

从上述两个过程的分析可以得知,对将使用IAP过程写入的程序要满足2个要求:
1、新程序必须从IAP程序之后的某个偏移量为x的地址开始;
2、必须将新程序的中断向量表相应的移动,移动的偏移量为x;
而设置程序起始位置的方法是(keil uvision5集成开发环境)在工程的“Option for Target….”界面中的“Target”页里将“IROM”的“Start”列改为欲使程序起始的地方,如图3中将程序起始位置设为0x8008000。

                                                                                图3
将中断向量表移动的方法是在程序中加入函数:
void NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset);
其中参数NVIC_VectTab为中断向量表起始位置,而参数Offset则为地址偏移量,如将中断向量表移至0x8008000处,则应调用该函数如下:
void NVIC_SetVectorTable(0x8000000, 0x8000);
同时有必要提醒读者注意的是,此函数只会修改STM32程序中用于存储中断向量的结构体变量,而不会实质地改变中断向量表在闪存中的物理位置,详情请研究该程序原型。
有了以上准备后就可以着手设计一个IAP方案了,如下:
1、STM32复位后,利用一个按键的状态进行同步,当按键按下时表示将要进行IAP过程;
2、IAP过程中,通过上位机软件向STM32的USART1设备发送所要更新的程序文件,STM32接收到数据后转而从0x8008000地址开始写入收到的数据;
3、STM32借助定时器来判断数据是否完全接收,完全接收后IAP过程结束;
4、再次复位后,跳转0x8008004地址开始运行新写入的程序;

最后提出几点注意事项:
1、具体实现的工程见附件;
2、利用IAP写入的程序文件最好是.bin格式的文件,但不能是.hex格式的文件;
3、向STM32发送程序文件时尽量慢一些,因为STM32对FLASH的写入速度往往跟不上通讯外设接口的速度;
4、建议在STM32和上位机之间设计一套握手机制和出错管理机制,这样可以大幅提高IAP的成功率;
5、附件中的IAP工程具体运行现象为,按着连接于GPIOA0引脚上的按键后对STM32进行复位操作,若连接于GPIOA4引脚上的LED被点亮则表示进入了IAP程序,等待从USART1接口传入欲更新的程序文件。程序文件更新完毕后,LED被熄灭。此时再度对STM32进行复位,就开始运行新写入的程序了。

3、bin文件

D:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\APP_LED.bin ..\OBJ\APP_LED.axf

D:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o :命令表示从可执行文件生成bin文件

\OBJ\APP_LED.bin   :生产的bin文件的路径和文件名 

\OBJ\APP_LED.axf   :可执行文件的路径和文件名

二、工程代码

 1、bootloard程序

main.c

#include "stdio.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	
#include "LED.h"
#include "STM32_FLASH.h"
#include "iap.h"int main(void){		u8 t=0;u16 oldcount=0;				//老的串口接收数据值u16 applenth=0;				//接收到的app代码长度delay_init();	    	 							//延时函数初始化	  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3); 	//设置NVIC中断分组3:3位抢占优先级(抢占优先级级别可以设置为0级~7级),1位响应优先级(响应优先级级别可以设置为0级~1级)uart_init(9600);	 								//uart_init初始化为9600IAP_Switch_IO_Init();								//IAP开关I/O口初始化LED_Init();for(;;){		//如果IAP_Switch_I/O口为低电平时等待接收串口发送的APP程序if(1 == IAP_Switch_IO_In){//接收的字节数if(USART_RX_CNT){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;		}		}LED1=!LED1;	 if(applenth){printf("开始更新固件...\r\n");	//SRAM在(0X20001000+4)的地址处保存了APP程序的复位中断向量(复位中断服务函数的入口地址)if( ((*(vu32*)(0X20001000+4)) >= 0x08000000) && ((*(vu32*)(0X20001000+4)) < 0x08040000)){	 iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   printf("固件更新完成!\r\n");	applenth = 0;}else {   printf("非FLASH应用程序!\r\n");}}else {printf("没有可以更新的固件!\r\n");delay_ms(1000);}}//如果IAP_Switch_I/O口为高电平时开始执行APP程序if(0 == IAP_Switch_IO_In){//开始执行FLASH用户代码,先要判断我们要跳转的APP地址是否合法//FALSH在(FLASH_APP1_ADDR+4)的地址处保存了APP程序的复位中断向量(复位中断服务函数的入口地址)if((*(vu32*)(FLASH_APP1_ADDR+4) >= 0x08000000) && (*(vu32*)(FLASH_APP1_ADDR+4) < 0x08040000)){	 printf("开始执行FLASH用户代码!!\r\n");iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码}else {printf("非FLASH应用程序,无法执行!\r\n");   }}}}

 iap.c

#include "stdio.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	
#include "LED.h"
#include "STM32_FLASH.h"
#include "iap.h"iapfun jump2app; 
u16 iapbuf[1024];   
//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{u16 t;u16 i=0;u16 temp;u32 fwaddr=appxaddr;//当前写入的地址u8 *dfu=appbuf;for(t=0;t<appsize;t+=2){						    temp=(u16)dfu[1]<<8;temp+=(u16)dfu[0];	  dfu+=2;//偏移2个字节iapbuf[i++]=temp;	    if(i==1024){i=0;STMFLASH_Write(fwaddr,iapbuf,1024);	fwaddr+=2048;//偏移2048  16=2*8.所以要乘以2.}}if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  
}//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{//STM32F103RCT6的SRAM为48k Byte,即0xC000 Byte。故SRAM的地址范围为0x20000000~0x2000BFFF,在此外的范围都是不合法的//appxaddr为APP程序的起始地址,FLASH在此地址处保存了栈顶地址,范围为0x20000000~0x2000BFFFif((*(vu32*)appxaddr)>=0x20000000 && (*(vu32*)appxaddr)<0x2000C000)//(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000){ jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户APP代码区第二个字为程序开始地址(复位中断服务函数的入口地址)	复位中断服务函数的入口地址	MSR_MSP(*(vu32*)appxaddr);								//初始化APP堆栈指针(用户APP代码区的第一个字用于存放栈顶地址) 通过函数指针 jump2app 跳转到用户应用程序的入口地址,开始执行应用程序。jump2app();																//跳转到APP.(函数指针)}
}		 //IAP开关I/O口初始化
void IAP_Switch_IO_Init(void)
{GPIO_InitTypeDef  GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	 //使能PC端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 				//PC4端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 			//上拉输入GPIO_Init(GPIOC, &GPIO_InitStructure);					//根据设定参数初始化GPIOC}	

iap.h

#ifndef _IAP_H_
#define _IAP_H_#include "stdio.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	
#include "LED.h"
#include "STM32_FLASH.h"
#include "iap.h"#define IAP_Switch_IO_In			PCin(4)							//IAP开关I/O口#define FLASH_APP1_ADDR		0x08008000  							//第一个APP应用程序起始地址(存放在FLASH)//保留0X08000000~0X08007FFF的空间为Bootloader使用(32KB)	  typedef  void (*iapfun)(void);										//定义一个函数类型的参数.void iap_load_app(u32 appxaddr);									//执行flash里面的app程序
void iap_load_appsram(u32 appxaddr);								//执行sram里面的app程序
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen);			//在指定地址开始,写入binvoid IAP_Switch_IO_Init(void);										//IAP开关I/O口初始化#endif

STM32_FLASH.c

#include "stdio.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	
#include "LED.h"
#include "STM32_FLASH.h"
#include "iap.h"//解锁STM32的FLASH
void STMFLASH_Unlock(void)										//void FLASH_Unlock(void)(官方库)
{FLASH->KEYR=FLASH_KEY1;//写入解锁序列.FLASH->KEYR=FLASH_KEY2;
}
//flash上锁
void STMFLASH_Lock(void)
{FLASH->CR|=1<<7;//上锁
}
//得到FLASH状态
u8 STMFLASH_GetStatus(void)
{	u32 res;		res=FLASH->SR; if(res&(1<<0))return 1;		    //忙else if(res&(1<<2))return 2;	//编程错误else if(res&(1<<4))return 3;	//写保护错误return 0;						//操作完成
}
//等待操作完成
//time:要延时的长短
//返回值:状态.
u8 STMFLASH_WaitDone(u16 time)
{u8 res;do{res=STMFLASH_GetStatus();if(res!=1)break;//非忙,无需等待了,直接退出.delay_us(1);time--;}while(time);if(time==0)res=0xff;//TIMEOUTreturn res;
}
//擦除页
//paddr:页地址
//返回值:执行情况
u8 STMFLASH_ErasePage(u32 paddr)					//FLASH_Status FLASH_ErasePage(uint32_t Page_Address);官方库
{u8 res=0;res=STMFLASH_WaitDone(0X5FFF);//等待上次操作结束,>20ms    if(res==0){ FLASH->CR|=1<<1;//页擦除FLASH->AR=paddr;//设置页地址 FLASH->CR|=1<<6;//开始擦除		  res=STMFLASH_WaitDone(0X5FFF);//等待操作结束,>20ms  if(res!=1)//非忙{FLASH->CR&=~(1<<1);//清除页擦除标志.}}return res;
}
//在FLASH指定地址写入半字
//faddr:指定地址(此地址必须为2的倍数!!)
//dat:要写入的数据
//返回值:写入的情况
u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat)			//FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);			
{u8 res;	   	    res=STMFLASH_WaitDone(0XFF);	 if(res==0)//OK{FLASH->CR|=1<<0;//编程使能*(vu16*)faddr=dat;//写入数据res=STMFLASH_WaitDone(0XFF);//等待操作完成if(res!=1)//操作成功{FLASH->CR&=~(1<<0);//清除PG位.}} return res;
} 
//读取指定地址的半字(16位数据) 
//faddr:读地址 
//返回值:对应数据.
u16 STMFLASH_ReadHalfWord(u32 faddr)							//FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
{return *(vu16*)faddr; 
}
#if STM32_FLASH_WREN	//如果使能了写   
//不检查的写入
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数   
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
{ 			 		 u16 i;for(i=0;i<NumToWrite;i++){STMFLASH_WriteHalfWord(WriteAddr,pBuffer[i]);WriteAddr+=2;//地址增加2.}  
} 
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //字节
#else 
#define STM_SECTOR_SIZE	2048
#endif		 
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
{u32 secpos;	   //扇区地址u16 secoff;	   //扇区内偏移地址(16位字计算)u16 secremain; //扇区内剩余地址(16位字计算)	   u16 i;    u32 offaddr;   //去掉0X08000000后的地址if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址STMFLASH_Unlock();						//解锁offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址.secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址  0~127 for STM32F103RBT6secoff=(offaddr%STM_SECTOR_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围while(1) {	STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  }if(i<secremain)//需要擦除{STMFLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区for(i=0;i<secremain;i++)//复制{STMFLASH_BUF[i+secoff]=pBuffer[i];	  }STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  }else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   if(NumToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;				//扇区地址增1secoff=0;				//偏移位置为0 	 pBuffer+=secremain;  	//指针偏移WriteAddr+=secremain*2;	//写地址偏移(16位数据地址,需要*2)	   NumToWrite-=secremain;	//字节(16位)数递减if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完else secremain=NumToWrite;//下一个扇区可以写完了}	 };	STMFLASH_Lock();//上锁
}
#endif
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)   	
{u16 i;for(i=0;i<NumToRead;i++){pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.ReadAddr+=2;//偏移2个字节.	}
}//测试用///
//WriteAddr:起始地址
//WriteData:要写入的数据
void Test_Write(u32 WriteAddr,u16 WriteData)   	
{STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 
}

STM32_FLASH.h

#ifndef _STM32_FLASH_H_
#define _STM32_FLASH_H_	#include "stdio.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	
#include "LED.h"
#include "STM32_FLASH.h"
#include "iap.h"//
//用户根据自己的需要设置
#define STM32_FLASH_SIZE 	256 	 			//所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_WREN 	1              		//使能FLASH写入(0,不是能;1,使能)
////FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 			//STM32 FLASH的起始地址
//FLASH解锁键值
#define FLASH_KEY1               0X45670123
#define FLASH_KEY2               0XCDEF89AB
void STMFLASH_Unlock(void);					  	//FLASH解锁
void STMFLASH_Lock(void);					  	//FLASH上锁
u8 STMFLASH_GetStatus(void);				  	//获得状态
u8 STMFLASH_WaitDone(u16 time);				  	//等待操作结束
u8 STMFLASH_ErasePage(u32 paddr);			  	//擦除页
u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat);	//写入半字
u16 STMFLASH_ReadHalfWord(u32 faddr);		  	//读出半字  
void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len);	//指定地址开始写入指定长度的数据
u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len);						//指定地址开始读取指定长度数据
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite);		//从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead);   		//从指定地址开始读出指定长度的数据//测试写入
void Test_Write(u32 WriteAddr,u16 WriteData);	#endif

2、APP程序

main.c

APP程序没什么特点,只需要在“Target”里面和“main.c”更改一下中断向量表位置即可;

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "stdio.h"
#include "usart.h"//GPIO输入实验  
//技术支持:
int i;int main(void){u8 t;/* Vector Table Relocation in Internal FLASH. */SCB->VTOR = FLASH_BASE | 0x8000;//中断向量表偏移量,基地址加偏移量uart_init(9600);delay_init();	    //延时函数初始化	  LED_Init();		  	//初始化LEDwhile(1){//LED1=0;//PA6 输出低电平 LED1亮//LED2=1;//PA7 输出高电平 LED2灭
//		GPIO_ResetBits(GPIOA,GPIO_Pin_6); //PA6 输出低电平 LED1亮
//		GPIO_SetBits(GPIOA,GPIO_Pin_7);   //PA7 输出高电平 LED2灭
//		delay_ms(300);
//		//LED1=1;//PA6 输出高电平 LED1灭
//		//LED2=0;//PA7 输出低电平 LED2亮
//		GPIO_SetBits(GPIOA,GPIO_Pin_6);  //PA6 输出高电平 LED1灭
//		GPIO_ResetBits(GPIOA,GPIO_Pin_7);//PA7 输出低电平 LED2亮
//		delay_ms(300);for(i=1;i<1500;i++)//    for(i=1;i<1500;i++){GPIO_SetBits(GPIOA,GPIO_Pin_6|GPIO_Pin_7);		 delay_us(i);GPIO_ResetBits(GPIOA,GPIO_Pin_7|GPIO_Pin_6);delay_us(1500-i);}for(i=1;i<1500;i++){GPIO_ResetBits(GPIOA,GPIO_Pin_7|GPIO_Pin_6);delay_us(i);GPIO_SetBits(GPIOA,GPIO_Pin_6|GPIO_Pin_7);delay_us(1500-i);}BEEP = !BEEP;printf("t = %d\r\n",t);t++;}
}

相关文章:

STM32----IAP远程升级

一、概述&#xff1a; IAP&#xff0c;全称是“In-Application Programming”&#xff0c;中文解释为“在程序中编程”。IAP是一种对通过微控制器的对外接口&#xff08;如USART&#xff0c;IIC&#xff0c;CAN&#xff0c;USB&#xff0c;以太网接口甚至是无线射频通道&#…...

C++优选算法 904. 水果成篮

文章目录 1.题目描述2.算法思路 3.完整代码容器做法数组做法 1.题目描述 看到这种题目&#xff0c;总觉得自己在做阅读理解&#xff0c;晕了&#xff0c;题目要求我们在一个数组里分别找出两种数字&#xff0c;并统计这两种数字分别出现一共是多少。 2.算法思路 采用哈希表滑…...

Python6.5打卡(day37)

DAY 37 早停策略和模型权重的保存 知识点回顾&#xff1a; 过拟合的判断&#xff1a;测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint&#xff0c;还包含训练状态 早停策略 作业&#xff1a;对信贷数据集训练后保存权重&#xf…...

大中型水闸安全监测管理系统建设方案

一、背景介绍 我国现已建成流量5m/s及以上的水闸共计100321座。其中&#xff0c;大型水闸923座&#xff0c;中型水闸6,697座。按功能类型划分&#xff0c;分洪闸8193座&#xff0c;排&#xff08;退&#xff09;水闸17808座&#xff0c;挡潮闸4955座&#xff0c;引水闸13796座&…...

Compose Multiplatform 实现自定义的系统托盘,解决托盘乱码问题

Compose Multiplatform是 JetBrains 开发的声明式 UI 框架&#xff0c;可让您为 Android、iOS、桌面和 Web 开发共享 UI。将 Compose Multiplatform 集成到您的 Kotlin Multiplatform 项目中&#xff0c;即可更快地交付您的应用和功能&#xff0c;而无需维护多个 UI 实现。 在…...

风控研发大数据学习路线

在如今信息爆炸时代&#xff0c;风控系统离不开大数据技术的支撑&#xff0c;大数据技术可以帮助风控系统跑的更快&#xff0c;算的更准。因此&#xff0c;风控技术研发需要掌握大数据相关技术。然而大数据技术栈内容庞大丰富&#xff0c;风控研发同学很可能会面临以下这些痛点…...

【设计模式】门面/外观模式

MySQL &#xff0c;MyTomcat 的启动 现在有 MySQL &#xff0c;MyTomcat 类&#xff0c;需要依次启动。 public class Application {public static void main(String[] args) {MySQL mySQL new MySQL();mySQL.initDate();mySQL.checkLog();mySQL.unlock();mySQL.listenPort(…...

spring的webclient与vertx的webclient的比较

Spring WebClient 和 Vert.x WebClient 都是基于响应式编程模型的非阻塞 HTTP 客户端&#xff0c;但在设计理念、生态整合和适用场景上存在显著差异。以下是两者的核心比较&#xff1a; &#x1f504; 1. 技术背景与架构 • Spring WebClient ◦ 生态定位&#xff1a;属于 Sp…...

贪心算法应用:埃及分数问题详解

贪心算法与埃及分数问题详解 埃及分数&#xff08;Egyptian Fractions&#xff09;问题是数论中的经典问题&#xff0c;要求将一个真分数表示为互不相同的单位分数之和。本文将用2万字全面解析贪心算法在埃及分数问题中的应用&#xff0c;涵盖数学原理、算法设计、Java实现、优…...

高效集成AI能力:使用开放API打造问答系统,不用训练模型,也能做出懂知识的AI

本文为分享体验感受&#xff0c;非广告。 一、蓝耘平台核心功能与优势 丰富的模型资源库 蓝耘平台提供涵盖自然语言处理、计算机视觉、多模态交互等领域的预训练模型&#xff0c;支持用户直接调用或微调&#xff0c;无需从零开始训练&#xff0c;显著缩短开发周期。 高性能…...

Qt 仪表盘源码分享

Qt 仪表盘源码分享 一、效果展示二、优点三、源码分享四、使用方法 一、效果展示 二、优点 直观性 数据以图表或数字形式展示&#xff0c;一目了然。用户可以快速获取关键信息&#xff0c;无需深入阅读大量文字。 实时性 仪表盘通常支持实时更新&#xff0c;确保数据的时效性。…...

Python数据可视化科技图表绘制系列教程(四)

目录 带基线的棒棒糖图1 带基线的棒棒糖图2 带标记的棒棒糖图 哑铃图1 哑铃图2 包点图1 包点图2 雷达图1 雷达图2 交互式雷达图 【声明】&#xff1a;未经版权人书面许可&#xff0c;任何单位或个人不得以任何形式复制、发行、出租、改编、汇编、传播、展示或利用本博…...

RPM 数据库修复

RPM 数据库修复 1、备份当前数据库&#xff08;重要&#xff01;&#xff09; sudo cp -a /var/lib/rpm /var/lib/rpm.backup此操作保护原始数据&#xff0c;防止修复失败导致数据丢失 2、清除损坏的锁文件 sudo rm -f /var/lib/rpm/__db.*这些锁文件&#xff08;如 __db.00…...

R语言基础知识总结(超详细整理)

一、R语言简介 R是一种用于统计分析、数据可视化和科学计算的开源编程语言和环境。其语法简洁&#xff0c;内置丰富的统计函数和图形函数&#xff0c;广泛应用于数据科学、机器学习和生物统计等领域。 整体知识点目录&#xff1a; R语言基础知识总结 │ ├─ 安装与配置 │ …...

深入理解系统:UML类图

UML类图 类图&#xff08;class diagram&#xff09; 描述系统中的对象类型&#xff0c;以及存在于它们之间的各种静态关系。 正向工程&#xff08;forward engineering&#xff09;在编写代码之前画UML图。 逆向工程&#xff08;reverse engineering&#xff09;从已有代码建…...

C# 中的 IRecipient

IRecipient<TMessage> 是 .NET 中消息传递机制的重要组成部分&#xff0c;特别是在 MVVM (Model-View-ViewModel) 模式中广泛使用。下面我将详细介绍这一机制及其应用。 基本概念 IRecipient<TMessage> 是 .NET Community Toolkit 和 MVVM Toolkit 中定义的一个接…...

大模型RNN

RNN&#xff08;循环神经网络&#xff09;是一种专门处理序列数据的神经网络架构&#xff0c;在自然语言处理&#xff08;NLP&#xff09;、语音识别、时间序列分析等领域有广泛应用。其核心作用是捕捉序列中的时序依赖关系&#xff0c;即当前输出不仅取决于当前输入&#xff0…...

Python环境搭建竞赛技术文章大纲

竞赛背景与意义 介绍Python在数据科学、机器学习等领域的重要性环境搭建对于竞赛项目效率的影响常见竞赛平台对Python环境的特殊要求 基础环境准备 操作系统选择与优化&#xff08;Windows/Linux/macOS&#xff09;Python版本选择&#xff08;3.x推荐版本&#xff09;解释器…...

Redisson - 实现延迟队列

Redisson 延迟队列 Redisson 是基于 Redis 的一款功能强大的 Java 客户端。它提供了诸如分布式锁、限流器、阻塞队列、延迟队列等高可用、高并发组件。 其中&#xff0c;RDelayedQueue 是对 Redis 数据结构的高阶封装&#xff0c;能让你将消息延迟一定时间后再进入消费队列。…...

软件工程的定义与发展历程

文章目录 一、软件工程的定义二、软件工程的发展历程1. 前软件工程时期(1940s-1960s)2. 软件工程诞生(1968)3. 结构化方法时期(1970s)4. 面向对象时期(1980s)5. 现代软件工程(1990s-至今) 三、软件工程的发展趋势 一、软件工程的定义 软件工程是应用系统化、规范化、可量化的方…...

艾利特协作机器人:重新定义工业涂胶场景的精度革命

品牌使命与技术基因 作为全球协作机器人领域成长最快的企业之一&#xff0c;艾利特始终聚焦于解决工业生产中的人机协作痛点。在汽车制造、3C电子、新能源等领域的涂胶工艺场景中&#xff0c;我们通过自主研发的EC系列协作机器人&#xff0c;实现了&#xff1a; 空间利用率&a…...

第十三节:第五部分:集合框架:集合嵌套

集合嵌套案例分析 代码&#xff1a; package com.itheima.day27_Collection_nesting;import java.util.*;/*目标:理解集合的嵌套。 江苏省 "南京市","扬州市","苏州市","无锡市","常州市" 湖北省 "武汉市","…...

Java设计模式之观察者模式详解

一、观察者模式简介 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了对象之间的一对多依赖关系。当一个对象&#xff08;主题&#xff09;的状态发生改变时&#xff0c;所有依赖于它的对象&#xff08;观察者&#xff09;都会自…...

freeRTOS 消息队列之一个事件添加到消息队列超时怎么处理

一 消息队列的结构框图 xTasksWaitingToSend‌&#xff1a;这个列表存储了所有因为队列已满而等待发送消息的任务。当任务尝试向一个已满的队列发送消息时&#xff0c;该任务会被挂起并加入到xTasksWaitingToSend列表中&#xff0c;直到队列中有空间可用‌&#xff0c; xTasksW…...

十八、【用户认证篇】安全第一步:基于 JWT 的前后端分离认证方案

【用户认证篇】安全第一步:基于 JWT 的前后端分离认证方案 前言什么是 JWT (JSON Web Token)?准备工作第一部分:后端 Django 配置 JWT 认证1. 安装 `djangorestframework-simplejwt`2. 在 `settings.py` 中配置 `djangorestframework-simplejwt`3. 在项目的 `urls.py` 中添加…...

RabbitMQ 开机启动配置教程

RabbitMQ 开机启动配置教程 在本教程中&#xff0c;我们将详细介绍如何配置 RabbitMQ 以实现开机自动启动。此配置适用于手动安装的 RabbitMQ 版本。 环境准备 操作系统&#xff1a;CentOS 7RabbitMQ 版本&#xff1a;3.8.4Erlang 版本&#xff1a;21.3 步骤 1. 安装 Erla…...

Authpf(OpenBSD)认证防火墙到ssh连接到SSH端口转发技术栈 与渗透网络安全的关联 (RED Team Technique )

目录 &#x1f50d; 1. Authpf概述与Shell设置的作用 什么是Authpf&#xff1f; Shell设置为/usr/sbin/authpf的作用与含义 &#x1f6e0;️ 2. Authpf工作原理与防火墙绕过机制 技术栈 工作原理 防火墙绕过机制 Shell关联 &#x1f310; 3. Authpf与SSH认证及服务探测…...

组合与排列

组合与排列主要有两个区别&#xff0c;区别在于是否按次序排列和符号表示不同。 全排列&#xff1a; 从n个不同元素中任取m&#xff08;m≤n&#xff09;个元素&#xff0c;按照一定的顺序排列起来&#xff0c;叫做从n个不同元素中取出m个元素的一个排列。当mn时所有的排列情况…...

神经网络-Day45

目录 一、tensorboard的基本操作1.1 发展历史1.2 tensorboard的原理 二、tensorboard实战2.1 cifar-10 MLP实战2.2 cifar-10 CNN实战 在神经网络训练中&#xff0c;为了帮助理解&#xff0c;借用了很多的组件&#xff0c;比如训练进度条、可视化的loss下降曲线、权重分布图&…...

【西门子杯工业嵌入式-1-基本环境与空白模板】

西门子杯工业嵌入式-1-基本环境与空白模板 项目资料一、软件安装与环境准备1. 安装MDK52. 安装驱动3. 安装GD32F470支持包 二、工程目录结构建议三、使用MDK创建工程流程1. 新建工程2. 添加工程组&#xff08;Group&#xff09;3. 添加源文件 四、编译配置设置&#xff08;Opti…...