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升级工具


ST官网IAP例程

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;		//@6SysTick->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();//指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。//@9pJump2App = ( 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);#elsemcu_flash_page_erase_cc_num(erase_pgn,nbr_of_pg);//按页序号来擦除FALSH的API#endifSend_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_lengthfor (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;   //写入出错(校验失败)}#endifflash_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.两款工具升级界面
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

未来工厂大脑:图扑组态软件在智能制造中的应用

组态软件&#xff1a;一般英文简称有三种分别为 HMI/MMI/SCADA&#xff0c;中文翻译为&#xff1a;人机界面/监视控制和数据采集软件。 运行于 PC 平台的一个通用工具软件&#xff0c;涉及各行各业&#xff0c;其主要功能是对生产现场的运行设备进行监控并就危险情况进行报警&…

【学习】使用VScode连接服务器。

step1: 安装 Remote - ssh 扩展 step2&#xff1a; 进入步骤2中&#xff0c;进行文件配置。 step3&#xff1a; 点击箭头进行连接。 step4&#xff1a; 输入密码即可。选择 platform时候&#xff0c;选择使用 Linux&#xff0c;而不是windows。

FreeRTOS创建第一个程序

使用freeRTOS创建任务时使用如下函数 函数的参数 创建一个FreeRTOS任务点亮led灯实现led灯500毫秒翻转一次 具体的代码实现 #include "stm32f10x.h" // Device header #include "Delay.h" #include "freeRTOS.h" #include &quo…

PMP持证者在面试项目经理时有加持吗?

对PMP认证获取后是否在面试中加持很多人是没有体验过的&#xff0c;因为大部分人考取PMP认证的原因是因为公司的要求&#xff0c;没有这个证书可能面临被“优化”的风险。理论上来说一样的道理&#xff0c;PMP认证既然能够保住工作岗位&#xff0c;那么在面试中一定会有相应的作…

利用AI开源引擎平台:构建文本、图片及视频内容审核系统|可本地部署

网络空间的信息量呈现出爆炸式增长。在这个信息多元化的时代&#xff0c;内容审核系统成为了维护网络秩序、保护用户免受有害信息侵害的重要工具。本文将探讨内容审核系统的核心优势、技术实现以及在不同场景下的应用。 开源项目介绍(可本地部署&#xff0c;支持国产化) 思通数…

12.C++常用的算法_遍历算法

文章目录 遍历算法1. for_each()代码工程运行结果 2. transform()代码工程运行结果 3. find()代码工程运行结果 遍历算法 1. for_each() 有两种方式&#xff1a; 1.普通函数 2.仿函数 代码工程 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vect…

layui复选框勾选取消勾选事件监听

监听事件放置位置&#xff1a; form.on(checkbox(equipInputClick), function(data){var a data.elem.checked;var val data.value;if(a true){}else{}});html部分 <input lay-filter"equipInputClick" type"checkbox" lay-skin"primary&quo…

前端做了快两年了,盘点一下我的前端技术栈

前言 前言很简单&#xff0c;年终了&#xff0c;自己写了快两年前端&#xff08;虽然刚毕业&#xff09;&#xff0c;但是大致总结一下哈哈哈哈 我觉得这个话题蛮有意思的&#xff0c;可以看看大家的技术广度&#xff0c;可以进行分享和学习以及讨论 所以这里说一下我对我的…

# ABAP SQL 字符串处理

经常我都要在ABAP的sql语句中对字符串进行处理&#xff0c;现在就总结一下可以用到的方法 文章目录 字符串处理拼接字段运行结果 填充字符串运行结果 截取字符串 SUBSTRING运行结果 CAST转换类型程序运行结果 字符串处理 在SQL语句中&#xff0c;有时候会有需要拼接字段或者是…

车载平板电脑丨车载终端丨浅聊叉车仓储出入库管理解决方案

众所周知&#xff0c;叉车是制造业自动化升级的核心设备。 十三五期间&#xff0c;提出中国制造 2025 战略&#xff0c;其核心内容就是要用机器代替人工&#xff0c;提升生产效率&#xff0c;降低对劳动力的依赖性。而叉车作为起重、搬运等普通劳动力替代的核心设备&#xff0…

