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


    目录

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

    初始化工作

    电机基础驱动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,电机停止。

    初始化工作

    1. TIM_HandleTypeDef g_atimx_cplm_pwm_handle; /* 定时器x句柄 */
    2. /**
    3. * @brief 高级定时器TIMX 互补输出 初始化函数(使用PWM模式1)
    4. * @note
    5. * 配置高级定时器TIMX 互补输出, 一路OCy 一路OCyN, 并且可以设置死区时间
    6. *
    7. * 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
    8. * 高级定时器时钟 = 168Mhz
    9. * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
    10. * Ft=定时器工作频率, 单位 : Mhz
    11. *
    12. * @param arr: 自动重装值。
    13. * @param psc: 时钟预分频数
    14. * @retval 无
    15. */
    16. void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
    17. {
    18. TIM_OC_InitTypeDef sConfigOC ;
    19. TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
    20. g_atimx_cplm_pwm_handle.Instance = TIM1; /* 定时器x */
    21. g_atimx_cplm_pwm_handle.Init.Prescaler = psc; /* 定时器预分频系数 */
    22. g_atimx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
    23. g_atimx_cplm_pwm_handle.Init.Period = arr; /* 自动重装载值 */
    24. g_atimx_cplm_pwm_handle.Init.RepetitionCounter = 0; /* 重复计数器寄存器为0 */
    25. g_atimx_cplm_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器TIMx_ARR */
    26. HAL_TIM_PWM_Init(&g_atimx_cplm_pwm_handle) ;
    27. /* 设置PWM输出 */
    28. sConfigOC.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
    29. sConfigOC.Pulse = 0; /* 比较值为0 */
    30. sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; /* OCy 低电平有效 */
    31. sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW; /* OCyN 低电平有效 */
    32. sConfigOC.OCFastMode = TIM_OCFAST_ENABLE; /* 使用快速模式 */
    33. sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; /* 主通道的空闲状态 */
    34. sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 互补通道的空闲状态 */
    35. HAL_TIM_PWM_ConfigChannel(&g_atimx_cplm_pwm_handle, &sConfigOC, ATIM_TIMX_CPLM_CHY); /* 配置后默认清CCER的互补输出位 */
    36. /* 设置死区参数,开启死区中断 */
    37. sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE; /* OSSR设置为1 */
    38. sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; /* OSSI设置为0 */
    39. sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; /* 上电只能写一次,需要更新死区时间时只能用此值 */
    40. sBreakDeadTimeConfig.DeadTime = 0X0F; /* 死区时间 */
    41. sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; /* BKE = 0, 关闭BKIN检测 */
    42. sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW; /* BKP = 1, BKIN低电平有效 */
    43. sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; /* 使能AOE位,允许刹车后自动恢复输出 */
    44. HAL_TIMEx_ConfigBreakDeadTime(&g_atimx_cplm_pwm_handle, &sBreakDeadTimeConfig); /* 设置BDTR寄存器 */
    45. }
    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引脚拉低。

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

    电机旋转方向设置:

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

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

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

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

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

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

    电机控制:电机旋转方向设置+电机速度控制。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的初始化。转换顺序为电压-温度-电流。

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

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

    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。

    采集工作

    电压采集:

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

    温度采集:

    1. /*
    2. Rt = Rp *exp(B*(1/T1-1/T2))
    3. Rt 是热敏电阻在T1温度下的阻值;
    4. Rp是热敏电阻在T2常温下的标称阻值;
    5. exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于 2.7182818;
    6. B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380;
    7. 这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)K
    8. T1就是所求的温度
    9. */
    10. const float Rp = 10000.0f; /* 10K */
    11. const float T2 = (273.15f + 25.0f); /* T2 */
    12. const float Bx = 3380.0f; /* B */
    13. const float Ka = 273.15f;
    14. /**
    15. * @brief 计算温度值
    16. * @param para: 温度采集对应ADC通道的值(已滤波)
    17. * @note 计算温度分为两步:
    18. 1.根据ADC采集到的值计算当前对应的Rt
    19. 2.根据Rt计算对应的温度值
    20. * @retval 温度值
    21. */
    22. float get_temp(uint16_t para)
    23. {
    24. float Rt;
    25. float temp;
    26. /*
    27. 第一步:
    28. Rt = 3.3 * 4700 / VTEMP - 4700 ,其中VTEMP就是温度检测通道采集回来的电压值,VTEMP = ADC值* 3.3/4096
    29. 由此我们可以计算出当前Rt的值:Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f;
    30. */
    31. Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f; /* 根据当前ADC值计算出Rt的值 */
    32. /*
    33. 第二步:
    34. 根据当前Rt的值来计算对应温度值:Rt = Rp *exp(B*(1/T1-1/T2))
    35. */
    36. temp = Rt / Rp; /* 解出exp(B*(1/T1-1/T2)) ,即temp = exp(B*(1/T1-1/T2)) */
    37. temp = log(temp); /* 解出B*(1/T1-1/T2) ,即temp = B*(1/T1-1/T2) */
    38. temp /= Bx; /* 解出1/T1-1/T2 ,即temp = 1/T1-1/T2 */
    39. temp += (1.0f / T2); /* 解出1/T1 ,即temp = 1/T1 */
    40. temp = 1.0f / (temp); /* 解出T1 ,即temp = T1 */
    41. temp -= Ka; /* 计算T1对应的摄氏度 */
    42. return temp; /* 返回温度值 */
    43. }
    44. printf("Temp:%.1fC \r\n", get_temp(g_adc_val[1])); /* 打印温度值*/

    电流采集:

    1. /* 电流计算公式:
    2. * I=(最终输出电压-初始参考电压)/(6*0.02)A
    3. * ADC值转换为电压值:电压=ADC值*3.3/4096,这里电压单位为V,我们换算成mV,4096/1000=4.096,后面就直接算出为mA
    4. * 整合公式可以得出电流 I= (当前ADC值-初始参考ADC值)* (3.3 / 4.096 / 0.12)
    5. */
    6. #define ADC2CURT (float)(3.3f / 4.096f / 0.12f)
    7. uint16_t init_adc_val;
    8. /* init_adc_val存储电流测量对应的参考电压ADC值,这里进行滤波 */
    9. init_adc_val = g_adc_val[2]; /* 取出第一次得到的值 */
    10. for(t=0;t<1000;t++)
    11. {
    12. init_adc_val += g_adc_val[2]; /* 现在的值和上一次存储的值相加 */
    13. init_adc_val /= 2; /* 取平均值 */
    14. delay_ms(1);
    15. }
    16. 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. /********************************* 1 通用定时器 编码器程序 *************************************/
    2. TIM_HandleTypeDef g_timx_encode_chy_handle; /* 定时器x句柄 */
    3. TIM_Encoder_InitTypeDef g_timx_encoder_chy_handle; /* 定时器编码器句柄 */
    4. /**
    5. * @brief 通用定时器TIMX 通道Y 编码器接口模式 初始化函数
    6. * @note
    7. * 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
    8. * 通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
    9. * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
    10. * Ft=定时器工作频率,单位:Mhz
    11. *
    12. * @param arr: 自动重装值。
    13. * @param psc: 时钟预分频数
    14. * @retval 无
    15. */
    16. void gtim_timx_encoder_chy_init(uint16_t arr, uint16_t psc)
    17. {
    18. /* 定时器x配置 */
    19. g_timx_encode_chy_handle.Instance = GTIM_TIMX_ENCODER; /* 定时器x */
    20. g_timx_encode_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
    21. g_timx_encode_chy_handle.Init.Period = arr; /* 自动重装载值 */
    22. g_timx_encode_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */
    23. /* 定时器x编码器配置 */
    24. g_timx_encoder_chy_handle.EncoderMode = TIM_ENCODERMODE_TI12; /* TI1、TI2都检测,4倍频 */
    25. g_timx_encoder_chy_handle.IC1Polarity = TIM_ICPOLARITY_RISING; /* 输入极性,非反向 */
    26. g_timx_encoder_chy_handle.IC1Selection = TIM_ICSELECTION_DIRECTTI; /* 输入通道选择 */
    27. g_timx_encoder_chy_handle.IC1Prescaler = TIM_ICPSC_DIV1; /* 不分频 */
    28. g_timx_encoder_chy_handle.IC1Filter = 10; /* 滤波器设置 */
    29. g_timx_encoder_chy_handle.IC2Polarity = TIM_ICPOLARITY_RISING; /* 输入极性,非反向 */
    30. g_timx_encoder_chy_handle.IC2Selection = TIM_ICSELECTION_DIRECTTI; /* 输入通道选择 */
    31. g_timx_encoder_chy_handle.IC2Prescaler = TIM_ICPSC_DIV1; /* 不分频 */
    32. g_timx_encoder_chy_handle.IC2Filter = 10; /* 滤波器设置 */
    33. HAL_TIM_Encoder_Init(&g_timx_encode_chy_handle, &g_timx_encoder_chy_handle);/* 初始化定时器x编码器 */
    34. HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle,GTIM_TIMX_ENCODER_CH1); /* 使能编码器通道1 */
    35. HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle,GTIM_TIMX_ENCODER_CH2); /* 使能编码器通道2 */
    36. __HAL_TIM_ENABLE_IT(&g_timx_encode_chy_handle,TIM_IT_UPDATE); /* 使能更新中断 */
    37. __HAL_TIM_CLEAR_FLAG(&g_timx_encode_chy_handle,TIM_IT_UPDATE); /* 清除更新中断标志位 */
    38. }
    39. /******************************** 2 基本定时器 编码器程序 ************************************/
    40. TIM_HandleTypeDef timx_handler; /* 定时器参数句柄 */
    41. /**
    42. * @brief 基本定时器TIMX定时中断初始化函数
    43. * @note
    44. * 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
    45. * 基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
    46. * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
    47. * Ft=定时器工作频率,单位:Mhz
    48. *
    49. * @param arr: 自动重装值。
    50. * @param psc: 时钟预分频数
    51. * @retval 无
    52. */
    53. void btim_timx_int_init(uint16_t arr, uint16_t psc)
    54. {
    55. timx_handler.Instance = BTIM_TIMX_INT; /* 基本定时器X */
    56. timx_handler.Init.Prescaler = psc; /* 设置预分频器 */
    57. timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */
    58. timx_handler.Init.Period = arr; /* 自动装载值 */
    59. timx_handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */
    60. HAL_TIM_Base_Init(&timx_handler);
    61. HAL_TIM_Base_Start_IT(&timx_handler); /* 使能基本定时器x和及其更新中断:TIM_IT_UPDATE */
    62. __HAL_TIM_CLEAR_IT(&timx_handler,TIM_IT_UPDATE); /* 清除更新中断标志位 */
    63. }
    1. gtim_timx_encoder_chy_init(0XFFFF, 0); /* 编码器定时器初始化,不分频直接84M的计数频率 */
    2. 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清除更新中断标志位。

    编码器测速工作

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

    1. volatile int g_timx_encode_count = 0; /* 溢出次数 */
    2. /**
    3. * @brief 获取编码器的值
    4. * @param 无
    5. * @retval 编码器值
    6. */
    7. int gtim_get_encode(void)
    8. {
    9. return ( int32_t )__HAL_TIM_GET_COUNTER(&g_timx_encode_chy_handle) + g_timx_encode_count * 65536; /* 当前计数值+之前累计编码器的值=总的编码器值 */
    10. }
    11. /**
    12. * @brief 定时器更新中断回调函数
    13. * @param htim:定时器句柄指针
    14. * @note 此函数会被定时器中断函数共同调用的
    15. * @retval 无
    16. */
    17. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    18. {
    19. if (htim->Instance == TIM3)
    20. {
    21. if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_timx_encode_chy_handle)) /* 判断CR1的DIR位 */
    22. {
    23. g_timx_encode_count--; /* DIR位为1,也就是递减计数 */
    24. }
    25. else
    26. {
    27. g_timx_encode_count++; /* DIR位为0,也就是递增计数 */
    28. }
    29. }
    30. else if (htim->Instance == TIM6)
    31. {
    32. int Encode_now = gtim_get_encode(); /* 获取编码器值,用于计算速度 */
    33. speed_computer(Encode_now, 50); /* 中位平均值滤除编码器抖动数据,50ms计算一次速度*/
    34. }
    35. }

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

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

    1. /************************************* 第三部分 编码器测速 ****************************************************/
    2. #define ROTO_RATIO 44 /* 线数*倍频系数,即11*4=44 */
    3. #define REDUCTION_RATIO 30 /* 减速比30:1 */
    4. /* 电机参数结构体 */
    5. typedef struct
    6. {
    7. uint8_t state; /*电机状态*/
    8. float current; /*电机电流*/
    9. float volatage; /*电机电压*/
    10. float power; /*电机功率*/
    11. float speed; /*电机实际速度*/
    12. int32_t motor_pwm; /*设置比较值大小 */
    13. } Motor_TypeDef;
    14. Motor_TypeDef g_motor_data; /*电机参数变量*/
    15. ENCODE_TypeDef g_encode; /*编码器参数变量*/
    16. /**
    17. * @brief 电机速度计算
    18. * @param encode_now:当前编码器总的计数值
    19. * ms:计算速度的间隔,中断1ms进入一次,例如ms = 5即5ms计算一次速度
    20. * @retval 无
    21. */
    22. void speed_computer(int32_t encode_now, uint8_t ms)
    23. {
    24. uint8_t i = 0, j = 0;
    25. float temp = 0.0;
    26. static uint8_t sp_count = 0, k = 0;
    27. static float speed_arr[10] = {0.0}; /* 存储速度进行滤波运算 */
    28. if (sp_count == ms) /* 计算一次速度 */
    29. {
    30. /* 计算电机转速
    31. 第一步 :计算ms毫秒内计数变化量
    32. 第二步 ;计算1min内计数变化量:g_encode.speed * ((1000 / ms) * 60 ,
    33. 第三步 :除以编码器旋转一圈的计数次数(倍频倍数 * 编码器分辨率)
    34. 第四步 :除以减速比即可得出电机转速
    35. */
    36. g_encode.encode_now = encode_now; /* 取出编码器当前计数值 */
    37. g_encode.speed = (g_encode.encode_now - g_encode.encode_old); /* 计算编码器计数值的变化量 */
    38. speed_arr[k++] = (float)(g_encode.speed * ((1000 / ms) * 60.0) / REDUCTION_RATIO / ROTO_RATIO ); /* 保存电机转速 */
    39. g_encode.encode_old = g_encode.encode_now; /* 保存当前编码器的值 */
    40. /* 累计10次速度值,后续进行滤波*/
    41. if (k == 10)
    42. {
    43. for (i = 10; i >= 1; i--) /* 冒泡排序*/
    44. {
    45. for (j = 0; j < (i - 1); j++)
    46. {
    47. if (speed_arr[j] > speed_arr[j + 1]) /* 数值比较 */
    48. {
    49. temp = speed_arr[j]; /* 数值换位 */
    50. speed_arr[j] = speed_arr[j + 1];
    51. speed_arr[j + 1] = temp;
    52. }
    53. }
    54. }
    55. temp = 0.0;
    56. for (i = 2; i < 8; i++) /* 去除两边高低数据 */
    57. {
    58. temp += speed_arr[i]; /* 将中间数值累加 */
    59. }
    60. temp = (float)(temp / 6); /*求速度平均值*/
    61. /* 一阶低通滤波
    62. * 公式为:Y(n)= qX(n) + (1-q)Y(n-1)
    63. * 其中X(n)为本次采样值;Y(n-1)为上次滤波输出值;Y(n)为本次滤波输出值,q为滤波系数
    64. * q值越小则上一次输出对本次输出影响越大,整体曲线越平稳,但是对于速度变化的响应也会越慢
    65. */
    66. g_motor_data.speed = (float)( ((float)0.48 * temp) + (g_motor_data.speed * (float)0.52) );
    67. k = 0;
    68. }
    69. sp_count = 0;
    70. }
    71. sp_count ++;
    72. }

    速度环控制实现

    PID相关函数

    1. #if INCR_LOCT_SELECT
    2. /* 增量式PID参数相关宏 */
    3. #define KP 8.50f /* P参数*/
    4. #define KI 5.00f /* I参数*/
    5. #define KD 0.10f /* D参数*/
    6. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms*/
    7. #else
    8. /* 位置式PID参数相关宏 */
    9. #define KP 10.0f /* P参数*/
    10. #define KI 6.00f /* I参数*/
    11. #define KD 0.5f /* D参数*/
    12. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms*/
    13. #endif
    14. /* PID参数结构体 */
    15. typedef struct
    16. {
    17. __IO float SetPoint; /* 设定目标 */
    18. __IO float ActualValue; /* 期望输出值 */
    19. __IO float SumError; /* 误差累计 */
    20. __IO float Proportion; /* 比例常数 P */
    21. __IO float Integral; /* 积分常数 I */
    22. __IO float Derivative; /* 微分常数 D */
    23. __IO float Error; /* Error[1] */
    24. __IO float LastError; /* Error[-1] */
    25. __IO float PrevError; /* Error[-2] */
    26. } PID_TypeDef;
    27. PID_TypeDef g_speed_pid; /* 速度环PID参数结构体 */
    28. /**
    29. * @brief pid初始化
    30. * @param 无
    31. * @retval 无
    32. */
    33. void pid_init(void)
    34. {
    35. g_speed_pid.SetPoint = 0; /* 设定目标值 */
    36. g_speed_pid.ActualValue = 0.0; /* 期望输出值 */
    37. g_speed_pid.SumError = 0.0; /* 积分值 */
    38. g_speed_pid.Error = 0.0; /* Error[1] */
    39. g_speed_pid.LastError = 0.0; /* Error[-1] */
    40. g_speed_pid.PrevError = 0.0; /* Error[-2] */
    41. g_speed_pid.Proportion = KP; /* 比例常数 Proportional Const */
    42. g_speed_pid.Integral = KI; /* 积分常数 Integral Const */
    43. g_speed_pid.Derivative = KD; /* 微分常数 Derivative Const */
    44. }
    45. /**
    46. * @brief pid闭环控制
    47. * @param *PID:PID结构体变量地址
    48. * @param Feedback_value:当前实际值
    49. * @retval 期望输出值
    50. */
    51. int32_t increment_pid_ctrl(PID_TypeDef *PID, float Feedback_value)
    52. {
    53. PID->Error = (float)(PID->SetPoint - Feedback_value); /* 计算偏差 */
    54. #if INCR_LOCT_SELECT /* 增量式PID */
    55. PID->ActualValue += (PID->Proportion * (PID->Error - PID->LastError)) /* 比例环节 */
    56. + (PID->Integral * PID->Error) /* 积分环节 */
    57. + (PID->Derivative * (PID->Error - 2 * PID->LastError + PID->PrevError)); /* 微分环节 */
    58. PID->PrevError = PID->LastError; /* 存储偏差,用于下次计算 */
    59. PID->LastError = PID->Error;
    60. #else /* 位置式PID */
    61. PID->SumError += PID->Error;
    62. PID->ActualValue = (PID->Proportion * PID->Error) /* 比例环节 */
    63. + (PID->Integral * PID->SumError) /* 积分环节 */
    64. + (PID->Derivative * (PID->Error - PID->LastError)); /* 微分环节 */
    65. PID->LastError = PID->Error;
    66. #endif
    67. return ((int32_t)(PID->ActualValue)); /* 返回计算后输出的数值 */
    68. }

    PID运算

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

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

    电流环控制实现

    PID相关函数

    1. #define INCR_LOCT_SELECT 0 /* 0:位置式,1:增量式 */
    2. #if INCR_LOCT_SELECT
    3. /* 增量式PID参数相关宏 */
    4. #define KP 0.0f /* P参数*/
    5. #define KI 6.0f /* I参数*/
    6. #define KD 4.0f /* D参数*/
    7. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms*/
    8. #else
    9. /* 位置式PID参数相关宏 */
    10. #define KP 10.0f /* P参数*/
    11. #define KI 7.0f /* I参数*/
    12. #define KD 2.0f /* D参数*/
    13. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms*/
    14. #endif
    15. /* PID参数结构体 */
    16. typedef struct
    17. {
    18. __IO float SetPoint; /* 设定目标 */
    19. __IO float ActualValue; /* 期望输出值 */
    20. __IO float SumError; /* 误差累计 */
    21. __IO float Proportion; /* 比例常数 P */
    22. __IO float Integral; /* 积分常数 I */
    23. __IO float Derivative; /* 微分常数 D */
    24. __IO float Error; /* Error[1] */
    25. __IO float LastError; /* Error[-1] */
    26. __IO float PrevError; /* Error[-2] */
    27. } PID_TypeDef;
    28. PID_TypeDef g_current_pid; /* 电流环PID参数结构体 */
    29. /**
    30. * @brief pid初始化
    31. * @param 无
    32. * @retval 无
    33. */
    34. void pid_init(void)
    35. {
    36. g_current_pid.SetPoint = 40; /* 设定目标值 */
    37. g_current_pid.ActualValue = 0.0; /* 期望输出值 */
    38. g_current_pid.SumError = 0.0; /* 积分值 */
    39. g_current_pid.Error = 0.0; /* Error[1] */
    40. g_current_pid.LastError = 0.0; /* Error[-1] */
    41. g_current_pid.PrevError = 0.0; /* Error[-2] */
    42. g_current_pid.Proportion = KP; /* 比例常数 Proportional Const */
    43. g_current_pid.Integral = KI; /* 积分常数 Integral Const */
    44. g_current_pid.Derivative = KD; /* 微分常数 Derivative Const */
    45. }

    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,则过滤掉这微弱浮动电流。

    1. uint16_t g_adc_val[ADC_CH_NUM]; /*ADC平均值存放数组*/
    2. /**
    3. * @brief ADC采集中断回调函数
    4. * @param 无
    5. * @retval 无
    6. */
    7. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
    8. {
    9. float temp_c = 0.0;
    10. static float add_adc = 0;
    11. static float init_adc_value = 0;
    12. static uint8_t adc_count1 = 0, adc_count2 = 0;
    13. if ( hadc->Instance == ADC_ADCX ) /* 判断是不是ADC1 */
    14. {
    15. adc_count1++;
    16. HAL_ADC_Stop_DMA(&g_adc_nch_dma_handle); /* 关闭DMA转换 */
    17. calc_adc_val(g_adc_val); /* 计算ADC的平均值 */
    18. add_adc += g_adc_val[2]; /* 取出电流通道对应的ADC值进行累计 */
    19. if (adc_count1 >= 15) /* 累计15次 */
    20. {
    21. add_adc = (float)(add_adc / adc_count1); /* 取平均值 */
    22. if (adc_count2 <= 16) /* 采集16次ADC平均值计算参考电压的ADC值 */
    23. {
    24. adc_count2++;
    25. init_adc_value += add_adc; /* 对平均值累计求和 */
    26. if (adc_count2 == 16) /* 平均值累计16次 */
    27. {
    28. adc_count2 = 17; /* 不再进入 */
    29. init_adc_value = (init_adc_value / 16.0f); /* 存储初始ADC值 */
    30. }
    31. }
    32. if (adc_count2 >= 17) /* 采集完参考ADC值后,采集电流通道当前ADC值 */
    33. {
    34. temp_c = (add_adc - init_adc_value) * ADC2CURT; /* 计算电流 */
    35. g_motor_data.current = (float)((g_motor_data.current * (float)0.60) + ((float)0.40 * temp_c)); /* 一阶低通滤波 */
    36. if (g_motor_data.current <= 20) /* 过滤掉微弱浮动电流 */
    37. {
    38. g_motor_data.current = 0.0;
    39. }
    40. }
    41. add_adc = 0;
    42. adc_count1 = 0;
    43. }
    44. HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)&g_adc_value, (uint32_t)(ADC_SUM)); /* 启动DMA转换 */
    45. }
    46. }

    然后进行电流的PID运算。

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

    位置环控制实现

    PID相关函数

    1. /* 增量式PID参数相关宏 */
    2. #define KP 15.0f /* P参数*/
    3. #define KI 0.00f /* I参数*/
    4. #define KD 7.50f /* D参数*/
    5. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms */
    6. /* PID参数结构体 */
    7. typedef struct
    8. {
    9. __IO float SetPoint; /* 设定目标 */
    10. __IO float ActualValue; /* 期望输出值 */
    11. __IO float SumError; /* 误差累计 */
    12. __IO float Proportion; /* 比例常数 P */
    13. __IO float Integral; /* 积分常数 I */
    14. __IO float Derivative; /* 微分常数 D */
    15. __IO float Error; /* Error[1] */
    16. __IO float LastError; /* Error[-1] */
    17. __IO float PrevError; /* Error[-2] */
    18. } PID_TypeDef;
    19. PID_TypeDef g_location_pid; /*位置环PID参数结构体*/
    20. /**
    21. * @brief pid初始化
    22. * @param 无
    23. * @retval 无
    24. */
    25. void pid_init(void)
    26. {
    27. g_location_pid.SetPoint = 0.0; /* 设定目标值 */
    28. g_location_pid.ActualValue = 0.0; /* 期望输出值 */
    29. g_location_pid.SumError = 0.0; /* 积分值 */
    30. g_location_pid.Error = 0.0; /* Error[1] */
    31. g_location_pid.LastError = 0.0; /* Error[-1] */
    32. g_location_pid.PrevError = 0.0; /* Error[-2] */
    33. g_location_pid.Proportion = KP; /* 比例常数 Proportional Const */
    34. g_location_pid.Integral = KI; /* 积分常数 Integral Const */
    35. g_location_pid.Derivative = KD; /* 微分常数 Derivative Const */
    36. }

    PID运算

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

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

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

    速度+位置双环控制实现

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

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

    PID相关函数

    1. /* 定义位置环(外环)PID参数相关宏 */
    2. #define L_KP 0.18f /* P参数 */
    3. #define L_KI 0.00f /* I参数 */
    4. #define L_KD 0.08f /* D参数 */
    5. /* 定义速度环(内环)PID参数相关宏 */
    6. #define S_KP 20.0f /* P参数 */
    7. #define S_KI 10.00f /* I参数 */
    8. #define S_KD 0.02f /* D参数 */
    9. PID_TypeDef g_location_pid; /* 位置环PID参数结构体 */
    10. PID_TypeDef g_speed_pid; /* 速度环PID参数结构体 */
    11. /**
    12. * @brief pid初始化
    13. * @param 无
    14. * @retval 无
    15. */
    16. void pid_init(void)
    17. {
    18. /* 初始化位置环PID参数 */
    19. g_location_pid.SetPoint = 0.0; /* 目标值 */
    20. g_location_pid.ActualValue = 0.0; /* 期望输出值 */
    21. g_location_pid.SumError = 0.0; /* 积分值 */
    22. g_location_pid.Error = 0.0; /* Error[1] */
    23. g_location_pid.LastError = 0.0; /* Error[-1] */
    24. g_location_pid.PrevError = 0.0; /* Error[-2] */
    25. g_location_pid.Proportion = L_KP; /* 比例常数 Proportional Const */
    26. g_location_pid.Integral = L_KI; /* 积分常数 Integral Const */
    27. g_location_pid.Derivative = L_KD; /* 微分常数 Derivative Const */
    28. /* 初始化速度环PID参数 */
    29. g_speed_pid.SetPoint = 0.0; /* 目标值 */
    30. g_speed_pid.ActualValue = 0.0; /* 期望输出值 */
    31. g_speed_pid.SumError = 0.0; /* 积分值 */
    32. g_speed_pid.Error = 0.0; /* Error[1] */
    33. g_speed_pid.LastError = 0.0; /* Error[-1] */
    34. g_speed_pid.PrevError = 0.0; /* Error[-2] */
    35. g_speed_pid.Proportion = S_KP; /* 比例常数 Proportional Const */
    36. g_speed_pid.Integral = S_KI; /* 积分常数 Integral Const */
    37. g_speed_pid.Derivative = S_KD; /* 微分常数 Derivative Const */
    38. }

    PID运算

    1. int Encode_now = gtim_get_encode(); /* 获取编码器值,用于计算速度 */
    2. speed_computer(Encode_now, 5); /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */
    3. if (val % SMAPLSE_PID_SPEED == 0) /* 进行一次pid计算 */
    4. {
    5. if (g_run_flag) /* 判断电机是否启动了 */
    6. {
    7. g_motor_data.location = (float)Encode_now; /* 获取当前编码器总计数值,用于位置闭环控制 */
    8. g_motor_data.motor_pwm = increment_pid_ctrl(&g_location_pid, g_motor_data.location); /* 位置环PID控制(外环) */
    9. if (g_motor_data.motor_pwm >= 150) /* 限制外环输出(目标速度) */
    10. {
    11. g_motor_data.motor_pwm = 150;
    12. }
    13. else if (g_motor_data.motor_pwm <= -150)
    14. {
    15. g_motor_data.motor_pwm = -150;
    16. }
    17. g_speed_pid.SetPoint = g_motor_data.motor_pwm; /* 设置目标速度,外环输出作为内环输入 */
    18. g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed); /* 速度环PID控制(内环) */
    19. if (g_motor_data.motor_pwm >= 8200) /* 限制占空比 */
    20. {
    21. g_motor_data.motor_pwm = 8200;
    22. }
    23. else if (g_motor_data.motor_pwm <= -8200)
    24. {
    25. g_motor_data.motor_pwm = -8200;
    26. }
    27. motor_pwm_set(g_motor_data.motor_pwm); /* 设置占空比(电机转速) */
    28. }
    29. val = 0;
    30. }
    31. val ++;

    电流+位置双环控制实现

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

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

    PID相关函数

    1. #define INCR_LOCT_SELECT 0 /* 0:位置式,1:增量式 */
    2. /* 注意:双环控制的时候,外环PID参数调节幅度不要太大,这对于整个曲线的影响很大 */
    3. #if INCR_LOCT_SELECT
    4. /* 定义位置环PID参数相关宏 */
    5. #define L_KP 0.18f /* P参数 */
    6. #define L_KI 0.00f /* I参数 */
    7. #define L_KD 0.08f /* D参数 */
    8. /* 定义电流环PID参数相关宏 */
    9. #define C_KP 10.00f /* P参数 */
    10. #define C_KI 9.00f /* I参数 */
    11. #define C_KD 0.00f /* D参数 */
    12. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms */
    13. #else
    14. /* 定义位置环PID参数相关宏 */
    15. #define L_KP 0.18f /* P参数 */
    16. #define L_KI 0.00f /* I参数 */
    17. #define L_KD 0.08f /* D参数 */
    18. /* 定义电流环PID参数相关宏 */
    19. #define C_KP 10.00f /* P参数 */
    20. #define C_KI 7.00f /* I参数 */
    21. #define C_KD 0.00f /* D参数 */
    22. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms */
    23. #endif
    24. PID_TypeDef g_location_pid; /* 位置环PID参数结构体 */
    25. PID_TypeDef g_current_pid; /* 电流环PID参数结构体 */
    26. /**
    27. * @brief pid初始化
    28. * @param 无
    29. * @retval 无
    30. */
    31. void pid_init(void)
    32. {
    33. /* 初始化位置环PID参数 */
    34. g_location_pid.SetPoint = 0.0; /* 目标值 */
    35. g_location_pid.ActualValue = 0.0; /* 期望输出值 */
    36. g_location_pid.SumError = 0.0; /* 积分值 */
    37. g_location_pid.Error = 0.0; /* Error[1] */
    38. g_location_pid.LastError = 0.0; /* Error[-1] */
    39. g_location_pid.PrevError = 0.0; /* Error[-2] */
    40. g_location_pid.Proportion = L_KP; /* 比例常数 Proportional Const */
    41. g_location_pid.Integral = L_KI; /* 积分常数 Integral Const */
    42. g_location_pid.Derivative = L_KD; /* 微分常数 Derivative Const */
    43. /* 初始化电流环PID参数 */
    44. g_current_pid.SetPoint = 0.0; /* 目标值 */
    45. g_current_pid.ActualValue = 0.0; /* 期望输出值 */
    46. g_current_pid.SumError = 0.0; /* 积分值*/
    47. g_current_pid.Error = 0.0; /* Error[1]*/
    48. g_current_pid.LastError = 0.0; /* Error[-1]*/
    49. g_current_pid.PrevError = 0.0; /* Error[-2]*/
    50. g_current_pid.Proportion = C_KP; /* 比例常数 Proportional Const */
    51. g_current_pid.Integral = C_KI; /* 积分常数 Integral Const */
    52. g_current_pid.Derivative = C_KD; /* 微分常数 Derivative Const */
    53. }

    PID运算

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

    1. int Encode_now = gtim_get_encode(); /* 获取编码器值,用于计算速度 */
    2. speed_computer(Encode_now, 5); /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */
    3. if (val % SMAPLSE_PID_SPEED == 0) /* 进行一次pid计算 */
    4. {
    5. if (g_run_flag) /* 判断电机是否启动了 */
    6. {
    7. g_motor_data.location = (float)Encode_now; /* 获取当前编码器总计数值,用于位置闭环控制 */
    8. g_motor_data.motor_pwm = increment_pid_ctrl(&g_location_pid, g_motor_data.location); /* 位置环PID控制(外环) */
    9. if ( g_motor_data.motor_pwm > 0) /* 判断位置环输出值是否为正数 */
    10. {
    11. dcmotor_dir(0); /* 输出为正数,设置电机正转 */
    12. }
    13. else
    14. {
    15. g_motor_data.motor_pwm = -g_motor_data.motor_pwm; /* 输出为负数,取反 */
    16. dcmotor_dir(1); /* 设置电机反转 */
    17. }
    18. if (g_motor_data.motor_pwm >= 120) /* 限制外环输出(目标电流) */
    19. {
    20. g_motor_data.motor_pwm = 120;
    21. }
    22. g_current_pid.SetPoint = g_motor_data.motor_pwm; /* 设置目标电流,外环输出作为内环输入 */
    23. g_motor_data.motor_pwm = increment_pid_ctrl(&g_current_pid, g_motor_data.current); /* 电流环PID控制(内环) */
    24. if (g_motor_data.motor_pwm >= 8200) /* 限制占空比 */
    25. {
    26. g_motor_data.motor_pwm = 8200;
    27. }
    28. else if (g_motor_data.motor_pwm <= 0)
    29. {
    30. g_motor_data.motor_pwm = 0;
    31. }
    32. dcmotor_speed(g_motor_data.motor_pwm); /* 设置占空比 */
    33. }
    34. val = 0;
    35. }
    36. val ++;

    电流+速度双环控制实现

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

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

    PID相关函数

    1. #define INCR_LOCT_SELECT 0 /* 0:位置式,1:增量式 */
    2. /* 注意:双环控制的时候,外环PID参数调节幅度不要太大,这对于整个曲线的影响很大 */
    3. #if INCR_LOCT_SELECT
    4. /* 定义速度环(外环)PID参数相关宏 */
    5. #define S_KP 1.500f /* P参数 */
    6. #define S_KI 0.023f /* I参数 */
    7. #define S_KD 0.010f /* D参数 */
    8. /* 定义电流环(内环)PID参数相关宏 */
    9. #define C_KP 1.00f /* P参数 */
    10. #define C_KI 3.00f /* I参数 */
    11. #define C_KD 0.00f /* D参数 */
    12. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms */
    13. #else
    14. /*定义速度环(外环)PID参数相关宏*/
    15. #define S_KP 1.500f /* P参数 */
    16. #define S_KI 0.023f /* I参数 */
    17. #define S_KD 0.002f /* D参数 */
    18. /* 定义电流环(内环)PID参数相关宏 */
    19. #define C_KP 1.00f /* P参数 */
    20. #define C_KI 3.75f /* I参数 */
    21. #define C_KD 0.00f /* D参数 */
    22. #define SMAPLSE_PID_SPEED 50 /* 采样周期 单位ms */
    23. #endif
    24. PID_TypeDef g_speed_pid; /* 速度环PID参数结构体 */
    25. PID_TypeDef g_current_pid; /* 电流环PID参数结构体 */
    26. /**
    27. * @brief pid初始化
    28. * @param 无
    29. * @retval 无
    30. */
    31. void pid_init(void)
    32. {
    33. /* 初始化速度环PID参数 */
    34. g_speed_pid.SetPoint = 0; /* 目标值 */
    35. g_speed_pid.ActualValue = 0.0; /* 期望输出值 */
    36. g_speed_pid.SumError = 0.0; /* 积分值 */
    37. g_speed_pid.Error = 0.0; /* Error[1] */
    38. g_speed_pid.LastError = 0.0; /* Error[-1] */
    39. g_speed_pid.PrevError = 0.0; /* Error[-2] */
    40. g_speed_pid.Proportion = S_KP; /* 比例常数 Proportional Const */
    41. g_speed_pid.Integral = S_KI; /* 积分常数 Integral Const */
    42. g_speed_pid.Derivative = S_KD; /* 微分常数 Derivative Const */
    43. /* 初始化电流环PID参数 */
    44. g_current_pid.SetPoint = 0.0; /* 目标值 */
    45. g_current_pid.ActualValue = 0.0; /* 期望输出值 */
    46. g_current_pid.SumError = 0.0; /* 积分值*/
    47. g_current_pid.Error = 0.0; /* Error[1]*/
    48. g_current_pid.LastError = 0.0; /* Error[-1]*/
    49. g_current_pid.PrevError = 0.0; /* Error[-2]*/
    50. g_current_pid.Proportion = C_KP; /* 比例常数 Proportional Const */
    51. g_current_pid.Integral = C_KI; /* 积分常数 Integral Const */
    52. g_current_pid.Derivative = C_KD; /* 微分常数 Derivative Const */
    53. }

    PID运算

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

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

    1. int Encode_now = gtim_get_encode(); /* 获取编码器值,用于计算速度 */
    2. speed_computer(Encode_now, 5); /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */
    3. if (val % SMAPLSE_PID_SPEED == 0) /* 进行一次pid计算 */
    4. {
    5. if (g_run_flag) /* 判断电机是否启动了 */
    6. {
    7. integral_limit( &g_speed_pid , 7000 , -7000 ); /* 速度环积分限幅 */
    8. integral_limit( &g_current_pid , 2500 , -2500 ); /* 电流环积分限幅 */
    9. g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed); /* 速度环PID控制(外环) */
    10. if ( g_motor_data.motor_pwm > 0) /* 判断速度环输出值是否为正数 */
    11. {
    12. dcmotor_dir(0); /* 输出为正数,设置电机正转 */
    13. }
    14. else
    15. {
    16. g_motor_data.motor_pwm = -g_motor_data.motor_pwm; /* 目标电流不能为负数,速度环输出要取反 */
    17. dcmotor_dir(1); /* 设置电机反转 */
    18. }
    19. if (g_motor_data.motor_pwm >= 200) /* 限制外环输出(目标电流) */
    20. {
    21. g_motor_data.motor_pwm = 200;
    22. }
    23. g_current_pid.SetPoint = g_motor_data.motor_pwm; /* 设置目标电流,外环输出作为内环输入 */
    24. g_motor_data.motor_pwm = increment_pid_ctrl(&g_current_pid, g_motor_data.current); /* 电流环PID控制(内环) */
    25. if (g_motor_data.motor_pwm >= 8200) /* 限制占空比 */
    26. {
    27. g_motor_data.motor_pwm = 8200;
    28. }
    29. else if (g_motor_data.motor_pwm <= 0) /* 滤掉无效输出 */
    30. {
    31. g_motor_data.motor_pwm = 0;
    32. }
    33. dcmotor_speed(g_motor_data.motor_pwm); /* 设置占空比 */
    34. }
    35. val = 0;
    36. }
    37. val ++;

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

    PID相关函数 

    1. /* 定义位置环PID参数相关宏 */
    2. #define L_KP 0.06f /* P参数 */
    3. #define L_KI 0.00f /* I参数 */
    4. #define L_KD 0.01f /* D参数 */
    5. /* 定义速度环PID参数相关宏 */
    6. #define S_KP 5.00f /* P参数 */
    7. #define S_KI 0.30f /* I参数 */
    8. #define S_KD 0.01f /* D参数 */
    9. /* 定义电流环PID参数相关宏 */
    10. #define C_KP 8.00f /* P参数 */
    11. #define C_KI 4.00f /* I参数 */
    12. #define C_KD 1.00f /* D参数 */
    13. PID_TypeDef g_location_pid; /* 位置环PID参数结构体 */
    14. PID_TypeDef g_speed_pid; /* 速度环PID参数结构体 */
    15. PID_TypeDef g_current_pid; /* 电流环PID参数结构体 */
    16. /**
    17. * @brief pid初始化
    18. * @param 无
    19. * @retval 无
    20. */
    21. void pid_init(void)
    22. {
    23. /* 初始化位置环PID参数 */
    24. g_location_pid.SetPoint = 0.0; /* 目标值 */
    25. g_location_pid.ActualValue = 0.0; /* 期望输出值 */
    26. g_location_pid.SumError = 0.0; /* 积分值*/
    27. g_location_pid.Error = 0.0; /* Error[1]*/
    28. g_location_pid.LastError = 0.0; /* Error[-1]*/
    29. g_location_pid.PrevError = 0.0; /* Error[-2]*/
    30. g_location_pid.Proportion = L_KP; /* 比例常数 Proportional Const */
    31. g_location_pid.Integral = L_KI; /* 积分常数 Integral Const */
    32. g_location_pid.Derivative = L_KD; /* 微分常数 Derivative Const */
    33. /* 初始化速度环PID参数 */
    34. g_speed_pid.SetPoint = 0.0; /* 目标值 */
    35. g_speed_pid.ActualValue = 0.0; /* 期望输出值 */
    36. g_speed_pid.SumError = 0.0; /* 积分值 */
    37. g_speed_pid.Error = 0.0; /* Error[1] */
    38. g_speed_pid.LastError = 0.0; /* Error[-1] */
    39. g_speed_pid.PrevError = 0.0; /* Error[-2] */
    40. g_speed_pid.Proportion = S_KP; /* 比例常数 Proportional Const */
    41. g_speed_pid.Integral = S_KI; /* 积分常数 Integral Const */
    42. g_speed_pid.Derivative = S_KD; /* 微分常数 Derivative Const */
    43. /* 初始化电流环PID参数 */
    44. g_current_pid.SetPoint = 0.0; /* 目标值 */
    45. g_current_pid.ActualValue = 0.0; /* 期望输出值 */
    46. g_current_pid.SumError = 0.0; /* 积分值*/
    47. g_current_pid.Error = 0.0; /* Error[1]*/
    48. g_current_pid.LastError = 0.0; /* Error[-1]*/
    49. g_current_pid.PrevError = 0.0; /* Error[-2]*/
    50. g_current_pid.Proportion = C_KP; /* 比例常数 Proportional Const */
    51. g_current_pid.Integral = C_KI; /* 积分常数 Integral Const */
    52. g_current_pid.Derivative = C_KD; /* 微分常数 Derivative Const */
    53. }

    PID运算

    1. int Encode_now = gtim_get_encode(); /* 获取编码器值,用于计算速度 */
    2. speed_computer(Encode_now, 5); /* 中位平均值滤除编码器抖动数据,5ms计算一次速度 */
    3. if (val % SMAPLSE_PID_SPEED == 0) /* 进行一次pid计算 */
    4. {
    5. if (g_run_flag) /* 判断电机是否启动了 */
    6. {
    7. g_motor_data.location = (float)Encode_now; /* 获取当前编码器总计数值,用于位置闭环控制 */
    8. integral_limit(&g_location_pid , 1000 ,-1000); /* 位置环积分限幅 */
    9. integral_limit(&g_speed_pid , 200 ,-200); /* 速度环积分限幅 */
    10. integral_limit(&g_current_pid , 150 ,-150); /* 电流环积分限幅 */
    11. if( (g_location_pid.Error <= 20) && (g_location_pid.Error >= -20) ) /* 设置闭环死区 */
    12. {
    13. g_location_pid.Error = 0; /* 偏差太小了,直接清零 */
    14. g_location_pid.SumError = 0; /* 清除积分 */
    15. }
    16. g_motor_data.motor_pwm = increment_pid_ctrl(&g_location_pid, g_motor_data.location); /* 位置环PID控制(最外环) */
    17. if (g_motor_data.motor_pwm >= 120) /* 限制外环输出(目标速度) */
    18. {
    19. g_motor_data.motor_pwm = 120;
    20. }
    21. else if (g_motor_data.motor_pwm <= -120)
    22. {
    23. g_motor_data.motor_pwm = -120;
    24. }
    25. g_speed_pid.SetPoint = g_motor_data.motor_pwm; /* 设置目标速度,外环输出作为内环输入 */
    26. g_motor_data.motor_pwm = increment_pid_ctrl(&g_speed_pid, g_motor_data.speed); /* 速度环PID控制(次外环) */
    27. if ( g_motor_data.motor_pwm > 0) /* 判断速度环输出值是否为正数 */
    28. {
    29. dcmotor_dir(0); /* 输出为正数,设置电机正转 */
    30. }
    31. else
    32. {
    33. g_motor_data.motor_pwm = -g_motor_data.motor_pwm; /* 输出取反 */
    34. dcmotor_dir(1); /* 设置电机反转 */
    35. }
    36. if (g_motor_data.motor_pwm >= 100) /* 限制外环输出(目标电流) */
    37. {
    38. g_motor_data.motor_pwm = 100;
    39. }
    40. g_current_pid.SetPoint = g_motor_data.motor_pwm; /* 设置目标电流,外环输出作为内环输入 */
    41. g_motor_data.motor_pwm = increment_pid_ctrl(&g_current_pid, g_motor_data.current); /* 电流环PID控制(内环) */
    42. if (g_motor_data.motor_pwm >= 8200) /* 限制占空比 */
    43. {
    44. g_motor_data.motor_pwm = 8200;
    45. }
    46. else if (g_motor_data.motor_pwm <= 0) /* 滤掉无效输出 */
    47. {
    48. g_motor_data.motor_pwm = 0;
    49. }
    50. dcmotor_speed(g_motor_data.motor_pwm); /* 设置占空比 */
    51. }
    52. val = 0;
    53. }
    54. val ++;

  • 相关阅读:
    商务呈现-售前评估&支持
    TOWE时序上电智能PDU产品在IDC数据中心机房的应用
    JS基本数据类型中null和undefined区别及应用
    PyTorch微调终极指南2:提升模型的准确性
    JavaScript-----DOM元素
    82.(cesium之家)cesium点在3d模型上运动
    Java集合实现(一) ArrayList源码以及手写ArrayList
    0109连续函数的运算和初等函数的连续性-函数与极限-高等数学
    香蕉派BPI-Wifi6迷你路由器公开发售
    内网渗透神器CobaltStrike之配置与基础操作(一)
  • 原文地址:https://blog.csdn.net/weixin_47077788/article/details/136364986