防抖函数(最全 最干净 最好理解)

文章介绍了JavaScript中的防抖(debounce)技术,主要用于优化高频场景如输入查询、验证等,防止重复渲染。通过调整函数执行时机,避免在短时间内多次触发同一操作。文中详细解释了防抖函数的实现,包括处理this问题、事件对象以及如何通过apply方法传递参数。并提供了一个具体的防抖函数实现示例,应用于鼠标移动事件以控制元素内容更新。

1.应用场景

1.input输入框 输入远程查询

2.邮箱,手机号验证,用户名验证

3.resize等高评率场景

2.解决问题

高频场景带来的重复渲染 等问题

多次操作 只在操作结束后再执行操作函数

3.具体实现

3.1this问题(因为settimeout是window的对象 所以函数中this指向window 除非箭头函数)

1.事件函数里的this才指向box

错误示范:return里面的this 与函数体内的this指向 在不同场景中 指向并不相同

解决方案: 可以使用 fun.call(this) 来重定向函数中this的指向 【call 传参是单个】

3.2事件对象

1.arguments 作为函数的关键字,它接收的是这个函数传递的所有实参,包括这个事件对象

因为事件对象是默认传递的参数,因为call只能传递一个参数,所以我们选择使用 fun.apply(this, args)


3.3具体代码实现
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html {
            height: 100%;
        }
        body {
            display: flex;
            height: 100%;
            justify-content: center;
            align-items: center;
        }
        .box {
            width: 100px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            background: #FF9800;
            color: #fff;
        }
    </style>
</head>
<body>
    <div class="box">  0 </div>
    <script>
        const debounce = function (fun, delay) {
            let timeout
            return function() {
                timeout && clearTimeout(timeout)
                // 解决事件函数绑定中this
                let that = this
                // 绑定事件中 事件函数的传递
                let argus = arguments
                timeout = setTimeout(() => {
                  fun.apply(that, argus)  
                }, delay);
            }
        }
        function fun(e) {
            count++
            console.log('>>>>>>>>>>>>', e, this)
            e.target.innerText = count
        }
        let count = 0
        console.log()
        document.querySelectorAll('.box')[0].addEventListener('mousemove', debounce(fun, 300))
    </script>
</body>
</html>