C++解决大学课设所有管理系统(增删查改)

C一篇解决大学课设所有**管理系统(增删查改) 文章目录 C一篇解决大学课设所有**管理系统(增删查改)1.引言1.1 使用结果展示 2. 基本原理3. 文件层次结构4.具体实现(通讯录管理系统为例)4.1 通讯录实体类(addressbook.h)4.2 通讯录实现类(addressbook.cpp)4.3 通讯录管理类&…

设备树的概念及引入、设备树如何变成device

在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离&#xff0c;但每次调整资源还是会涉及到内核&#xff0c;所以现在更加流行的是设备树方式。设备树的好处是通过独立于内核存在&#xff0c;这样如果设备上外设功能启用与否以及位置变动的话很多时候不用修改…

【股权激励】“三级股权分配法”——某互联网电商企业股权激励管理

【客户背景及面临难题】K公司于2014年上线&#xff0c;是由多位创始人联合创办的一家以众包微物流配送为核心模式&#xff0c;主营同城水果、蔬菜、生鲜、日常生活用品、零食等商品在线销售&#xff0c;专注于社区生鲜最后一公里配送&#xff0c;主打一小时之内闪电送达的互联网…

使用C语言函数对数组进行操作

前言 在我们了解数组和函数之后&#xff0c;我们对数组和函数进行结合&#xff0c;之后完成一些操作吧 题目描述 杰克想将函数与数组结合进行一些操作&#xff0c;以下是他想要达到的效果&#xff0c;请你帮帮他吧&#xff01; 创建一个整型数组&#xff0c;完成对数组的操作 1…

签约速递 | 看零信任如何打造安全互联的数字底座?

近期&#xff0c;易安联再传佳音&#xff0c;签约南通市公安局、隆基绿能科技股份有限公司、南京体育学院等&#xff0c;持续为客户提供功能更完善、体验更好的零信任安全解决方案&#xff0c;赋能千行百业数字化转型升级&#xff0c;共创网络安全繁荣生态。 南通市公安局 数字…

K-means聚类算法的原理、应用与实例

文章目录 K-means 聚类算法&#xff1a;原理K-means 聚类算法的应用K-means 聚类算法的优化与改进 一个使用 K-means 聚类算法进行客户细分的简单实例 K-means 聚类算法&#xff1a;原理 K-means 算法是一种经典的无监督学习方法&#xff0c;用于对未标记的数据集进行分群&…

智慧公厕是智慧城市建设中不可或缺的一部分

智慧城市的数字化转型正在取得显著成效&#xff0c;各项基础设施的建设也在迅速发展&#xff0c;其中智慧公厕成为了智慧城市体系中不可或缺的一部分。作为社会生活中必要的设施&#xff0c;公共厕所的信息化、数字化、智慧化升级转型能够实现全区域公共厕所管理的横向打通和纵…

macOS 启动 Nacos(2),50家大厂面试万字精华总结

进来之后一定要选择支持 arm64 的版本不然会出现因为版本不支持导致无法启动 WARNING: The requested images platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested点击 copy docker pull nacos/naco…

Blender2.83 下载地址及安装教程

Blender是一款开源的3D计算机图形软件&#xff0c;广泛应用于动画制作、游戏开发、建模、渲染等领域。它提供了一套强大的工具和功能&#xff0c;让用户能够进行三维建模、动画制作和视觉效果的创作。 Blender支持多种文件格式的导入和导出&#xff0c;使用户能够与其他软件进…

Redis从入门到精通(七)Redis实战(四)库存超卖、一人一单与Redis分布式锁

↑↑↑请在文章开头处下载测试项目源代码↑↑↑ 文章目录 前言4.3 优惠券秒杀4.3.4 库存超卖问题及其解决4.3.4.1 问题分析4.3.4.2 问题解决 4.3.5 一人一单需求4.3.5.1 需求分析4.3.5.2 代码实现4.3.5.3 并发问题4.3.5.4 悲观锁解决并发问题4.3.5.5 集群环境下的并发问题 4.4 …