电机应用-正点原子直流有刷电机例程笔记

目录

基础驱动实验:调速和换向

初始化工作

电机基础驱动API

电压、电流、温度检测实验

初始化工作

采集工作

编码器测速实验

编码器接口计数原理

初始化工作

编码器测速工作

速度环控制实现

PID相关函数

PID运算

电流环控制实现

PID相关函数

PID运算

位置环控制实现

PID相关函数

PID运算

速度+位置双环控制实现

PID相关函数

PID运算

电流+位置双环控制实现

PID相关函数

PID运算

电流+速度双环控制实现

PID相关函数

PID运算

电流+速度+位置三环控制实现

PID相关函数 

PID运算


基础驱动实验:调速和换向

硬件资源:

TIM1_CH1TIM1_CH1NSHDN刹车引脚
PA8PB13PF10

电路原理:

假设让TIM1_CH1输出PWM波,TIM1_CH1N固定输出高电平,此时只要调节TIM1_CH1输出的PWM占空比即可调整电机上的电压,进而控制电机的转速。

当电机需要换向时,我们就让TIM1_CH1固定输出高电平,TIM1_CH1N输出PWM波即可。

功能描述:

比较值变量的绝对值越大,电机速度越快。比较值变量正数为正转,负数反转。

按下KEY0,增大PWM的比较值变量。

按下KEY1,减小PWM的比较值变量。

按下KEY2,电机停止。

初始化工作

TIM_HandleTypeDef g_atimx_cplm_pwm_handle;                              /* 定时器x句柄 *//*** @brief       高级定时器TIMX 互补输出 初始化函数(使用PWM模式1)* @note*              配置高级定时器TIMX 互补输出, 一路OCy 一路OCyN, 并且可以设置死区时间**              高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此*              高级定时器时钟 = 168Mhz*              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.*              Ft=定时器工作频率, 单位 : Mhz** @param       arr: 自动重装值。* @param       psc: 时钟预分频数* @retval      无*/void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
{TIM_OC_InitTypeDef sConfigOC ;TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;g_atimx_cplm_pwm_handle.Instance = TIM1;                                /* 定时器x */g_atimx_cplm_pwm_handle.Init.Prescaler = psc;                           /* 定时器预分频系数 */g_atimx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;          /* 向上计数模式 */g_atimx_cplm_pwm_handle.Init.Period = arr;                              /* 自动重装载值 */g_atimx_cplm_pwm_handle.Init.RepetitionCounter = 0;                     /* 重复计数器寄存器为0 */g_atimx_cplm_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;        /* 使能影子寄存器TIMx_ARR */HAL_TIM_PWM_Init(&g_atimx_cplm_pwm_handle) ;/* 设置PWM输出 */sConfigOC.OCMode = TIM_OCMODE_PWM1;                                     /* PWM模式1 */sConfigOC.Pulse = 0;                                                    /* 比较值为0 */sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;                              /* OCy 低电平有效 */sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;                            /* OCyN 低电平有效 */sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;                               /* 使用快速模式 */sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;                          /* 主通道的空闲状态 */sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;                        /* 互补通道的空闲状态 */HAL_TIM_PWM_ConfigChannel(&g_atimx_cplm_pwm_handle, &sConfigOC, ATIM_TIMX_CPLM_CHY);    /* 配置后默认清CCER的互补输出位 *//* 设置死区参数,开启死区中断 */sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;                 /* OSSR设置为1 */sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;               /* OSSI设置为0 */sBreakDeadTimeConfig.LockLevel =  TIM_LOCKLEVEL_OFF;                    /* 上电只能写一次,需要更新死区时间时只能用此值 */sBreakDeadTimeConfig.DeadTime = 0X0F;                                   /* 死区时间 */sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;                    /* BKE = 0, 关闭BKIN检测 */sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;             /* BKP = 1, BKIN低电平有效 */sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;     /* 使能AOE位,允许刹车后自动恢复输出 */HAL_TIMEx_ConfigBreakDeadTime(&g_atimx_cplm_pwm_handle, &sBreakDeadTimeConfig);         /* 设置BDTR寄存器 */}
atim_timx_cplm_pwm_init(8400 - 1, 0);    /* 168 000 000 / 1 = 168 000 000 168Mhz的计数频率,计数8400次为50us */

初始化主要分为三部分:

1、HAL_TIM_PWM_Init 函数初始化定时器参数,内部会调用HAL_TIM_PWM_MspInit 函数完成引脚配置。

2、HAL_TIM_PWM_ConfigChannel 函数定义定时器PWM1模式。

3、 HAL_TIMEx_ConfigBreakDeadTime 函数来设置死区参数。该实验没用到。

当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开主通道,互补通道0.2低,0.8高。主通道高电平。

当PWM1递增模式,OCPolarity为低电平,OCNPolarity为低电平,8400的重装载值,比较值为1680。不开互补通道,主通道0.2低,0.8高。互补通道高电平。

不管上面哪种情况,调大占空比,低电平变宽,转速变快。

电机基础驱动API

电机开启:SD引脚拉高。

电机停止:关闭主通道和互补通道输出,SD引脚拉低。

HAL_TIM_PWM_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);          /* 关闭主通道输出 */
HAL_TIMEx_PWMN_Stop(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);       /* 关闭互补通道输出 */

电机旋转方向设置:

关闭主通道和互补通道输出,参数0开启主通道输出。

关闭主通道和互补通道输出,参数1开启互补通道输出。

HAL_TIM_PWM_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);     /* 开启主通道输出 */
HAL_TIMEx_PWMN_Start(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1);  /* 开启互补通道输出 */

电机速度控制:先判断参数是否小于重装载值,符合则设置参数为比较值。

if (para < (__HAL_TIM_GetAutoreload(&g_atimx_cplm_pwm_handle) - 0x0F))  /* 限速 */
{  __HAL_TIM_SetCompare(&g_atimx_cplm_pwm_handle, TIM_CHANNEL_1, para);
}

调大比较值,转速就加快。 

电机控制:电机旋转方向设置+电机速度控制。motor_pwm_set(float para)。para可正可负。

电压、电流、温度检测实验

电压采集电路: 

由于直流有刷电机驱动板的电源电压远超STM32内部ADC所能采集的范围,并不能直接使用ADC进行电压采集,因此需要使用一些硬件电路对电源电压进行处理,使其减小到ADC采集范围。

电流采集电路: 

由于STM32内部ADC并不能对电流进行采集,所以需要先把电流的信号转换为电压的信号。

在H桥中加入了一个20mR(0.02R)的采样电阻,这样子就可以得到一个采样电压I-V = 0.02R * 实际电流I。

但是这个采样电压太小了,直接用ADC进行采集的话偏差较大,所以需要对它进行差分放大。

温度采集电路:

利用NCP18XH103F03RB热敏电阻对温度进行检测,该热敏电阻是负温度系数电阻,温度越高,其电阻值越低。

硬件资源:

ADC1_CH0ADC1_CH8ADC1_CH9
PA0PB0PB1
温度检测引脚电流检测引脚电压检测引脚

初始化工作

初始化工作为ADC+DMA的初始化。转换顺序为电压-温度-电流。

