STM32F4 IAP跳转APP问题及STM32基于Ymodem协议IAP升级笔记

STM32F4 IAP 跳转 APP问题

  • ST官网IAP例程
  • Chapter1 STM32F4 IAP 跳转 APP问题
    • 1. 概念
    • 2. 程序
      • 2.1 Bootloader 程序
    • 问题现象
      • 2.2. APP程序
    • 3. 代码
    • 4. 其他问题
  • Chapter2 STM32-IAP基本原理及应用 | ICP、IAP程序下载流程 | 程序执行流程 | 配置IAP到STM32F4xxx
  • Chapter3 STM32基于Ymodem协议IAP升级笔记
    • 1.YMODEM 协议说明
    • 3.Ymodem升级工具
  • Chapter4 基于Ymodem协议的stm32f405rgt6+CubeMx+IAP在线升级
    • 准备工作
    • 工程下载地址(0积分下载):
    • 一、CubeMX的配置
      • 1.IAP
      • 2.APP
    • 二、移植Ymodem官方代码
      • 1.文件移植
    • 2.MDK文件、路径添加
    • 3.修改代码
    • 三、烧录、测试


ST官网IAP例程

STM32 + IAP + Ymodem完美结合

ST官网提供的IAP例程有很多很多,比如:

库:有使用标准外设库(SPL)的、有使用硬件抽象层库(HAL)的;

通信口:有使用USART的,有使用I2C的,有使用ETH的等。

MCU型号:STM8S、STM32F1、F4、L1等几乎全系列都有。

ST官网提供的各种IAP,其方法和原理其实都类似:就是将程序文件(二进制文件)写入FLASH。

本文以最简单的基于(STM32F10x)利用SPL库+UART的IAP为例来给大家讲述一下。(复杂都是从基础开始,后续逐步为大家更新更高级的IAP功能)

STM32F10xxx in-application programming using the USART官方地址:

https://www.stmicroelectronics.com.cn/content/st_com/en/products/embedded-software/mcus-embedded-software/stm32-embedded-software/stm32-standard-peripheral-library-expansion/stsw-stm32008.html
在这里插入图片描述

Chapter1 STM32F4 IAP 跳转 APP问题

原文链接:https://blog.csdn.net/qinbo1234567890/article/details/128318895

1. 概念

IAP 的作用,网上其他资料已经有很多介绍了,这里放一个链接,不进行深入的介绍。本文的关注重点是Bootloader在跳转APP程序中出现的问题。
IAP的实现原理讲解以及中断向量表的偏移

2. 程序

本人主要做应用层的开发,所有Bootloader和APP程序使用的是STM32CubeMX工具生成代码后,然后进行修改。

2.1 Bootloader 程序

1. CubeMX 配置
  步骤1:使用的芯片为STM32F407ZGT6

在这里插入图片描述
步骤2:选择时钟源(根据自己的板子进行选择)
在这里插入图片描述
步骤3:时钟配置
在这里插入图片描述
步骤4:项目配置
在这里插入图片描述
2. 代码(只介绍跳转函数)

完整代码

