当前位置:   article > 正文

stm32实现舵机速度控制_stm32普通定时器控制舵机

stm32普通定时器控制舵机

一、舵机介绍

舵机是一种常用的器件,可以用于机械臂,云台等项目中,通过pwm的占空比来调节实现舵机的旋转角度,所以首先你得会stm32输出pwm波。常用的舵机有90度舵机,180度舵机,270度舵机和360度舵机,根据你的需求来选择舵机的种类,注意一点就是360度舵机控制不了旋转角度。同时如果想要控制舵机的话需要知道PWM占空比和角度的对应关系,占空比0.5ms到2.5ms对应角度0到最大角度,是一个线性关系,所以无论是180还是270的舵机控制起来都差不多。

二、输出pwm波

1、定时器初始化

以下代码实现的是stm32f103定时器2输出3路pwm波,不知道什么原因stm32f103的CH3都输出不了PWM波。

  1. void PWM_Init(void)
  2. {
  3. TIM_OCInitTypeDef TIM_OCInitStructure;
  4. GPIO_InitTypeDef GPIO_InitStructure;
  5. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  7. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
  8. GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
  9. GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_2|GPIO_Pin_1|GPIO_Pin_3;
  10. GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  11. GPIO_Init(GPIOA,&GPIO_InitStructure);
  12. TIM_InternalClockConfig(TIM2);
  13. TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
  14. TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
  15. TIM_TimeBaseInitStructure.TIM_Period=20000-1 ; //ARR
  16. TIM_TimeBaseInitStructure.TIM_Prescaler=72-1; //PSC
  17. TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
  18. TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
  19. TIM_OCStructInit(&TIM_OCInitStructure);
  20. TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
  21. TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
  22. TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
  23. TIM_OCInitStructure.TIM_Pulse=0 ; //CCR
  24. TIM_OC1Init(TIM2,&TIM_OCInitStructure);
  25. TIM_OC2Init(TIM2,&TIM_OCInitStructure);
  26. // TIM_OC3Init(TIM2,&TIM_OCInitStructure);
  27. TIM_OC4Init(TIM2,&TIM_OCInitStructure);
  28. TIM_Cmd(TIM2,ENABLE);
  29. }

2、控制角度

这里使用的是270度舵机,如果使用的是180度舵机就直接将270改成180就行了

  1. //角度转换成脉冲高电平
  2. void translate_angle_to_pulse(void)
  3. {
  4. angle_1=j1;
  5. angle_2=j2;
  6. angle_3=j3;
  7. angle_4=j4;
  8. pulse1 = (j1)/270*2000+500;
  9. pulse2 = (j2)/270*2000+500;//参与姿态解算,控制倾转角度,范围 270102
  10. pulse3 =(j3)/270*2000+500;//参与姿态解算,控制倾转角度,范围 340170
  11. pulse4 = (j4)/270*2000+500;//参与姿态解算,控制倾转角度,范围 140330
  12. }

三、高级控制

如果你已经学会如何驱动舵机旋转对应的角度,那么就可以进阶高级的控制了,直接给定角度的话舵机旋转速度较快,长时间如此使用会对舵机产生损害,而且不够丝滑,于是我们可以通过算法来实现控制舵机的速度控制。

1、均匀插值法(我顺便取得名字)

