STM32HAL库学习笔记

目录

定时器

一些小细节

输入捕获计算信号频率

输入捕获计算占空比与频率

使用定时器不改变占空比的同时改变频率的方法

串口

重定向原理

重定向代码

怎么从串口接收到的字符串数据中解析出float型的数据

strchr

sscanf

memset

第一种实现方法

RTC实时时钟

LCD显示

%s占位符与%c占位符的区别

I2C读写操作


定时器

一些小细节

其中HAL_TIM_Base_Start和HAL_TIM_Base_Start_IT这两个函数是不能够同时调用的,

  • ​​HAL_TIM_Base_Start()​​:仅启动定时器,不涉及中断
  • ​​HAL_TIM_Base_Start_IT()​​:启动定时器并开启更新中断
  • ​​不能同时调用​​:二者会冲突操作硬件寄存器和 HAL 库状态机
  • ​​正确做法​​:根据需求选择其中一个函数,或通过 HAL_TIM_ENABLE_IT() 动态管理中断

输入捕获计算信号频率

仅仅用于信号频率的检测就只需要使用定时器的上升沿触发就可以了 , 捕获两个高电平的之间时间 ,用定时器时钟比上时间差就能得到频率 , 需要注意的细节在于+1的操作 , 因为计数都是从零开始的

下面是定时器17的输入捕获配置

 

//主函数里面打开定时器17的输入捕获中断
HAL_TIM_IC_Start_IT(&htim17,TIM_CHANNEL_1);//编写中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM17){//测量频率就是定时器的时钟比上上升沿计数值capture = HAL_TIM_ReadCapturedValue(&htim17 ,TIM_CHANNEL_1);TIM17->CNT = 0;fre_out = main_clock/(capture*((TIM17->PSC)+1));}
}

输入捕获计算占空比与频率

使用定时器3 , combined mode的PWM input on CH1实现

通道2配置为直接模式 , 通道2配置为间接模式(硬件自动配置) , 通道1和通道2分别捕获上升沿和下降沿 , 设置滤波系数都为3.

配置定时器预分频系数为80(从0开始计数) , 主频80M , 所以定时器时钟为1M , 频率的计算就是定时器时钟/上升沿到上升沿的时间

//主函数中打开定时器三的输入捕获中断,两个通道都需要打开
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);//编写中断函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM3){//如果是定时器3的中断period = TIM3->CCR1+1;//硬件自动捕获周期上升沿到上升沿high_time = TIM3->CCR2+1;//硬件自动捕获高电平时间 , 上升沿到下降沿fre_cap = main_clock / (period * (TIM3->PSC + 1));//直接计算频率duty = ((float)high_time / period) * 100;//计算占空比}
}

发现的hal库小细节

	//HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1 | TIM_CHANNEL_2);
//同时打开定时器三的中断是不行的 , 需要像下面这样一个通道一个通道的打开才能正常进入中断HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);

使用定时器不改变占空比的同时改变频率的方法

思路就是直接在定时器中改变定时器的预分频系数 , 这样占空比就能保持不变 , 但是频率会逐渐改变

好像使用__HAL_TIM_SET_PRESCALER(&htim15 ,prescaler);这个函数在中断中去进行改变定时器的预分频系数总是会卡死的 , 改成直接操作寄存器的方式似乎就可以了TIM->PSC = prescaler

定时器中断中不要改变其他定时器的预分频系数 ,不然会程序会直接卡死,测试了几个其他的函数发现只有setprescaler会卡死 , 然后加上先失能全局中断后使能全局中断之后就正常了 , 说明可能修改的定时器的中断函数正在运行 , 但是预分频系数突然被改变了导致的,所以需要先关闭全局中断 , 修改完成之后再使能全局中断 , 也可以直接操作寄存器实现预分频系数的修改 .
也就是加上这两句 , 更加具体的内容可以问Ai

__disable_irq();//失能全局中断

.........

__enable_irq();//使能全局中断

下面是代码 , 还有一些细节需要注意的就是ARR , CCR , PSC都是从零开始的所以需要仔细的调节一下

