当前位置:   article > 正文

基于OpenMV和正点原子开发的自动追球小车(带云台舵机)_openmv追小球的云台

openmv追小球的云台

电赛备赛前,通过OpenMV加舵机云平台由,做了一个追着球跑的小车,由于疫情,以前录制的视频也删除了,最终呈现的效果和B站一位Up主的相似,大家可以参考参考,链接如下:STM32 颜色识别 自动跟随小车_哔哩哔哩_bilibili,首先把我使用到的硬件的图片给大家看看。

 电机的驱动我是用的是两路PWM波控制一个电机,OpenMV板子上面的两路PWM波控制云台的转动,小车跟随云台的转动通过两块板子之间的通信,同时物体与摄像头的距离也通过通信发送给STM32,距离和小车转动都通过PID的调节。

首先我们看Openmv上面的代码:

  1. import sensor, image, time
  2. from pyb import UART
  3. from pid import PID
  4. from pyb import Servo
  5. pan_servo=Servo(1)
  6. tilt_servo=Servo(2)
  7. #pan_servo.calibration(500,2500,500)
  8. #tilt_servo.calibration(500,2500,500)
  9. pan_pid = PID(p=0.15, i=0, imax=90) #脱机运行或者禁用图像传输,使用这个PID
  10. tilt_pid = PID(p=0.15, i=0, imax=90) #脱机运行或者禁用图像传输,使用这个PID
  11. #pan_pid = PID(p=0.1, i=0, imax=90)#在线调试使用这个PID
  12. #tilt_pid = PID(p=0.1, i=0, imax=90)#在线调试使用这个PID
  13. def find_max(blobx):
  14. max_size=0
  15. for blob in blobx:
  16. if blob[2]*blob[3] >max_size:
  17. global max_blob
  18. max_blob=blob
  19. max_size=blob[2]*blob[3]
  20. return max_blob
  21. yellow_threshold = (12, 100, 11, 127, -65, 0)
  22. sensor.reset()
  23. sensor.set_pixformat(sensor.RGB565)
  24. sensor.set_framesize(sensor.QQVGA)
  25. #sensor.skip_frames(time = 2000)
  26. sensor.skip_frames(10)
  27. sensor.set_auto_gain(False) # must be turned off for color tracking
  28. sensor.set_auto_whitebal(False) # must be turned off for color tracking
  29. clock = time.clock()
  30. K=685
  31. uart = UART(1, 115200)
  32. uart_buf=[]
  33. while(True):
  34. clock.tick()
  35. img = sensor.snapshot()
  36. #img = sensor.snapshot().lens_corr(1.8)
  37. #for c in img.find_circles(threshold = 3500, x_margin = 10, y_margin = 10, r_margin = 10,
  38. #r_min = 2, r_max = 100, r_step = 2):
  39. #area = (c.x()-c.r(), c.y()-c.r(), 2*c.r(), 2*c.r())
  40. ##area为识别到的圆的区域,即圆的外接矩形框
  41. #statistics = img.get_statistics(roi=area)#像素颜色统计
  42. #if 45<statistics.l_mode()<100 and -102<statistics.a_mode()<58 and 49<statistics.b_mode()<96:#if the circle is red
  43. #img.draw_circle(c.x(), c.y(), c.r(), color = (255, 0, 0))
  44. blobx = img.find_blobs([yellow_threshold])
  45. if blobx:
  46. b = find_max(blobx)
  47. pan_error = b.cx()-img.width()/2
  48. tilt_error = b.cy()-img.height()/2
  49. #img.draw_rectangle(b[0:4]) # rect
  50. #img.draw_cross(b[5], b[6]) # cx, cy
  51. Lm = (b[2]+b[3])/2
  52. length = K//Lm
  53. img.draw_cross(int(b[5]),int(b[6]),color=(0,255,0)) #色块中心坐标CX,CY
  54. print('cx0:'+str(b[5]),'cy0:'+str(b[6])) #将色块中心坐标输出
  55. img.draw_rectangle(b.rect())
  56. img.draw_cross(b.cx(), b.cy())
  57. pan_output=pan_pid.get_pid(pan_error,1)/2
  58. tilt_output=tilt_pid.get_pid(tilt_error,1)
  59. pan_servo.angle(pan_servo.angle()+pan_output)
  60. tilt_servo.angle(tilt_servo.angle()-tilt_output)
  61. print(int(length))
  62. uart_buf =bytearray([0x6B,b[5],b[6],int(length),0x6A])
  63. uart.write(uart_buf)
  64. #uart.writechar(0x6B)