实际上就是将角度均匀的分成二十份,通过定时器来控制角度均匀增加,以下代码仅供参考,不能拿来直接使用,因为我注释了蛮多东西,自己看懂之后可以尝试自己写,逻辑蛮简单。

  1. #include "stm32f10x.h" // Device header
  2. #include "OLED.h"
  3. #include "Delay.h"
  4. #include "Servo.h"
  5. #include "Key.h"
  6. #include "TIM1.h"
  7. #include "LED.h"
  8. #include "PWM.h"
  9. #include "USART.h"
  10. #include "math.h"
  11. uint8_t KeyNum;
  12. int Flag=0;
  13. int Key_TR[6]={1,2,1,2,1,2};
  14. float Angle;
  15. uint16_t jiao_du1;
  16. uint16_t CPWM[5]={1500,1500,1500,1500,1500};
  17. uint16_t pos[2][5]={
  18. {2500,1530,1300,2100,2100},
  19. {500,1530,2500,2500,2500}
  20. };
  21. uint8_t flag_vpwm=0;
  22. unsigned char flag_Tover; //舵机旋转标志位
  23. uint8_t flag_pwm=0;//定时器计次
  24. char point_now=0;
  25. char point_aim=1;
  26. uint16_t sum=50; //用来计算需要建立多少个中间数据
  27. uint16_t cnt=1;//用来累计已经执行了多少中间数据
  28. double dp;
  29. double dp0[6];
  30. void change(uint16_t a,uint16_t b,uint16_t c,uint16_t d); //改变舵机目标值
  31. void vpwm(void) ; //进行舵机运动
  32. void PWM_OUT(int a); //设置占空比
  33. int ABS(int a,int b); //相减求绝对值
  34. int main(void)
  35. {
  36. // LED_Init();
  37. int flag=0;
  38. Serial_Init();
  39. Servo_Init();
  40. // servo_angle_calculate(0,15,0);
  41. // translate_angle_to_pulse();
  42. // Timer1_Init(2000,72);
  43. // change(1530,1300,2100,2100);
  44. while(1)
  45. {
  46. // if(flag_vpwm==1){
  47. // vpwm();
  48. // flag_vpwm=0;
  49. //
  50. // }
  51. // if(flag_Tover==1){
  52. // TIM_Cmd(TIM1,DISABLE);
  53. // change((int)pulse1,(int)pulse2,(int)pulse3,(int)pulse4);
  54. // flag_Tover=0;
  55. Delay_ms(100);
  56. // TIM_Cmd(TIM1,ENABLE);
  57. //
  58. // }
  59. }
  60. }
  61. int ABS(int a,int b)
  62. {
  63. if(a>b)
  64. {
  65. return a-b;
  66. }
  67. else return b-a;
  68. }
  69. /***************************************************************************************************************
  70. 函 数 名:设置pwm波
  71. 功能描述:从缓存中取一个新的目标位置替换原来的目标位置,原来的目标位置变为新的初位置,一次更替
  72. :有效的数据是插补增量,和插补次数,知道这两个量,和当前初位置即可
  73. 备 注: 先进先出,循环访问
  74. ****************************************************************************************************************/
  75. void PWM_OUT(int a)
  76. {
  77. switch(a){
  78. case 1:TIM_SetCompare1(TIM2,CPWM[a]) ; break;
  79. case 2:TIM_SetCompare2(TIM2,CPWM[a]) ; break;
  80. case 3:TIM_SetCompare4(TIM2,CPWM[a]) ; break;
  81. case 4:TIM_SetCompare1(TIM3,CPWM[a]) ; break;
  82. // case 5:TIM_SetCompare1(TIM2,CPWM[a]) ; break;
  83. }
  84. // TIM_SetCompare1(TIM2,(int)pulse1); //越大向左转 1530 1530
  85. // TIM_SetCompare2(TIM2,(int)pulse2); //越大向前转 1300 1560
  86. // TIM_SetCompare4(TIM2,(int)pulse3); //越大向前转 2100 1500
  87. // TIM_SetCompare1(TIM3,(int)pulse4); //越大向前转 2100 1490
  88. }
  89. /***************************************************************************************************************
  90. 函 数 名:作业初位置,末尾置更新函数
  91. 功能描述:从缓存中取一个新的目标位置替换原来的目标位置,原来的目标位置变为新的初位置,一次更替
  92. :有效的数据是插补增量,和插补次数,知道这两个量,和当前初位置即可
  93. 备 注: 先进先出,循环访问
  94. ****************************************************************************************************************/
  95. void change(uint16_t a,uint16_t b,uint16_t c,uint16_t d)
  96. {
  97. unsigned char s;
  98. pos[point_aim][1]=a;
  99. pos[point_aim][2]=b;
  100. pos[point_aim][3]=c;
  101. pos[point_aim][4]=d;
  102. // if(point_aim==1)
  103. // {
  104. // point_aim=0;
  105. // point_now=1;
  106. // }
  107. // else
  108. // {
  109. // point_aim=1;
  110. // point_now=0;
  111. // }
  112. // sum=time/20; //计算新的插补次数
  113. for(s=1;s<5;s++) //计算新的插补增量
  114. {
  115. if(pos[point_aim][s]>pos[point_now][s])
  116. {
  117. dp=pos[point_aim][s]-pos[point_now][s];
  118. dp0[s]=dp/sum;
  119. }
  120. if(pos[point_aim][s]<=pos[point_now][s])
  121. {
  122. dp=pos[point_now][s]-pos[point_aim][s];
  123. dp0[s]=dp/sum;
  124. dp0[s]=-dp0[s];
  125. }
  126. }
  127. cnt=0; //m清0
  128. }
  129. /***************************************************************************************************************
  130. 函 数 名:vpwm()
  131. 功能描述:数据插补,插补时间间隔为20/12ms,由timer0控制,使舵机平滑实现速度控制
  132. :另一个功能是执行完一行后去更新下一行数据,即调用change()
  133. 备 注:
  134. ****************************************************************************************************************/
  135. void vpwm(void)
  136. {
  137. unsigned char j=0;
  138. unsigned char k;
  139. cnt++; //用来累加插补过的次数
  140. if(cnt==sum) //n是本行作业要插补的总次数
  141. {
  142. flag_Tover=1; //一行数据的执行时间已经完成
  143. }
  144. for(j=1;j<5;j++)
  145. {
  146. if(ABS(CPWM[j],pos[point_aim][j])<5)
  147. { //检测靠近终点位置
  148. // how++; //是,则累加一个
  149. CPWM[j]=pos[point_aim][j];//并且直接过度到终点位置
  150. // Serial_Printf("%d,%d\r\n",CPWM[j],cnt);
  151. PWM_OUT(j);
  152. // Delay_ms(20);
  153. }
  154. else //不靠近终点,继续插补
  155. {
  156. CPWM[j]=pos[point_now][j]+cnt*dp0[j];
  157. // Serial_Printf("%d,%d\r\n",CPWM[j],cnt);
  158. PWM_OUT(j);
  159. // Delay_ms(20);
  160. }
  161. }
  162. if(flag_Tover==1)
  163. { //从插补次数,和脉宽宽度两方面都到达终点,本作业行完成
  164. // flag_Tover=0;
  165. for(k=1;k<5;k++){
  166. pos[point_now][k]=pos[point_aim][k];
  167. }
  168. }
  169. //return;
  170. }
  171. void TIM1_UP_IRQHandler(void)
  172. {
  173. if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
  174. {
  175. flag_pwm++;
  176. if(flag_pwm==10){
  177. flag_pwm=0;
  178. flag_vpwm=1;
  179. }
  180. TIM_ClearITPendingBit(TIM1, TIM_IT_Update ); //清除TIMx更新中断标志
  181. /*写入执行的操作*/
  182. }
  183. }