//按键部分
else if(short_press == 2){//选择if(jiemian_mode == 0){//如果是数据界面__HAL_TIM_ENABLE_IT(&htim15, TIM_IT_UPDATE);//重新使能更新中断
}//实现定时器的使能
就是手动开启定时器的中断 , 中断中关闭定时器的中断//定时器就15实现上升或者下降沿
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//中断实际上是只能有一个的
{//如果是定时器15的中断if(htim->Instance == TIM15){if(fre_mode == 0){//如果现在是低频,需要缓慢变化到高频if(TIM2->PSC >= 40){//40进来之后-最后一次//__disable_irq();//失能所有中断TIM2->PSC-=1;if(TIM2->PSC <= 39){//这里也应该是减1的值fre_mode =1;__HAL_TIM_DISABLE_IT(&htim15,TIM_IT_UPDATE);//完成之后关闭更新中断}//__enable_irq();//使能所有中断}}else if(fre_mode == 1){//如果现在是高频需要变化到低频if(TIM2->PSC <= 78){//78进来再+最后一次//__disable_irq();//失能所有中断TIM2->PSC+=1;if(TIM2->PSC >= 79){//应该是-1的值也就是80-1的值fre_mode =0;__HAL_TIM_DISABLE_IT(&htim15,TIM_IT_UPDATE);//完成之后关闭更新中断}//__enable_irq();//使能所有中断}}}
}

串口

重定向原理

就是更改了printf与scanf的底层函数,从另外的地址进行读写操作

重定向代码

可以直接套用 ,模式配置成最异步最简单的就可以

这个是普通的阻塞发送的串口重定向

/*--------------------串口重定向------------------*/
int fputc(int ch,FILE *f)
{
//采用轮询方式发送1字节数据,超时时间设置为无限等待HAL_UART_Transmit(&huart2,(uint8_t *)&ch,1,HAL_MAX_DELAY);return ch;
}int fgetc(FILE *f)
{uint8_t ch;// 采用轮询方式接收 1字节数据,超时时间设置为无限等待HAL_UART_Receive( &huart2,(uint8_t*)&ch,1, HAL_MAX_DELAY);return ch;
}

使用串口中断进行数据接受的时候使用会使用到

HAL_UART_Receive_IT(&huart2,(uint8_t *)rec_data,num_byte);

这个函数会使能串口中断接受然后我们在串口接受回调函数中接受数据 ,需要注意的点在于这个函数只要接收到1个字节就会触发中断,所以需要进行逐个字节的解析,这是比较麻烦的下面怎么利用串口的空闲中断和DMA进行数据传输

配置界面如下

 

	//主函数中