void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
	int i = 0;
	pIapFun_TypeDef pJump2App; 

	
	if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )	 //@1 //检查栈顶地址是否合法.
	{ 
		
		HAL_SPI_MspDeInit(&hspi1);	//@2
		__HAL_RCC_GPIOB_CLK_DISABLE();
		__HAL_RCC_GPIOG_CLK_DISABLE();
		
		
		 /* 设置所有时钟到默认状态,使用HSI时钟 */
		HAL_RCC_DeInit();		//@3
		
		__set_BASEPRI(0x20);		//@4
		__set_PRIMASK(1);
        __set_FAULTMASK(1);
		
		/* 关闭所有中断,清除所有中断挂起标志 */
		for (i = 0; i < 8; i++)		//@5
		{
			NVIC->ICER[i]=0xFFFFFFFF;
			NVIC->ICPR[i]=0xFFFFFFFF;
		}
		
		SysTick->CTRL = 0;		//@6
		SysTick->LOAD = 0;
		SysTick->VAL = 0;

		__set_BASEPRI(0);		//@7
		__set_PRIMASK(0);
		__set_FAULTMASK(0);
 		
 		//@8
        /*
        1)不使用OS时: 只用到MSP(中断和非中断都使用MSP);
        2)使用OS时(如UCOSII): main函数和中断使用MSP; 各个Task(线程)使用PSP(即任务栈);
        */
        __set_MSP(*(uint32_t*)ulAddr_App);//当带操作系统从APP区跳转到BOOT区的时候需要将SP设置为MSP,否则在BOOT区中使用中断将会引发硬件错误!
        __set_PSP(*(uint32_t*)ulAddr_App);
        __set_CONTROL(0);  /* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
        __ISB();//指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。
		//@9
		pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 );	//用户代码区第二个字为程序开始地址(复位地址)		
		pJump2App ();								                                    	//跳转到APP.
	}
}	

@1 代码作用:检查栈顶地址是否合法.0x20000000是sram的起始地址,也是程序的栈顶地址;

if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )

@2 代码作用:下面这几个关闭的是在Bootloader中初始化过的外设,如果没有初始化过其他外设,则不需要;

HAL_SPI_MspDeInit(&hspi1);	
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOG_CLK_DISABLE();

@3 代码作用:PLL在Bootloader中已经配置启动了,在APP程序中如果想再进行配置启动的话会返回错误。

问题现象

将Bootloader和APP程序分别下载到板子上,Bootlader程序可以正常运行,而APP程序会死在Error_Handler()的while(1)循环中。具体调试发现程序是在执行HAL_RCC_OscConfig()函数的PLL 配置部分检测到当前PLL已经被配置为了系统时钟而返回了HAL_ERROR的返回值导致进入了Error_Handler()。
 方案一: 网上有人的建议是不使用PLL,使用HSI作为系统的时钟源,经过测试可以正常运行;但是这样会有个问题是,使用STM32F4的HSI是16M,这显然和通过PLL倍频之后使用的168M不在一个数量级上。
 方案二: 通过查找资料发现,可以在Bootloader启动的时候配置PLL倍频到168M,然后在跳转程序之前将RCC的配置反初始化为默认设置(使用HSI时钟)。

HAL_RCC_DeInit();	

@4 代码作用:关闭所有的中断;此处的特殊寄存器设置值,可以看如下链接: 嵌入式–Keil5–调试状态下Registers界面解析(nrf52832–Cortex-M4内核)

__set_BASEPRI(0x20);
__set_PRIMASK(1);
__set_FAULTMASK(1);

@5 代码作用:清除所有中断挂起标志,防止在APP中触发该中断,导致运行错误;

	/* 关闭所有中断,清除所有中断挂起标志 */
		for (i = 0; i < 8; i++)		//@5
		{
			NVIC->ICER[i]=0xFFFFFFFF;
			NVIC->ICPR[i]=0xFFFFFFFF;
		}

@6 代码作用:关闭掉系统滴答定时器,该定时器会在APP的程序中重新启动,调用HAL_Init();函数会启动;
 注意:@6 的代码一定要放到@3 之后,因为HAL_RCC_DeInit();函数会再次开启滴答定时器。

SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;

@7 代码作用:重新启动中断开关;

	__set_BASEPRI(0);
	__set_PRIMASK(0);
	__set_FAULTMASK(0);

注意:必须重新启动中断的开关,本人调试过程中没有开启__set_FAULTMASK(0) ;的中断,导致滴答定时器(SysTick)无法正常运行,执行HAL_Delay();,死循环在了延时中,耽误了一天的时间查找问题。

@8 和@9 代码都有详细的注释;

注意: __set_PSP((uint32_t)ulAddr_App); 和 __set_CONTROL(0); 函数是使用RTOS(实时操作系统的读者必须要添加的)

2.2. APP程序

CubeMX 配置基本和Bootloader 基本相同,不赘述。本人使用的FreeRTOS的实时操作系统。

1. Keil 配置
    步骤1:配置程序的起始位置(根据自己的需要修改),配置的时候发现只配置这块没用,需要配置步骤2才行;
在这里插入图片描述
步骤2:Linker 中修改ScatterFile 文件;
在这里插入图片描述
这是本人的配置,每个人的可能都不一样,本人是需要用ccmram,所以使用自己的ScatterFile 文件,修改的内容如下图。
在这里插入图片描述
步骤2:设置APP的中断向量表的位置;
  首先找到 startup_stm32f407xx.s 文件中调用 SystemInit 函数的地方,找到SystemInit 函数的实现,然后 找到 USER_VECT_TAB_ADDRESS 的宏定义的地方,取消注释。先修改下图 1的位置,然后修改 2的位置,2的位置根据自己的APP偏移位置来修改。
在这里插入图片描述

3. 代码

在main 函数的一开始添加如下代码。

  /* USER CODE BEGIN 1 */
	HAL_DeInit();
	HAL_RCC_DeInit();
  /* USER CODE END 1 */

在这里插入图片描述
HAL_DeInit(); HAL库反初始化;
HAL_RCC_DeInit(); RCC配置反初始化;

注意: 如果直接查找HAL_RCC_DeInit() 的实现,发现是个空函数,实际这是HAL库的一个机制,实际编译使用的另外一个文件中的,如下图1和2
在这里插入图片描述
在这里插入图片描述

4. 其他问题

4.1 可能导致APP死机的原因
Bootloader启动的外设比APP的多,导致死机,需要在跳转到APP时清理不需要的外设配置;
Bootloader开启了某个终端,在APP中没有配置相应的处理函数,导致死机;该问题可通过Bootloader程序的@4 @5 @7来解决;

Chapter2 STM32-IAP基本原理及应用 | ICP、IAP程序下载流程 | 程序执行流程 | 配置IAP到STM32F4xxx

原文链接

Chapter3 STM32基于Ymodem协议IAP升级笔记

原文链接:https://blog.csdn.net/weixin_43940932/article/details/128918183

1.YMODEM 协议说明

YMODEM它分为YMODEM-1K与YMODEM-g,平时说的YMODEM传输指是YMODEM-1K传输。
YMODEM-1K可以一次传输1024字节的信息块,同时支持传输多个文件。数据的发送会使用CRC校验,保证数据传输的正确性。它每传输一个信息块数据时,就会等待接收端回应ACK信号,接收到回应后才会继续传输下一个信息块,保证数据已经全部接收。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.升级代码分析
代码是用ST官方代码修改的,此处只讲升级API部分,调用的就是Ymodem_Receive函数(只需要ymodem.c & common.c两个文件),上传API实际中用的少,就忽略了。


#define APP_FLASH_START_ADDR (0x08002800U)   //APP程序跳转运行地址
#define IMAGE_UPDATA_FLASH_SIZE (MCU_FLASH_APP_MAIN_AREA_SIZE) //
unsigned char recv_buff[PACKET_1K_SIZE] ={0};//开辟数据包缓存区
unsigned char FileName[] = {0}; //文件名缓存区
/*
**app跳转引导函数
*/
void iap_load_app(const unsigned int app_addr)
{
   //app_addr为新程序的起始地址,检查栈顶地址是否合法,即栈顶地址是否为0x2000xxxx(内置SRAM)
    if (0x20000000 == ((*(volatile unsigned int*)app_addr) & 0x2FFE0000)) 
    {
        //__set_PRIMASK(1);//有中断,需要先关闭所有中断
        const unsigned int jump_addr = *(volatile unsigned int*)(app_addr + 4); 
        __set_MSP(*(volatile unsigned int*)app_addr);
        ((void (*)(void))jump_addr)();// 设置PC指针为新程序复位中断函数的地址
    }
}

/*
** Ymodem OTA升级执行函数
*/
void ymodem_ota_task(void)
{
  //在执行的过程中,随时都可以按下 `A`按键主动结束升级动作。
  printf(">> IAP already,you can pressed `A` key to end ota....\r\n");

 //返回值携带升级信息
  const int file_sz = Ymodem_Receive(&recv_buff[0],APP_FLASH_START_ADDR);//API改造了一下,增加APP跳转地址形参,便于全局维护。

    if (0 < file_sz)
    {
    printf("\r\n--------- updata sucess --------\r\n");
    printf("file name: %s\r\n", FileName);
    printf("file size: %d bytes\r\n",file_sz);
    printf("--------------------------------\r\n");
        iap_load_app(APP_FLASH_START_ADDR);
    }
    else
    {
        printf("ota failed ...\r\n\r\n");
        if (-1 == file_sz)//升级文件大于分配的FLASH大小
        {
                printf("\r\n alloc flash size is small ...\r\n");
        }
        else if (-2 == file_sz)//升级文件校验出错
        {
                printf("\r\n verif failed ...\r\n");
        }
        else if  (-3 == file_sz)//用户主动结束升级(即按下了`A`按键)
        {
                printf("\r\n aborted by user ...\r\n");
        }
        else//升级超时或者其它
        {
                printf("\r\n receive failed ....\r\n");
        }	
       //NVIC_SystemReset();
     }
}

/*
**
*/
int Ymodem_Receive(unsigned char *buf, unsigned int app_addr)
{
    unsigned char packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD] , 
    file_size[FILE_SIZE_LENGTH] ,
    *file_ptr, //
    *buf_ptr;
    int i, 
    packet_length, 
    session_done, 
    file_done, 
    packets_received, 
    errors, 
    session_begin, 
    image_size = 0;

    volatile unsigned int flash_dst_addr, bin_data;
    flash_dst_addr = app_addr;
 
//双循环
    for (session_done = 0, errors = 0, session_begin = 0;;)//等价 while(1)
    {
        for (packets_received = 0, file_done = 0, buf_ptr = buf;;)//等价 while(1)
        {
                /* 0x00: 正常返回 | -1:时间溢出或数据包错误 | 0x01:用户终止*/
    //分类处理接收包数据
                switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT))
                {
                        case 0://收到完整协议包
                        errors = 0;//清除累计错误
                        switch (packet_length)//判断包长
                        {
                        case -1:/* 连续的两个CA信号终止传输 */
                                Send_Byte(ACK);
                                return 0;
                        case 0:/*正常返回*/
                                Send_Byte(ACK);
                                file_done = 1;
                                break;
                        default:/* 数据区长度 */
                                if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))//检验包序号
                                {
                                        Send_Byte(NAK);//包序号不一致
                                }
                                else//包序号一致
                                {
                                        if (packets_received == 0)//起始帧
                                        {
                                                if (packet_data[PACKET_HEADER] != 0)
                                                {
                        //注:file_ptr = packet_data + PACKET_HEADER 等价 file_ptr = &packet_data[PACKET_HEADER],即从第PACKET_HEADER字节开始取数据指针
                                                        for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
                                                        {
                                                                FileName[i++] = *file_ptr++;//获取文件名
                                                        }
                                                        FileName[i++] = '\0';//补\0字符串结束符操作。
                                                        for (i = 0, file_ptr++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
                                                        {
                                                                file_size[i++] = *file_ptr++;//获取文件大小
                                                        }
                                                        file_size[i++] = '\0';//补\0字符串结束符操作。

                                                        Str2Int(file_size, &image_size);//将字符串转为hex数据

                                                        if (IMAGE_UPDATA_FLASH_SIZE < image_size )//判断将要升级的文件是否超过预留的FALSH升级空间大小
                                                        {
                                                                /* End session */
                                                                Send_Byte(CA);//超过FALSH升级空间大小,发送传输中止应答符'CA',结束升级
                                                                Send_Byte(CA);
                                                                return -1; //返回错误码
                                                        }

                                                        /* Erase the needed pages where the user application will be loaded */
                                                        /* Define the number of page to be erased */
                                                        const unsigned int nbr_of_pg = mcu_flash_page_alloc(image_size);//计算当前文件大小要擦除的页数
                                                        /* Erase the FLASH pages */
                                                        const unsigned int erase_pgn = mcu_flash_page_num_calc(flash_dst_addr);//计算APP起始地址所在的页序号(因为后面擦除FALSH的API是按页序号来操作的)
                                                        //#define SIMLATE_DEBUG
                                                        #ifdef SIMLATE_DEBUG //仿真接收数据信息
                                                        printf("image size:%uBytes |erase page num :%d | erase page nbr:%d\r\n",image_size,erase_pgn,nbr_of_pg);
                                                        #else
                                                        mcu_flash_page_erase_cc_num(erase_pgn,nbr_of_pg);//按页序号来擦除FALSH的API
                                                        #endif
                                                        Send_Byte(ACK);
                                                        Send_Byte(CRC16);//发送正常应答符  + CRC校验
                                                }
                                                else //结束帧
                                                {
                                                        Send_Byte(ACK);
                                                        file_done = 1; //
                                                        session_done = 1;//传输完毕
                                                        break;
                                                }
                                        }
                                        else //数据帧
                                        {
                    //注同样:packet_data + PACKET_HEADER 等价 &packet_data[PACKET_HEADER]
                                                memcpy(buf_ptr,packet_data + PACKET_HEADER,packet_length);//取有效程序数据(去掉前三个帧头)
                                                bin_data = (volatile unsigned int )buf;	

                    //注:packet_length/4:表示下面写FLASH函数是按照4字节(字长度)
                    //若是按2字节写入,则用packet_length/2
                    //1字节则按packet_length
                                                for (unsigned int j = 0; (j < packet_length/4);j++)
                                                {
                                                        #ifdef SIMLATE_DEBUG //先仿真交互信息
                                                        printf("image dating addr :0x%08X | image data:%08X\r\n",flash_dst_addr,*(unsigned int *)bin_data);
                                                        #else
                                                        /* Program the data received into STM32F10x Flash */
                                                        mcu_flash_word_write(flash_dst_addr,*(volatile unsigned int *)bin_data);//每次按4字节写入(1包则按packet_length/4次写完)
                                                        if (*(volatile unsigned int *)flash_dst_addr != *(volatile unsigned int *)bin_data)//每次写入后再读取出来进行对比。
                                                        {
                                                                /* End session */
                                                                Send_Byte(CA);
                                                                Send_Byte(CA);
                                                                return -2;   //写入出错(校验失败)
                                                        }
                                                        #endif
                                                        flash_dst_addr += 4;//每次地址按写入的字节长度偏移,当前API是4字节。
                                                        bin_data +=4;//同样数据源(来自包数据)一起同步偏移。
                                                }										
                                                Send_Byte(ACK);//写完后一包发送ACK.
                                        }
                                        packets_received++;//升级包数目计数
                                        session_begin = 1;//接收一包标记
    //						printf("received packets :%d\r\n",packets_received);
                                }
                        }
                        break;
                case 1:/*用户终止*/
                        Send_Byte(CA);
                        Send_Byte(CA);
                        return -3;
                default:/*时间溢出或数据包错误*/
                        if (session_begin > 0) //
                        {
                                errors++;
                        }
                        if (errors > MAX_ERRORS)//允许出错最大次数
                        {
                                Send_Byte(CA);
                                Send_Byte(CA);
                                return 0;
                        }
                        Send_Byte(CRC16);
                        break;
                }//end Receive_Packet...
                if (file_done != 0)
                {
                        break;
                }
        }//end 接收while(1);
            if (session_done != 0)
            {
                    break;
            }
    }

	return (int)image_size;
}