2、三条插值法(数学上好像是叫这个名字)

这种算法相比于上一种算法均匀的增加角度更加高级,可以控制舵机一开始的加速度慢慢增加,然后到目标角度,加速度逐渐减少到零,这种非常丝滑。

\Theta (t)=\Theta (0)+3*(\Theta (f)-\Theta (0))*t^{2}/tf^{2}-2*(\Theta(f)-\Theta (0) )*t^{3}/tf^{3}

\Theta (0)是初始的角度

\Theta(f)为最终的角度

\Theta (t)为对应时刻的角度

tf为舵机旋转1度的时间的一倍到两倍,可以自己设定,一般舵机旋转1度为4ms,这个参数可以给8ms

t是当前的时间,可以由定时器来计时

对公式求导一次可以得到角速度,求导两次可以得到角加速度,可以尝试用软件将图画出来,可以看到一个加速度的一个曲线。

具体代码,

代码是在是一个算法的基础上改动的,其实本质就是实现上面的公式,每2ms进入定时器算出对应的角度,然后设定,可以自己尝试写一下。

  1. #include "stm32f10x.h" // Device header
  2. #include "OLED.h"
  3. #include "Delay.h"
  4. #include "Servo.h"
  5. #include "Key.h"
  6. #include "TIM1.h"
  7. #include "LED.h"
  8. #include "PWM.h"
  9. #include "USART.h"
  10. #include "math.h"
  11. uint8_t KeyNum;
  12. int Flag=0;
  13. int Key_TR[6]={1,2,1,2,1,2};
  14. float Angle;
  15. uint16_t jiao_du1;
  16. float CPWM[5]={1500,1500,1500,1500,1500};
  17. float pos[2][5]={
  18. {2500,94,-35,216,216.4},
  19. {500,1530,2500,2500,2500}
  20. };
  21. uint8_t flag_vpwm=0;
  22. unsigned char flag_Tover; //舵机旋转标志位
  23. uint8_t flag_pwm=0;//定时器计次
  24. char point_now=0;
  25. char point_aim=1;
  26. uint16_t sum=50; //用来计算需要建立多少个中间数据
  27. uint16_t cnt=1;//用来累计已经执行了多少中间数据
  28. double dp;
  29. double dp0[6];
  30. void change(void); //改变舵机目标值
  31. void vpwm(void) ; //进行舵机运动
  32. void PWM_OUT(int a); //设置占空比
  33. float ABS(float a,float b); //相减求绝对值
  34. //舵机加减速算法
  35. double s0=90,s1=0,s2;
  36. double tf[6];
  37. int t=0; //时间基准
  38. int t1[6];
  39. int g;
  40. int main(void)
  41. {
  42. // LED_Init();
  43. int flag=0;
  44. Serial_Init();
  45. // tf=(s1-s0)*4;
  46. Servo_Init();
  47. Delay_ms(500);
  48. // servo_angle_calculate(0,15,0);
  49. // translate_angle_to_pulse();
  50. change();
  51. Timer1_Init(2000,72);
  52. // change(1530,1300,2100,2100);
  53. while(1)
  54. {
  55. // if(flag_vpwm==1){
  56. // vpwm();
  57. // flag_vpwm=0;
  58. //
  59. // }
  60. // if(flag_Tover==1){
  61. // TIM_Cmd(TIM1,DISABLE);
  62. // change((int)pulse1,(int)pulse2,(int)pulse3,(int)pulse4);
  63. // flag_Tover=0;
  64. Delay_ms(100);
  65. // TIM_Cmd(TIM1,ENABLE);
  66. //
  67. // }
  68. }
  69. }
  70. float ABS(float a,float b)
  71. {
  72. if(a>b)
  73. {
  74. return a-b;
  75. }
  76. else return b-a;
  77. }
  78. /***************************************************************************************************************
  79. 函 数 名:设置pwm波
  80. 功能描述:从缓存中取一个新的目标位置替换原来的目标位置,原来的目标位置变为新的初位置,一次更替
  81. :有效的数据是插补增量,和插补次数,知道这两个量,和当前初位置即可
  82. 备 注: 先进先出,循环访问
  83. ****************************************************************************************************************/
  84. void PWM_OUT(int a)
  85. {
  86. switch(a){
  87. case 1:TIM_SetCompare1(TIM2,(CPWM[1]+49)/270*2000+500) ; break;
  88. case 2:TIM_SetCompare2(TIM2,(CPWM[2]+143)/270*2000+500) ; break;
  89. case 3:TIM_SetCompare4(TIM2,(CPWM[3]+135)/270*2000+500) ; break;
  90. case 4:TIM_SetCompare1(TIM3,(CPWM[4]+133)/270*2000+500) ; break;
  91. // case 5:TIM_SetCompare1(TIM2,CPWM[a]) ; break;
  92. }
  93. // TIM_SetCompare1(TIM2,(int)pulse1); //越大向左转 1530 1530
  94. // TIM_SetCompare2(TIM2,(int)pulse2); //越大向前转 1300 1560
  95. // TIM_SetCompare4(TIM2,(int)pulse3); //越大向前转 2100 1500
  96. // TIM_SetCompare1(TIM3,(int)pulse4); //越大向前转 2100 1490
  97. }
  98. /***************************************************************************************************************
  99. 函 数 名:作业初位置,末尾置更新函数
  100. 功能描述:从缓存中取一个新的目标位置替换原来的目标位置,原来的目标位置变为新的初位置,一次更替
  101. :有效的数据是插补增量,和插补次数,知道这两个量,和当前初位置即可
  102. 备 注: 先进先出,循环访问
  103. ****************************************************************************************************************/
  104. void change(void)
  105. {
  106. // unsigned char s;
  107. pos[point_aim][1]=135;
  108. pos[point_aim][2]=0;
  109. pos[point_aim][3]=216;
  110. // pos[point_aim][4]=216;
  111. // for(s=1;s<5;s++) //计算新的插补增量
  112. // {
  113. // if(pos[point_aim][s]>pos[point_now][s])
  114. // {
  115. // dp=pos[point_aim][s]-pos[point_now][s];
  116. // dp0[s]=dp/sum;
  117. // }
  118. // if(pos[point_aim][s]<=pos[point_now][s])
  119. // {
  120. // dp=pos[point_now][s]-pos[point_aim][s];
  121. // dp0[s]=dp/sum;
  122. // dp0[s]=-dp0[s];
  123. // }
  124. //
  125. // }
  126. // cnt=0; //m清0
  127. }
  128. /***************************************************************************************************************
  129. 函 数 名:vpwm()
  130. 功能描述:数据插补,插补时间间隔为20/12ms,由timer0控制,使舵机平滑实现速度控制
  131. :另一个功能是执行完一行后去更新下一行数据,即调用change()
  132. 备 注:
  133. ****************************************************************************************************************/
  134. void vpwm(void)
  135. {
  136. unsigned char j=0;
  137. unsigned char k;
  138. cnt++; //用来累加插补过的次数
  139. if(cnt==sum) //n是本行作业要插补的总次数
  140. {
  141. flag_Tover=1; //一行数据的执行时间已经完成
  142. }
  143. for(j=1;j<5;j++)
  144. {
  145. if(ABS(CPWM[j],pos[point_aim][j])<5)
  146. { //检测靠近终点位置
  147. // how++; //是,则累加一个
  148. CPWM[j]=pos[point_aim][j];//并且直接过度到终点位置
  149. // Serial_Printf("%d,%d\r\n",CPWM[j],cnt);
  150. PWM_OUT(j);
  151. // Delay_ms(20);
  152. }
  153. else //不靠近终点,继续插补
  154. {
  155. CPWM[j]=pos[point_now][j]+cnt*dp0[j];
  156. // Serial_Printf("%d,%d\r\n",CPWM[j],cnt);
  157. PWM_OUT(j);
  158. // Delay_ms(20);
  159. }
  160. }
  161. if(flag_Tover==1)
  162. { //从插补次数,和脉宽宽度两方面都到达终点,本作业行完成
  163. // flag_Tover=0;
  164. for(k=1;k<5;k++){
  165. pos[point_now][k]=pos[point_aim][k];
  166. }
  167. }
  168. //return;
  169. }
  170. void TIM1_UP_IRQHandler(void)
  171. {
  172. if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
  173. {
  174. // flag_pwm++;
  175. // if(flag_pwm==10){
  176. // flag_pwm=0;
  177. // flag_vpwm=1;
  178. // }
  179. t+=8;
  180. for(g=1;g<4;g++){
  181. if(CPWM[g]==pos[point_aim][g]){
  182. continue;
  183. }
  184. else t1[g]=t;
  185. }
  186. // s2=s0+3/pow(tf,2)*(s1-s0)*pow(t,2)-2/pow(tf,3)*(s1-s0)*pow(t,3);
  187. //
  188. // Serial_Printf("%.4lf\r\n",(s2+45)/270*2000+500);
  189. // if(ABS(s2,s1)<0.3){
  190. // TIM_Cmd(TIM1,DISABLE);
  191. // }
  192. for(g=1;g<4;g++){
  193. tf[g]=(pos[point_aim][g]-pos[point_now][g])*4; //舵机转动总时间
  194. }
  195. for(g=1;g<4;g++){
  196. CPWM[g]=pos[point_now][g]+3/pow(tf[g],2)*(pos[point_aim][g]-pos[point_now][g])*pow(t1[g],2)-2/pow(tf[g],3)*(pos[point_aim][g]-pos[point_now][g])*pow(t1[g],3);
  197. if(ABS(CPWM[g],pos[point_aim][g])<0.5)
  198. {
  199. CPWM[g]=pos[point_aim][g];
  200. }
  201. // TIM_SetCompare1(TIM2,(CPWM[g]+46)/270*2000+500);
  202. PWM_OUT(g);
  203. Serial_Printf("%lf,%lf,%lf,%lf\r\n",CPWM[1]*100,CPWM[2]*100,CPWM[3]*100,CPWM[4]*100);
  204. }
  205. if(CPWM[1]==pos[point_aim][1]&&CPWM[2]==pos[point_aim][2]&&CPWM[3]==pos[point_aim][3]&&CPWM[3]==pos[point_aim][3] ){
  206. t=0;
  207. for(g=1;g<4;g++)
  208. {
  209. pos[point_now][g]=CPWM[g];
  210. t1[g]=0;
  211. }
  212. TIM_Cmd(TIM1,DISABLE);
  213. }
  214. TIM_ClearITPendingBit(TIM1, TIM_IT_Update ); //清除TIMx更新中断标志
  215. /*写入执行的操作*/
  216. }
  217. }

四、总结

心血来潮,想写这篇文章,缓解一下学习蓝桥杯的痛苦,算法谁爱学去学吧,真的是学不懂

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/913511
推荐阅读
相关标签
  

闽ICP备14008679号