技术标签: stm32 c语言 嵌入式硬件 STM32_报警、错误 单片机
本例程仅供参考(个人学习总结_有需要文中有的封装好的跳转函数可私信),
例程可举一反三完成FDCAN通信和USART通信。
链接:https://pan.baidu.com/s/1avuZRA8Lbm8K-cBPkvpshw?pwd=idnj
提取码:idnj
复制这段内容后打开百度网盘手机App,操作更方便哦
目录
2.IAP(BootLoader 程序)配置(HAL库,Cubemax)
2.3CAN配置(版本例程CAN接收数据和发送数据为普通模式,配合TIM2定时器使用)
IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产 品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,
第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;
第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它做如下操作:
1)检查是否需要对第二部分代码进行更新
2)如果不需要更新则转到 4)
3)执行更新操作
4)跳转到第二部分代码执行
第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP 代码更新。
我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32G4 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的),这样我们就是要实现 2 个程序:Bootloader 程序和 APP程序。
STM32G4 的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行,我们选用在 FLASH 运行。Flash存储器是一种非易失性存储器,可以在掉电之后保存数据,通常用于存储程序代码。Flash存储器的可写入次数有限,且需要执行擦除操作才能写入新的数据,因此,在使用过程中需要注意擦写周期和数据备份问题。SRAM存储器则是一种易失性存储器,具有相对较快的读写速度和无限的读写次数,但掉电时将会丢失所有内容。SRAM存储器主要用于暂存数据和临时变量,读写操作由CPU直接完成,访问速度较快。
单片机的Flash存储器和SRAM存储器都嵌入在单片机芯片内部,能够方便的实现对程序和数据、变量的读写操作。通常,编译器会把程序烧录在Flash存储器,并使用SRAM存储器来存储变量、函数堆栈以及其他的临时数据
图中 IROM1 的起始地址(Start)为 0x08000000,大小(Size)为 0x80000,即从 0x08000000 开始的 512K 空间为我们的程序存储区。
APP程序将设置起始地址(Start)为 0x08010000,即偏移量为 0x10000(64K 字节,即留给 BootLoader 的空间),因而,留给 APP 用的 FLASH 空间(Size)只有 0x80000- 0x10000=0x70000(448K 字节)大小了。设置好 Start 和 Size,就完成 APP 程序的起始地址设置。IRAM 是内存的地址,APP 可以独占这些内存,不需要修改。
注意:需要确保 APP 起始地址在 Bootloader 程序结束位置之后,并且偏移量为 0x200 的倍数即可
/**
* @brief 设置中断向量表偏移地址
* @param baseaddr : 基址
* @param offset : 偏移量
* @retval 无
*/
void sys_nvic_set_vector_table(uint32_t baseaddr, uint32_t offset)
{
/* 设置 NVIC 的向量表偏移寄存器,VTOR 低 9 位保留,即[8:0]保留 */
SCB->VTOR = baseaddr | (offset & (uint32_t)0xFFFFFE00);
}
该函数用于设置中断向量偏移,baseaddr 为基地址(即 APP 程序首地址),Offset 为偏移量,需要根据自己的实际情况进行设置。比如 FLASH APP 设置中断向量表偏移量为0x10000,调用情况如下:
/* 设置中断向量表偏移量为 0x10000 */
sys_nvic_set_vector_table(FLASH_BASE, 0x10000);
通过以上两个步骤的设置,我们就可以生成 APP 程序了,只要 APP 程序的 FLASH大小不超过我们的设置即可 MDK 默认生成的文件是.hex 文件,并不方便我们用作 IAP更新,工程直接生成.bin 文件,可以方便进行 IAP 升级。
通过 MDK 自带的格式转换工具 fromelf.exe 来生成 bin 文件。注意:如果用户安装 MDK 在 C 盘的默认路径,那它的位置一般C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe。
其他位置可点击鼠标右键 KEIL 软件 打开文件所在目录 ,然后跳转到上一级文件目录的ARM ->>ARMCC ->>获得绝对路径。
点击Options for Target→User,在 After Build/Rebuild 一栏中,勾选 Run #1,输入转换语句
D:\A_A_ProgramFiles\A_Keil5\ARM\ARMCC\bin\fromelf --bin -o ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\Output\@L.bin ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\MDK-ARM\Can_IAP_003\%L
推荐相对地址,(不同的工程文件地址可能不同,需要根据自己的工程来定,文件路径较深推荐用绝对路径。)
转换语句的组成解释如下:
首先是MDK 自带的格式转换工具 fromelf.exe的绝对路径 + 命令fromelf --bin -o 路径(生成bin文件保存路径)\@L.bin 路径(生.axf的文件路径)%L。
D:\A_A_ProgramFiles\A_Keil5\ARM\ARMCC\bin\fromelf --bin -o ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\Output\@L.bin ..\..\..\..\..\A_ADesktop\Program\Stm32_00X\Can_IAP_003\MDK-ARM\Can_IAP_003\%L
总结APP 程序的生成步骤
以上 3 个步骤,就可以得到一个.bin 的 APP 程序,通过 Bootlader 程序即可实现更新
APP程序中加入文件夹中的.c和.h文件在工程中添加包含路径,并在Main函数最开始部分添加语句
生成工程
/* USER CODE BEGIN 0 */
FDCAN_FilterTypeDef sFilterConfig; //接收过滤器类型定义结构体
/* USER CODE END 0 */
/* USER CODE BEGIN FDCAN1_Init 2 *///配置接收过滤
sFilterConfig.IdType = FDCAN_STANDARD_ID;
// 配置为过滤标准帧
sFilterConfig.FilterIndex = 0;
// 过滤器的索引号 如果有2组标准帧过滤,那么就是通过index区别,0 1 2这种
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
// 过滤方式为范围,即从FilterID1~FilterID2之间的值
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x0000;
sFilterConfig.FilterID2 = 0x07ff;
//标准帧为11位ID,即0x7ff,本例配置为接收所有帧
/*设置接收过滤器,根据FDCAN_FilterTypeDef结构中指定的参数配置FDCAN接收筛选器。*/
if(HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/*配置FDCAN全局过滤器*/
if(HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,FDCAN_REJECT,FDCAN_REJECT,DCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
Error_Handler();
}
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END FDCAN1_Init 2 */
/*发送函数*/
void CAN_Send_Msg(FDCAN_HandleTypeDef hcan,uint32_t can_id,uint8_t tx_buff[])
{
FDCAN_TxHeaderTypeDef TxHeader;
TxHeader.Identifier = can_id; /* 32位ID */
TxHeader.IdType = FDCAN_STANDARD_ID; /* 标准ID */
TxHeader.TxFrameType = FDCAN_DATA_FRAME; /* 数据帧 */
TxHeader.DataLength = FDCAN_DLC_BYTES_8; /* 数据长度 */
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_ON; //用于设置发送是否波特率可变。
TxHeader.FDFormat = FDCAN_CLASSIC_CAN; //普通can还是FDcan
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
//是用于设置发送事件FIFO控制事件是否保存
TxHeader.MessageMarker = 0;
//marker++; //用于设置复制到TX EVENT FIFO的消息Maker,来识别消息状态,范围0到0xFF
HAL_FDCAN_AddMessageToTxFifoQ(&hcan, &TxHeader, tx_buff); //发送
}
void CAN_Receive_Msg(void)
{ /*函数的作用是获取CAN接收FIFO的填充级别。*/
if(HAL_FDCAN_GetRxFifoFillLevel(&hfdcan1,FDCAN_RX_FIFO0) >= 1)
{
/*从Rx FIFO 收取一个 CAN 帧*/
if(HAL_FDCAN_GetRxMessage(&hfdcan1, FDCAN_RX_FIFO0, &CANRec_msg, CANRec_buff) == HAL_OK ) {
CANRecFlag = 1;//接收完成标志位
}
}
}
在数函数中全局变量
uint8_t CANRecFlag ; //CAN接收表示,完成 1
uint8_t CANRec_buff[8]; //CAN接受到的数据位
FDCAN_RxHeaderTypeDef CANRec_msg;
在Main.h中extern
extern uint8_t CANRecFlag ;
extern uint8_t CANRec_buff[8];
extern FDCAN_RxHeaderTypeDef CANRec_msg;
void CAN_Send_Msg(FDCAN_HandleTypeDef hcan,uint32_t can_id,uint8_t tx_buff[]);
void CAN_Receive_Msg(void);
void Power_ON_Start_TX(void);
void Power_ON_Start_TX(void)
{
FDCAN_TxHeaderTypeDef TxHeader;
uint8_t tx_data[8] = {0x20, 0x23, 0x11, 0x09, 0x12, 0x20, 0x00, 0x00};
uint32_t can_id = 0x601;
TxHeader.Identifier = can_id;
TxHeader.IdType = FDCAN_STANDARD_ID;
TxHeader.TxFrameType = FDCAN_DATA_FRAME;
TxHeader.DataLength = FDCAN_DLC_BYTES_8;
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_ON; //用于设置发送是否波特率可变。
TxHeader.FDFormat = FDCAN_CLASSIC_CAN; //普通can还是FDcan
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
//是用于设置发送事件FIFO控制事件是否保存
TxHeader.MessageMarker = 0;
//marker++; //用于设置复制到TX EVENT FIFO的消息Maker,来识别消息状态,范围0到0xFF
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, tx_data);//向 Tx 邮箱中增加一个消息,并且激活对应的传输请求
}
在#include "stm32g4xx_it.c文件中添加CAN头文件和TIM2中断函数中添加接收函数
#include "fdcan.h"
int TickMs = 0;
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
/* USER CODE END TIM2_IRQn 0 */
HAL_TIM_IRQHandler(&htim2);
/* USER CODE BEGIN TIM2_IRQn 1 */
CAN_Receive_Msg();//FDCAN接收函数
TickMs++;//定时器计数
/* USER CODE END TIM2_IRQn 1 */
}
主函数Main.c中while中添加如下代码
//while前
HAL_TIM_Base_Start_IT(&htim2); //定时器 中断
Power_ON_Start_TX(); //上电后发送一串数据
//While中
if (CANRecFlag)
{
CANRecFlag = 0;
CAN_Send_Msg(hfdcan1,CANRec_msg.Identifier,CANRec_buff);
//接收到数据网CAN上位机反馈一下
}
上电程序运行,会发送一组数据 20 23 11 09 12 20 00 00,然后上位机发送数据后会将发送的数据进行反馈给上位机,表示数据接收成功。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) /* 如果是串口1 */
{
if ( UART_RX_Cont < USART1_MAX_REC_LENGTH)
{
UART1_Rx_Buff[UART_RX_Cont] = UART1_Rx_Temp[0];
UART_RX_Cont++;
}
/* 接收完成后在开启写一次中断接收 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)UART1_Rx_Temp, RXBUFF_ERSIZE);
}
}
串口相关定义
/* USER CODE BEGIN 0 */
uint16_t UART1_Rx_Sta=0; /* 接收状态 */
uint8_t UART1_Rx_Buff[USART1_MAX_REC_LENGTH]; /* HAL库使用的串口接收缓冲 */
uint8_t UART1_Rx_Temp[RXBUFF_ERSIZE]; /* 接收字节 */
uint32_t UART_RX_Cont = 0;
UART_HandleTypeDef huart1;
/* USER CODE END 0 */
usart.h声明
#include "stdio.h"
int fputc(int ch, FILE *f);
#define RXBUFF_ERSIZE 1 /* 缓存大小 */
#define USART1_MAX_REC_LENGTH 64*1024 /* 定义最大接收字节数 200 */
extern uint8_t UART1_Rx_Buff[USART1_MAX_REC_LENGTH];
/* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t UART1_Rx_Sta; /* 接收状态标记 */
extern uint8_t UART1_Rx_Temp[RXBUFF_ERSIZE]; /* HAL库USART接收Buffer */
extern uint32_t UART_RX_Cont ; /* 接收到的app代码长度 */
Main.c
uint32_t lastcount = 0; /* 上一次串口接收数据值 */
uint32_t applenth = 0; /* 接收到的app代码长度 */
printf("STM32_CAN_UASRT_IAP_End\n"); //程序串口运行发送STM32_CAN_UASRT_IAP_End
在usart.c中MX_USART1_UART_Init函数中,打开中断接收
/* USER CODE BEGIN USART1_Init 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)UART1_Rx_Temp, RXBUFF_ERSIZE);//开启中断
/* USER CODE END USART1_Init 2 */
int fputc(int ch, FILE *f)
{
uint8_t temp[1] = {ch};
HAL_UART_Transmit(&huart1, temp, 1, 0xffff);
return ch;
}
/*----------串口接收1--------------------------------------*/
if (UART_RX_Cont)
{
if (lastcount == UART_RX_Cont) /* 新周期内,没有收到任何数据,认为本次数据接收完成 */
{
applenth = UART_RX_Cont;
lastcount = 0;
UART_RX_Cont = 0;
for(int i = 0; i<applenth;i++)
{
printf("UART1_Rx_Buff[%d] == %x \r\n",i,UART1_Rx_Buff[i]);
}
printf("用户程序接收完成!\r\n");
printf("代码长度:%dBytes\r\n", applenth);
}
else lastcount = UART_RX_Cont;
}
上电发送STM32_CAN_UASRT_IAP_End,通过上位机发送数据,将数据每一位转化成ascll码后16进制进行打印,发送的bin文件的所有数据都存放在UART1_Rx_Buff的数组中
FLASH写入数据是引用封装后的函数,在文件夹的文件中的文件可以直接添加到工程中,并添加头文件的路径。使用时直接引用相关函数。
跳转函数同样也是引用的封装好的函数,在跳转函数中做了一些改进,可以直接添加工程中直接进行使用
注意:由于是因引用的封装后的库,建议将
中的所有源文件(.c)和头文件(.h)同时添加到工程文件中。
到此部分Bootloader 程序的CAN通讯和usart通讯已经完成(接收升级固件包数据)
下面程序的升级逻辑介绍
升级程序流程图如下
函数声明部分
/* USER CODE BEGIN Includes */
#include "sys.h"
#include "delay.h"
#include "iap.h"
uint8_t CANRecFlag ; //CAN接收表示,完成 1
uint8_t CANRec_buff[8]; //CAN接受到的数据位
FDCAN_RxHeaderTypeDef CANRec_msg;
#define APP_LEN_MAX 60*1024
uint8_t APP_RX_BUF[APP_LEN_MAX] = {0}; //接收缓冲,最大64kb.
uint32_t APPLength = 0; //升级程序数据长度
extern int TickMs;
/* USER CODE END Includes */
Bootloader程序升级或跳转选择while
/* USER CODE BEGIN 2 */
//delay_init(170); //若有报错打开注释
uint32_t lastcount = 0; /* 上一次串口接收数据值 */
uint32_t applenth = 0; /* 接收到的app代码长度 */
HAL_TIM_Base_Start_IT(&htim2); //定时器 中断
Power_ON_Start_TX(); //上电后发送一串数据
printf("STM32_IAP_02\n");
while(1)
{
if(TickMs>5000)//5秒后跳转
{
if (((*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000)
/* 判断FLASH里面是否有APP,有的话执行 */
{
printf("开始执行FLASH用户代码!!\r\n\r\n");
printf("RUN addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR )));
iap_load_app(FLASH_APP1_ADDR); /* 执行FLASH APP代码 */ }else{
printf("非APP———FLASH应用程序,无法执行!\r\n");
printf("进入主程序等待升级!!\r\n");
break;
}
}
if(CANRecFlag)//升级命令CAN口有接收任意消息
{
CANRecFlag = 0;
printf("进入主程序等待升级!!\r\n");
break;
}
}
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/*-----------串口接收数据处理--------------------------------------*/
if (UART_RX_Cont)
{
if (lastcount == UART_RX_Cont) /* 新周期内,没有收到任何数据,认为本次数据接收完成 */
{
applenth = UART_RX_Cont;
lastcount = 0;
UART_RX_Cont = 0;
// for(int i = 0; i<applenth;i++)
// {
// printf("UART1_Rx_Buff[%d] == %x \r\n",i,UART1_Rx_Buff[i]);
// }
printf("用户程序接收完成!\r\n");
printf("代码长度:%dBytes\r\n", applenth);
}
else lastcount = UART_RX_Cont;
}
/*----------CAN升级命令------------------------------*/
if (CANRecFlag)
{
CANRecFlag = 0;
// CAN_Send_Msg(hfdcan1,CANRec_msg.Identifier,CANRec_buff);
//接收到数据网CAN上位机反馈一下
if (CANRec_msg.Identifier ==0x123) //升级指令包
{
if (applenth < APP_LEN_MAX)
{
for(int i=0 ;i < 8;i++)
{
APP_RX_BUF[applenth] = CANRec_buff[i];
applenth++;
}
}else{
printf("缓冲区容量不足...\r\n");
}
}
if (CANRec_msg.Identifier ==0x111)
{
printf("APP_RX_BUF[%d] \n",applenth);//APPLength
// for(int i = 0; i<applenth;i++)
// {
// printf("APP_RX_BUF[%d] = %x\n",i,APP_RX_BUF[i]);
// }
if(applenth)
{
/*-------------串口升级----------------------------------------------------------------------*/ if (((*(volatile uint32_t *)(UART1_Rx_Buff + 4)) & 0xFF000000) == 0x08000000)
/* 判断是否为0X08XXXXXX */
{
printf(" \n串口升级开始更新固件...\r\n");
printf("updata addr :%x \r\n",(*(volatile uint32_t *)(UART1_Rx_Buff + 4)) & 0xFF000000); iap_write_appbin(FLASH_APP1_ADDR, UART1_Rx_Buff, applenth);/* 更新FLASH代码 */ printf("UPdata addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR ))); printf("\n!!!串口升级固件更新完成!!!\r\n");
}
/*---------------CAN升级-------------------------------------------------------------------*/
else if (((*(volatile uint32_t *)(APP_RX_BUF + 4)) & 0xFF000000) == 0x08000000)
/* 判断是否为0X08XXXXXX */
{
printf(" \nCAN升级开始更新固件...\r\n");
printf("updata addr :%x \r\n",(*(volatile uint32_t *)(APP_RX_BUF + 4)) & 0xFF000000); iap_write_appbin(FLASH_APP1_ADDR, APP_RX_BUF, applenth); /* 更新FLASH代码 */ printf("UPdata addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR ))); printf("\n!!!固件更新完成!!!\r\n");
}else{
printf("\n!!!非FLASH应用程序!!!\r\n");
}
}else{
printf("没有可以更新的固件!\r\n");
}
}
if (CANRec_msg.Identifier == 0x222) /* KEY1按键按下, 运行FLASH APP代码 */
{
printf("flash Run addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000);
if (((*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000) /* 判断FLASH里面是否有APP,有的话执行 */
{
printf("开始执行FLASH用户代码!!\r\n\r\n");
printf("RUN addr :%x \r\n",(*(volatile uint32_t *)(FLASH_APP1_ADDR )));
iap_load_app(FLASH_APP1_ADDR);/* 执行FLASH APP代码 */ }else {
printf("没有可以运行的固件!\r\n");
}
}
}
/*-------------------Bootloader LED反馈--------------------------------------------------*/
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_1|GPIO_PIN_0);
HAL_Delay(500);
}
/* USER CODE END 3 */
通过Ecantools发送更新,升级指令实现升级跳转程序
文章浏览阅读5.8k次,点赞12次,收藏34次。前端系列——vue2+高德地图web端开发(poi搜索)前言基础什么是poi搜索1. 输入提示结合poi搜索官方代码步骤1.进行plugins插件注册2.data中编写placeSearch变量3.在methods中编写select函数4.在initMap函数中增加poi搜索处理逻辑解释2.直接进行poi搜索步骤1.在Search.vue中我们把接收到的值传到MapContainer.vue中2.在MapContainer.vue中接收3.编写watch进行监听完整代码(MapContainer.vue)结_前端开发的时候地图搜索一般用什么
文章浏览阅读600次。请求XML示例<xml> <appid>wx2421b1c4370ec43b</appid> <mch_id>10000100</mch_id> <nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str> <out_refund_no>1415701182</out_refund_no> <out_trad_微信退款签名生成
文章浏览阅读7.6k次。js delete javascript_js delete
文章浏览阅读857次。点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析..._writerequest.refreshpolicy.immediate save
文章浏览阅读5.1k次。分析:异常信息提示错误出在C3P0连接池,检查applicationContext.xml后发现并没有什么错误,于是我们可以锁定问题出在 jar 库依赖,问题的出现可能就是 jar 库冲突,选择性地删掉一个就OK了。严重: 在路径为[/SSH02-SpringWebPojo]的上下文中,servlet[servlets.RegisterServlet]的Servlet.service()引发异常org.springframework.beans.factory.BeanCreationException_error creating bean with name 'datasource' defined in class path resource [c
文章浏览阅读218次。kafka一种极端的的消息丢失。_kafka auto.offset.reset 找不到配置
文章浏览阅读1.7w次,点赞10次,收藏39次。1.如果图片有扩展名,可以直接跳过直接看第2步,没有就继续往下看在文件扩展名前面的方框打上对号,方便判断要操作的图片是png还是jpg2.这时图片显示出了扩展名例如:001.png就是有扩展名 001就是没有扩展名3.打开excel表格插入公式="ren “&A1&”.png “&B1&”.png"- 重点解说一下="ren “&A1&”.png “&B1&”.png"这个公式A1就是重命名之前的图片名称B1就是想改成的那_用excel批量重命名图片
文章浏览阅读437次。(3)将下载到的M4S文件拖到.bat文件上即可作为参数执行,转为MP3需要一个音频文件,转为MP4需要音频文件和视频文件两个一起拖到.bat上。(1)上面的代码两段set是用于修改下载到的文件的后缀名,从m4s改为MP3或者MP4。的是延迟变量,~n是取出文件的文件名,~x是取出文件的扩展名。(2)修改文件后缀名,将.txt改为.bat。(1)创建文本文件,并将需要代码复制进去。_ffmpeg批处理脚本
文章浏览阅读130次。从Gist上发现的,挺全的IMPORTANT! Remember to check out the wiki page at https://github.com/bebraw/jswiki/wiki/Game-Engines for the most up to date version. There's also a "notes" column in the table but it s...
文章浏览阅读255次。1、答案及运行结果:递归(逆推):直接或者间接地调用自身递归算法解决问题的特点:(1) 递归就是在过程或函数里调用自身。(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一..._小易准备去魔法王国采购魔法神器购买魔法神器需要使用魔法币但是小易现在一枚魔
文章浏览阅读2k次。HashCode是在Java中用于获取对象的唯一标识符的方法。它是根据对象的内容生成的一个整数值。对象的hashCode()方法被调用时,它返回的是对象的哈希码。哈希码可以用于在哈希表等数据结构中快速定位对象。在Java中,hashCode()方法是被Object类定义的,所有的对象都可以调用该方法。默认情况下,hashCode()方法返回的是对象的内存地址的哈希码表示。通常情况下,如果两个对象的equals()方法返回true,那么它们的hashCode()方法应该返回相同的值。_hashcode
文章浏览阅读231次,点赞5次,收藏6次。其次,通过智能化的学习算法,软件能够根据用户的学习进度和记忆能力,提供定制化的学习计划和复习提醒,从而确保学习效果的最大化。此外,软件中的互动元素和游戏化设计,增加了学习的趣味性,激发了用户的学习动力。最后,随着用户词汇量的增加,他们将更加自信地运用英语进行沟通和表达,这不仅有助于个人职业发展,也促进了跨文化交流,增进了不同文化之间的理解和尊重。因此,英语单词学习软件APP的开发和应用,不仅是技术进步的体现,更是推动语言学习和文化交流的重要力量。随着科技的发展和移动设备的普及,学习英语的方式也在不断进化。