#define ADC_ADCX_CH0                        ADC_CHANNEL_9                                       /* 电压测量通道 */ 
#define ADC_ADCX_CH1                        ADC_CHANNEL_0                                       /* 温度测量通道 */ 
#define ADC_ADCX_CH2                        ADC_CHANNEL_8                                       /* 电流测量通道 */ /*** @brief       ADC初始化函数* @param       无* @retval      无*/
void adc_init(void)
{ADC_ChannelConfTypeDef sConfig = {0};g_adc_nch_dma_handle.Instance = ADC1;                                           /* ADCx */g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;            /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B;                      /* 12位模式 */g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE;                                /* 扫描模式 多通道使用 */g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;                          /* 连续转换模式,转换完成之后接着继续转换 */g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;                      /* 禁止不连续采样模式 */g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;                /* 软件触发 */g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;                      /* 右对齐 */g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM;                         /* 使用转换通道数,需根据实际转换通道去设置 */g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE;                       /* 开启DMA连续转换请求 */g_adc_nch_dma_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV;HAL_ADC_Init(&g_adc_nch_dma_handle);/* 配置使用的ADC通道,采样序列里的第几个转换,增加或者减少通道需要修改这部分 */sConfig.Channel = ADC_ADCX_CH0;sConfig.Rank = 1;sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);sConfig.Channel = ADC_ADCX_CH1;sConfig.Rank = 2;HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);sConfig.Channel = ADC_ADCX_CH2;sConfig.Rank = 3;HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);}
#define ADC_CH_NUM                          3                                             /* 需要转换的通道数目 */
#define ADC_COLL                            1000                                          /* 单采集次数 */
#define ADC_SUM                             ADC_CH_NUM * ADC_COLL                         /* 总采集次数 */uint16_t g_adc_value[ADC_SUM] = {0};                                                      /* 存储ADC原始值 */
uint16_t g_adc_val[ADC_CH_NUM];                                                           /* ADC平均值存放数组 */
HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)g_adc_value, ADC_SUM);               /* 开启ADC的DMA传输 */
/*** @brief       ADC 采集中断服务回调函数* @param       无 * @retval      无*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{if (hadc->Instance == ADC_ADCX){HAL_ADC_Stop_DMA(&g_adc_nch_dma_handle);  /* 关闭DMA转换 */calc_adc_val(g_adc_val);                  /* 计算ADC的平均值 */HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)&g_adc_value, (uint32_t)(ADC_SUM)); /* 启动DMA转换 */}
}/*** @brief       计算ADC的平均值(滤波)* @param       * p :存放ADC值的指针地址* @note        此函数对电压、温度、电流对应的ADC值进行滤波,*              p[0]-p[2]对应的分别是电压、温度和电流* @retval      无*/
void calc_adc_val(uint16_t * p)
{uint32_t temp[3] = {0,0,0};int i;for(i=0; i<ADC_COLL; i++)         /* 循环ADC_COLL次取值,累加 */{temp[0] += g_adc_value[0 + i * ADC_CH_NUM];temp[1] += g_adc_value[1 + i * ADC_CH_NUM];temp[2] += g_adc_value[2 + i * ADC_CH_NUM];}temp[0] /= ADC_COLL;            /* 取平均值 */temp[1] /= ADC_COLL;temp[2] /= ADC_COLL;p[0] = temp[0];                 /* 存入电压ADC通道平均值 */p[1] = temp[1];                 /* 存入温度ADC通道平均值 */p[2] = temp[2];                 /* 存入电流ADC通道平均值 */
}

初始化工作主要分为四部分:

1、开时钟,引脚初始化。

2、HAL_ADC_Init函数初始化ADC基础参数,HAL_ADC_ConfigChannel函数配置ADC使用通道。

3、HAL_DMA_Init函数初始化DMA通道,__HAL_LINKDMA函数关联ADC和DMA。

4、开启DMA中断,HAL_ADC_Start_DMA函数开启ADC的DMA传输。

ADC转换完成中断中,先关闭DMA传输,然后进行ADC值取平均,再开始DMA传输。calc_adc_val函数就是取g_adc_value的平均值,然后存入g_adc_val。

采集工作

电压采集:

/* 电压计算公式:* V_POWER = V_BUS * 25* ADC值转换为电压值:电压=ADC值*3.3/4096* 整合公式可以得出电压V_POWER= ADC值 *(3.3f * 25 / 4096)*/
#define ADC2VBUS    (float)(3.3f * 25 / 4096)
printf("Valtage:%.1fV \r\n",    g_adc_val[0]*ADC2VBUS);                   /* 打印电压值*/

温度采集:

/*Rt = Rp *exp(B*(1/T1-1/T2))Rt 是热敏电阻在T1温度下的阻值;Rp是热敏电阻在T2常温下的标称阻值;exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于 2.7182818;B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380;这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)KT1就是所求的温度
*/const float Rp = 10000.0f;          /* 10K */
const float T2 = (273.15f + 25.0f); /* T2 */
const float Bx = 3380.0f;           /* B */
const float Ka = 273.15f;/*** @brief       计算温度值* @param       para: 温度采集对应ADC通道的值(已滤波)* @note        计算温度分为两步:1.根据ADC采集到的值计算当前对应的Rt2.根据Rt计算对应的温度值* @retval      温度值*/
float get_temp(uint16_t para)
{float Rt;float temp;/* 第一步:Rt = 3.3 * 4700 / VTEMP - 4700 ,其中VTEMP就是温度检测通道采集回来的电压值,VTEMP = ADC值* 3.3/4096由此我们可以计算出当前Rt的值:Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f; */Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f;       /* 根据当前ADC值计算出Rt的值 *//* 第二步:根据当前Rt的值来计算对应温度值:Rt = Rp *exp(B*(1/T1-1/T2)) */temp = Rt / Rp;                 /* 解出exp(B*(1/T1-1/T2)) ,即temp = exp(B*(1/T1-1/T2)) */temp = log(temp);               /* 解出B*(1/T1-1/T2) ,即temp = B*(1/T1-1/T2) */temp /= Bx;                     /* 解出1/T1-1/T2 ,即temp = 1/T1-1/T2 */temp += (1.0f / T2);            /* 解出1/T1 ,即temp = 1/T1 */temp = 1.0f / (temp);           /* 解出T1 ,即temp = T1 */temp -= Ka;                     /* 计算T1对应的摄氏度 */return temp;                    /* 返回温度值 */
}
printf("Temp:%.1fC \r\n",       get_temp(g_adc_val[1]));                  /* 打印温度值*/

电流采集:

/* 电流计算公式:* I=(最终输出电压-初始参考电压)/(6*0.02)A* ADC值转换为电压值:电压=ADC值*3.3/4096,这里电压单位为V,我们换算成mV,4096/1000=4.096,后面就直接算出为mA* 整合公式可以得出电流 I= (当前ADC值-初始参考ADC值)* (3.3 / 4.096 / 0.12)*/
#define ADC2CURT    (float)(3.3f / 4.096f / 0.12f)uint16_t init_adc_val;/* init_adc_val存储电流测量对应的参考电压ADC值,这里进行滤波 */
init_adc_val = g_adc_val[2];            /* 取出第一次得到的值 */
for(t=0;t<1000;t++)
{init_adc_val += g_adc_val[2];       /* 现在的值和上一次存储的值相加 */init_adc_val /= 2;                  /* 取平均值 */delay_ms(1);
}printf("Current:%.1fmA \r\n",   abs(g_adc_val[2]-init_adc_val)*ADC2CURT); /* 打印电流值*/

编码器测速实验

正点原子的直流有刷电机编码器使用的是磁电增量式编码器,安装在直流有刷电机的尾部,分辨率是11线

正点原子的直流有刷电机编码器有A、B两相,它们会输出两个相位差为90°的脉冲。当电机正转时,A相脉冲在前;当电机反转时,B相脉冲在前。

STM32定时器的编码器接口模式相当于带有方向选择的外部时钟。在此模式下,外部输入的脉冲信号可以作为计数器的时钟,而计数的方向则是由脉冲相位的先后所决定。

A、B两相脉冲信号从TIMx_CH1和TIMx_CH2这两个通道输入,经过滤波器和边沿检测器(可以设置滤波和反相)的处理,进入到编码器接口控制器中。

Tips:TIMx_CH1和TIMx_CH2这两个通道才支持编码器功能,TIMx_CH3和TIMx_CH4这两个通道不支持。

硬件资源: 

TIM3_CH1TIM3_CH2TIM6
PC6PC7中断计算速度
TIM3编码器A相输入通道TIM3编码器B相输入通道

编码器接口计数原理

编码器接口可以利用输入脉冲的边沿进行计数,通过计数值的变化量可以算出输入脉冲信号的变化量,也就可以计算出电机的转速。

TIMx从模式控制寄存器(TIMx_SMCR)的0~2位:SMS[2:0]为从模式选择,控制边沿检测的方式。

选择外部信号时,触发信号(TRGI)的有效边沿与外部输入上所选的极性相关。

001:编码器模式1-计数器根据TI1FP1电平在TI2FP2边沿递增/递减计数。

010:编码器模式2-计数器根据TI2FP2电平在TI1FP1边沿递增/递减计数。

011:编码器模式3-计数器根据TI1FP1和TI2FP2的边沿计数,计数的方向取决于另外一个信号的电平。

Tips:

        选择仅在TI1或TI2处计数,就相当于对脉冲信号进行了2倍频(上身沿和下降沿),如果编码器输出11个脉冲信号,那么就会计数22次。

        选择在TI1和TI2处均计数,就相当于对脉冲信号进行了4倍频(上身沿和下降沿),如果编码器输出11个脉冲信号,那么就会计数44次。

        因此可以我们通过计数次数来计算电机速度时,需要除以相应的倍频系数。至此,A、B两相脉冲信号的变化就转换成了定时器的计数变化。

        可以通过一分钟内计数的变化量来计算电机速度:

                电机转速 = 一分钟内计数的变化量 / 倍频系数 / 编码器线数 / 减速比

