根据//a[@target=“_blank“ a标签查找taget属性是_blank的值

本文详细讲解如何使用XPath表达式来定位HTML中`<a>`标签,特别是那些`target='_blank'`属性的链接,这些链接通常会在新窗口中打开。通过这个教程,读者将学会如何解析和提取这样的链接信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[root@yyjk sbin]# cat s1.pl 
use  LWP::UserAgent;
use DBI;  
use POSIX;
use Data::Dumper;
use HTML::TreeBuilder;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
$ua->agent("Mozilla/8.0");
 
 
  use HTML::TreeBuilder::XPath;
   $tree= HTML::TreeBuilder::XPath->new;
  $tree->parse_file( "fh2.html");
my @title=  $tree->findvalues('//a[@target="_blank"]');
print @title;
[root@yyjk sbin]# 
[root@yyjk sbin]# 
[root@yyjk sbin]# cat fh2.html 



<div class="daohang-kuai">
<div class="daohang-org"><span>合规部</span></div>
<div class="daohang-links"><a href="http://xxx3.246.2:7001/newaml?tgt=TGT-518341-bAJd4aeK0sZnlJQC3ET2U2VPUCSDmNlMm3dq0VITiIrOQUx6ik-cas01.example.org" target="_blank">反洗钱系统</a>
以此为模板,我想要一个单通道adc+dma来采集电压,看看我先面这个adc模块还可以怎么改#include "./adc/bsp_adc.h" #include "bsp_GeneralTim.h" #include "stm32f4xx_adc.h" __IO uint16_t ADC_ConvertedValue[1]={0}; volatile uint8_t current_buffer = 0; extern volatile uint32_t time; static void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; /*=====================通道1======================*/ // 使能 GPIO 时钟 RCC_AHB1PeriphClockCmd(ADC_GPIO_CLK1,ENABLE); // 配置 IO GPIO_InitStructure.GPIO_Pin = ADC_GPIO_PIN1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //不上拉不下拉 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(ADC_GPIO_PORT1, &GPIO_InitStructure); } void ADC_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 1. 使能 DMA 时钟 RCC_AHB1PeriphClockCmd(ADC_DMA_CLK, ENABLE); // 2. 配置 DMA 参数 DMA_InitStructure.DMA_Channel = ADC_DMA_CHANNEL; // DMA 通道 0 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedValue; // 目标地址:内存缓冲区 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 修正外设地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; // 外设到内存 DMA_InitStructure.DMA_BufferSize = 1; // 缓冲区大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; // 单通道禁用地址递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据大小:半字(16位) DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 内存数据大小:半字(16位) DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 高优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // 禁用 FIFO 模式 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO 阈 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 内存突发传输:单次 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 外设突发传输:单次 // 3. 初始化 DMA DMA_Init(ADC_DMA_STREAM, &DMA_InitStructure); // 4. 使能 DMA 中断(传输完成、传输错误) DMA_ITConfig(ADC_DMA_STREAM, DMA_IT_TC | DMA_IT_TE | DMA_IT_HT, ENABLE); // 5. 使能 DMA 流 DMA_Cmd(ADC_DMA_STREAM, ENABLE); } void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; // 1. 使能 ADC 时钟 RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE); // 2. 配置 ADC 通用参数 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; // ADC 时钟分频:PCLK2/4 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1 ; // DMA 访问模式 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; // 采样延迟 ADC_CommonInit(&ADC_CommonInitStructure); // 3. 配置 ADC 参数 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // 12位分辨率 ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 扫描模式使能 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // 软件触发 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; // 任意 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐 ADC_InitStructure.ADC_NbrOfConversion = 1 ; // 转换通道数 ADC_Init(ADC_, &ADC_InitStructure); // 4. 配置 ADC 通道(通道4,PA4) ADC_RegularChannelConfig(ADC_, ADC_Channel_4, 1, ADC_SampleTime_84Cycles); // 5. 使能 ADC DMA ADC_DMACmd(ADC_, ENABLE); // 6. 使能 ADC ADC_Cmd(ADC_, ENABLE); // 7. 启动 ADC 转换 ADC_SoftwareStartConv(ADC_); } static void ADC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void Adc_Init(void) { ADC_GPIO_Config(); ADC_DMA_Config(); ADC_Config(); ADC_NVIC_Config(); } //float ADC_ReadVoltage(void) // { // // static uint8_t initialized = 0; // if(!initialized) // { // Adc_Init(); // initialized = 1; // } // // // // 直接读取全局数组 // return ADC_ConvertedValue[0] * 3.3f /4096; //} 而且按下面这样可以读到正确的adc吗 #include "stm32f4xx_it.h" #include "oled.h" #include <math.h> #include "./adc/bsp_adc.h" #include "pid.h" #include "bsp_GeneralTim.h" float Taget=12; extern uint16_t ADC_ConvertedValue[RHEOSTAT_NOFCHANEL]; extern float voltage1; extern float pid_out; extern float Vout_actual; extern uint16_t TIM1_Impluse;//高级定时器占空比 volatile uint8_t adc_data_ready = 0; volatile uint8_t tim_update_flag=0 ; volatile uint32_t last_adc_value = 0; extern volatile uint32_t time; extern float six; // 在DMA中断中仅设置标志 void DMA2_Stream0_IRQHandler(void) { if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) { adc_data_ready = 1; // 仅设置标志 DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0); } } void TIM1_UP_TIM10_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_Update)) { if(adc_data_ready) { // 修正ADC转换公式 Vout_actual = ADC_ConvertedValue[0] * 3.3f / 4096.0f; adc_data_ready = 0; // // PID计算 pid_out = pid_control(2.6, 1.5, 0.1, 12, Vout_actual); TIM1_Impluse=pid_out*84; // 限幅保护 if(pid_out > TIM1->ARR) TIM1_Impluse = TIM1->ARR; // 更新PWM TIM1->CCR2 = TIM1_Impluse; six = 1; // 存储实际设置的PWM } TIM_ClearITPendingBit(TIM1, TIM_IT_Update); } } void NMI_Handler(void) { } void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) {} } void MemManage_Handler(void) { /* Go to infinite loop when Memory Manage exception occurs */ while (1) {} } void BusFault_Handler(void) { /* Go to infinite loop when Bus Fault exception occurs */ while (1) {} } void UsageFault_Handler(void) { /* Go to infinite loop when Usage Fault exception occurs */ while (1) {} } void DebugMon_Handler(void) { } void SVC_Handler(void) { } void PendSV_Handler(void) { } void SysTick_Handler(void) { }
最新发布
07-14
<think>我们有一个单通道ADC(通道4,PA4)使用DMA进行数据采集,并且通过定时器中断来触发数据处理和PID控制。 主要问题:在定时器中断中处理ADC数据并更新PWM输出,但存在一些潜在问题。 分析代码: 1. ADC配置:独立模式,单通道,连续转换,使用DMA循环传输到数组ADC_ConvertedValue[0]。 2. DMA配置:循环模式,传输完成中断使能,每次传输一个数据(半字)。 3. 中断处理: - DMA传输完成中断:设置标志adc_data_ready=1。 - 定时器更新中断:检查adc_data_ready,若为1则读取ADC,计算电压,进行PID计算,更新PWM占空比。 潜在问题: 1. 数据竞争:ADC_ConvertedValue[0]在DMA传输中可能被更新,而同时被定时器中断读取。由于DMA是循环传输,且只有一个数据,所以实际上每次DMA传输完成都会覆盖这个。但是,因为DMA传输是连续的,而读取是在定时器中断中,所以存在读取时DMA正在写入的风险(尽管是32位操作,在Cortex-M4上是原子的,但最好避免这种风险)。 2. 采样率与处理频率:定时器中断频率决定了处理ADC数据的频率。如果定时器中断频率过高,而ADC采样率跟不上,那么adc_data_ready可能为0,导致没有新数据被处理。相反,如果ADC采样率高于定时器中断频率,那么可能会丢失一些样本(因为DMA缓冲区只有一个元素,新数据会覆盖旧数据)。 3. 单元素缓冲区:DMA缓冲区只有一个元素,这意味着每次转换完成就会触发DMA中断,导致中断过于频繁(每个样本都触发中断),增加系统负担。 4. PID计算频率:PID控制频率等于定时器中断频率,但要注意PID参数需要根据实际控制周期调整。 改进建议: 1. 使用更大的DMA缓冲区,并利用半传输中断(Half Transfer)和全传输中断(Transfer Complete)实现双缓冲,减少中断次数,并避免数据竞争。 2. 或者,如果只需要在固定周期内处理一个样本,可以不用DMA中断,而是直接在定时器中断中读取ADC(但这样会丢失DMA的意义,因为需要CPU参与)。另一种做法是使用定时器触发ADC采样,然后由DMA传输,这样采样时刻是精确的。 3. 调整定时器中断频率与ADC采样率匹配,避免数据丢失或处理空数据。 当前代码中,ADC配置为连续转换,所以ADC会以最大速度采样(由ADC时钟和采样周期决定)。而DMA会不断传输数据到ADC_ConvertedValue[0](每次覆盖)。在定时器中断中,我们检查DMA传输完成标志(adc_data_ready)来读取最新。 如何确保读到的是最新? 因为DMA传输完成中断在每次转换完成后都会触发,所以adc_data_ready会被频繁置1。在定时器中断中,我们读取后将其清零。这样,在两次定时器中断之间可能发生了多次ADC转换,但每次读取的只是最近一次转换的(因为DMA不断覆盖,所以数组里总是最新的)。这样,我们实际上是以定时器中断频率进行PID控制,而ADC采样率是独立的(可能更高)。这样处理不会丢失最新的数据,但中间的数据会被跳过。 因此,当前设计在以下情况下是可行的: - 我们只关心最新采样(例如,系统响应要求不高,或者ADC采样率远高于控制频率)。 - 中断嵌套不会导致问题(DMA中断可以打断定时器中断,但这里没有共享资源的复杂竞争)。 但是,频繁的DMA中断(每个ADC样本一次)会增加系统负担。如果ADC采样率是100KHz,那么DMA中断频率也是100KHz,这显然是不可接受的。 因此,改进方向是:去掉DMA中断,直接读取ADC(在定时器中断中读取,但这样会浪费DMA),或者使用更大的缓冲区,降低中断频率。 修改方案: 方案1:不使用DMA中断,在定时器中断中直接读取ADC(适用于单通道,且控制频率不高的情况): - 禁用DMA中断,只使用DMA循环传输,但定时器中断中直接读取ADC_ConvertedValue[0](因为DMA会不断更新这个,所以读取的是最新)。 - 这样就不需要DMA中断了,也就没有了adc_data_ready标志。在定时器中断中直接读取ADC并计算电压,然后进行PID计算。 方案2:使用双缓冲(两个元素的DMA缓冲区),在DMA传输完成中断中处理数据(但这样处理频率由ADC采样率决定,而不是由定时器决定)。 根据当前需求(定时器周期控制),方案1更简单且符合当前架构。 修改步骤(方案1): 1. 在ADC和DMA配置中,禁用DMA传输完成中断(去掉DMA_ITConfig中的DMA_IT_TC)。 2. 去掉DMA2_Stream0_IRQHandler中断服务函数,或者至少不启用TC中断。 3. 在定时器中断中,直接读取ADC_ConvertedValue[0](不需要等待中断标志)。 4. 注意:由于DMA在后台不断更新ADC_ConvertedValue[0],所以读取的是最近一次转换的结果。 方案1代码调整: 修改ADC_DMA_Config函数: - 去掉DMA_ITConfig(ADC_DMA_STREAM, DMA_IT_TC | DMA_IT_TE | DMA_IT_HT, ENABLE); - 或者,如果保留中断,但不在中断里做任何事情,则可以在NVIC中禁用该中断。 修改定时器中断处理函数(去掉adc_data_ready标志): ```c void TIM1_UP_TIM10_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_Update)) { // 直接读取最新的ADC(因为DMA在后台不断更新) Vout_actual = ADC_ConvertedValue[0] * 3.3f / 4096.0f; // PID计算 pid_out = pid_control(2.6, 1.5, 0.1, 12, Vout_actual); TIM1_Impluse = pid_out * 84; // 假设84是ARR的?注意:这里pid_out应该是一个0-1之间的数吗? // 限幅保护 if (TIM1_Impluse > TIM1->ARR) TIM1_Impluse = TIM1->ARR; // 更新PWM TIM1->CCR2 = TIM1_Impluse; six = TIM1_Impluse; // 记录实际设置的PWM TIM_ClearITPendingBit(TIM1, TIM_IT_Update); } } ``` 方案2:使用大缓冲区,并在DMA中断中处理数据(但这样处理频率由ADC采样率决定,可能不符合定时器控制频率的要求),所以不推荐。 因此,我们采用方案1。 另外,注意:在当前的ADC配置中,ADC是连续转换的,所以它会一直转换。而DMA会循环传输。这样,ADC_ConvertedValue[0]总是最新的转换。 但是,我们还需要注意: - 在初始化后,ADC已经开始转换,所以数组里已经有数据了。 - 由于DMA是循环模式,所以不需要在中断中重新启动DMA。 修改后的代码: 1. 在ADC_DMA_Config函数中,注释掉中断配置: ```c void ADC_DMA_Config(void) { // ... 其他代码不变 // 注释掉中断使能 // DMA_ITConfig(ADC_DMA_STREAM, DMA_IT_TC | DMA_IT_TE | DMA_IT_HT, ENABLE); // ... 其他代码不变 } ``` 2. 在NVIC配置中,不要使能DMA中断(或者注释掉ADC_NVIC_Config函数中的相关配置,并不要调用ADC_NVIC_Config)。所以,在Adc_Init中,去掉调用ADC_NVIC_Config。 3. 修改定时器中断函数,如上所示。 4. 删除DMA中断服务函数(或者至少不处理TC中断,因为我们已经禁用了中断)。 5. 注意:由于DMA中断被禁用,DMA传输完成中断不会发生,因此adc_data_ready标志永远不会被设置。所以定时器中断中直接读取ADC。 这样修改后,系统负担降低(没有频繁的DMA中断),而控制频率由定时器中断频率决定。 另外,关于ADC采样时间的计算: - 当前配置:ADC时钟为APB2时钟(假设84MHz)除以4,即21MHz。 - 采样时间:84个周期,转换时间=采样时间+12个周期(固定)=84+12=96周期。 - 转换时间:96/21e6 ≈ 4.57us,即采样率约为218.7KHz(理论,实际可能有微小差别)。 因此,在定时器中断中读取的ADC最多滞后4.57us,这是可以接受的。 最后,关于PID控制: - 注意pid_control函数的参数:目标12,实际Vout_actual(范围0-3.3V?)。这里目标12可能超出了实际电压范围(0-3.3V),需要确认。 - 另外,pid_out乘以84得到TIM1_Impluse,然后赋给CCR2。这里假设TIM1的ARR是84?需要确认。 总结:按方案1修改后,代码更高效,且避免了频繁中断。 修改后的完整代码示例(只列出修改部分): 修改Adc_Init函数(去掉NVIC配置): ```c void Adc_Init(void) { ADC_GPIO_Config(); ADC_DMA_Config(); ADC_Config(); // 注释掉:ADC_NVIC_Config(); // 不再需要DMA中断 } ``` 定时器中断函数修改(如上)。 删除DMA中断服务函数中的TC处理(或者整个函数删除,如果没有其他流中断的话): ```c void DMA2_Stream0_IRQHandler(void) { // 可以保留错误中断处理,或者完全删除这个中断服务函数(如果不再需要) // 因为我们禁用了TC中断,所以这个中断不会发生 } ``` 注意:如果其他代码使用了同一个DMA流的中断,则不能删除整个中断服务函数。 这样修改后,应该能正确读取ADC。 但是,请注意:在定时器中断中,我们读取的是ADC_ConvertedValue[0],这个数组只有一个元素,且DMA不断更新它。所以读取的是最新的ADC转换。 因此,可以正常工作。 如果遇到问题,可以检查: - ADC的GPIO配置是否正确(PA4,模拟输入,无上下拉) - ADC和DMA的时钟是否使能 - 定时器中断是否使能,频率是否合适 - PID参数是否合理 最后,关于定时器中断频率:需要根据系统需求设定。例如,如果控制频率为1kHz,则定时器中断频率应为1kHz(ARR=定时器时钟/1000 -1)。 以上。</think>### 单通道ADC+DMA采集优化方案 您的代码实现了单通道ADC采集并使用DMA传输数据,结合定时器中断进行数据处理和PID控制。以下是关键分析及优化建议: #### 1. 当前代码问题分析 1. **DMA配置问题**: - 缓冲区大小仅为1,导致频繁DMA中断 - 内存地址不自增(单通道不需要) - 缺少ADC校准步骤,影响精度 2. **中断处理问题**: - DMA中断频率过高(每个ADC样本都触发中断) - PID计算放在高优先级定时器中断中,可能影响实时性 3. **数据安全**: - 缺少volatile声明,可能被编译器优化 - 无数据缓冲机制,存在数据覆盖风险 #### 2. 优化方案代码实现 ```c // adc.h #ifndef __ADC_H #define __ADC_H #include "stm32f4xx.h" #define ADC_CHANNEL ADC_Channel_4 #define ADC_GPIO_PORT GPIOA #define ADC_GPIO_PIN GPIO_Pin_4 #define ADC_CLK RCC_APB2Periph_ADC1 #define ADC_DMA_STREAM DMA2_Stream0 #define ADC_DMA_CHANNEL DMA_Channel_0 #define ADC_DMA_CLK RCC_AHB1Periph_DMA2 #define SAMPLE_BUFFER_SIZE 16 // 增加缓冲区大小 extern volatile uint16_t adc_buffer[SAMPLE_BUFFER_SIZE]; extern volatile uint8_t adc_data_ready; void ADC_Config(void); void ADC_DMA_Config(void); void ADC_GPIO_Config(void); void ADC_NVIC_Config(void); void Adc_Init(void); #endif ``` ```c // adc.c #include "adc.h" volatile uint16_t adc_buffer[SAMPLE_BUFFER_SIZE] = {0}; volatile uint8_t adc_data_ready = 0; volatile uint8_t current_buffer = 0; void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = ADC_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(ADC_GPIO_PORT, &GPIO_InitStructure); } void ADC_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(ADC_DMA_CLK, ENABLE); DMA_InitStructure.DMA_Channel = ADC_DMA_CHANNEL; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)adc_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = SAMPLE_BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(ADC_DMA_STREAM, &DMA_InitStructure); // 仅使能半传输和全传输中断 DMA_ITConfig(ADC_DMA_STREAM, DMA_IT_TC | DMA_IT_HT, ENABLE); DMA_Cmd(ADC_DMA_STREAM, ENABLE); } void ADC_Config(void) { ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE); // ADC通用配置 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(&ADC_CommonInitStructure); // ADC初始化 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(ADC1, &ADC_InitStructure); // 配置ADC通道 ADC_RegularChannelConfig(ADC1, ADC_CHANNEL, 1, ADC_SampleTime_84Cycles); // ADC校准 ADC_VoltageRegulatorCmd(ADC1, ENABLE); delay_ms(10); // 短暂延时 ADC_SelectCalibration(ADC1, ADC_Calibration_Single); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1) != RESET); // 使能ADC DMA ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); // 软件触发启动转换 ADC_SoftwareStartConv(ADC1); } void ADC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 低于定时器中断 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void Adc_Init(void) { ADC_GPIO_Config(); ADC_DMA_Config(); ADC_Config(); ADC_NVIC_Config(); } ``` ```c // stm32f4xx_it.c (优化中断处理) #include "stm32f4xx_it.h" #include "adc.h" #include "pid.h" extern volatile uint8_t adc_data_ready; extern volatile uint16_t adc_buffer[SAMPLE_BUFFER_SIZE]; extern volatile uint8_t current_buffer; // DMA中断处理 void DMA2_Stream0_IRQHandler(void) { if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) { // 全缓冲区数据就绪 adc_data_ready = 1; DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0); } else if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0)) { // 半缓冲区数据就绪 adc_data_ready = 1; DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0); } } // 定时器中断处理(低优先级) void TIM1_UP_TIM10_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_Update)) { if(adc_data_ready) { // 计算平均减少噪声 uint32_t sum = 0; for(int i = 0; i < SAMPLE_BUFFER_SIZE; i++) { sum += adc_buffer[i]; } float adc_value = sum / (float)SAMPLE_BUFFER_SIZE; // 转换为电压 float Vout_actual = adc_value * 3.3f / 4096.0f; // PID计算(确保在可接受范围内) float pid_out = pid_control(2.6f, 1.5f, 0.1f, 12.0f, Vout_actual); // 限幅保护 pid_out = (pid_out < 0) ? 0 : (pid_out > 100.0f) ? 100.0f : pid_out; // 转换为PWM uint16_t TIM1_Impluse = (uint16_t)(pid_out * 0.01f * TIM1->ARR); // 更新PWM TIM1->CCR2 = TIM1_Impluse; adc_data_ready = 0; } TIM_ClearITPendingBit(TIM1, TIM_IT_Update); } } ``` #### 3. 关键优化说明 1. **增加DMA缓冲区**: - 使用16个元素的缓冲区减少中断频率 - 循环模式自动填充数据 - 半传输(HT)和全传输(TC)中断提高效率 2. **ADC校准**: ```c // 添加校准流程 ADC_VoltageRegulatorCmd(ADC1, ENABLE); delay_ms(10); ADC_SelectCalibration(ADC1, ADC_Calibration_Single); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1) != RESET); ``` 3. **中断优先级管理**: - DMA中断优先级(2)低于定时器中断(1) - 避免高优先级中断阻塞PID计算 4. **数据处理优化**: - 在定时器中断中计算平均降噪 - 添加PID输出限幅保护 - 使用浮点数计算提高精度 5. **电压转换公式修正**: ```c // 正确的电压转换 float voltage = adc_value * 3.3f / 4096.0f; ``` #### 4. 性能计算示例 - ADC时钟:84MHz / 4 = 21MHz - 转换时间:84(采样) + 12(转换) = 96周期 - 单次转换时间:96 / 21MHz ≈ 4.57μs - 最大采样率:1 / 4.57μs ≈ 218.7KSPS - 实际采样率:218.7KSPS / 16 ≈ 13.67KSPS(缓冲区大小) #### 5. 使用示例 ```c // 主循环中读取最新电压 float get_voltage(void) { uint32_t sum = 0; for(int i = 0; i < SAMPLE_BUFFER_SIZE; i++) { sum += adc_buffer[i]; } return (sum / (float)SAMPLE_BUFFER_SIZE) * 3.3f / 4096.0f; } ``` ### 总结 优化后的方案: 1. 增加DMA缓冲区减少中断频率 2. 添加必要的ADC校准流程 3. 优化中断优先级管理 4. 实现数据平均降噪 5. 修正电压转换公式 6. 增强系统稳定性保护 这些改进提高了ADC采样精度,减少了CPU中断负载,并增强了系统鲁棒性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scan724

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值