#include "stm32f10x.h" // Device header #include "motor.h" #include "Encoder.h" #include "AD.h" //高级定时器1初始化 //arr:自动重装值。 //psc:时钟预分频数 //这里使用的是定时器1! void TIM3_IRQHandler(void) //中断处理函数,此处为定时器3,10ms中断 { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { Encoder_Get(); //得到编码器读数 guiyi(); adcwork(); weizhi_pid(); pid_1(20-dif); pid_r(20+dif); TIM_ClearITPendingBit(TIM3, TIM_IT_Update);// 清除中断标志 } } void TIM1_PWM_Init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; /* 1. 开启时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); /* 2. 配置 PA8、PA9、PA10、PA11 为复用推挽输出 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* 3. 配置 TIM1 基本定时参数 */ TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分割 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); /* 4. 配置 PWM 模式 */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1 模式 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效 TIM_OC1Init(TIM1, &TIM_OCInitStructure); // CH1 -> PA8 TIM_OC2Init(TIM1, &TIM_OCInitStructure); // CH2 -> PA9 TIM_OC3Init(TIM1, &TIM_OCInitStructure); // CH3 -> PA10 TIM_OC4Init(TIM1, &TIM_OCInitStructure); // CH4 -> PA11 /* 5. 启动定时器 */ TIM_Cmd(TIM1, ENABLE); /* 6. 高级定时器必须开启主输出才能有 PWM 波形 */ TIM_CtrlPWMOutputs(TIM1, ENABLE); } // 定时器3中断初始化函数 void TIM3_ISR_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1. 使能TIM3时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 2. 定时器参数配置 TIM_TimeBaseStructure.TIM_Period = 10000 - 1; // 自动重装载值,定时周期 TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频,假设72MHz时钟,分频720后变100kHz TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分割,不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 3. 配置NVIC中断分组和优先级 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 建议在主程序初始化时调用一次即可 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // TIM3中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断 NVIC_Init(&NVIC_InitStructure); // 4. 使能TIM3更新中断 TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 5. 使能定时器 TIM_Cmd(TIM3, ENABLE); } /************************************************************************** Function: Initialize TIM2 to encoder interface mode Input : none Output : none 函数功能:把TIM2初始化为编码器接口模式 **************************************************************************/ void TIM2_Encoder_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* 1. 使能时钟 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // GPIOA, GPIOB和AFIO时钟 /* 2. 禁用JTAG,释放PA15和PB3 ,将TIM2的引脚重映射到PA15和PB3*/ GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE); /* 3. 配置PA15和PB3为上拉输入 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOB, &GPIO_InitStructure); /* 4. 定时器基本参数配置 */ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频 TIM_TimeBaseStructure.TIM_Period = 65535; // 大计数值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* 5. 编码器接口配置,模式3 */ TIM_EncoderInterfaceConfig( TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising ); /* 6. 输入捕获滤波器 */ TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 10; // 滤波10,防抖 TIM_ICInit(TIM2, &TIM_ICInitStructure); /* 7. 计数器清零并启动 */ TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); } /************************************************************************** Function: Initialize TIM4 to encoder interface mode Input : none Output : none 函数功能:把TIM4初始化为编码器接口模式 **************************************************************************/ void TIM4_Encoder_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* 1. 使能时钟 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//使能定时器4的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB端口时钟 /* 2. 配置PB6和PB7为上拉输入 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入 GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB /* 3. 定时器基本参数配置 */ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler = 0; // 预分频器 TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器自动重装值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;////TIM向上计数 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); /* 4. 编码器接口配置,模式3 */ TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3 /* 5. 输入捕获滤波器 */ TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 10; TIM_ICInit(TIM4, &TIM_ICInitStructure); /* 6. 计数器清零并启动 */ TIM_SetCounter(TIM4,0); TIM_Cmd(TIM4, ENABLE); } 这个是我的timer。c文件,#include "stm32f10x.h" // Device header #include "Encoder.h" #include "AD.h" int err=0; int dif=0; int err_old; float kp= 0.5 ; float kd= 0; void motor_control_L(int pwm_l) { if(pwm_l>=0) { TIM_SetCompare1(TIM1,0); TIM_SetCompare2(TIM1,pwm_l); } else if(pwm_l<0) { TIM_SetCompare1(TIM1,-pwm_l); TIM_SetCompare2(TIM1,0); } } void motor_control_R(int pwm_r) { if(pwm_r>=0) { TIM_SetCompare3(TIM1,0); TIM_SetCompare4(TIM1,pwm_r); } else if(pwm_r<0) { TIM_SetCompare3(TIM1,-pwm_r); TIM_SetCompare4(TIM1,0); } } float kp_1 = 4 ; float ki_1 = 1.6; int pwm_out_1,error_1,error_1_old; void pid_1(int expect_1) { error_1=expect_1-Encoder_Left; pwm_out_1+=kp_1*(error_1-error_1_old)+ki_1 * error_1; error_1_old=error_1; if(pwm_out_1>750) { pwm_out_1=750; } if(pwm_out_1<-750) { pwm_out_1=-750; } motor_control_L(pwm_out_1); } float kp_r = 4 ; float ki_r = 1.6; int pwm_out_r,error_r,error_r_old; void pid_r(int expect_r) { error_r=expect_r-Encoder_Right; pwm_out_r+=kp_r*(error_r-error_r_old)+ki_r * error_r; error_r_old=error_r; if(pwm_out_r>750) { pwm_out_r=750; } if(pwm_out_r<-750) { pwm_out_r=-750; } motor_control_R(pwm_out_r); } void weizhi_pid() { err=adcerror; dif=kp*err+kd*(err-err_old); if(dif>20) { dif=20; } if(dif<-20) { dif=-20; } err_old=err; } 这个是我的motor。c文件#include "stm32f10x.h" // Device header #include "math.h" uint16_t AD_Value[5]; //定义用于存放AD转换结果的全局数组 uint16_t L; // 左侧传感器归一化值 uint16_t L_M; // 左中侧传感器归一化值 uint16_t R_M; // 右中侧传感器归一化值 uint16_t R; // 右侧传感器归一化值 uint16_t ADC_min = 10; // 传感器无信号时的小ADC值(需实测) uint16_t ADC_max = 2000; // 传感器在赛道上的大ADC值(需实测) int32_t fenzi; uint32_t fenmu; int16_t adcerror; // 终输出的转向偏差(范围-30~30) /** * 函 数:AD初始化 * 参 数:无 * 返 回 值:无 */ void AD_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1的时钟 /*设置ADC时钟*/ RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入 /*规则组通道配置*/ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置为通道1 ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置,配置为通道4 ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道6 ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 5, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道7 /*ADC初始化*/ ADC_InitTypeDef ADC_InitStructure; //定义结构体变量 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换,使能,每转换一次规则组序列后立刻开始下一次转换 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定 ADC_InitStructure.ADC_NbrOfChannel = 5; //通道数,为4,扫描规则组的前4个通道 ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1 /*DMA初始化*/ DMA_InitTypeDef DMA_InitStructure; //定义结构体变量 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,给定形参AddrA DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择失能,始终以ADC数据寄存器为源 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器基地址,给定存放AD转换结果的全局数组AD_Value DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据宽度,选择半字,与源数据宽度对应 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能,每次转运后,数组移到下一个位置 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组 DMA_InitStructure.DMA_BufferSize = 5; //转运的数据大小(转运次数),与ADC通道数一致 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //模式,选择循环模式,与ADC的连续转换一致 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等 DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1 /*DMA和ADC使能*/ DMA_Cmd(DMA1_Channel1, ENABLE); //DMA1的通道1使能 ADC_DMACmd(ADC1, ENABLE); //ADC1触发DMA1的信号使能 ADC_Cmd(ADC1, ENABLE); //ADC1使能 /*ADC校准*/ ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准 while (ADC_GetResetCalibrationStatus(ADC1) == SET); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); /*ADC触发*/ ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作 } void guiyi(void) { R = (uint16_t)(((uint32_t)(AD_Value[3] - ADC_min) * 100) / (ADC_max - ADC_min)); R_M = (uint16_t)(((uint32_t)(AD_Value[2] - ADC_min) * 100) / (ADC_max - ADC_min)); L_M = (uint16_t)(((uint32_t)(AD_Value[1] - ADC_min) * 100) / (ADC_max - ADC_min)); L = (uint16_t)(((uint32_t)(AD_Value[0] - ADC_min) * 100) / (ADC_max - ADC_min)); if (R > 100) R = 100; if (R_M > 100) R_M = 100; if (L_M > 100) L_M = 100; if (L > 100) L = 100; } void adcwork(void) { fenzi = 60 *( (L - R) + 3 * (L_M - R_M)); fenmu = (L + R) + 3*(L_M + R_M) + 1; adcerror = (int16_t)(fenzi / fenmu); if (adcerror > 30) { adcerror = 30; } else if (adcerror < -30) { adcerror = -30; } } 这个是我的adc。c文件#include "stm32f10x.h" // Device header #include "timer.h" int Encoder_Left=0; int Encoder_Right=0; /************************************************************************** Function: Read encoder count per unit time Input : TIMX:Timer Output : none 函数功能 :单位时间读取编码器计数 返 回 值 :速度值 **************************************************************************/ int Read_Encoder_L (void) { int Encoder_TIM_L; Encoder_TIM_L=TIM2->CNT; //读取计数 if(Encoder_TIM_L>0xefff)Encoder_TIM_L=Encoder_TIM_L-0xffff; //转化计数值为有方向的值,大于0正转,小于0反转。 //TIM4->CNT范围为0-0xffff,初值为0。 TIM2->CNT=0; //读取完后计数清零 return Encoder_TIM_L; //返回值 } int Read_Encoder_R (void) { int Encoder_TIM_R; Encoder_TIM_R=TIM4->CNT; //读取计数 if(Encoder_TIM_R>0xefff)Encoder_TIM_R=Encoder_TIM_R-0xffff; //转化计数值为有方向的值,大于0正转,小于0反转。 //TIM4->CNT范围为0-0xffff,初值为0。 TIM4->CNT=0; //读取完后计数清零 return Encoder_TIM_R; //返回值 } void Encoder_Get(void) { Encoder_Left=Read_Encoder_L(); //读取左轮编码器的值,前进为正,后退为负 Encoder_Right=Read_Encoder_R(); //读取右轮编码器的值,前进为正,后退为负 } 这个是我的encorder。c文件为什么我的stm32f103c8t6为核心板的电磁循迹智能小车不能循迹,只会乱跑
最新发布
11-12
<think> 首先,用户的问题是:“我想找出以stm32f103c8t6为核心板的电磁循迹智能小车不能循迹、只会乱跑的原因”。意思是他们想找出为什么基于STM32F103C8T6核心板的电磁循迹智能小车无法遵循轨道,而是随机乱跑。 用户提供了三个引用: - [^1]:描述了电磁循迹小车的基本要求,包括20kHz交变磁场、信号放大、电压变化等。 - [^2]:是一个赛后总结,讨论了小车架构、关键模块如电磁传感器、电路、控制逻辑等。 - [^3]:提到了使用STM32F103C8T6的ADC和DMA来读取电感值,以及使用滤波算法和归一化。 我需要构建一个结构清晰的回答,帮助用户逐步解决问题。回答应该基于引用内容,并确保真实可靠。系统指令要求使用中文、正确LaTeX语法、添加引用标识,并在末尾生成相关问题。 关键步骤: 1. **理解问题核心**:小车不能循迹,而是乱跑。原因可能包括硬件问题(如传感器读取错误)、软件问题(如控制算法故障)、或系统集成问题。 2. **参考引用内容**: - 从[^1]:电磁循迹需要处理20kHz信号,放大后0-5V,电感位置影响电压。 - 从[^2]:讨论了硬件部分(电磁传感器、信号处理)、软件部分(滤波、归一化、PID控制)、控制逻辑。关键点包括信号滤波、放大、检波、布局改进。 - 从[^3]:使用STM32的ADC和DMA读取电感值,滤波算法(如归一化),以及调试工具(蓝牙、VOFA+)。 3. **常见原因分析**:基于一般知识和引用,列出可能原因: - **传感器问题**:电磁杆(电感传感器)可能没有正确读取信号。原因:电路故障、布局不当、信号失真。 - **信号处理问题**:放大、滤波、检波电路可能有问题,导致ADC读取的值不准确。 - **软件算法问题**:滤波算法(如归一化)或控制算法(如PID)可能未正确实现或参数不当。 - **控制逻辑问题**:电机驱动或舵机控制逻辑错误,导致小车行为异常。 - **调试和校准问题**:没有正确调试传感器或算法,参数未优化。 - **硬件故障**:如L298N电机驱动模块、直流电机、舵机等故障。 4. **逐步解决思路**:在回答中,提供一个结构化的方法,帮助用户诊断问题: - **检查硬件**:确保传感器电路工作正常,信号放大和滤波正确。 - **验证信号读取**:使用ADC和DMA读取电感值,检查是否在预期范围内(0-5V)。 - **应用滤波和归一化**:确保软件中滤波算法正确实现,电感值被归一化处理。 - **实现控制算法**:检查PID控制逻辑是否合理,参数是否调优。 - **测试和调试**:使用工具如OLED、蓝牙和VOFA+上位机观察数据,调整参数。 5. **引用标识**:在回答中自然地添加引用标识,例如[^1]、[^2]、[^3]。 6. **生成相关问题**:在末尾添加相关问题,以帮助用户进一步探索。 回答结构: - **引言**:确认问题并概述可能原因。 - **详细原因分析**:分点列出可能导致小车乱跑的原因,每个点参考引用内容。 - **诊断步骤**:提供逐步方法检查每个潜在问题。 - **总结**:强调调试和优化的重要性。 - **相关问题**:生成3-5个相关问题。 确保LaTeX正确:行内数学表达式用$...$,独立公式用$$...$$。但在这个问题中,可能不需要数学,除非涉及算法公式。如果需要,可以添加PID公式等。 终,回答用中文。 </think>### STM32F103C8T6电磁循迹智能小车不能循迹、只会乱跑的原因分析 基于您提供的引用内容(特别是[^2]中的硬件和软件架构总结),我为您分析基于STM32F103C8T6核心板的电磁循迹智能小车无法正确循迹、只会乱跑的可能原因。电磁循迹小车依赖电感传感器检测导线产生的交变磁场(20kHz频率,导线电流50mA),通过信号处理和控制算法实现路径跟踪[^1]。乱跑问题通常源于硬件、软件或系统集成故障。下面我将逐步分析原因并提供诊断建议,帮助您定位问题。 --- #### **一、常见原因分析(基于引用内容)** 1. **电磁传感器(电感)信号处理电路故障** - **原因**:电感传感器信号处理电路(包括滤波、放大和检波)可能未正确工作,导致ADC读取值失真或波动。引用[^2]强调信号处理是关键: - **滤波不充分**:20kHz信号可能受噪声干扰(如电机电磁干扰),若未有效滤波(如使用带通滤波器),ADC值会不稳定。 - **放大失真**:放大电路增益不当或电源波动时,信号幅值可能超出0-5V范围([^1]要求不失真),导致位置判断错误。 - **检波失效**:交流信号未正确转为直流电压(用于位置检测),电感接近中心线时电压变化不明显([^1]),小车无法感知轨道位置。 - **诊断方法**: - 用示波器检查电感输出信号:在导线附近,信号应为稳定的20kHz正弦波,幅值变化平滑。 - 测试ADC读取值:通过STM32的DMA传输(如[^3]所述),检查值是否在0-4095范围内(12位ADC)。若值跳变大或饱和(接近0或4095),表明电路问题。 2. **软件算法实现错误** - **原因**:引用[^2][^3]提到滤波算法、归一化和PID控制是实现循迹的核心。常见错误包括: - **滤波算法未应用或错误**:如未使用移动平均滤波或卡尔曼滤波,原始ADC噪声未被抑制,导致位置误判。引用[^3]指出“电感数值滤波”是必要的。 - **归一化算法失效**:电感值需归一化到固定范围(如0-100%)以消除环境干扰[^2]。若未实现或参数错误,小车无法稳定响应赛道变化。 - **PID控制参数不当**:PID算法是控制电机和舵机的核心。比例系数$K_p$、积分时间$T_i$或微分时间$T_d$未调优时,小车易振荡或乱跑。例如,$K_p$过大导致过冲,$K_p$过小导致响应迟钝。 - **赛道特征值响应逻辑缺失**:引用[^2]提到需根据赛道特征(如弯道、直道)调整PID参数。若逻辑未编码,小车在复杂赛道失控。 - **诊断方法**: - 通过调试工具(如[^3]中的VOFA+上位机)绘制电感值和电机输出:观察归一化后数据是否平滑,PID输出是否稳定。 - 简化测试:在直道上手动设置固定PWM输出,检查小车能否直线行驶。若可行,则问题在PID算法。 3. **传感器布局或硬件集成问题** - **原因**:电磁杆布局不当或硬件模块故障: - **电感布局不合理**:引用[^2]强调“磁传感器的布局原理及改进”,若电感间距过大或不对称,中心线检测不灵敏,小车易偏离轨道。 - **电机驱动模块故障**:L298N模块([^2])或电机损坏时,PWM信号无法正确驱动车轮,导致乱转。 - **电源干扰**:电机和传感器共用电源,未隔离时噪声耦合到信号电路,影响ADC精度。 - **舵机校准错误**:模拟舵机([^2])中位未校准,转向角度偏移。 - **诊断方法**: - 检查电感布局:确保电感对称分布在导线两侧,中心间距10-15cm。 - 测试电机驱动:直接给L298N输入PWM信号,观察车轮响应是否同步。 4. **调试和参数优化不足** - **原因**:引用[^2]总结“大道至简...只有适合的才是最好的”,强调参数调试的重要性: - **未使用调试工具**:如未用OLED或VOFA+上位机([^3])实时监视数据,问题难定位。 - **参数随机调整**:PID参数或归一化阈值未系统调优,引用[^2]建议“一点一点逐渐调参”。 - **诊断方法**: - 启用蓝牙模块和VOFA+:实时发送ADC值、归一化值和PID输出,分析数据异常点。 --- #### **二、逐步诊断与解决建议** 按照以下步骤排查问题,确保每一步验证后再继续: 1. **验证硬件电路**: - 用万用表/示波器检查电感信号处理电路:测试滤波后信号是否干净,放大后幅值在0-5V。 - 确保STM32 ADC配置正确:使用DMA传输(如[^3]),采样率与20kHz信号匹配。 2. **检查软件基础**: - 实现简单滤波算法(如移动平均): ```c // 示例代码:移动平均滤波(基于[^3]) #define FILTER_SIZE 5 uint16_t filter_buffer[FILTER_SIZE]; uint16_t moving_average(uint16_t new_value) { static uint8_t index = 0; static uint32_t sum = 0; sum -= filter_buffer[index]; filter_buffer[index] = new_value; sum += new_value; index = (index + 1) % FILTER_SIZE; return sum / FILTER_SIZE; } ``` - 应用归一化算法:将ADC值映射到[0,100]范围,参考位置误差。 3. **测试控制逻辑**: - 简化PID控制:初始使用纯比例控制($u(t) = K_p e(t)$),其中$e(t)$为位置误差。 - 在直道上调试:固定$K_p$(如1.0),观察小车行为。若仍乱跑,检查电机PWM输出是否对称。 4. **系统调试优化**: - 使用VOFA+上位机监控数据,调整参数:从低$K_p$开始,逐步增加至响应稳定。 - 引用[^2]建议:记录赛道特征(如弯道半径),实现响应函数优化PID。 --- #### **三、总结** 电磁循迹小车乱跑的核心原因通常是**信号处理电路故障**、**软件算法错误**或**调试不足**。优先检查硬件信号质量(引用[^1][^2]),再逐步验证软件算法。引用[^2]强调:“在开始做车之前,先理清楚思路”,建议重新审视系统架构和控制逻辑。大多数问题可通过工具调试和参数优化解决[^3]。 如果上述步骤后问题依旧,考虑硬件故障(如电感损坏或电机异常)。实践中,90%的类似问题源于信号处理或PID参数不当。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值