HAL_UARTEx_ReceiveToIdle_DMA(&huart1 , rx_buffer , sizeof(rx_buffer));
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx , DMA_IT_HT);
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if(huart->Instance == USART1){//如果是串口1的中断HAL_UARTEx_ReceiveToIdle_DMA(&huart1 , rx_buffer ,sizeof(rx_buffer));sscanf(rx_buffer ,"%d-%d",&a,&b);//读取特定字符,强大好使printf("%d-%d\n",a,b);if(rx_buffer[0]=='1'&&rx_buffer[1]=='2'&&rx_buffer[2]=='3'&&rx_buffer[3]=='\n'){light(1,1);}else if(rx_buffer[0]=='3'&&rx_buffer[1]=='2'&&rx_buffer[2]=='1'&&rx_buffer[3]=='\n'){light(1,0);}}__HAL_DMA_DISABLE_IT(&hdma_usart1_rx , DMA_IT_HT);memset(rx_buffer,0,sizeof(rx_buffer));
}void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{if(huart->Instance == USART1){//错误中断HAL_UARTEx_ReceiveToIdle_DMA(&huart1 , rx_buffer ,sizeof(rx_buffer));__HAL_DMA_DISABLE_IT(&hdma_usart1_rx , DMA_IT_HT);memset(rx_buffer,0,sizeof(rx_buffer));}
}

怎么从串口接收到的字符串数据中解析出float型的数据

其实不只是浮点数像整数型等等都是可以的

从串口接收到的字符数据中解析出我们想要的数据总共有三种方法 , 分别是使用ssacnf函数,使用atof函数以及纯手动进行数据解析 ,我将讲解其中的第一和第二种

首先来讲讲C语言中的字符串处理函数

strchr

char *strchr(const char *str, int c)

这个函数用于查找字符串中我们想要的字符 , 其中str是给定字符串 , 其中c这个字符是在str中要搜索的字符串 ,返回值是指向该字符串中第一次出现的字符的指针,不包含时返回空指针

sscanf

sscanf()这个函数与我们常见的scanf函数的区别主要是sscanf()函数从给定的字符串中读取指定字符,而scanf函数从屏幕中读取指定字符 ,他们的函数原型分别如下

Int sscanf( string str, string fmt, mixed var1, mixed var2 ... );//str就是给定字符串,fmt是我们要的格式 ,var是我们指定的格式字符串保存的变量

int scanf( const char *format [,argument]... );//这个不过多讲了

下面的资料来自于百度百科 , 这个函数真的超级强大 ,才注意到

1、一般用法

char buf[512] = ;

sscanf("123456 ", "%s", buf);

printf("%s\n", buf);

结果为:123456

2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。

sscanf("123456 ", "%4s", buf);

printf("%s\n", buf);

结果为:1234

3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。

sscanf("123456 abcdedf", "%[^ ]", buf);

printf("%s\n", buf);

结果为:123456

4. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。

sscanf("123456abcdedfBCDEF", "%[1-9a-z]", buf);

printf("%s\n", buf);

结果为:123456abcdedf

5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。

sscanf("123456abcdedfBCDEF", "%[^A-Z]", buf);

printf("%s\n", buf);

结果为:123456abcdedf

6.给定一个字符串iios/12DDWDFF@122,获取 / 和 @ 之间的字符串,先将 "iios/"过滤掉,再将非'@'的一串内容送到buf中

sscanf("iios/12DDWDFF@122", "%*[^/]/%[^@]", buf);

printf("%s\n", buf);

结果为:12DDWDFF

7.给定一个字符串"hello, world",仅保留"world"。(注意:“,”之后有一空格)

sscanf("hello, world", "%*s%s", buf);

printf("%s\n", buf);

结果为:world  P.S. %*s表示第一个匹配到的%s被过滤掉,即hello,被过滤了,如果没有空格则结果为NULL

memset

memset是C和C++中的内存初始化函数,可以将指定内存区域设置为特定值

void *memset(void *s, int ch, size_t n);//函数原型

void *s是要填充的内存区域的起始地址

int ch指定了要填充到内存的值(实际填充时ch 会被转换为unsigned char 类型)

size_t n 决定了要填充的字节数

第一种实现方法

使用时记得在keil中勾选use micro lib这个选项

strchr+sscanf实现

#include <stdio.h>  // 确保标准库支持char rx_data[] = "k0.2\n";
float value = 0;// 查找'k'的位置并解析
char *key_ptr = strchr(rx_data, 'k');
if (key_ptr != NULL) {if (sscanf(key_ptr + 1, "%f", &value) == 1) { // +1跳过'k'// 成功解析,value = 0.2} else {// 解析失败处理}
}
uint8_t rx_buffer[128];//接受缓存区
uint8_t rx_index = 0;
_Bool rx_complete = 0;
float new_k = 0;
//在这里我们发送的数据是k0.4\n,总共是5个字符
//中断进行数据解析
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{if (huart->Instance == USART2){HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer, 128); //接收完毕后重启串口DMA模式接收数据//数据处理,从字符串中解析数据变成0.4,使用字符函数实现if(rx_buffer[0] == 'k'&& rx_buffer[4] == '\n')//进行{//其中target是指向字符串中第一次出现我们想要字符的指针char* target = strchr((const char *)rx_buffer , 'k');if(target != NULL){if(sscanf((const char*)target + 1,"%f",&new_k) == 1)//解析成功的处理{light(3,1);}	else {//解析失败的处理light(4,1);}}}//HAL_UART_Transmit(&huart2, rx_buffer, Size, 0xffff);   // 将接收到的数据再发出__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT);	   // 手动关闭DMA_IT_HT中断memset(rx_buffer, 0, 128);							   // 清除接收缓存}
}

第二种

使用atof函数

RTC实时时钟

RTC实时时钟就是32内部的一个独立的定时器 ,如果使用外部低速晶振那就是32.768khz,进行预分频,如果是127和255,就是32768/((127+1)*(255+1)) = 1Hz

即便不需要获取日期,也需要添加获取日期的函数,不然时间是不会流动的

	RTC_TimeTypeDef sTime = {0};RTC_DateTypeDef sDate = {0};RTC_AlarmTypeDef sAlarm = {0};//在上面先定义结构体HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN);//获取时间保存到Time结构体HAL_RTC_GetDate(&hrtc,&sDate,RTC_FORMAT_BIN);//获取日期保存到Date结构体

如果想要手动修改RTC闹钟的时间可以自己再写一个函数

void Set_RTC_Alarm(uint8_t hours ,uint8_t minutes ,uint8_t seconds)
{RTC_AlarmTypeDef sAlarm = {0};HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);//弃用旧闹钟//实际测试中好像不需要这两句代码也能正常修改,不知道为什么__HAL_RCC_PWR_CLK_ENABLE();//解除备份域时钟保护HAL_PWR_EnableBkUpAccess();//使能备份域时钟操作sAlarm.AlarmTime.Hours = hours;sAlarm.AlarmTime.Minutes = minutes;sAlarm.AlarmTime.Seconds = seconds;sAlarm.AlarmTime.SubSeconds = 0;sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;//这个接口是弃用的sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;sAlarm.AlarmDateWeekDaySel = RTC_ALARMSUBSECONDMASK_NONE;sAlarm.AlarmDateWeekDay = 1;sAlarm.Alarm = RTC_ALARM_A;HAL_RTC_SetAlarm_IT(&hrtc , &sAlarm,RTC_FORMAT_BIN);HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

LCD显示

主要点在于通过定时器定时中断设置标志为进行闪烁显示,在中断里面写一个标志位 ,不断的翻转这个标志位的状态实现,不同的状态显示不同的内容就能实现闪烁的显示 , 或者下划线什么的 ,这个比较简单了

显示一些不常见字符的方法 例如显示百分号的方法

char baifen = '%';
sprintf(showstring,"     P:%.2f%c    ",duty,baifen);
LCD_DisplayStringLine(Line4,(uint8_t *)showstring);
//这样就能显示百分号了

%s占位符与%c占位符的区别

特性%c%s
操作对象单个字符 (char)字符串(字符数组/指针)
内存操作1字节连续内存直到 \0
输入行为读取任何字符(包括空格)跳过空白符,读到空格停止
是否需要 &需要(scanf 中)不需要(数组名即地址)
输出终止条件无(固定1字符)遇到 \0 停止

 C语言中 , 不要直接写3/8这样你会得到0 , 想要得到浮点数就写3.0/8.0这样计算得到浮点数

I2C读写操作

实际上就是E2PROM的读写操作的实现应当是较为简单的 , 下面给出代码

//0xa0是写地址  
//0xa1是读地址
void eeprom_write(uint8_t addr ,uint8_t dat)
{I2CStart();I2CSendByte(AT24C02_Adress);//写哪个从机I2CWaitAck();//等待应答I2CSendByte(addr);//发送要写地址I2CWaitAck();I2CSendByte(dat);//发送要写的数据I2CWaitAck();I2CStop();HAL_Delay(20);//防止连续写入时出错
}uint8_t eeprom_read(uint8_t addr)
{I2CStart();I2CSendByte(AT24C02_Adress);//写哪个从机I2CWaitAck();//等待应答I2CSendByte(addr);//发送要写地址I2CWaitAck();//等待应答I2CStop();I2CStart();//重新开始是为了将控制权交给从机I2CSendByte(0xa1);//发送要读的地址I2CWaitAck();//等待应答uint8_t data = I2CReceiveByte();//接受数据I2CSendNotAck();//发送不回应,也就是不继续读了I2CStop();//停止return data;
}

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

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

相关文章

Docker 镜像、容器与数据卷的高效管理:最佳实践与自动化脚本20250411

Docker 镜像、容器与数据卷的高效管理&#xff1a;最佳实践与自动化脚本 引言 在现代软件开发中&#xff0c;容器化技术正变得越来越重要。Docker 作为容器化的代表工具&#xff0c;在各大企业中得到了广泛的应用。然而&#xff0c;随着容器化应用的增多&#xff0c;如何高效…

Selenium之Actions事件

鼠标、键盘组合键 在使用selenium的时候&#xff0c;有的时候我们需要鼠标单击、双击、拖动&#xff1b;或者是按下键盘的某个键&#xff0c;松开某个按键&#xff0c;以及组合键的使用&#xff1b;今天我们就来看一看&#xff0c;怎么样实现上面的操作 先把准备工作做好&…

如何在 CentOS 7 系统上以容器方式部署 GitLab,使用 ZeroNews 通过互联网访问 GitLab 私有仓库,进行代码版本发布与更新

第 1 步&#xff1a; 部署 GitLab 容器​ 在开始部署 GitLab 容器之前&#xff0c;您需要创建本地目录来存储 GitLab 数据、配置和日志&#xff1a; #创建本地目录 mkdir -p /opt/docker/gitlab/data mkdir -p /opt/docker/gitlab/config mkdir -p /opt/docker/gitlab/log#gi…

.py文件和.ipynb文件的区别:完整教程

一、概述 Python开发者常用的两种文件格式.py和.ipynb各有特点&#xff0c;本教程将通过对比分析、代码示例和场景说明&#xff0c;帮助开发者全面理解二者的区别与联系。 二、核心区别对比 1. 文件格式本质 特性.ipynb文件.py文件文件类型JSON结构化文档纯文本文件存储内容…

Go 字符串四种拼接方式的性能对比

简介 使用完整的基准测试代码文件&#xff0c;可以直接运行来比较四种字符串拼接方法的性能。 for 索引 的方式 for range 的方式 strings.Join 的方式 strings.Builder 的方式 写一个基准测试文件 echo_bench_test.go package mainimport ("os""stri…

从代码学习深度学习 - Bahdanau注意力 PyTorch版

文章目录 1. 前言为什么选择Bahdanau注意力本文目标与预备知识2. Bahdanau注意力机制概述注意力机制简述加性注意力与乘性注意力对比Bahdanau注意力的数学原理与流程图数学原理流程图可视化与直观理解3. 数据准备与预处理数据集简介数据加载与预处理1. 读取数据集2. 预处理文本…

19【动手学深度学习】卷积层

1. 从全连接到卷积 2. 图像卷积 3. 图形卷积代码 互相关操作 import torch from torch import nn from d2l import torch as d2ldef corr2d(X, K):"""计算2维互相关运算"""h, w K.shapeY torch.zeros((X.shape[0]-h1, X.shape[1]-w 1))for …

Linux xorg-server 解析(一)- 编译安装Debug版本的xorg-server

一:下载代码 1. 配置源,以Ubuntu24.04 为例( /etc/apt/sources.list.d/ubuntu.sources): 2. apt source xserver-xorg-core 二:编译代码 1. sudo apt build-dep ./ 2. DEB_BUILD_OPTIONS="nostrip" DEB_CFLAGS_SET="-g -O0" dpkg-buildpac…

大模型SFT用chat版还是base版 SFT后灾难性遗忘怎么办

大模型SFT用chat版还是base版 进行 SFT 时&#xff0c;基座模型选用 Chat 还是 Base 模型&#xff1f; 选 Base 还是 Chat 模型&#xff0c;首先先熟悉 Base 和 Chat 是两种不同的大模型&#xff0c;它们在训练数据、应用场景和模型特性上有所区别。 在训练数据方面&#xf…

【图像生成之21】融合了Transformer与Diffusion,Meta新作Transfusion实现图像与语言大一统

论文&#xff1a;Transfusion: Predict the Next Token and Diffuse Images with One Multi-Modal Model 地址&#xff1a;https://arxiv.org/abs/2408.11039 类型&#xff1a;理解与生成 Transfusion模型‌是一种将Transformer和Diffusion模型融合的多模态模型&#xff0c;旨…

动态多目标进化算法:基于知识转移和维护功能的动态多目标进化算法(KTM-DMOEA)求解CEC2018(DF1-DF14)

一、KTM-DMOEA介绍 在实际工程和现实生活中&#xff0c;许多优化问题具有动态性和多目标性&#xff0c;即目标函数会随着环境的变化而改变&#xff0c;并且存在多个相互冲突的目标。传统的多目标进化算法在处理这类动态问题时面临着一些挑战&#xff0c;如收敛速度慢、难以跟踪…

部署NFS版StorageClass(存储类)

部署NFS版StorageClass存储类 NFS版PV动态供给StorageClass(存储类)基于NFS实现动态供应下载NFS存储类资源清单部署NFS服务器为StorageClass(存储类)创建所需的RBAC部署nfs-client-provisioner的deployment创建StorageClass使用存储类创建PVC NFS版PV动态供给StorageClass(存储…

Vue使用el-table给每一行数据上面增加一行自定义合并行

// template <template><el-table:data"flattenedData":span-method"objectSpanMethod"borderclass"custom-header-table"style"width: 100%"ref"myTable":height"60vh"><!-- 订单详情列 -->&l…

vue项目使用html2canvas和jspdf将页面导出成PDF文件

一、需求&#xff1a; 页面上某一部分内容需要生成pdf并下载 二、技术方案&#xff1a; 使用html2canvas和jsPDF插件 三、js代码 // 页面导出为pdf格式 import html2Canvas from "html2canvas"; import jsPDF from "jspdf"; import { uploadImg } f…

大模型LLM表格报表分析:markitdown文件转markdown,大模型markdown统计分析

整体流程&#xff1a;用markitdown工具文件转markdown&#xff0c;然后大模型markdown统计分析 markitdown https://github.com/microsoft/markitdown 在线体验&#xff1a;https://huggingface.co/spaces/AlirezaF138/Markitdown 安装&#xff1a; pip install markitdown…

Linux 第二讲 --- 基础指令(二)

前言 这是基础指令的第二部分&#xff0c;但是该部分的讲解会大量使用到基础指令&#xff08;一&#xff09;的内容&#xff0c;为了大家的观感&#xff0c;如果对Linux的一些基本指令不了解的话&#xff0c;可以先看基础指令&#xff08;一&#xff09;&#xff0c;同样的本文…

python格式化字符串漏洞

什么是python格式化字符串漏洞 python中&#xff0c;存在几种格式化字符串的方式&#xff0c;然而当我们使用的方式不正确的时候&#xff0c;即格式化的字符串能够被我们控制时&#xff0c;就会导致一些严重的问题&#xff0c;比如获取敏感信息 python常见的格式化字符串 百…

LLaMA-Factory双卡4090微调DeepSeek-R1-Distill-Qwen-14B医学领域

unsloth单卡4090微调DeepSeek-R1-Distill-Qwen-14B医学领域后&#xff0c;跑通一下多卡微调。 1&#xff0c;准备2卡RTX 4090 2&#xff0c;准备数据集 医学领域 pip install -U huggingface_hub export HF_ENDPOINThttps://hf-mirror.com huggingface-cli download --resum…

React Hooks: useRef,useCallback,useMemo用法详解

1. useRef&#xff08;保存引用值&#xff09; useRef 通常用于保存“不会参与 UI 渲染&#xff0c;但生命周期要长”的对象引用&#xff0c;比如获取 DOM、保存定时器 ID、WebSocket等。 新建useRef.js组件&#xff0c;写入代码&#xff1a; import React, { useRef, useSt…

Spring AI 结构化输出详解

一、Spring AI 结构化输出的定义与核心概念 Spring AI 提供了一种强大的功能&#xff0c;允许开发者将大型语言模型&#xff08;LLM&#xff09;的输出从字符串转换为结构化格式&#xff0c;如 JSON、XML 或 Java 对象。这种结构化输出能力对于依赖可靠解析输出值的下游应用程…