/*
**获取输入\接收单字节数据
*/
unsigned int SerialKeyPressed(unsigned char *key)
{
  //串口查询方式接收数据
  if (RESET!=USART_GetFlagStatus(USART1,USART_FLAG_RXNE))
  {
    *key = (uint8_t)USART_ReceiveData(USART1);
    return 1;
  }
  return 0;
}

/*
** 接收字节函数
*/
static int Receive_Byte(unsigned char *c, unsigned int timeout)
{
	volatile unsigned int count = timeout;

	while (count-- > 0)
	{
        //收到数据立马退出
		if (SerialKeyPressed(c) == 1)
		{
			return 0;
		}
	}
	return -1;
}

/*
** 接收包数据函数
*/
int Receive_Packet(unsigned char *data, int *length, unsigned int timeout)
{
	unsigned short int i, packet_size;
	unsigned char c;
	unsigned short int crc;

	*length = 0;

	/* 接收一个字符 */
	if (Receive_Byte(&c, timeout) != 0) //没有收到数据,退出
	{
		return -1;
	}

	switch (c)
	{
	/* SOH表示数据区有128字节 */
	case SOH:
		packet_size = PACKET_SIZE;
		break;
	/* STX表示数据区有1k字节 */
	case STX:
		packet_size = PACKET_1K_SIZE;
		break;
	/* 传输结束 end of transmission */
	case EOT:
		return 0;
	/* 连续的两个CA信号终止传输 */
	case CA:
		/* 收到两个连续的CA信号 */
		if ((Receive_Byte(&c, timeout) == 0) && (c == CA))
		{
			*length = -1;
			return 0;
		}
		else/* 只收到一个CA信号 */
		{
			return -1;
		}
	/* 用户终止传输 */
	case ABORT1:
	case ABORT2:
		return 1;
	default:
		return -1;
	}

	*data = c;
	for (i = 1; i < (packet_size + PACKET_OVERHEAD); i++)
	{
        //data + i 等价 &data[i]
		if (Receive_Byte(data + i, timeout) != 0)//没有收到数据,退出
		{
			return -1;
		}
	}
	/* 第PACKET_SEQNO_COMP_INDEX(数字2)字节是PACKET_SEQNO_INDEX(数字1)字节的反码 */
	if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff))//正反帧序号校验不通过
	{
		return -1;
	}

	/* 计算CRC */
	crc = data[packet_size + PACKET_HEADER] << 8;
	crc += data[packet_size + PACKET_HEADER + 1];

	if (Cal_CRC16(&data[PACKET_HEADER], packet_size) != crc)//数据包校验不通过
	{
		return -1;
	}

	/* 取数据区长度 */
	*length = packet_size;
	return 0;
}