这段是摄像头检测物体并计算出物体与摄像头距离的代码,我目前是寻找色卡,本来这是我做的一个自动捡球小车,但是发现,如果我把寻找轮廓和颜色一起加入到代码当中后,云台舵机寻找物体的转动会不丝滑,因此只采用了颜色的追踪。具体可以去参考OpenMV的官方例程,这里主要的是通信  uart_buf =bytearray([0x6B,b[5],b[6],int(length),0x6A])  bytearray将参数转换为一个新的字节数组,b[5],b[6]这两个参数主要是物体在摄像头画面中的x和y轴,即为物体存在的坐标,程序找到物体后会将物体画面居中,当物体移动将会产生一个变化量,我们就通过这个变化量去控

制车与车上云台的偏移角度。uart.write(uart_buf)这一段就是将我们的信息发送给STM32

  1. int openmv[5]; //stm32接收数据数组
  2. int16_t OpenMV_RX; /*OPENMV X 轴反馈坐标*/
  3. int16_t OpenMV_RY; /*OPENMV X 轴反馈坐标*/
  4. int8_t distan;
  5. int i=0;
  6. void Openmv_Receive_Data(int16_t data) //接收Openmv传过来的数据
  7. {
  8. static u8 state = 0;
  9. if(state==0&&data==0x6B)
  10. {
  11. state=1;
  12. openmv[0]=data;
  13. }
  14. else if(state==1)
  15. {
  16. state=2;
  17. openmv[1]=data;
  18. }
  19. else if(state==2)
  20. {
  21. state=3;
  22. openmv[2]=data;
  23. }
  24. else if(state==3)
  25. {
  26. state = 4;
  27. openmv[3]=data;
  28. }
  29. else if(state==4) //检测是否接受到结束标志
  30. {
  31. if(data == 0x6A)
  32. {
  33. state = 0;
  34. openmv[4]=data;
  35. Openmv_Data();
  36. data=0;
  37. }
  38. else if(data != 0x6A)
  39. {
  40. state = 0;
  41. for(i=0;i<8;i++)
  42. {
  43. openmv[i]=0x00;
  44. }
  45. }
  46. }
  47. else
  48. {
  49. state = 0;
  50. data=0;
  51. for(i=0;i<8;i++)
  52. {
  53. openmv[i]=0x00;
  54. }
  55. }
  56. }

这里即为通信的代码。最后重点为PID的运算,云台控制的PID直接是使用的OpenMV官方代码,大家也可以参考官方的例程追小球的云台 · OpenMV中文入门教程

