暗网下载 [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 的一些驱动源码时,均能够寻觅到某些官方库的踪迹。

关键词:

客户评论

我要评论