//FLASH擦写函数API参考
/*
** 根据提供的文件大小,计算所需要的FLASH页数。
*/
unsigned int  mcu_flash_page_alloc(volatile unsigned int img_size)
{
  unsigned int pg_num = 0x0;
  unsigned int img_sz = img_size;

  if ((img_sz % MCU_FLASH_PAGE_SIZE) != 0)
  {
    pg_num = (img_sz / MCU_FLASH_PAGE_SIZE) + 1;
  }
  else
  {
    pg_num = img_sz / MCU_FLASH_PAGE_SIZE;
  }
	
  return pg_num;
}

/*
**页所在的FLASH地址计算当前页序号
*/
unsigned int mcu_flash_page_num_calc(const unsigned int flash_addr)
{
     /* calculate the number of page to be programmed/erased */
    return (flash_addr - MCU_FLASH_START_ADDR) / MCU_FLASH_PAGE_SIZE;
}

/*
**(按序号)连擦除FLASH函数
*/
void mcu_flash_page_erase_cc_num(const unsigned int erase_pgn,unsigned int erase_pg_nbr)
{
    unsigned int erase_pgaddr_ofs=mcu_flash_page_addr_calc(erase_pgn);/*计算当前页所在FLASH地址*/
    /* Unlocks the FLASH Program Erase Controller */
    FLASH_Unlock();
    /* clear all pending flags */
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR | FLASH_FLAG_PGERR);

    /* erase the flash pages */
    for(unsigned int i=0;i<erase_pg_nbr;i++)
    {
       while(FLASH_COMPLETE != FLASH_ErasePage(erase_pgaddr_ofs));//擦除当前FLASH地址所在页
	   FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR | FLASH_FLAG_PGERR);
	   erase_pgaddr_ofs+=MCU_FLASH_PAGE_SIZE;
    }
    /* Locks the FLASH Program Erase Controller */
    FLASH_Lock();
}