这里为PID控制小车与物体距离的计算:

  1. int detPID1_PWM_out(void)//前进后退PID
  2. {
  3. float time ; //记录时间,配合millis函数用来计时
  4. float echo_value; //echo返回的值,用来计算距离
  5. float target=12; //目标距离
  6. float error; //当前的误差
  7. float error_pre; //上一次的误差
  8. float kp=15; //pid的参数
  9. float ki=0.08; //pid的参数
  10. float kd=0; //pid的参数
  11. float P; //比例项误差
  12. float I;//积分项误差
  13. float D; //微分项误差
  14. //误差总和,用来驱动马达
  15. int deta_t=50; //50ms计算一次
  16. error = distan - target;
  17. P = kp*error; //P
  18. //积分分离,根据实际情况,防止不断累加而产生震荡
  19. if(error > 0 && error < 0.8) ki = 0;
  20. if(error < 0 && error > -0.8)ki = 0;
  21. if(error == 0 )ki =0;
  22. else ki = 0.08;
  23. if(-10 < error && error < 10) I += ki*error;
  24. else I = 0; //I ,在一定误差内I才作用
  25. //D = kd*((error - error_pre)/deta_t); //D,误差的变化率
  26. D = 0; //这里没有用到D项,因为没有突然的变化可以不需要用D项
  27. PID=P + I + D ; //PID
  28. /*限幅*/
  29. if (PID>100) {
  30. PID=20;
  31. return PID;
  32. }
  33. if (PID<-100)
  34. {
  35. PID=-20;
  36. return PID;
  37. }
  38. if (PID==0)
  39. {
  40. PID=0;
  41. return PID;
  42. }
  43. error_pre = error; //记录此次误差为上一刻误差
  44. }

这里为PID控制云台转动与小车转动角度:

 

  1. int detPID2_PWM_out(void)//对正车位PID
  2. {
  3. float time ; //记录时间,配合millis函数用来计时
  4. float echo_value; //echo返回的值,用来计算距离
  5. float target=70; //目标距离
  6. float error; //当前的误差
  7. float error_pre; //上一次的误差
  8. float kp=15; //pid的参数
  9. float ki=0; //pid的参数
  10. float kd=0; //pid的参数
  11. float P; //比例项误差
  12. float I;//积分项误差
  13. float D; //微分项误差 //误差总和,用来驱动马达
  14. int deta_t=50; //50ms计算一次
  15. error = OpenMV_RX - target;
  16. P = -kp*error; //P
  17. //积分分离,根据实际情况,防止不断累加而产生震荡
  18. if(error > 0 && error < 0.8) ki = 0;
  19. if(error < 0 && error > -0.8)ki = 0;
  20. if(error == 0 )ki =0;
  21. else ki = 0.08;
  22. if(-10 < error && error < 10) I += ki*error;
  23. else I = 0; //I ,在一定误差内I才作用
  24. //D = kd*((error - error_pre)/deta_t); //D,误差的变化率
  25. D = 0; //这里没有用到D项,因为没有突然的变化可以不需要用D项
  26. PID=P + I + D ; //PID
  27. /*限幅*/
  28. if (PID>100) {
  29. PID=40;
  30. return PID;
  31. }
  32. if (PID<-100)
  33. {
  34. PID=-40;
  35. return PID;
  36. }
  37. if (PID==0)
  38. {
  39. PID=0;
  40. return PID;
  41. }
  42. error_pre = error; //记录此次误差为上一刻误差
  43. }

