前言
本人是一名嵌入式学习者,在大学期间也参加了不少的竞赛并获奖,包括但不限于:江苏省电子设计竞赛省一、睿抗机器人国二、中国高校智能机器人国二、嵌入式设计竞赛国三、光电设计竞赛国三、节能减排竞赛国三。
后面会经常写一下博客,分享一下资料、经验,如果想了解其他项目、技术,可以去B站搜:星汇极客,主页链接:星汇极客
此外,还开发了个人网站,里面有一些资料和源码供下载:个人网站
一、2024江苏省电子设计竞赛(H小车题)
1、题目解析
一、 任务:设计主控采用TI MSPM0系列MCU控制的小车,能在地图上自动行驶。弧线的四个顶点分别定义为A、B、C和D点。(实际地图没有标有ABCD,初始位置没有要求)
二、 要求:
(1)将小车放在位置A点,小车能自动行驶到B点停车,停车时有声光提示。用时不大于15秒。(20分)。
思路:要求从A点出发,小车跑到B点停下来会有声光。使用编码器控制行驶距离,陀螺仪PID走直线,停下来蜂鸣器响、LED亮。
注意:停下来时最好黑线在轮子间,蜂鸣器的纸要撕下来不然太小声。
(2)将小车放在位置A点,小车能自动行驶到B点后,沿半弧线行驶到C点,再由C点自动行驶到D点,最后沿半弧线行驶到A点停车,每经过一个点, 声光提示一次。完成一圈用时不大于30秒。(20分)
思路:从A->B->C->D->A走一圈,AB、CD处用编码器+陀螺仪PID走直线,在BC、DA段用灰度传感器循迹。
(3)将小车放在位置A点,小车能自动行驶到C点后,沿半弧线行驶到B 点,再由B点自动行驶到D点,最后沿半弧线行驶到A点停车。每经过一个点, 声光提示一次。完成一圈用时不大于40秒。(30分)
思路:从1->2->3->4->1,从A出发,用陀螺仪设定角度(我当时是47°这样),编码器计算距离行驶到2处将目标角度设为0摆正车身,到C点开始沿黑线循迹到B点,又将目标角度设为135°这样,行驶到4处,将目标角度设为178°摆正车身,从到D点沿黑线循迹回到A点。
注意:现场测试时,车头在B点时就转弯,那个老师说要车尾离开才能转弯,不过影响不大。
先转弯到2点处再直行是担心,直接到C点可能会黑线误判。
(4)按要求3的路径自动行驶4圈停车,用时越少越好(30分) (5)设计报告。(20分)
思路:没啥的,就是要求3连续走三圈。
注意:我使用了按键控制功能,可以随时选择任务几,也很方便。
比完赛的总结:总体难度不大,就是比赛第一天现学的TI板子,用keil写代码,不过要配置环境挺麻烦的,而且它那个图形化配置软件没有CubeMX好用,一些函数名称跟STM32HAL库也不一样。基本上大家都能实现功能,拉分点在于看谁的小车更稳定、不会跑出轨迹,我的小车使用了PID算法,在走直线和黑线循迹都很稳定。
2、小车模块选用
1、轮趣科技的车体 2、TB6612电机
3、感为的灰度传感器 4、维特JY901陀螺仪
5、小车实物
3、关键代码
说明:一些初始代码都是网上的,用到轮趣科技的车体、电机、电机驱动,感为的灰度传感器,维特的陀螺仪。相关源码大家都有,看谁单片机的基础好在短时间边学Ti板子边做整合调试好。比赛时一直担心给不给用感为的传感器,我是江苏省的,后面测评时那个老师说只要主控是TI MSPM0的芯片就行,最后也是顺利完赛了。
下面是一些关键代码,最后会有免费的完整源码分享。
1、LED、蜂鸣器、按键
//GPIO控制
DL_GPIO_clearPins(GPIOB, RGB_Red_PIN_26_PIN);// RGB红灯 置0
DL_GPIO_clearPins(GPIOA, BEEF_PIN_27_PIN );//BEEF 置0
//按键控制选任务
if(DL_GPIO_readPins(KEY_PORT,KEY_PIN_21_PIN )==0)
{
key_count++;
if(key_count >= 4)
{
key_count = 0;
if(DL_GPIO_readPins(KEY_PORT,KEY_PIN_21_PIN )==0 )
{
while(DL_GPIO_readPins(KEY_PORT,KEY_PIN_21_PIN)==0);
Task_Num ++;
if(Task_Num >4)
{
Task_Num = 1;
}
}
}
}
2、PWM驱动电机
//设置PWm值 选择定时器 PWM值 选择通道
DL_Timer_setCaptureCompareValue(PWM_0_INST,ABS(pwma),GPIO_PWM_0_C0_IDX);
3、编码器
//获取编码器值
void GROUP1_IRQHandler(void)
{
//E1A连接PA15、E1B连接PA16、E2A连接PA17,E2B连接PA22
//获取中断信号
gpio_interrup = DL_GPIO_getEnabledInterruptStatus(GPIOA,ENCODERA_E1A_PIN|ENCODERA_E1B_PIN|ENCODERB_E2A_PIN|ENCODERB_E2B_PIN);
//encoderA
if((gpio_interrup & ENCODERA_E1A_PIN)==ENCODERA_E1A_PIN)
{
if(!DL_GPIO_readPins(GPIOA,ENCODERA_E1B_PIN))
{
Get_Encoder_countA--;
}
else
{
Get_Encoder_countA++;
}
}
else if((gpio_interrup & ENCODERA_E1B_PIN)==ENCODERA_E1B_PIN)
{
if(!DL_GPIO_readPins(GPIOA,ENCODERA_E1A_PIN))
{
Get_Encoder_countA++;
}
else
{
Get_Encoder_countA--;
}
}
//encoderB
if((gpio_interrup & ENCODERB_E2A_PIN)==ENCODERB_E2A_PIN)
{
if(!DL_GPIO_readPins(GPIOA,ENCODERB_E2B_PIN))
{
Get_Encoder_countB--;
}
else
{
Get_Encoder_countB++;
}
}
else if((gpio_interrup & ENCODERB_E2B_PIN)==ENCODERB_E2B_PIN)
{
if(!DL_GPIO_readPins(GPIOA,ENCODERB_E2A_PIN))
{
Get_Encoder_countB++;
}
else
{
Get_Encoder_countB--;
}
}
//清除中断标志
DL_GPIO_clearInterruptStatus(GPIOA,ENCODERA_E1A_PIN|ENCODERA_E1B_PIN|ENCODERB_E2A_PIN|ENCODERB_E2B_PIN);
}
//定时器中断
void TIMER_0_INST_IRQHandler(void)
{
if(DL_TimerA_getPendingInterrupt(TIMER_0_INST))
{
if(DL_TIMER_IIDX_ZERO)
{
encoderA_cnt = Get_Encoder_countA; //两个电机安装相反,所以编码器值也要相反
encoderB_cnt = -Get_Encoder_countB;
}
}
}
//计算行驶距离
#define PI 3.14159265
int One_Wheel_len = 204; //mm 一轮长度 PI*65mm半径
int One_Wheel_Mai = 730; //转一圈脉冲数
float Wheel_count = 0.2794; //mm 一个脉冲数转长度 204/730
carL_dis = Wheel_count * Get_Encoder_countA; //计算左轮行驶距离
4、灰度传感器
uint8_t sensor_arr[8]; //灰度传感器值数组
//读取灰度值
void sensor_read()
{
if(DL_GPIO_readPins(Sensor_PORT,Sensor_Sensor1_PIN )==0)
sensor_arr[0] = 0;
else
sensor_arr[0] = 1;
}
5、维特JY901陀螺仪
它是串口输出数值,一开始用的其他串口接收没有用,后面用的串口0才有用,不知道为什么。
//串口的中断服务函数
void UART_0_INST_IRQHandler(void)
{
uint8_t uartdata = DL_UART_Main_receiveData(UART_0_INST); // 接收一个uint8_t数据
switch (RxState) {
case WAIT_HEADER1:
if (uartdata == 0x55) {
RxState = WAIT_HEADER2;
}
break;
case WAIT_HEADER2:
if (uartdata == 0x53) {
RxState = RECEIVE_DATA;
dataIndex = 0;
} else {
RxState = WAIT_HEADER1; // 如果不是期望的第二个头,重置状态
}
break;
case RECEIVE_DATA:
receivedData[dataIndex++] = uartdata;
if (dataIndex == 9) {
// 数据接收完毕,分配给具体的变量
RollL = receivedData[0];
RollH = receivedData[1];
PitchL = receivedData[2];
PitchH = receivedData[3];
YawL = receivedData[4];
YawH = receivedData[5];
VL = receivedData[6];
VH = receivedData[7];
SUM = receivedData[8];
// 校验SUM是否正确
uint8_t calculatedSum = 0x55 + 0x53 + RollH + RollL + PitchH + PitchL + YawH + YawL + VH + VL;
if (calculatedSum == SUM) {
// 校验成功,可以进行后续处理
if((float)(((uint16_t)RollH << 8) | RollL)/32768*180>180){
Roll = (float)(((uint16_t)RollH << 8) | RollL)/32768*180 - 360;
}else{
Roll = (float)(((uint16_t)RollH << 8) | RollL)/32768*180;
}
if((float)(((uint16_t)PitchH << 8) | PitchL)/32768*180>180){
Pitch = (float)(((uint16_t)PitchH << 8) | PitchL)/32768*180 - 360;
}else{
Pitch = (float)(((uint16_t)PitchH << 8) | PitchL)/32768*180;
}
if((float)(((uint16_t)YawH << 8) | YawL)/32768*180 >180){
Yaw = (float)(((uint16_t)YawH << 8) | YawL)/32768*180 - 360;
}else{
Yaw = (float)(((uint16_t)YawH << 8) | YawL)/32768*180;
}
//LED_Toggle();
} else {
// 校验失败,处理错误
}
//LED_Toggle();
RxState = WAIT_HEADER1; // 重置状态以等待下一个数据包
}
break;
}
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); //UART
}
6、PID
//定义一个结构体类型变量
tPid PID_Link; //定义一个循迹结构体
//给结构体类型变量赋初值 结构体名,设定值,Kp,Ki,Kd,最大限幅值,最小限幅值
void PID_init(tPid *pid,float target_val,float Kp,float Ki,float Kd,float MAX,float MIN)
{
pid->target_val = target_val; //目标值设定
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->MAX = MAX;
pid->MIN = MIN;
pid->err = 0; //误差清零
pid->err_last = 0;
pid->err_pre = 0;
pid->err_sum = 0;
pid->actual_val = 0;//实际输出值
}
// 位置式PID控制函数
float PID_realize(tPid * pid,float actual_val)
{
pid->actual_val = actual_val;//传递真实值
pid->err = pid->target_val - pid->actual_val; //当前误差=目标值-真实值
pid->err_sum += pid->err;//误差累计值 = 当前误差累计和
//使用PID控制 输出 = Kp*当前误差 + Ki*误差累计值 + Kd*(当前误差-上次误差)
pid->actual_val = pid->Kp*pid->err + pid->Ki*pid->err_sum + pid->Kd*(pid->err - pid->err_last);
//保存上次误差: 这次误差赋值给上次误差
pid->err_last = pid->err;
//PID限幅
if(pid->actual_val > pid->MAX)
{
pid->actual_val = pid -> MAX;
}
else if(pid->actual_val < pid->MIN)
{
pid->actual_val = pid -> MIN;
}
return pid->actual_val;
}
//主函数里
PID_init(&PID_Link,0,70,0,200,1000,-1000); //循迹PID初始化
deviation = (int32_t)PID_realize(&PID_Link,imu_err/1.8); //获取PID的计算结果
PWML = PWML_Base - deviation;
PWMR = PWMR_Base + deviation;
Set_PWM(PWML,PWMR);
7、中断使能
//中断初始化,很重要,之前编码器一直获取不了
DL_Timer_startCounter(PWM_0_INST); //PWM
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); //UART
NVIC_ClearPendingIRQ(TIMER_0_INST_INT_IRQN);//TIMER_INT
NVIC_ClearPendingIRQ(GPIO_MULTIPLE_GPIOA_INT_IRQN);
NVIC_EnableIRQ(UART_0_INST_INT_IRQN); //使能串口中断
NVIC_EnableIRQ(UART_JY61P_INST_INT_IRQN); //使能中断
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);//使能定时器中断
NVIC_EnableIRQ(GPIO_MULTIPLE_GPIOA_INT_IRQN);
8、任务执行
//使用按键选择任务,任务里面采用switch case、RunMode++的形式
if(Task_Flag == 1) //任务1
{
switch(RunMode) //执行功能
{
case 0:
{
Get_Encoder_countA=0;
Get_Encoder_countB=0;
carL_dis=0,carR_dis=0;
RunMode++;
break;
}
case 1:
{}
}
}
4、结
完整版电赛源码:24电赛小车题省一源码
视频可以去阿B看看:24电赛H小车题省一视频
你也可以关注一下我的公众号获取资源。