在2020年底,我参加了在山东淄博举办的“中联重科”杯第六届大学生智能农业装备国际创新大赛,这篇文章是复盘与部分代码的解析。
这次比赛,我首次使用CubeMX+Keil5的工作流程,在上一届学长成果的基础上进行了修改,完成了硬件设计工作。整体的工作自由度比较高,非常适合用来进行创新设计。软件根据题目要求可以分成两个部分:行走部分和栽苗部分。行走部分通过超声波和编码器检测测距控制电机、转向舵机。栽苗部分通过自制的Openmv4摄像头检测,反馈给行走机构调整位置,最后完成栽苗工作。
整体硬件结构是使用跨垄大车的结构,使用2020欧标的铝材进行搭建,使用连接件在必要的位置进行连接和加强。在跨垄大车的两垄间部分,每个部分安装两组云台+电机的行走机构,每组行走机构均为全向,即舵机云台实现180°旋转,直流电机实现正反转。在每个部分还安装5只超声波距离传感器,以保证在前后左右每个方向都有至少两个超声波传感器进行测距。在跨垄大车的垄上部分上设计任务所需的作业机构,在第五届是割草机构,在第六届是栽苗机构,在第七届是采摘机构。根据每个机构的作业需求,可适当加高车辆。
云台结构为普通云台结构,在尺寸上进行缩小,整个直径控制在15cm。电机为普通的12V有刷直流电机,每个电机配有编码器,可进行单个电机的单独调速。
作业机构为使用精确切割的亚克力板和3D打印的套筒。使用两个舵机进行控制,一个舵机负责安放套筒,防止放苗的时候的倒伏,一个舵机负责转动苗盘进行放苗操作。
整体上来说,多使用板材和已有的型材进行拼装。思路是多使用一维(2020铝型材)和二维(云台定制铝板、切割的亚克力板)进行DIY制作,这种条状和平面状的材料自由度高,而且获取方便,用来DIY一些即兴的作品非常有效。
整个软件控制集中在主控板上,主控板用于控制所有的作业舵机、云台舵机和行走电机,同时接收来自从板发过来的距离信号和Openmv发过来的栽苗信号,通过这些信号来决定动作。使用的是STM32F407VGT6的芯片,共有100pin。总计使用10路PWM输出(4个用于直流电机调速,6个用来驱动舵机)、4路AB相编码器(用于记录直流电机转速,以进行闭环调速)、2路USART串口(一个接收距离信号,一个接收栽苗信号)、一个IIC(MPU9250的陀螺仪读参)、一个SPI(驱动OLED屏幕)、4个LED和4个按键(用于操作)。
在每个垄间行走机构的周围都有5个超声波距离传感器,它们的供电和信号集中在一块超声波扩展板上。5个超声波传感器的触发和接收各需要5根线,一共是10根。两个垄间行走机构一共是20根。这些信号线都被接在了一块从控板上,因为主控板上的资源已经不够用了。从控板的性能与主控板一致,只是在外设上取消了较多的PWM和编码器信号,用来统一处理超声波信号,然后通过串口发送给主控板。
采用开源的OpenMV v4的结构,使用STM32H743芯片作为主控,整个板子为自制,用于检测红色色块。具体的设计以后有机会可以介绍。
软件使用HAL库进行编程,主要设计为在main函数中设计赛道数据,在联合中断函数(控制函数)中设计各级中断,和它们分别处理的功能,每相功能都留了开关,以便于随时切换。
在电机部分是从单个电机控制到横向和纵向的控制,再到以编码器数据为主的控制。其中编码器的PID计算参数有一些不同,大致分为三种情况,一种情况是Position模式,第二种情况是Distance模式,第三种是Openmv模式。
舵机部分比较简单,就是一个驱动代码,然后整合各个位置参数进行封装,最后云台位置舵机联调后整体封装。
串口方面,主控板用一个简单的串口中断进行接收,从控板在一个10ms的中断内发送距离信息(每10ms发送最新数据)。Openmv板不停检测,检测不到就发送0,检测到就会发送目标的坐标,主控板的第二个串口中断也不停接收信息,如果信息有效,并进行操作。
其余的MPU9250就是用一个简单的IIC协议进行读取访问,SPI的OLED屏幕进行显示参数和当前状态,LED和按键也是一些简单的配置。
在只使用一个时钟中断中,通过记录中断次数,可以做出不同时间间隔的中断,虽然来说时间不能严格遵守,因为执行中断也需要一部分时间。但是只要中断操作内容比较简单,或者降低整个设备操作对时间的容忍度,这些时间误差是可以忽略的。
在这个项目的控制代码中,我主要使用了10ms和20ms两个中断。在20秒的中断中,仅进行MPU9250的数据采集。在10ms的中断中,进行编码器状态选择和Running Mode选择。编码器状态选择之前已经介绍了,Running Mode分为5个状态,就是横向左右,纵向上下和停止。在不同Running Mode中采用不同的PID布局。
运行的PID方面,我们整体使用MPU的陀螺仪位置PID以及内侧超声波距离PID,两个PID并联来调整车辆动态,通过设定参数来调节权重,保证车辆的行走动态。在轮上的编码器也写了一个PID,它是常驻的,主要是用来调速;当然编码器还有一个用途是记录车辆行驶距离。
OpenMV的控制代码是独立的。每当检测到靶心后,就会自动运行栽苗程序,栽苗步骤完成后,车会继续跑完编码器设定的路程。
我们结合主函数,把代码具体过一遍。下图是逻辑编号。
整个行驶分为溜边进场,1-2-3-4-5-6,出场。其中6为快速通过,不需要检测苗位。1-2-3-4-5每横向走过一段后要纵向走一小段,来切换到下一条垄。
初始化后,根据场上放靶子的垄,进行手动设置,没有靶子的垄迅速通过。然后等待陀螺仪指数不再进行偏移后,清零,并在裁判的指令下发车。
/***************车辆进场******************/
START_GAME(8000);
/*************Y_Up直行溜边****************/
Running_Mode_Set(Y_Up, 50);
HAL_Delay(3500);
Running_Mode_Set(Y_Up, 20);
HAL_Delay(500);
while(1)
{
if(SonicDistance[Sonic_5] < 18)
break;
else
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
OLED_ShowNum(20, 52, (uint32_t)SonicDistance[4], 3, 12);
OLED_Refresh_Gram();
HAL_Delay(10);
}
Running_Mode_Set(STOP, 0);
HAL_Delay(200);
Stop_d();
快速进场(START_GAME)后,将Running Mode设置为Y_Up,速度为50。Y_Up专门为溜边进场优化过,仅开启左侧超声波进行PID姿态校准。3500ms后手动减速,再过500ms后进入死循环。死循环中的显示均为问题记录,出问题后可以调查相应参数,对跑垄没有实际意义。在死循环中,当超声波5位置小于18cm时跳出循环,并将Running Mode设置为STOP,为停车模式,关闭所有PID处理,200ms后停车。
/*****************-1-********************/
Choose_Fieldspeed(1);
steer_Init_X();
HAL_Delay(200);
Running_Mode_Set(X_Right, Fieldspeed);
Distance_STA = Distance_ON;
HAL_Delay(800);
while(1)
{
if(Encoder_Distance <= -FieldEncoder)
break;
else
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
Openmv_scaning(X_Right);
HAL_Delay(10);
}
Distance_STA = Distance_OFF;
Encoder_Distance = 0;
SpeedSmooth(Fieldspeed,20);
Running_Mode_Set(X_Right, 20);
HAL_Delay(200);
while(1)
{
if((SonicDistance[Sonic_4] < 12) && (SonicDistance[Sonic_7] < 12))
{
OLED_ShowNum(60, 0, (uint32_t)SonicDistance[3], 3, 12);
OLED_ShowNum(60, 13, (uint32_t)SonicDistance[6], 3, 12);
OLED_Refresh_Gram();
break;
}
else
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
HAL_Delay(10);
}
Running_Mode_Set(STOP, 0);
HAL_Delay(200);
Stop_d();
根据预先设置的有靶子的垄进行速度选择,有靶子慢速检测,无靶子快速通过。steer_Init_X旋转舵机至横向。延时200ms等待舵机运动。将Running Mode设置为X_Right,速度为设置速度,并打开编码器的测距功能,开始测距。延时800ms后进入死循环,在编码器没有走完FieldEncoder前,会同时检测OpenMV发来的信号,一旦有有效信号,就会进行一段固定代码,进行栽苗操作,期间编码器数据保持不变。
不管栽苗与否,当编码器走完FieldEncoder规定路程后,跳出循环,并进行SpeedSmooth(平滑降速)从Fieldspeed降到20。无缝衔接X_Right后进入死循环,这个死循环是超声波检测,距离小于12cm时跳出循环,并停车。
/*****************-1short-********************/
steer_Init_Y();
HAL_Delay(200);
Motor_Distance_Y_Move(-5200);
HAL_Delay(200);
移向2号垄,切换舵机状态为纵向行走模式,打开编码器行走5200编码器单位后,关闭编码器,并停车。随后循环开始2号垄操作。
OpenMV有关的代码和栽苗代码是临时写的APP,比较杂乱,也因为bug经过多次调整。这里就不多介绍了。
这虽然是一个两年前的项目,但带给我的启发是很多的。在机械结构方面,我的化繁为简,用简单的结构分布再去实现任务需要的功能。在软件方面,我试着用操作系统的思考方式来进行自顶向下的设计,而且功能打包后用函数开关控制在终端中的启停,还是很有意思的。还要感谢我的队友,在那几个月也非常努力地准备着比赛。
更具体的内容由于时间关系,就不再整理了。这个2020年的比赛项目也在我人生中留下了难忘的记忆节点。