编码器模式1仅在TI1处计数--计数器根据TI1FP1电平在TI2FP2边沿递增/递减计数。

假设把A相接在CH1(TI1),B相接在CH2(TI2),选择仅在TI1处计数(仅检测A相边沿)。脉冲信号会有两种,当编码器正转时A相在前、当编码器反转时B相在前。

正转:当A相上身沿时,B相低电平,表格中对应的是递增计数。当A相下降沿时,B相高电平,表格中对应的是递增计数。即编码器正转对应的计数方向是递增计数

反转:当A相上身沿时,B相高电平,表格中对应的是递减计数。当A相下降沿时,B相低电平,表格中对应的是递减计数。即编码器反转对应的计数方向是递减计数

编码器模式2仅在TI2处计数--计数器根据TI2FP2电平在TI1FP1边沿递增/递减计数。

假设把A相接在CH1(TI1),B相接在CH2(TI2),选择仅在TI2处计数(仅检测B相边沿)。脉冲信号会有两种,当编码器正转时A相在前、当编码器反转时B相在前。

正转:当B相上身沿时,A相高电平,表格中对应的是递增计数。当B相下降沿时,A相低电平,表格中对应的是递增计数。即编码器正转对应的计数方向是递增计数

反转:当B相上身沿时,A相低电平,表格中对应的是递减计数。当B相下降沿时,A相高电平,表格中对应的是递减计数。即编码器反转对应的计数方向是递减计数

编码器模式3在TI1和TI2处均计数--计数器根据TI1FP1和TI2FP2的边沿计数,计数的方向取决于另外一个信号的电平。

假设把A相接在CH1(TI1),B相接在CH2(TI2),选择仅在TI2处计数(仅检测B相边沿)。脉冲信号会有两种,当编码器正转时A相在前、当编码器反转时B相在前。

正转:当A相上身沿时,B相低电平,表格中对应的是递增计数。当B相上身沿时,A相高电平,表格中对应的是递增计数。当A相下降沿时,B相高电平,表格中对应的是递增计数。当B相下降沿时,A相低电平,表格中对应的是递增计数。即编码器正转对应的计数方向是递增计数

反转:当B相上身沿时,A相低电平,表格中对应的是递减计数。当A相上身沿时,B相高电平,表格中对应的是递减计数。当B相下降沿时,A相高电平,表格中对应的是递减计数。当A相下降沿时,B相低电平,表格中对应的是递减计数。即编码器反转对应的计数方向是递减计数。 

计数溢出处理:

编码器在电机运行时会一直旋转并输出脉冲信号,如果时间较长,那么定时器计数就会溢出,我们必须对溢出进行处理,否则电机速度的计算结果就不准了。 

当前总计数值 = 计数器当前值 + 溢出次数(可在溢出中断中记录) * 65536。

溢出时读取TIMx控制寄存器1(TIMx_CR1)的位4(DIR,计数方向),以计算总的计数次数变化量。

        0:计数器递增计数

        1:计数器递减计数

作用:在计数溢出时,读取到溢出方向,后续才能计算总的计数变化量。

相关寄存器:

TIMx_CR1:CEN为计数器使能。

TIMx_CR1:DIR为计数方向。

TIMx_CCMR1(TIMx_CCMR1对应通道1、2,TIMx_CCMR2对应通道3、4):CC1S选择IC1映射再TI1上。

TIMx_CCMR1(TIMx_CCMR1对应通道1、2,TIMx_CCMR2对应通道3、4):IC1PSC选择00。一次边沿就触发一次计数。

TIMx_CCMR1(TIMx_CCMR1对应通道1、2,TIMx_CCMR2对应通道3、4):IC1F用来设置TI1输入采样频率和数据滤波器长度。其中,Fck_int是定时器时钟源频率,按照例程为84MHz,而Fdts是根据TIMx_CR1:CKD来确定的。如果TIMx_CR1:CKD设置为00,则Fdts = Fck_int。N值为采样次数。举例:假设IC1F为0011,并设置IC1映射到TI1上,则表示以Fck_int为采样频率,当连续8次都是采样到TI1为高电平或低电平,滤波器才输出一个有效输出边沿。当8次采样中有高有低,则保持原来的输出,这样就可以滤除高频干扰信号,从而达到滤波的效果。

TIMx_CCER控制各输入输出通道的开关和极性。要使能编码器接口模式,必须设置CC1E和CC22为1,而CC1P和CC2P设置的是边沿触发的方向。

TIMx_SMCR:SMS用于从模式选择,其实就是选择计数器输入时钟的来源。

初始化工作

/********************************* 1 通用定时器 编码器程序 *************************************/TIM_HandleTypeDef g_timx_encode_chy_handle;         /* 定时器x句柄 */
TIM_Encoder_InitTypeDef g_timx_encoder_chy_handle;  /* 定时器编码器句柄 *//*** @brief       通用定时器TIMX 通道Y 编码器接口模式 初始化函数* @note*              通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候*              通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz*              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.*              Ft=定时器工作频率,单位:Mhz** @param       arr: 自动重装值。* @param       psc: 时钟预分频数* @retval      无*/
void gtim_timx_encoder_chy_init(uint16_t arr, uint16_t psc)
{/* 定时器x配置 */g_timx_encode_chy_handle.Instance = GTIM_TIMX_ENCODER;                      /* 定时器x */g_timx_encode_chy_handle.Init.Prescaler = psc;                              /* 定时器分频 */g_timx_encode_chy_handle.Init.Period = arr;                                 /* 自动重装载值 */g_timx_encode_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 *//* 定时器x编码器配置 */g_timx_encoder_chy_handle.EncoderMode = TIM_ENCODERMODE_TI12;               /* TI1、TI2都检测,4倍频 */g_timx_encoder_chy_handle.IC1Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */g_timx_encoder_chy_handle.IC1Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */g_timx_encoder_chy_handle.IC1Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */g_timx_encoder_chy_handle.IC1Filter = 10;                                   /* 滤波器设置 */g_timx_encoder_chy_handle.IC2Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */g_timx_encoder_chy_handle.IC2Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */g_timx_encoder_chy_handle.IC2Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */g_timx_encoder_chy_handle.IC2Filter = 10;                                   /* 滤波器设置 */HAL_TIM_Encoder_Init(&g_timx_encode_chy_handle, &g_timx_encoder_chy_handle);/* 初始化定时器x编码器 */HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle,GTIM_TIMX_ENCODER_CH1);     /* 使能编码器通道1 */HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle,GTIM_TIMX_ENCODER_CH2);     /* 使能编码器通道2 */__HAL_TIM_ENABLE_IT(&g_timx_encode_chy_handle,TIM_IT_UPDATE);               /* 使能更新中断 */__HAL_TIM_CLEAR_FLAG(&g_timx_encode_chy_handle,TIM_IT_UPDATE);              /* 清除更新中断标志位 */}/******************************** 2 基本定时器 编码器程序 ************************************/TIM_HandleTypeDef timx_handler;         /* 定时器参数句柄 *//*** @brief       基本定时器TIMX定时中断初始化函数* @note*              基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候*              基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz*              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.*              Ft=定时器工作频率,单位:Mhz** @param       arr: 自动重装值。* @param       psc: 时钟预分频数* @retval      无*/
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{timx_handler.Instance = BTIM_TIMX_INT;                              /* 基本定时器X */timx_handler.Init.Prescaler = psc;                                  /* 设置预分频器  */timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP;                 /* 向上计数器 */timx_handler.Init.Period = arr;                                     /* 自动装载值 */timx_handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;           /* 时钟分频因子 */HAL_TIM_Base_Init(&timx_handler);HAL_TIM_Base_Start_IT(&timx_handler);                               /* 使能基本定时器x和及其更新中断:TIM_IT_UPDATE */__HAL_TIM_CLEAR_IT(&timx_handler,TIM_IT_UPDATE);                    /* 清除更新中断标志位 */
}
gtim_timx_encoder_chy_init(0XFFFF, 0);  /* 编码器定时器初始化,不分频直接84M的计数频率 */
btim_timx_int_init(1000 - 1 , 84 - 1);  /* 基本定时器初始化,1ms计数周期 */

初始化主要分为三部分:

1、HAL_TIM_Encoder_Init函数初始化编码器参数,内部会调用HAL_TIM_Encoder_MspInit函数完成引脚配置并开启中断。

2、HAL_TIM_Encoder_Start函数使能编码器通道。

3、__HAL_TIM_ENABLE_IT函数带进参数TIM_IT_UPDATE使能更新中断。

4、__HAL_TIM_CLEAR_FLAG函数带进参数TIM_IT_UPDATE清除更新中断标志位。

5、HAL_TIM_Base_Init函数初始化基本定时器,内部会调用HAL_TIM_Base_MspInit函数完成引脚配置并开启中断。

6、HAL_TIM_Base_Start_IT函数使能基本定时器中断。

7、__HAL_TIM_CLEAR_IT函数带进参数TIM_IT_UPDATE清除更新中断标志位。

编码器测速工作

在定时器中断中进行这样的处理:

volatile int g_timx_encode_count = 0;                                   /* 溢出次数 *//*** @brief       获取编码器的值* @param       无* @retval      编码器值*/
int gtim_get_encode(void)
{return ( int32_t )__HAL_TIM_GET_COUNTER(&g_timx_encode_chy_handle) + g_timx_encode_count * 65536;       /* 当前计数值+之前累计编码器的值=总的编码器值 */
}/*** @brief       定时器更新中断回调函数* @param        htim:定时器句柄指针* @note        此函数会被定时器中断函数共同调用的* @retval      无*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM3){if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_timx_encode_chy_handle))   /* 判断CR1的DIR位 */{g_timx_encode_count--;                                      /* DIR位为1,也就是递减计数 */}else{g_timx_encode_count++;                                      /* DIR位为0,也就是递增计数 */}}else if (htim->Instance == TIM6){int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 50);                                 /* 中位平均值滤除编码器抖动数据,50ms计算一次速度*/}
}