3.Ymodem升级工具

1.网上大多推荐的是SecureCRT终端工具,我用的是Tera Term这款终端工具,里面有支持Xmodem,Ymodem,Zmodem等多协议文件发送功能,

2.Ymodem升级文件使用: 打开终端

2.1 首先设置串口:点击"设置"-> “串口”,选择相应串口号以及波特率。
2.2 加载并升级文件:点击"文件"-> “传输” -> “YMODEM” -> “发送” ,选择app升级bin文件即可。
注意:貌似调试发现,其发送的包大小是1KB的。
在这里插入图片描述
3.后来在gitee上找到了作者biglu制作的专门用于Ymodem升级的上位机Ymodem_tool,其支持发送包大小是128B/1024B的,界面简洁好用。
在这里插入图片描述
4.两款工具升级界面
在这里插入图片描述
在这里插入图片描述

Chapter4 基于Ymodem协议的stm32f405rgt6+CubeMx+IAP在线升级

原文链接

准备工作

1.STM32F4开发板

2.USB转串口工具

3.MDK Keil5

4.CubeMX

5.secureCRT上位机软件

6.en.stsw-stm32067官方固件库

工程下载地址(0积分下载):

Ymodem_IAP_F405_CubeMx+secureCRT上位机软件

一、CubeMX的配置

需要配置IAP和APP两个工程,这两个工程的配置都比较简单

1.IAP

打开CubeMX软件,选好芯片型号,这里我的芯片型号是STM32F405RGT6

配置时钟

配置主时钟,我配置的是内部时钟,主频为168MHz

在这里插入图片描述
配置串口

我这里配置的是串口1,对应引脚为PA9和PA10,波特率为115200,串口采用轮询的方式通讯,不采用中断的方式,故不配置串口中断,其他默认。
在这里插入图片描述
生成代码

H和C文件分开,配置完成后,生成代码,为后续IAP移植修改作准备
在这里插入图片描述

2.APP

需要再配置一个APP工程,做一个简单的APP,就是让一个LED灯不断闪烁。

这里简单的配置了PB12口为输出,作为LED灯,其他配置过程这里就省略了。
在这里插入图片描述

二、移植Ymodem官方代码

1.文件移植

打开之前生成好的IAP工程目录,在该目录下新建一个Ymodem的文件夹
在这里插入图片描述
打开我们从官网下载好en.stsw-stm32067官方固件库,将en.stsw-stm32067->STM32F4xx_AN3965_V1.0.0->Project->STM32F4xx_IAP下的inc和src文件夹中的部分文件复制到新建的Ymodem的文件夹中
在这里插入图片描述
复制完的Ymodem的文件夹内容如下,一共8个文件
在这里插入图片描述

2.MDK文件、路径添加

打开IAP的MDK工程,添加一个Ymodem组,将Ymodem文件夹中的.c文件添加进去
在这里插入图片描述
添加Ymodem文件夹中的.H文件的路径
在这里插入图片描述

3.修改代码

完成1和2步后编译工程会报很多错误,需要修改代码
(1)删除不必要的代码
打开common.h文件,删除#include “stm324xg_eval.h” ,这个头文件是官方用在一个官方的开发板上的,我们用不到,故删之
在这里插入图片描述
(2)修改IAP代码
common.c文件
打开common.c文件,在193行有报错,这里官方用的不是HAL库,而且官方用的开发板的接口有不同的定义,因此,我们根据自己的实际情况修改下面的代码
在这里插入图片描述
我们将这个函数修改如下,并且要添加一个头文件,从这个函数的内容可知,主要实现接收一个字节

/** @addtogroup STM32F4xx_IAP
  * @{
  */
 
/* Includes ------------------------------------------------------------------*/
#include "common.h"
#include "usart.h"   //添加的头文件
/* Private typedef -----------------------------------------------------------*/
/**
  * @brief  Test to see if a key has been pressed on the HyperTerminal
  * @param  key: The key pressed
  * @retval 1: Correct
  *         0: Error
  */
uint32_t SerialKeyPressed(uint8_t *key)
{
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
  {
    *key = (uint16_t)((&huart1)->Instance->DR & (uint16_t)0x01FF);	  
     return 1;
  }
  else
  {
    return 0;
  }
}

同样,修改下面的代码
在这里插入图片描述
修改成这样

/**
  * @brief  Print a character on the HyperTerminal
  * @param  c: The character to be printed
  * @retval None
  */
void SerialPutChar(uint8_t c)
{
  USART_SendData(USART1, c);
  while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) == RESET)
  {
  }
}

在common.c末尾添加代码,添加void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)函数

//添加代码
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

在common.h添加声明void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

/* Exported functions ------------------------------------------------------- */
void Int2Str(uint8_t* str,int32_t intnum);
uint32_t Str2Int(uint8_t *inputstr,int32_t *intnum);
uint32_t GetIntegerInput(int32_t * num);
uint32_t SerialKeyPressed(uint8_t *key);
uint8_t GetKey(void);
void SerialPutChar(uint8_t c);
void Serial_PutString(uint8_t *s);
void GetInputString(uint8_t * buffP);
 
//添加声明
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data); 

