暗网下载 [project] 2019 年恩智浦杯大学生智能汽车竞赛,代码 review
比赛视频
比赛用车
0.介绍
2019年7月,我参与了当年称恩智浦杯的智能汽车竞赛,其前身是叫飞思卡尔杯,对自动化专业而言,此比赛颇为重要。那时我处于大三,该比赛(项目)算得上是我首个完成度较高的作品。我所负责的范畴,涵盖硬件开发,驱动开发,以及整个软件框架的开发。
一直以来,这辆车都在我身旁放置着,那时或许仅仅是出于兴趣,感觉有趣好玩。而如今看来,这理应是我整个嵌入式开发生涯的起始点。当下的我能够拿出更优的方案,更完备的设计,甚至还能有更高效的代码结构。然而,要我去评判当年编写这些代码的自我,我认为做得已然不错了。这既是一个项目总结,也是关乎如何写出好代码的一些想法。
这车的主控芯片是 NXP imx 的跨界处理器,借助 IAR 开展裸机开发,当时大量参照了 NXP 官方的源码,虽是裸机开发但当时也经由 NXP 例程学到了不少内容,后面内容我会详细撰写出来。
1.比赛要求,智能车工作原理
固定元素,实现智能车的自动运行。
底盘控制
硬件方面,需要对电机予以考量,还要顾及舵机驱动,电磁传感器模拟信号的处理工作也得考虑,整车供电同样不容忽视。
车辆控制方面,要考虑借助图像来获取智能车与赛道的偏差,这一内容是由我的队友负责的,还要通过偏差计算出舵机的控制量暗网下载,这部分同样是由我的队友负责的。也就是说,我要搭建一个相对较为可靠的运动平台,并且提供控制算法所需的原始数据,之后把控制算法的输出应用到执行机构比如电机、舵机上。
2.硬件方案设计
多年来,我停下了做硬件与画板的工作,然而,此项目却别有例外,它是历经完备的硬件与软件打造而成的项目,故而,在此必须将其提取出来予以介绍。此项目运用AltiumDesigner进行开发,在开发时,对整车的电源设计予以考量,对驱动板设计予以思考,对电磁传感器设计予以斟酌,对MCU控制板的设计予以审视。
这个车具备的硬件而言,其情形是相对较为单一的,所运用的 MCU 采用的是 BGA 封装芯片,该芯片运用了核心板,底板开展了传感器以及控制接口 的引出操作,同时施行供电设计。除此之外还设有电机驱动以及集成的半桥驱动芯片,这两个芯片共同构建成一个 H 桥直流电机驱动电路。另外还有电池传感器,于赛道之上交流电享有的频率为 20KHz,依据磁场频率挑选适配的电感以及电容进而组建形成一个 LC 电路,在磁场里电感两端便会产生电压信号 ,进而开展放大检波等相关处理后输入至 ADC 接口便可实现操作。
3.软件总体方案设计
于此,鉴于软件于设计层面存在的不周全之处,仅能够借助 main 函数主循环的达成,去揣度彼时的设计构思了。
int main(void)
{
以下是按照要求进行的改写:/*,---------------------,硬件初始化,--------------------------,*/。
system_init();/* MCU初始化 */
//汽车方向障碍物测试函数调用;/* 单个功能测试函数所处位置 */。
lpuart1_init(115200); /* 蓝牙发送串口启动 */
key.init(); /* 按键启动 */
led.init(); /* 指示灯启动 */
NVIC_SetPriorityGrouping(2); /* 2: 4个抢占优先级 4个子优先级*/
oled.init(); /* LCD启动 */
进行中断启动的相关操作,具体为执行ExInt_Init(); 。
adc.init();
motor.init(); 行车速度的PID控制部分开启初始设定相关操作,其中囊括了ENC的第一步初始设置,以及PWM的初始参数设定,还有PID参数的初始配置 。
img.init(); /* 相机接口初始化 */
delayms(200); /* 必要的延时,等待相机感光元件稳定 */
//UI_debugsetting();
pit_init(kPIT_Chnl_0, 10000);
while(1)
{
/* 10ms中断置位? */
while (status.interrupt_10ms == 0)
{
adc.refresh(); 对赛道电磁引导线信息予以更新,adc_roadtype数据包实施更新, 。
adc.circle_check(); /* 圆环检测、偏差检测,转换为电磁引导模式 */
}
key.barrier_check(); /* 路障检查 */
/* 如果图像就绪,图像刷新,道路类型判断 */
if(kStatus_Success == CAMERA_RECEIVER_GetFullBuffer(&cameraReceiver, &CameraBufferAddr))
{
img.refresh(); /* 更新图像和偏差等控制信息 */
adc.error_check(); /* 电磁引导线偏差检查 */
}
car.direction_control(); /* 舵机打角更新 */
car_speed_calculate(); /* 更新一次左右电机目标速度 */
/* 两个电机转速控制 */
motor.pidcontrol(&motor_speed);
status_indicator.light_road(); /* 状态灯指示更新 */
status_indicator.oled_circle(); /* 屏幕显示更新 */
char txt[16];
sprintf(txt,"ENC2: %6d ",(int16_t)ENC_GetPositionValue(ENC2));
LCD_P6x8Str(0,5,(uint8_t*)txt);
/* 中断复位 */
status.interrupt_10ms = 0;
}
}
这会儿我瞅这段代码,已然瞅不大明白了,好在函数名起得还行,扔给chatgpt,它给我的答复 。
该代码的控制逻辑借由一个定时中断予以驱动,它会周期性地展开对传感器数据的采集行为,还要进行路障检测工作,以及图像处理操作,同时实施车辆控制。主循环会保证每个任务于固定时间间隔内得以执行,并且依据传感器以及相机的数据实时对车辆的运动状态作出调整。
就目前状况而言,主循环代码并非那种一看便知其含义的,能够推测出此代码在维护方面会面临不少难题,它是靠着分布于各处的全局变量去达成数据传递的。最为关键的是,处于最上层的主函数并未充分展现出应用层的控制逻辑要点 ,一些函数的参数显得莫名其妙,比方说motor.pidcontrol(&motor_speed); 所传入的参数毫无缘由可寻,极为孤立。
我当下对这个代码发出评价,它在形式方面模仿了描述符 ,描述符可以是句柄 ,也可以是一个对象 ,其控制着一个对象的想法 ,然而仅仅是参考了形式上的设计 ,并且并不清楚为何要这么去做 。
4.关于一些驱动的讨论
名叫MT9V034的摄像头,与芯片的CSI接口相连接,这部分代码运用了nxp官方的例程,其中类似的代码大量出现 。
没办法按照要求改写,因为这不是一个完整的句子,它看起来像是一段代码中的注释开头部分 ,请提供可改写的完整句子 。
* Variables
这段内容似乎并不是可改写的句子呀,它看起来像是代码中的注释分隔符等内容,请你提供正确的可改写的句子以便我进行操作。
const camera_receiver_operations_t csi_ops = {
.init = CSI_ADAPTER_Init,
.deinit = CSI_ADAPTER_Deinit,
.start = CSI_ADAPTER_Start,
.stop = CSI_ADAPTER_Stop,
.submitEmptyBuffer =用于提交为空的缓冲区的CSI适配器操作,。。。。。。。。。,。
.getFullBuffer = CSI_ADAPTER_GetFullBuffer,
.init_ext = CSI_ADAPTER_InitExt
};
不能够不说,这些所列举的代码,给予了我对于代码设计的不错的启蒙。按照我当下的理解来讲,这些代码针对设备对象以及操作进行了很好的封装,模块化的设计开展得十分出色,。
我也做了形式上的模仿如
/* LED设备初始化 */
static void led_pin_init(void);
/* LED操作 */
static void led_on(led_name_t choose);
static void led_off(led_name_t choose);
static void led_reverse(led_name_t color);
static void led_flash_fast(led_name_t color);
static void led_flash_slow(led_name_t color);
static void led_off_all(void);
const led_operations_t led_ops = {
.on = led_on,
.off = led_off,
.reverse = led_reverse,
.flash_fast = led_flash_fast,
.flash_slow = led_flash_slow,
.off_a = led_off_all,
};
const led_device_t led = {
.init = led_pin_init,
.ops = &led_ops
};
具体而言,实现的时候直接运用nxp的库,这属于我头一回学习运用这种语法,这对于我后续学习rt-thread乃至linux极具帮助,linux内核里头这种类型的表达同样为数众多。
5.关于算法上的实现
目标在于运动控制,存在方法方面的内容,借助编程来达成理论层面的事物,运用控制算法。
智能车运行最主要依靠的是摄像头所呈现的图像,经过对图像进行处理计算出车辆于赛道的偏差情况,进而据此控制舵机实现转向,比如有一些当时调试阶段所拍摄的照片,。
高斯滤波
赛道图像滤波、二值化,找中线
当下,我发觉是自己拖累了队友。方法皆是以matlab进行过仿真的,此刻的我审视这些控制方法,寻不出丝毫问题,然而查看软件实现却破绽百出。实际上,从视频亦能看出方法在当时已然具备了良好的控制效果。
抵达这一阶段之后,实际上便能够开展相对较为简易的方向把控了。出于针对车辆施行更为精准控制的目的,也能够再深入一步,相机的视角处于固定状态,能够进行一次逆透视操作 。
采集照片和数学计算
实际采集到的照片和透视效果:
在 matlab 上验证过方法没问题后,将其在车上实现
/* 逆透视变换矩阵 */
const double N1[8] = { -0.0649852534450704,2.53734549914701,-240.905004855923,1.72242380270896,-0.115684719493100,423.076434304688,0.130208451031495,-0.00342074612370382 };
const double N2[8] = { -0.0108680223123130,1.74103387838086,-172.804762452853,1.48135911232730,-0.324225542774878,294.738452846723,0.0903126321121198,-0.00636880777526755 };
const double N3[8] = { -0.0649852534450704,2.53734549914701,-240.905004855923,1.72242380270896,-0.115684719493100,423.076434304688,0.130208451031495,-0.00342074612370382 };
/* 坐标变换函数:像素坐标->实际坐标 */
static point_t img_locaion_transform(uint16_t pix_i, uint16_t pix_j) //坐标变换函数,输入行&列,返回结构体
{
point_t real_coordinate;
if (pix_i < 50)
{
real_coordinate.x = (N3[0] * pix_i + N3[1] * pix_j + N3[2]) / (N3[6] * pix_i + N3[7] * pix_j + 1) + 0.9;
real_coordinate.y = (N3[3] * pix_i + N3[4] * pix_j + N3[5]) / (N3[6] * pix_i + N3[7] * pix_j + 1);
}
else if (pix_i > 70)
{
real_coordinate.x = (N1[0] * pix_i + N1[1] * pix_j + N1[2]) / (N1[6] * pix_i + N1[7] * pix_j + 1) + 0.9;
real_coordinate.y = (N1[3] * pix_i + N1[4] * pix_j + N1[5]) / (N1[6] * pix_i + N1[7] * pix_j + 1);
}
else
{
real_coordinate.x = (N2[0] * pix_i + N2[1] * pix_j + N2[2]) / (N2[6] * pix_i + N2[7] * pix_j + 1) + 0.9;
real_coordinate.y = (N2[3] * pix_i + N2[4] * pix_j + N2[5]) / (N2[6] * pix_i + N2[7] * pix_j + 1);
}
return real_coordinate;
}
/* 三点(实际坐标)算曲率 */
static double _img_curvature(point_t A, point_t B, point_t C)
{
/* 三边长和三角形面积 */
double AB, BC, AC, S;
AB = distance(A, B);
AC = distance(A, C);
BC = distance(B, C);
S = fabs((B.x - A.x)*(C.y - A.y) - (C.x - A.x)*(B.y - A.y)) / 2; //三点构成的三角形面积
return (4 * S / (AB*BC*AC));
}
/* 赛道半径计算函数,返回单位cm */
static double _img_calculate_r(void)
{
/* 三点算曲率 */
point_t A, B, C;
/* 边线数组指针 */
int16_t *p_line;
/* 左弯曲率用右边算 */
if (status.img_roadtype == RoadLeft)
p_line = rightline;
/* 右弯曲率用左边算 */
else if (status.img_roadtype == RoadRight)
p_line = leftline;
else /* 程序出错。直路不进入此函数 */
return 0;
/* 像素位置逆透视为实际位置,这三行位置可以改变 */
A = img_locaion_transform(70, p_line[70]);
B = img_locaion_transform(60, p_line[60]);
C = img_locaion_transform(50, p_line[50]);
/* 半径 = 1/曲率 */
return (1/_img_curvature(A, B, C));
}
是以图像径直去计算弯道的半径,而后依据所算出的转弯半径能够进行更为精准的控制,甚至于能够开展实验径直寻觅到所需的舵机转角值。
6.总结
对这个比赛展开分析,从视频呈现效果来讲确实是颇为不错的,欠缺之处却是在软件架构的设计方面出现了。针对赛题的要求而言,实际上存在着一个红色障碍物,还得进入圆环,还得在跑完一整圈之后自行停车。这些统统都是业务范畴内的逻辑,按理来说应当是由我负责的,当时所编写的代码仅仅完成了最基础的沿着赛道行进的逻辑,随后在检测不到赛道之时切换电磁控制逻辑。电磁导航的算法也是由我完成的。当时的问题在于,还不存在诸如使用 RTOS 多线程开发,或者运用状态去完成运行逻辑,也就是还没有一套良好的软件框架。整个业务逻辑是硬着头皮,扶着脑袋想出来的。前后台的软件结构是依靠好多全局标志变量,才硬是完成了这个事情。所以实际上,拿到华东二等奖是一个合理的结果。
nxp 官方库的源码存在诸多进行封装、集成的面向对象设计实现,形式方面的模仿也使我获取了不少,后续再去研习 rt-thread 源码,乃至是 linux 的一些驱动源码时,均能够寻觅到某些官方库的踪迹。
鲁ICP备18019460号-4
我要评论