在TIM3编码器模式下经过65536次计数后进入中断,中断中记录是此时是递减计数进入还是递增计数进入。(从0开始,递减的话一开始就会进入中断了,计数器变为65536开始递减)

在TIM6中1ms进入一次中断,中断记录编码器值(相当于总的计数值),然后进行电机速度计算(用到冒泡排序和一阶低通滤波算法)后传值给g_motor_data.speed,单位转/分(RPM)。

/*************************************    第三部分    编码器测速    ****************************************************/#define ROTO_RATIO      44  /* 线数*倍频系数,即11*4=44 */
#define REDUCTION_RATIO 30  /* 减速比30:1 *//* 电机参数结构体 */
typedef struct 
{uint8_t state;          /*电机状态*/float current;          /*电机电流*/float volatage;         /*电机电压*/float power;            /*电机功率*/float speed;            /*电机实际速度*/int32_t motor_pwm;      /*设置比较值大小 */
} Motor_TypeDef;Motor_TypeDef g_motor_data;  /*电机参数变量*/
ENCODE_TypeDef g_encode;     /*编码器参数变量*//*** @brief       电机速度计算* @param       encode_now:当前编码器总的计数值*              ms:计算速度的间隔,中断1ms进入一次,例如ms = 5即5ms计算一次速度* @retval      无*/
void speed_computer(int32_t encode_now, uint8_t ms)
{uint8_t i = 0, j = 0;float temp = 0.0;static uint8_t sp_count = 0, k = 0;static float speed_arr[10] = {0.0};                     /* 存储速度进行滤波运算 */if (sp_count == ms)                                     /* 计算一次速度 */{/* 计算电机转速 第一步 :计算ms毫秒内计数变化量第二步 ;计算1min内计数变化量:g_encode.speed * ((1000 / ms) * 60 ,第三步 :除以编码器旋转一圈的计数次数(倍频倍数 * 编码器分辨率)第四步 :除以减速比即可得出电机转速*/g_encode.encode_now = encode_now;                                /* 取出编码器当前计数值 */g_encode.speed = (g_encode.encode_now - g_encode.encode_old);    /* 计算编码器计数值的变化量 */speed_arr[k++] = (float)(g_encode.speed * ((1000 / ms) * 60.0) / REDUCTION_RATIO / ROTO_RATIO );    /* 保存电机转速 */g_encode.encode_old = g_encode.encode_now;          /* 保存当前编码器的值 *//* 累计10次速度值,后续进行滤波*/if (k == 10){for (i = 10; i >= 1; i--)                       /* 冒泡排序*/{for (j = 0; j < (i - 1); j++) {if (speed_arr[j] > speed_arr[j + 1])    /* 数值比较 */{ temp = speed_arr[j];                /* 数值换位 */speed_arr[j] = speed_arr[j + 1];speed_arr[j + 1] = temp;}}}temp = 0.0;for (i = 2; i < 8; i++)                         /* 去除两边高低数据 */{temp += speed_arr[i];                       /* 将中间数值累加 */}temp = (float)(temp / 6);                       /*求速度平均值*//* 一阶低通滤波* 公式为:Y(n)= qX(n) + (1-q)Y(n-1)* 其中X(n)为本次采样值;Y(n-1)为上次滤波输出值;Y(n)为本次滤波输出值,q为滤波系数* q值越小则上一次输出对本次输出影响越大,整体曲线越平稳,但是对于速度变化的响应也会越慢*/g_motor_data.speed = (float)( ((float)0.48 * temp) + (g_motor_data.speed * (float)0.52) );k = 0;}sp_count = 0;}sp_count ++;
}

速度环控制实现

PID相关函数

#if INCR_LOCT_SELECT/* 增量式PID参数相关宏 */
#define  KP      8.50f               /* P参数*/
#define  KI      5.00f               /* I参数*/
#define  KD      0.10f               /* D参数*/
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms*/#else/* 位置式PID参数相关宏 */
#define  KP      10.0f               /* P参数*/
#define  KI      6.00f               /* I参数*/
#define  KD      0.5f                /* D参数*/
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms*/#endif/* PID参数结构体 */
typedef struct
{__IO float  SetPoint;            /* 设定目标 */__IO float  ActualValue;         /* 期望输出值 */__IO float  SumError;            /* 误差累计 */__IO float  Proportion;          /* 比例常数 P */__IO float  Integral;            /* 积分常数 I */__IO float  Derivative;          /* 微分常数 D */__IO float  Error;               /* Error[1] */__IO float  LastError;           /* Error[-1] */__IO float  PrevError;           /* Error[-2] */
} PID_TypeDef;PID_TypeDef  g_speed_pid;           /* 速度环PID参数结构体 *//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{g_speed_pid.SetPoint = 0;       /* 设定目标值 */g_speed_pid.ActualValue = 0.0;  /* 期望输出值 */g_speed_pid.SumError = 0.0;     /* 积分值 */g_speed_pid.Error = 0.0;        /* Error[1] */g_speed_pid.LastError = 0.0;    /* Error[-1] */g_speed_pid.PrevError = 0.0;    /* Error[-2] */g_speed_pid.Proportion = KP;    /* 比例常数 Proportional Const */g_speed_pid.Integral = KI;      /* 积分常数 Integral Const */g_speed_pid.Derivative = KD;    /* 微分常数 Derivative Const */ 
}/*** @brief       pid闭环控制* @param       *PID:PID结构体变量地址* @param       Feedback_value:当前实际值* @retval      期望输出值*/
int32_t increment_pid_ctrl(PID_TypeDef *PID, float Feedback_value)
{PID->Error = (float)(PID->SetPoint - Feedback_value);                   /* 计算偏差 */#if  INCR_LOCT_SELECT                                                       /* 增量式PID */PID->ActualValue += (PID->Proportion * (PID->Error - PID->LastError))                          /* 比例环节 */+ (PID->Integral * PID->Error)                                             /* 积分环节 */+ (PID->Derivative * (PID->Error - 2 * PID->LastError + PID->PrevError));  /* 微分环节 */PID->PrevError = PID->LastError;                                        /* 存储偏差,用于下次计算 */PID->LastError = PID->Error;#else                                                                       /* 位置式PID */PID->SumError += PID->Error;PID->ActualValue = (PID->Proportion * PID->Error)                       /* 比例环节 */+ (PID->Integral * PID->SumError)                    /* 积分环节 */+ (PID->Derivative * (PID->Error - PID->LastError)); /* 微分环节 */PID->LastError = PID->Error;#endifreturn ((int32_t)(PID->ActualValue));                                   /* 返回计算后输出的数值 */
}

PID运算