flash_if.c文件
修改FLASH_If_Init(void)
在这里插入图片描述
修改如下

void FLASH_If_Init(void)
{ 
   HAL_FLASH_Unlock();
 
  /* Clear pending flags (if any) */  
   __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | 
                  FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
  
}

修改FLASH_If_Erase(uint32_t StartSector)函数
在这里插入图片描述
修改成HAL库的方式,修改如下:

/**
  * @brief  This function does an erase of all user flash area
  * @param  StartSector: start of user flash area
  * @retval 0: user flash area successfully erased
  *         1: error occurred
  */
uint32_t FLASH_If_Erase(uint32_t StartSector)
{
    uint32_t UserStartSector;
    uint32_t SectorError;
    FLASH_EraseInitTypeDef pEraseInit;
 
    /* Unlock the Flash to enable the flash control register access *************/
 
    /* Get the sector where start the user flash area */
    UserStartSector = GetSector(APPLICATION_ADDRESS);
 
    pEraseInit.TypeErase = TYPEERASE_SECTORS;
    pEraseInit.Sector = UserStartSector;
    pEraseInit.NbSectors = GetSector(USER_FLASH_END_ADDRESS)-UserStartSector+1 ;
    pEraseInit.VoltageRange = VOLTAGE_RANGE_3;
 
    if (HAL_FLASHEx_Erase(&pEraseInit, &SectorError) != HAL_OK)
    {
        /* Error occurred while page erase */
        return (1);
    }
    return (0);
 
}

修改FLASH_If_Write(__IO uint32_t* FlashAddress, uint32_t* Data ,uint32_t DataLength)函数
在这里插入图片描述
修改成如下:

/**
  * @brief  This function writes a data buffer in flash (data are 32-bit aligned).
  * @note   After writing data buffer, the flash content is checked.
  * @param  FlashAddress: start address for writing data buffer
  * @param  Data: pointer on data buffer
  * @param  DataLength: length of data buffer (unit is 32-bit word)   
  * @retval 0: Data successfully written to Flash memory
  *         1: Error occurred while writing data in Flash memory
  *         2: Written Data in flash memory is different from expected one
  */
uint32_t FLASH_If_Write(__IO uint32_t* FlashAddress, uint32_t* Data ,uint32_t DataLength)
{
  uint32_t i = 0;
 
  for (i = 0; (i < DataLength) && (*FlashAddress <= (USER_FLASH_END_ADDRESS-4)); i++)
  {
    /* Device voltage range supposed to be [2.7V to 3.6V], the operation will
       be done by word */ 
    FLASH_ProgramWord(*FlashAddress, *(uint32_t*)(Data+i));  
     /* Check the written value */
      if (*(uint32_t*)*FlashAddress != *(uint32_t*)(Data+i))
      {
        /* Flash content doesn't match SRAM content */
        return(2);
      }
      /* Increment FLASH destination address */
      *FlashAddress += 4;
  }
 
  return (0);
}

在flash_if.c末尾添加如下函数

void FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
  /* Check the parameters */
  assert_param(IS_FLASH_ADDRESS(Address));
  
  /* If the previous operation is completed, proceed to program the new data */
  CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE);
  FLASH->CR |= FLASH_PSIZE_WORD;
  FLASH->CR |= FLASH_CR_PG;
 
  *(__IO uint32_t*)Address = Data;
}

在flash_if.h添加void FLASH_ProgramWord(uint32_t Address, uint32_t Data)声明

/* Exported functions ------------------------------------------------------- */
void FLASH_If_Init(void);
uint32_t FLASH_If_Erase(uint32_t StartSector);
uint32_t FLASH_If_Write(__IO uint32_t* FlashAddress, uint32_t* Data, uint32_t DataLength);
uint16_t FLASH_If_GetWriteProtectionStatus(void);
uint32_t FLASH_If_DisableWriteProtection(void);
 
//添加声明
void FLASH_ProgramWord(uint32_t Address, uint32_t Data);
#endif  /* __FLASH_IF_H */

修改 FLASH_If_GetWriteProtectionStatus(void)函数
在这里插入图片描述
修改如下

/**
  * @brief  Returns the write protection status of user flash area.
  * @param  None
  * @retval 0: No write protected sectors inside the user flash area
  *         1: Some sectors inside the user flash area are write protected
  */
uint16_t FLASH_If_GetWriteProtectionStatus(void)
{
  uint32_t UserStartSector = FLASH_SECTOR_1;
 
  /* Get the sector where start the user flash area */
  UserStartSector = GetSector(APPLICATION_ADDRESS);
 
  /* Check if there are write protected sectors inside the user flash area */
  if ((*(__IO uint16_t *)(OPTCR_BYTE2_ADDRESS) >> (UserStartSector/8)) == (0xFFF >> (UserStartSector/8)))
  { /* No write protected sectors inside the user flash area */
    return 1;
  }
  else
  { /* Some sectors inside the user flash area are write protected */
    return 0;
  }
}

修改FLASH_If_DisableWriteProtection(void)函数
在这里插入图片描述
修改如下

/**
  * @brief  Disables the write protection of user flash area.
  * @param  None
  * @retval 1: Write Protection successfully disabled
  *         2: Error: Flash write unprotection failed
  */
uint32_t FLASH_If_DisableWriteProtection(void)
{
  __IO uint32_t UserStartSector = FLASH_SECTOR_1, UserWrpSectors = OB_WRP_SECTOR_1;
 
  /* Get the sector where start the user flash area */
  UserStartSector = GetSector(APPLICATION_ADDRESS);
 
  /* Mark all sectors inside the user flash area as non protected */  
  UserWrpSectors = 0xFFF-((1 << (UserStartSector/8))-1);
   
  /* Unlock the Option Bytes */
 
  HAL_FLASH_Unlock();
  /* Disable the write protection for all sectors inside the user flash area */
  FLASH_OB_DisableWRP(UserWrpSectors, FLASH_BANK_1);
 
  /* Start the Option Bytes programming process. */  
  if (HAL_FLASH_OB_Launch( ) != HAL_OK)
  {
    /* Error: Flash write unprotection failed */
    return (2);
  }
 
  /* Write Protection successfully disabled */
  return (1);
}