我的PID是直接等同于PWM的占空比的,为了大家能清晰理解,小车的控制代码如下:

  1. u8 stop = 0;
  2. /************************************************
  3. 正反待定
  4. TIM3:
  5. PA6:前左轮 TIM3_CH1
  6. PA7: TIM3_CH2
  7. PB0:前右轮 TIM3_CH3
  8. PB1: TIM3_CH4
  9. TIM4:
  10. PA2:后左轮 TIM2_CH3
  11. PA3: TIM2_CH4
  12. PB8:后右轮 TIM4_CH3
  13. PB9: TIM4_CH4
  14. ************************************************/
  15. void Car_advance()
  16. {
  17. //前进
  18. TIM_SetCompare1(TIM3, PID);
  19. TIM_SetCompare2(TIM3, stop);
  20. TIM_SetCompare3(TIM3, PID);
  21. TIM_SetCompare4(TIM3, stop);
  22. TIM_SetCompare3(TIM2, PID);
  23. TIM_SetCompare4(TIM2, stop);
  24. TIM_SetCompare3(TIM4, PID);
  25. TIM_SetCompare4(TIM4, stop);
  26. //USART1_Send_String("1");
  27. }
  28. void Car_Back_off()
  29. {
  30. //后退
  31. TIM_SetCompare1(TIM3, stop);
  32. TIM_SetCompare2(TIM3, PID);
  33. TIM_SetCompare3(TIM3, stop);
  34. TIM_SetCompare4(TIM3, PID);
  35. TIM_SetCompare3(TIM2, stop);
  36. TIM_SetCompare4(TIM2, PID);
  37. TIM_SetCompare3(TIM4, stop);
  38. TIM_SetCompare4(TIM4, PID);
  39. //USART1_Send_String("2");
  40. }
  41. void Car_right()
  42. {
  43. //右转
  44. TIM_SetCompare1(TIM3, PID);
  45. TIM_SetCompare2(TIM3, stop);
  46. TIM_SetCompare3(TIM3, stop);
  47. TIM_SetCompare4(TIM3, PID);
  48. TIM_SetCompare3(TIM2, PID);
  49. TIM_SetCompare4(TIM2, stop);
  50. TIM_SetCompare3(TIM4, stop);
  51. TIM_SetCompare4(TIM4, PID);
  52. //USART1_Send_String("3");
  53. }
  54. void Car_left()
  55. {
  56. //左转
  57. TIM_SetCompare1(TIM3, stop);
  58. TIM_SetCompare2(TIM3, PID);
  59. TIM_SetCompare3(TIM3, PID);
  60. TIM_SetCompare4(TIM3, stop);
  61. TIM_SetCompare3(TIM2, stop);
  62. TIM_SetCompare4(TIM2, PID);
  63. TIM_SetCompare3(TIM4, PID);
  64. TIM_SetCompare4(TIM4, stop);
  65. //USART1_Send_String("3");
  66. }
  67. void Car_Stop()
  68. {
  69. //停止
  70. TIM_SetCompare1(TIM3, 0);
  71. TIM_SetCompare2(TIM3, 0);
  72. TIM_SetCompare3(TIM3, 0);
  73. TIM_SetCompare4(TIM3, 0);
  74. TIM_SetCompare3(TIM2, 0);
  75. TIM_SetCompare4(TIM2, 0);
  76. TIM_SetCompare3(TIM4, 0);
  77. TIM_SetCompare4(TIM4, 0);
  78. //USART1_Send_String("3");
  79. }
  80. /************************************************
  81. 判断车左偏还是右偏
  82. 主要是判断OpenMV_RX的值:范围在(0~150左右),OpenMV_RY代表上下的值,因为球在地下所以不需要判断默认值在(50~55左右),上为增加,下为减少,整体范围一致
  83. 默认状态在90左右,左偏为增加,右偏为减少
  84. ************************************************/
  85. void Car_azimuth(void)
  86. {
  87. //左偏:这个值需要测量可能每个值不一样
  88. if(OpenMV_RX>80&&OpenMV_RX!=0)
  89. {
  90. detPID2_PWM_out();
  91. Car_left();
  92. }
  93. //右偏
  94. else if(OpenMV_RX<60&&OpenMV_RX!=0)
  95. {
  96. detPID2_PWM_out();
  97. Car_right();
  98. }
  99. else if(OpenMV_RX>60&&OpenMV_RX<80)
  100. {
  101. if(distan>12)
  102. {
  103. detPID1_PWM_out();
  104. Car_advance();
  105. }
  106. else if(distan<12)
  107. {
  108. detPID1_PWM_out();
  109. Car_Back_off();
  110. }
  111. else
  112. {
  113. Car_Stop();
  114. }
  115. }

做一个带云台追踪小车的项目就到此为止,作者当初做这个项目的时候也参考了许多的博主,东拼西凑完成了这个项目,还有不理解的部分,欢迎大家在评论区留言,如果有错误的地方,也请各路大佬指点,谢谢大家。

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

闽ICP备14008679号