主要体现在TIM6定时中断中用来编码器测速时。

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度*/if (val % SMAPLSE_PID_SPEED == 0)                               /* 50ms进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了*/{ /* PID计算,输出比较值(占空比) */g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed);if (g_motor_data.motor_pwm >= 8200)                     /* 限速 */{g_motor_data.motor_pwm = 8200;}else if (g_motor_data.motor_pwm <= -8200){g_motor_data.motor_pwm = -8200;}motor_pwm_set(g_motor_data.motor_pwm);                                  /* 设置电机转速 */}val = 0;
}
val ++;

电流环控制实现

PID相关函数

#define  INCR_LOCT_SELECT  0         /* 0:位置式,1:增量式 */#if INCR_LOCT_SELECT/* 增量式PID参数相关宏 */
#define  KP      0.0f                /* P参数*/
#define  KI      6.0f                /* I参数*/
#define  KD      4.0f                /* D参数*/
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms*/#else/* 位置式PID参数相关宏 */
#define  KP      10.0f               /* P参数*/
#define  KI      7.0f                /* I参数*/
#define  KD      2.0f                /* D参数*/
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms*/#endif/* PID参数结构体 */
typedef struct
{__IO float  SetPoint;            /* 设定目标 */__IO float  ActualValue;         /* 期望输出值 */__IO float  SumError;            /* 误差累计 */__IO float  Proportion;          /* 比例常数 P */__IO float  Integral;            /* 积分常数 I */__IO float  Derivative;          /* 微分常数 D */__IO float  Error;               /* Error[1] */__IO float  LastError;           /* Error[-1] */__IO float  PrevError;           /* Error[-2] */
} PID_TypeDef;PID_TypeDef  g_current_pid;             /* 电流环PID参数结构体 *//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{g_current_pid.SetPoint = 40;        /* 设定目标值 */g_current_pid.ActualValue = 0.0;    /* 期望输出值 */g_current_pid.SumError = 0.0;       /* 积分值 */g_current_pid.Error = 0.0;          /* Error[1] */g_current_pid.LastError = 0.0;      /* Error[-1] */g_current_pid.PrevError = 0.0;      /* Error[-2] */g_current_pid.Proportion = KP;      /* 比例常数 Proportional Const */g_current_pid.Integral = KI;        /* 积分常数 Integral Const */g_current_pid.Derivative = KD;      /* 微分常数 Derivative Const */ 
}

PID运算

ADC中相对于电压、电流、温度检测实验,ADC转换完成中断回调函数处理增加了处理内容。

旧机制是求ADC通道的平均值g_adc_val[2]。

新机制:

1、求ADC通道的平均值g_adc_val[2]。

2、然后累加16次g_adc_val[2]后平均到add_adc。这个步骤不断重复,充当当前ADC值。

3、然后继续累加17次add_adc后平均到init_adc_value。这个步骤只可以满足一次,然后充当初始ADC值。

4、根据电流I = (当前ADC值 - 初始ADC值) * (3.3 / 4096 / 0.12 / 1000),即可求得temp_c,单位mA。

5、根据一阶低通滤波 (float)((g_motor_data.current * (float)0.60) + ((float)0.40 * temp_c))求得g_motor_data.current。

6、如果g_motor_data.current小于20mA,则过滤掉这微弱浮动电流。

uint16_t g_adc_val[ADC_CH_NUM];                     /*ADC平均值存放数组*/
/*** @brief       ADC采集中断回调函数* @param       无 * @retval      无*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{float temp_c = 0.0;static float add_adc = 0;static float init_adc_value = 0;static uint8_t adc_count1 = 0, adc_count2 = 0;if ( hadc->Instance == ADC_ADCX )                               /* 判断是不是ADC1 */{ adc_count1++;HAL_ADC_Stop_DMA(&g_adc_nch_dma_handle);                    /* 关闭DMA转换 */calc_adc_val(g_adc_val);                                    /* 计算ADC的平均值 */add_adc += g_adc_val[2];                                    /* 取出电流通道对应的ADC值进行累计 */if (adc_count1 >= 15)                                       /* 累计15次 */{add_adc = (float)(add_adc / adc_count1);                /* 取平均值 */if (adc_count2 <= 16)                                   /* 采集16次ADC平均值计算参考电压的ADC值 */{adc_count2++;init_adc_value += add_adc;                          /* 对平均值累计求和 */if (adc_count2 == 16)                               /* 平均值累计16次 */{ adc_count2 = 17;                                /* 不再进入 */init_adc_value = (init_adc_value / 16.0f);      /* 存储初始ADC值 */}}if (adc_count2 >= 17)                                   /* 采集完参考ADC值后,采集电流通道当前ADC值 */{temp_c = (add_adc - init_adc_value) * ADC2CURT;                                                 /* 计算电流 */g_motor_data.current = (float)((g_motor_data.current * (float)0.60) + ((float)0.40 * temp_c));  /* 一阶低通滤波 */if (g_motor_data.current <= 20)                                                                 /* 过滤掉微弱浮动电流 */{g_motor_data.current = 0.0;}}add_adc = 0;adc_count1 = 0;}HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)&g_adc_value, (uint32_t)(ADC_SUM));                /* 启动DMA转换 */}
}

然后进行电流的PID运算。

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */if (val % SMAPLSE_PID_SPEED == 0)                               /* 进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了 */{ /* PID计算,输出比较值(占空比),再进行一阶低通滤波 */motor_pwm_temp = increment_pid_ctrl(&g_current_pid, g_motor_data.current);g_motor_data.motor_pwm = (int32_t)((g_motor_data.motor_pwm * 0.5) + (motor_pwm_temp * 0.5));if (g_motor_data.motor_pwm >= 8200)                 /* 限制占空比 */{g_motor_data.motor_pwm = 8200;}else if (g_motor_data.motor_pwm <= 0){g_motor_data.motor_pwm = 0;}motor_pwm_set(g_motor_data.motor_pwm);              /* 设置占空比(电机转速) */}val = 0;
}val ++;

位置环控制实现

PID相关函数

/* 增量式PID参数相关宏 */
#define  KP      15.0f               /* P参数*/
#define  KI      0.00f               /* I参数*/
#define  KD      7.50f               /* D参数*/
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms *//* PID参数结构体 */
typedef struct
{__IO float  SetPoint;            /* 设定目标 */__IO float  ActualValue;         /* 期望输出值 */__IO float  SumError;            /* 误差累计 */__IO float  Proportion;          /* 比例常数 P */__IO float  Integral;            /* 积分常数 I */__IO float  Derivative;          /* 微分常数 D */__IO float  Error;               /* Error[1] */__IO float  LastError;           /* Error[-1] */__IO float  PrevError;           /* Error[-2] */
} PID_TypeDef;PID_TypeDef  g_location_pid;             /*位置环PID参数结构体*//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{g_location_pid.SetPoint = 0.0;       /* 设定目标值 */g_location_pid.ActualValue = 0.0;    /* 期望输出值 */g_location_pid.SumError = 0.0;       /* 积分值 */g_location_pid.Error = 0.0;          /* Error[1] */g_location_pid.LastError = 0.0;      /* Error[-1] */g_location_pid.PrevError = 0.0;      /* Error[-2] */g_location_pid.Proportion = KP;      /* 比例常数 Proportional Const */g_location_pid.Integral = KI;        /* 积分常数 Integral Const */g_location_pid.Derivative = KD;      /* 微分常数 Derivative Const */ 
}

PID运算

ADC部分继续沿用电流环的采集方法。

使用g_motor_data.location存储编码器计数值,然后进行PID运算。

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */g_motor_data.location = Encode_now;                             /* 获取当前计数总值,用于位置闭环控制 */if (val % SMAPLSE_PID_SPEED == 0)                               /* 进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了 */{ /* PID计算,输出比较值(占空比),再进行一阶低通滤波 */motor_pwm_temp = increment_pid_ctrl(&g_location_pid, g_motor_data.location);g_motor_data.motor_pwm = (int32_t)((g_motor_data.motor_pwm * 0.5) + (motor_pwm_temp * 0.5));if (g_motor_data.motor_pwm >= 4200)                     /* 限制占空比 */{g_motor_data.motor_pwm = 4200;}else if (g_motor_data.motor_pwm <= -4200){g_motor_data.motor_pwm = -4200;}motor_pwm_set(g_motor_data.motor_pwm);                  /* 设置占空比(电机转速)*/}val = 0;
}
val ++;

速度+位置双环控制实现

双环控制中,外环控制的是优先考虑对象,内环用于对控制效果进行优化。