需在末尾处添加 FLASH_OB_DisableWRP(uint32_t WRPSector, uint32_t Banks)函数

HAL_StatusTypeDef FLASH_OB_DisableWRP(uint32_t WRPSector, uint32_t Banks)
{
  HAL_StatusTypeDef status = HAL_OK;
  
  /* Check the parameters */
  assert_param(IS_OB_WRP_SECTOR(WRPSector));
  assert_param(IS_FLASH_BANK(Banks));
    
  /* Wait for last operation to be completed */
  status = FLASH_WaitForLastOperation(50000);
 
  if(status == HAL_OK)
  { 
    *(__IO uint16_t*)OPTCR_BYTE2_ADDRESS |= (uint16_t)WRPSector; 
  }
  
  return status;
}

并在flash_if.h添加函数声明HAL_StatusTypeDef FLASH_OB_DisableWRP(uint32_t WRPSector, uint32_t Banks);

/* Exported functions ------------------------------------------------------- */
void FLASH_If_Init(void);
uint32_t FLASH_If_Erase(uint32_t StartSector);
uint32_t FLASH_If_Write(__IO uint32_t* FlashAddress, uint32_t* Data, uint32_t DataLength);
uint16_t FLASH_If_GetWriteProtectionStatus(void);
uint32_t FLASH_If_DisableWriteProtection(void);
 
//添加声明
void FLASH_ProgramWord(uint32_t Address, uint32_t Data);
HAL_StatusTypeDef FLASH_OB_DisableWRP(uint32_t WRPSector, uint32_t Banks);
 
#endif  /* __FLASH_IF_H */

修改GetSector(uint32_t Address)函数
在这里插入图片描述
修改如下:

/**
 * @brief  Gets the sector of a given address
 * @param  Address: Flash address
 * @retval The sector of a given address
  */
static uint32_t 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/*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11))*/
  {
    sector = FLASH_SECTOR_11;  
  }
    return sector;
}
  • ymodem.h

在ymodem.h文件添加头文件#include “stdint.h”

#include "stdint.h"
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __YMODEM_H_
#define __YMODEM_H_
  • main.c文件

在main.c文件添加头文件#include “menu.h”

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "menu.h"

main()函数添加 FLASH_If_Init()和 Main_Menu ()函数

int main(void)
{
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  FLASH_If_Init();
  Main_Menu ();
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

编译一下,已经没有错误了
在这里插入图片描述
(3)修改APP代码
打开APP工程,while(1)添加如下函数

int main(void)
{
  /* USER CODE BEGIN 1 */
 SCB->VTOR = FLASH_BASE | 0x4000; //中断向量表偏移
  /* USER CODE END 1 */
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12);
    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

从IAP的flash_if.h的宏定义可以看出,这里的APP起始地址为0x08004000
在这里插入图片描述
在这里插入图片描述
生成bin文件,复制

fromelf.exe --bin -o "$L@L.bin" "#L

在这里插入图片描述
修改FLASH烧录地址
在这里插入图片描述
设置完成后编译一下,生成bin文件
在这里插入图片描述

三、烧录、测试

打开secureCRT上位机软件,选好端口,设置好波特率,去掉RTS/CTS的勾,点击连接,如下图
在这里插入图片描述
点击选项,选择会话选项,弹出如下窗口,按下图设置好,点击确定
在这里插入图片描述将IAP代烧录到开发板,代码开始执行后,如下图
在这里插入图片描述

在下方窗口输入1,此时串口不断接受到’C’,如下图所示
在这里插入图片描述
选择传输,点击发送Ymodem
在这里插入图片描述
选择添加好APP生成的bin文件,点击确定
在这里插入图片描述
等待一段时间,等待擦除flash,就会完成传输,此时,在下方窗口输入3,即可运行APP
在这里插入图片描述
至此,全部工作已结束

其他待更新。。。。。。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信息系统项目管理师——第5章信息系统工程(三)

近几期的考情来看&#xff0c;本章选择题稳定考4分&#xff0c;考案例的可能性有&#xff0c;需要重点学习。本章节专业知识点特别多。但是&#xff0c;只考课本原话&#xff0c;大家一定要把本章至少通读一遍&#xff0c;还要多刷题&#xff0c;巩固重点知识。 3 系统集成 3…

Qt5 编译 Qt Creator 源码中的 linguist 模块

文章目录 下载 Qt Creator 源码手动翻译多语言自动翻译多语言 下载 Qt Creator 源码 Github: https://github.com/qt/qttools 笔记打算用 Qt 5.12.12 来编译 qt creator-linguist 所以笔者下载的是 tag - 5.12.12 &#xff0c;解压后如下&#xff0c;先删除多余的文件&#xf…

30岁《爱·回家》小花多次得罪高层,正式宣布离巢TVB。

30岁的苏韵姿&#xff08;Andrea&#xff09;16年选港姐入行&#xff0c;虽然无三甲名次&#xff0c;但靠着皇后大学戏剧学士学位背景&#xff0c;她很快已有机会入剧组&#xff0c;凭《爱回家之开心速递》熊心如&#xff08;红衫鱼&#xff09;一角成功入屋&#xff0c;不过去…

先进电机技术 —— 步进电机控制综述

一、背景 随着自动化技术的发展和精密控制需求的增长&#xff0c;步进电机作为一种重要的执行元件在众多领域展现出了卓越的性能优势。步进电机&#xff0c;又称为步进驱动器或步进马达&#xff0c;是一种能够将电脉冲信号精确转换为角位移或直线位移的特殊电动机。其工作原理…

防止狗上沙发,写一个浏览器实时识别目标检测功能

家里有一条狗&#x1f436;&#xff0c;很喜欢乘人不备睡沙发&#x1f6cb;️&#xff0c;恰好最近刚搬家 狗迎来了掉毛期 不想让沙发上很多毛。所以希望能识别到狗&#xff0c;然后播放“gun 下去”的音频&#x1f4e3;。 需求分析 需要一个摄像头&#x1f4f7; 利用 chrome…

14款DevOps/SRE工具,助力提升运维效率

简介 随着平台工程的兴起&#xff0c;DevOps 和 SRE 不断发展&#xff0c;带来了新一代工具&#xff0c;旨在提高软件开发和运维的效率、可扩展性和可靠性。 在本篇文章中&#xff0c;我们将深入探讨一些最具发展前景的工具&#xff0c;它们正在塑造持续集成与部署、监控与可观…

Redis -- 缓存击穿问题

缓存击穿问题也叫热点Key问题&#xff0c;就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了&#xff0c;无数的请求访问会在瞬间给数据库带来巨大的冲击。 常见的解决方案有两种&#xff1a; 互斥锁 逻辑过期 逻辑分析&#xff1a;假设线程1在查询缓存之后&…

2024认证杯数学建模A题思路模型代码

目录 2024认证杯数学建模A题思路模型代码&#xff1a;4.11开赛后第一时间更更新&#xff0c;获取见文末名片 2023年认证杯数学建模 2024年认证杯思路代码获取见此 2024认证杯数学建模A题思路模型代码&#xff1a;4.11开赛后第一时间更更新&#xff0c;获取见文末名片 2023年认…

花样鼠标悬停特效

代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style&…

python如何输入多行

Python中的Input()函数在输入时&#xff0c;遇到回车符&#xff0c;那么一次输入就结束了。这不能满足输入多行文本并且行数也不确定的情形&#xff0c;当然输入空行也是允许的。 方法1&#xff1a;利用异常处理机制实现 lines[] while True:try:lines.append(input())except:…

Node.js 的 5 个常见服务器漏洞

Node.js 是一个强大且广泛使用的 JavaScript 运行时环境&#xff0c;用于构建服务器端应用程序。然而&#xff0c;与任何其他软件一样&#xff0c;Node.js 也有自己的一些漏洞&#xff0c;如果处理不当&#xff0c;可能会导致安全问题。请注意&#xff0c;这些漏洞并不是 Node.…

数据驱动目标:如何通过OKR实现企业数字化转型

在数字化转型的浪潮中&#xff0c;企业管理者面临着前所未有的挑战和机遇。如何确保企业在变革中不仅能够生存&#xff0c;还能蓬勃发展&#xff1f;答案可能就在于有效的目标管理——特别是采用OKR&#xff08;Objectives and Key Results&#xff0c;目标与关键成果&#xff…

Hive概述与基本操作

一、Hive基本概念 1.什么是hive? &#xff08;1&#xff09;hive是数据仓库建模的工具之一 &#xff08;2&#xff09;可以向hive传入一条交互式的sql,在海量数据中查询分析得到结果的平台 2.Hive简介 Hive本质是将SQL转换为MapReduce的任务进行运算&#xff0c;底层由HDFS…

使用Vivado Design Suite进行物理优化(二)

物理优化是对设计的negative-slack路径进行时序驱动的优化。而phys_opt_design 命令是用于对设计进行物理优化。这个命令可以在布局后的后置模式&#xff08;post-place mode&#xff09;中运行&#xff0c;也就是在放置所有组件之后&#xff1b;还可以在完全布线后的后置模式&…

【oracle数据库安装篇一】Linux5.6基于LVM安装oracle10gR2单机

说明 本篇文章主要介绍了Linux5.6基于LVM安装oracle10gR2单机的配置过程&#xff0c;比较详细&#xff0c;基本上每一个配置部分的步骤都提供了完整的脚本&#xff0c;安装部分都提供了简单的说明和截图&#xff0c;帮助你100%安装成功oracle数据库。 安装过程有不明白的地方…

爬虫学习第一天

爬虫-1 爬虫学习第一天1、什么是爬虫2、爬虫的工作原理3、爬虫核心4、爬虫的合法性5、爬虫框架6、爬虫的挑战7、难点8、反爬手段8.1、Robots协议8.2、检查 User-Agent8.3、ip限制8.4、SESSION访问限制8.5、验证码8.6、数据动态加载8.7、数据加密-使用加密算法 9、用python学习爬…

Flody算法求解多源最短路问题

Flody算法求解多源最短路问题 蓝桥公园 #include <bits/stdc.h> using namespace std; #define int long long const int N409; int n,m,q,d[N][N]; signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m>>q;memset(d,0x3f,sizeof…

mac 配置前端开发环境brew,git,nvm,nrm

我的电脑是mac 3 pro 一、配置Homebrew 打开终端&#xff0c;执行指令 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"查看版本 brew -v 安装nvm brew install nvm 再执行 brew reinstall nvm 我这边安装好了…

云服务器上Docker启动的MySQL会自动删除数据库的问题

一、问题说明 除了常见的情况&#xff0c;例如没有实现数据挂载&#xff0c;导致数据丢失外&#xff0c;还需要考虑数据库是否被攻击&#xff0c;下图 REVOVER_YOUR_DATA 就代表被勒索了&#xff0c;这种情况通常是数据库端口使用了默认端口&#xff08;3306&#xff09;且密码…

Coding and Paper Letter(八十九)

CPL之第八十九期。 1 Coding: 1.openai通用代理转换是一个用于将其他厂商服务转为openai 标准接口相应的工具. 通过该工具, 可以将其他厂商的服务转为openai 标准接口. 讯飞星火,通义千问,gemini,openai,copilot,double&#xff0c;kimi&#xff0c;智谱清言 使用spring2webf…