Tips:双环控制时,外环PID参数调节幅度不能太大,这对于整个曲线的影响很大。 

PID相关函数

/* 定义位置环(外环)PID参数相关宏 */
#define  L_KP      0.18f                 /* P参数 */
#define  L_KI      0.00f                 /* I参数 */
#define  L_KD      0.08f                 /* D参数 *//* 定义速度环(内环)PID参数相关宏 */
#define  S_KP      20.0f                 /* P参数 */
#define  S_KI      10.00f                /* I参数 */
#define  S_KD      0.02f                 /* D参数 */PID_TypeDef  g_location_pid;             /* 位置环PID参数结构体 */
PID_TypeDef  g_speed_pid;                /* 速度环PID参数结构体 *//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{/* 初始化位置环PID参数 */g_location_pid.SetPoint = 0.0;       /* 目标值 */g_location_pid.ActualValue = 0.0;    /* 期望输出值 */g_location_pid.SumError = 0.0;       /* 积分值 */g_location_pid.Error = 0.0;          /* Error[1] */g_location_pid.LastError = 0.0;      /* Error[-1] */g_location_pid.PrevError = 0.0;      /* Error[-2] */g_location_pid.Proportion = L_KP;    /* 比例常数 Proportional Const */g_location_pid.Integral = L_KI;      /* 积分常数 Integral Const */g_location_pid.Derivative = L_KD;    /* 微分常数 Derivative Const */ /* 初始化速度环PID参数 */g_speed_pid.SetPoint = 0.0;          /* 目标值 */g_speed_pid.ActualValue = 0.0;       /* 期望输出值 */g_speed_pid.SumError = 0.0;          /* 积分值 */g_speed_pid.Error = 0.0;             /* Error[1] */g_speed_pid.LastError = 0.0;         /* Error[-1] */g_speed_pid.PrevError = 0.0;         /* Error[-2] */g_speed_pid.Proportion = S_KP;       /* 比例常数 Proportional Const */g_speed_pid.Integral = S_KI;         /* 积分常数 Integral Const */g_speed_pid.Derivative = S_KD;       /* 微分常数 Derivative Const */ }

PID运算

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */if (val % SMAPLSE_PID_SPEED == 0)                               /* 进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了 */{ g_motor_data.location = (float)Encode_now;              /* 获取当前编码器总计数值,用于位置闭环控制 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_location_pid, g_motor_data.location);    /* 位置环PID控制(外环) */if (g_motor_data.motor_pwm >= 150)                      /* 限制外环输出(目标速度) */{g_motor_data.motor_pwm = 150;}else if (g_motor_data.motor_pwm <= -150){g_motor_data.motor_pwm = -150;}g_speed_pid.SetPoint = g_motor_data.motor_pwm;          /* 设置目标速度,外环输出作为内环输入 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed);          /* 速度环PID控制(内环) */if (g_motor_data.motor_pwm >= 8200)                     /* 限制占空比 */{g_motor_data.motor_pwm = 8200;}else if (g_motor_data.motor_pwm <= -8200){g_motor_data.motor_pwm = -8200;}motor_pwm_set(g_motor_data.motor_pwm);                  /* 设置占空比(电机转速) */}val = 0;
}
val ++;

电流+位置双环控制实现

双环控制中,外环控制的是优先考虑对象,内环用于对控制效果进行优化。

Tips:双环控制时,外环PID参数调节幅度不能太大,这对于整个曲线的影响很大。 

PID相关函数

#define  INCR_LOCT_SELECT  0         /* 0:位置式,1:增量式 *//* 注意:双环控制的时候,外环PID参数调节幅度不要太大,这对于整个曲线的影响很大 */#if INCR_LOCT_SELECT/* 定义位置环PID参数相关宏 */
#define  L_KP      0.18f             /* P参数 */
#define  L_KI      0.00f             /* I参数 */
#define  L_KD      0.08f             /* D参数 *//* 定义电流环PID参数相关宏 */
#define  C_KP      10.00f            /* P参数 */
#define  C_KI      9.00f             /* I参数 */
#define  C_KD      0.00f             /* D参数 */
#define SMAPLSE_PID_SPEED  50        /* 采样周期 单位ms */#else/* 定义位置环PID参数相关宏 */
#define  L_KP      0.18f             /* P参数 */
#define  L_KI      0.00f             /* I参数 */
#define  L_KD      0.08f             /* D参数 *//* 定义电流环PID参数相关宏 */
#define  C_KP      10.00f            /* P参数 */
#define  C_KI      7.00f             /* I参数 */
#define  C_KD      0.00f             /* D参数 */
#define SMAPLSE_PID_SPEED  50        /* 采样周期 单位ms */#endifPID_TypeDef  g_location_pid;             /* 位置环PID参数结构体 */
PID_TypeDef  g_current_pid;              /* 电流环PID参数结构体 *//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{/* 初始化位置环PID参数 */g_location_pid.SetPoint = 0.0;       /* 目标值 */g_location_pid.ActualValue = 0.0;    /* 期望输出值 */g_location_pid.SumError = 0.0;       /* 积分值 */g_location_pid.Error = 0.0;          /* Error[1] */g_location_pid.LastError = 0.0;      /* Error[-1] */g_location_pid.PrevError = 0.0;      /* Error[-2] */g_location_pid.Proportion = L_KP;    /* 比例常数 Proportional Const */g_location_pid.Integral = L_KI;      /* 积分常数 Integral Const */g_location_pid.Derivative = L_KD;    /* 微分常数 Derivative Const */ /* 初始化电流环PID参数 */g_current_pid.SetPoint = 0.0;        /* 目标值 */g_current_pid.ActualValue = 0.0;     /* 期望输出值 */g_current_pid.SumError = 0.0;        /* 积分值*/g_current_pid.Error = 0.0;           /* Error[1]*/g_current_pid.LastError = 0.0;       /* Error[-1]*/g_current_pid.PrevError = 0.0;       /* Error[-2]*/g_current_pid.Proportion = C_KP;     /* 比例常数 Proportional Const */g_current_pid.Integral = C_KI;       /* 积分常数 Integral Const */g_current_pid.Derivative = C_KD;     /* 微分常数 Derivative Const */
}

PID运算

电流在内环,不能有负数。需对传入的位置环做处理。 

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */if (val % SMAPLSE_PID_SPEED == 0)                               /* 进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了 */{ g_motor_data.location = (float)Encode_now;              /* 获取当前编码器总计数值,用于位置闭环控制 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_location_pid, g_motor_data.location);    /* 位置环PID控制(外环) */if ( g_motor_data.motor_pwm > 0)                        /* 判断位置环输出值是否为正数 */{dcmotor_dir(0);                                     /* 输出为正数,设置电机正转 */}else{g_motor_data.motor_pwm = -g_motor_data.motor_pwm;   /* 输出为负数,取反 */dcmotor_dir(1);                                     /* 设置电机反转 */}if (g_motor_data.motor_pwm >= 120)                      /* 限制外环输出(目标电流) */{g_motor_data.motor_pwm = 120;}g_current_pid.SetPoint = g_motor_data.motor_pwm;        /* 设置目标电流,外环输出作为内环输入 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_current_pid, g_motor_data.current);      /* 电流环PID控制(内环) */if (g_motor_data.motor_pwm >= 8200)                     /* 限制占空比 */{g_motor_data.motor_pwm = 8200;}else if (g_motor_data.motor_pwm <= 0){g_motor_data.motor_pwm = 0;}dcmotor_speed(g_motor_data.motor_pwm);                  /* 设置占空比 */}val = 0;
}
val ++;

电流+速度双环控制实现

双环控制中,外环控制的是优先考虑对象,内环用于对控制效果进行优化。

Tips:双环控制时,外环PID参数调节幅度不能太大,这对于整个曲线的影响很大。 

PID相关函数

#define  INCR_LOCT_SELECT  0         /* 0:位置式,1:增量式 *//* 注意:双环控制的时候,外环PID参数调节幅度不要太大,这对于整个曲线的影响很大 */#if INCR_LOCT_SELECT/* 定义速度环(外环)PID参数相关宏 */
#define  S_KP      1.500f            /* P参数 */
#define  S_KI      0.023f            /* I参数 */
#define  S_KD      0.010f            /* D参数 *//* 定义电流环(内环)PID参数相关宏 */
#define  C_KP      1.00f             /* P参数 */
#define  C_KI      3.00f             /* I参数 */
#define  C_KD      0.00f             /* D参数 */
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms */#else/*定义速度环(外环)PID参数相关宏*/
#define  S_KP      1.500f            /* P参数 */
#define  S_KI      0.023f            /* I参数 */
#define  S_KD      0.002f            /* D参数 *//* 定义电流环(内环)PID参数相关宏 */
#define  C_KP      1.00f             /* P参数 */
#define  C_KI      3.75f             /* I参数 */
#define  C_KD      0.00f             /* D参数 */
#define  SMAPLSE_PID_SPEED  50       /* 采样周期 单位ms */#endifPID_TypeDef  g_speed_pid;       /* 速度环PID参数结构体 */
PID_TypeDef  g_current_pid;     /* 电流环PID参数结构体 *//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{/* 初始化速度环PID参数 */g_speed_pid.SetPoint = 0;           /* 目标值 */g_speed_pid.ActualValue = 0.0;      /* 期望输出值 */g_speed_pid.SumError = 0.0;         /* 积分值 */g_speed_pid.Error = 0.0;            /* Error[1] */g_speed_pid.LastError = 0.0;        /* Error[-1] */g_speed_pid.PrevError = 0.0;        /* Error[-2] */g_speed_pid.Proportion = S_KP;      /* 比例常数 Proportional Const */g_speed_pid.Integral = S_KI;        /* 积分常数 Integral Const */g_speed_pid.Derivative = S_KD;      /* 微分常数 Derivative Const *//* 初始化电流环PID参数 */g_current_pid.SetPoint = 0.0;       /* 目标值 */g_current_pid.ActualValue = 0.0;    /* 期望输出值 */g_current_pid.SumError = 0.0;       /* 积分值*/g_current_pid.Error = 0.0;          /* Error[1]*/g_current_pid.LastError = 0.0;      /* Error[-1]*/g_current_pid.PrevError = 0.0;      /* Error[-2]*/g_current_pid.Proportion = C_KP;    /* 比例常数 Proportional Const */g_current_pid.Integral = C_KI;      /* 积分常数 Integral Const */g_current_pid.Derivative = C_KD;    /* 微分常数 Derivative Const */
}

PID运算

电流在内环,不能有负数。需对传入的速度环做处理。 

还增加了积分限幅处理,即给定可接受的偏差累计值最大值和最小值。

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */if (val % SMAPLSE_PID_SPEED == 0)                               /* 进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了 */{ integral_limit( &g_speed_pid , 7000 , -7000 );          /* 速度环积分限幅 */integral_limit( &g_current_pid , 2500 , -2500 );        /* 电流环积分限幅 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed);         /* 速度环PID控制(外环) */if ( g_motor_data.motor_pwm > 0)                        /* 判断速度环输出值是否为正数 */{dcmotor_dir(0);                                     /* 输出为正数,设置电机正转 */}else{g_motor_data.motor_pwm = -g_motor_data.motor_pwm;   /* 目标电流不能为负数,速度环输出要取反 */dcmotor_dir(1);                                     /* 设置电机反转 */}if (g_motor_data.motor_pwm >= 200)                      /* 限制外环输出(目标电流) */{g_motor_data.motor_pwm = 200;}g_current_pid.SetPoint = g_motor_data.motor_pwm;        /* 设置目标电流,外环输出作为内环输入 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_current_pid, g_motor_data.current);      /* 电流环PID控制(内环) */if (g_motor_data.motor_pwm >= 8200)                     /* 限制占空比 */{g_motor_data.motor_pwm = 8200;}else if (g_motor_data.motor_pwm <= 0)                   /* 滤掉无效输出 */{g_motor_data.motor_pwm = 0;}dcmotor_speed(g_motor_data.motor_pwm);                  /* 设置占空比 */}val = 0;
}
val ++;

电流+速度+位置三环控制实现

PID相关函数 

/* 定义位置环PID参数相关宏 */
#define  L_KP      0.06f             /* P参数 */
#define  L_KI      0.00f             /* I参数 */
#define  L_KD      0.01f             /* D参数 *//* 定义速度环PID参数相关宏 */
#define  S_KP      5.00f             /* P参数 */
#define  S_KI      0.30f             /* I参数 */
#define  S_KD      0.01f             /* D参数 *//* 定义电流环PID参数相关宏 */
#define  C_KP      8.00f             /* P参数 */
#define  C_KI      4.00f             /* I参数 */
#define  C_KD      1.00f             /* D参数 */PID_TypeDef  g_location_pid;    /* 位置环PID参数结构体 */
PID_TypeDef  g_speed_pid;       /* 速度环PID参数结构体 */
PID_TypeDef  g_current_pid;     /* 电流环PID参数结构体 *//*** @brief       pid初始化* @param       无* @retval      无*/
void pid_init(void)
{/* 初始化位置环PID参数 */g_location_pid.SetPoint = 0.0;      /* 目标值 */g_location_pid.ActualValue = 0.0;   /* 期望输出值 */g_location_pid.SumError = 0.0;      /* 积分值*/g_location_pid.Error = 0.0;         /* Error[1]*/g_location_pid.LastError = 0.0;     /* Error[-1]*/g_location_pid.PrevError = 0.0;     /* Error[-2]*/g_location_pid.Proportion = L_KP;   /* 比例常数 Proportional Const */g_location_pid.Integral = L_KI;     /* 积分常数 Integral Const */g_location_pid.Derivative = L_KD;   /* 微分常数 Derivative Const *//* 初始化速度环PID参数 */g_speed_pid.SetPoint = 0.0;         /* 目标值 */g_speed_pid.ActualValue = 0.0;      /* 期望输出值 */g_speed_pid.SumError = 0.0;         /* 积分值 */g_speed_pid.Error = 0.0;            /* Error[1] */g_speed_pid.LastError = 0.0;        /* Error[-1] */g_speed_pid.PrevError = 0.0;        /* Error[-2] */g_speed_pid.Proportion = S_KP;      /* 比例常数 Proportional Const */g_speed_pid.Integral = S_KI;        /* 积分常数 Integral Const */g_speed_pid.Derivative = S_KD;      /* 微分常数 Derivative Const *//* 初始化电流环PID参数 */g_current_pid.SetPoint = 0.0;       /* 目标值 */g_current_pid.ActualValue = 0.0;    /* 期望输出值 */g_current_pid.SumError = 0.0;       /* 积分值*/g_current_pid.Error = 0.0;          /* Error[1]*/g_current_pid.LastError = 0.0;      /* Error[-1]*/g_current_pid.PrevError = 0.0;      /* Error[-2]*/g_current_pid.Proportion = C_KP;    /* 比例常数 Proportional Const */g_current_pid.Integral = C_KI;      /* 积分常数 Integral Const */g_current_pid.Derivative = C_KD;    /* 微分常数 Derivative Const */
}

PID运算

int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */speed_computer(Encode_now, 5);                                  /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */if (val % SMAPLSE_PID_SPEED == 0)                               /* 进行一次pid计算 */
{if (g_run_flag)                                             /* 判断电机是否启动了 */{ g_motor_data.location = (float)Encode_now;              /* 获取当前编码器总计数值,用于位置闭环控制 */integral_limit(&g_location_pid , 1000 ,-1000);          /* 位置环积分限幅 */integral_limit(&g_speed_pid , 200 ,-200);               /* 速度环积分限幅 */integral_limit(&g_current_pid , 150 ,-150);             /* 电流环积分限幅 */if( (g_location_pid.Error <= 20) && (g_location_pid.Error >= -20) )                     /* 设置闭环死区 */{g_location_pid.Error = 0;                           /* 偏差太小了,直接清零 */g_location_pid.SumError = 0;                        /* 清除积分 */}g_motor_data.motor_pwm = increment_pid_ctrl(&g_location_pid, g_motor_data.location);    /* 位置环PID控制(最外环) */if (g_motor_data.motor_pwm >= 120)                      /* 限制外环输出(目标速度) */{g_motor_data.motor_pwm = 120;}else if (g_motor_data.motor_pwm <= -120){g_motor_data.motor_pwm = -120;}g_speed_pid.SetPoint = g_motor_data.motor_pwm;          /* 设置目标速度,外环输出作为内环输入 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed);         /* 速度环PID控制(次外环) */if ( g_motor_data.motor_pwm > 0)                        /* 判断速度环输出值是否为正数 */{dcmotor_dir(0);                                     /* 输出为正数,设置电机正转 */}else{g_motor_data.motor_pwm = -g_motor_data.motor_pwm;   /* 输出取反 */dcmotor_dir(1);                                     /* 设置电机反转 */}if (g_motor_data.motor_pwm >= 100)                      /* 限制外环输出(目标电流) */{g_motor_data.motor_pwm = 100;}g_current_pid.SetPoint = g_motor_data.motor_pwm;        /* 设置目标电流,外环输出作为内环输入 */g_motor_data.motor_pwm = increment_pid_ctrl(&g_current_pid, g_motor_data.current);      /* 电流环PID控制(内环) */if (g_motor_data.motor_pwm >= 8200)                     /* 限制占空比 */{g_motor_data.motor_pwm = 8200;}else if (g_motor_data.motor_pwm <= 0)                   /* 滤掉无效输出 */{g_motor_data.motor_pwm = 0;}dcmotor_speed(g_motor_data.motor_pwm);                  /* 设置占空比 */}val = 0;
}
val ++;

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

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

相关文章

Shell管道和过滤器

一、Shell管道 Shell 还有一种功能&#xff0c;就是可以将两个或者多个命令&#xff08;程序或者进程&#xff09;连接到一起&#xff0c;把一个命令的输出作为下一个命令的输入&#xff0c;以这种方式连接的两个或者多个命令就形成了管道&#xff08;pipe&#xff09;。 重定…

数仓实战——京东数据指标体系的构建与实践

目录 一、如何理解指标体系 1.1 指标和指标体系的基本含义 1.2 指标和和标签的区别 1.3 指标体系在数据链路中的位置和作用 1.4 流量指标体系 1.5 指标体系如何向上支撑业务应用 1.6 指标体系背后的数据加工逻辑 二、如何搭建和应用指标体系 2.1 指标体系建设方法—OS…

IDEA中Maven无法下载jar包问题解决

在项目中经常会遇到jar包无法下载的问题&#xff0c;可以根据以下几种方法进行排查。 1. 排查网络连接 网络连接失败&#xff0c;会导致远程访问Maven仓库失败&#xff0c;所以应确保网络连接正常。 2. 排查Maven的配置 Maven配置文件&#xff08;settings.xml&#xff09;…

【C语言基础】:深入理解指针(二)

文章目录 深入理解指针一、指针运算1. 指针 - 整数2. 指针 - 指针3. 指针的关系运算 二、野指针1. 野指针成因2. 如何避免野指针 三、assert断言四、指针的使用和传址调用4.1 strlen的模拟实现4.2 传值调用和传址调用 五、指针与数组5.1 数组名的理解5.2 指针访问数组5.3 一维数…

JeeSite Vue3:前端开发的未来之路

JeeSite Vue3&#xff1a;前端开发的未来之路 随着技术的飞速发展&#xff0c;前端开发技术日新月异。在这个背景下&#xff0c;JeeSite Vue3 作为一个基于 Vue3、Vite、Ant-Design-Vue、TypeScript 和 Vue Vben Admin 的前端框架&#xff0c;引起了广泛关注。它凭借其先进的技…

web小游戏,蜘蛛纸牌

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的订阅后,私信本人,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、贪吃蛇、飞机大战、坦克大战、开心消消乐、扑鱼达人、扫雷、打地鼠、斗地主等等。 <!DOCTYPE h…

Python图像处理【21】基于卷积神经网络增强微光图像

基于卷积神经网络增强微光图像 0. 前言1. MBLLEN 网络架构2. 增强微光图像小结系列链接 0. 前言 在本节中&#xff0c;我们将学习如何基于预训练的深度学习模型执行微光/夜间图像增强。由于难以同时处理包括亮度、对比度、伪影和噪声在内的所有因素&#xff0c;因此微光图像增…

排序——堆排序

本节继续复习排序算法。这次复习排序算法中的堆排序。 堆排序属于选择排序。 目录 什么是堆&#xff1f; 堆排序 堆排序的思想 堆排代码 向下调整算法 堆排整体 什么是堆&#xff1f; 在复习堆排序之前&#xff0c; 首先我们需要回顾一下什么是堆 。 堆的本质其实是一个数…

游戏引擎用什么语言开发上层应用

现在主流的游戏引擎包括&#xff1a; 1、Unity3D&#xff0c;C#语言&#xff0c;优点在于支持几乎所有平台 丹麦创立的一家公司&#xff0c;现已被微软收购。在中国市场占有率最高&#xff0c;也是社群很强大&#xff0c;一般解决方案也能在网上找到&#xff0c;教程丰富。物理…

Golang pprof 分析程序的使用内存和执行时间

一、分析程序执行的内存情况 package mainimport ("os""runtime/pprof" )func main() {// ... 你的程序逻辑 ...// 将 HeapProfile 写入文件f, err : os.Create("heap.prof")if err ! nil {panic(err)}defer f.Close()pprof.WriteHeapProfile(f…

139.乐理基础-一四五八度为何用纯?

上一个内容&#xff1a;138.乐理基础-等音、等音程的意义-CSDN博客 上一个内容里练习的答案&#xff1a; 以乐理里写的知识&#xff0c;没办法完全解释透彻 一四五八度为何用纯&#xff1f;这个问题&#xff0c;要透彻的话要从各个文明怎么发现音高、发明音高、制定规则等&…

Vue3+element-plus复杂表单分组处理

一、为什么表单要分组处理&#xff1f; 方便表单字段的复用&#xff1a;例如&#xff0c;你的表单有十个字段会在很多的表单都会用到&#xff0c;那么表单则需要进行分组进行表单复用&#xff1b;实现不同角色的表单权限控制&#xff1a;例如一个表单有60个字段&#xff0c;角…

VisualStudio 2022的安装

1.IDE 推荐最新版VisualStudio2022&#xff0c;功能十分强大&#xff0c;社区版就够用了。下载地址 2.安装 工作负载选择桌面开发&#xff0c;Web开发可以暂时不选中&#xff08;大部分都用不到&#xff09;。 单个组件选中NET 6.0和NET Frameword4.6.1 也就可以了。 后面安…

14-RPC-自研微服务框架

RPC RPC 框架是分布式领域核心组件&#xff0c;也是微服务的基础。 RPC &#xff08;Remote Procedure Call&#xff09;全称是远程过程调用&#xff0c;相对于本地方法调用&#xff0c;在同一内存空间可以直接通过方法栈实现调用&#xff0c;远程调用则跨了不同的服务终端&a…

汽车零部件制造中的信息抽取技术:提升效率与质量的关键

一、引言 在汽车制造业中&#xff0c;零部件的生产是整个制造流程的关键一环。这些零部件&#xff0c;包括但不限于制动系统、转向系统和传动系统&#xff0c;是确保汽车安全、可靠运行的基础。为了满足现代汽车工业对效率和质量的严格要求&#xff0c;制造商们纷纷投入到高度…

Jetpack Compose: Hello Android

Jetpack Compose 是一个现代化的工具包&#xff0c;用于使用声明式方法构建原生 Android UI。在本博文中&#xff0c;我们将深入了解一个基本的 “Hello Android” 示例&#xff0c;以帮助您开始使用 Jetpack Compose。我们将探讨所提供代码片段中使用的函数和注解。 入门 在…

软件测试--性能测试工具JMeter

软件测试--性能测试工具JMeter 主流性能测试工具1.主流性能测试工具Loadrunner和Jmeter对比 —— 相同点2.主流性能测试工具Loadrunner和Jmeter对比 —— 不同点JMeter基本使用JMeter环境搭建1.安装JDK:2.安装Jmeter:3.注意点:JMeter功能概要1. JMeter文件目录介绍1.1 bin目…

瑞_23种设计模式_享元模式

文章目录 1 享元模式&#xff08;Flyweight Pattern&#xff09;1.1 介绍1.2 概述1.3 享元模式的结构1.4 享元模式的优缺点1.5 享元模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析&#xff08;Integer类&#xff09; &#x1f64a; …

13-Java代理模式 ( Proxy Pattern )

Java代理模式 摘要实现范例 代理模式&#xff08;Proxy Pattern&#xff09;使用一个类代表另一个类的功能 代理模式创建具有现有对象的对象&#xff0c;以便向外界提供功能接口 代理模式属于结构型模式 摘要 1. 意图 为其他对象提供一种代理以控制对这个对象的访问2. 主…

力扣206反转链表

206.反转链表 力扣题目链接(opens new window) 题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 1&#xff0c;双指针 2&#xff0c;递归。递归参考双指针更容易写&#xff0c; 为什么不用头插…