【星汇极客】单片机竞赛项目分享之24年江苏省电赛(持续更新)

前言

        本人是一名嵌入式学习者,在大学期间也参加了不少的竞赛并获奖,包括但不限于:江苏省电子设计竞赛省一、睿抗机器人国二、中国高校智能机器人国二、嵌入式设计竞赛国三、光电设计竞赛国三、节能减排竞赛国三

后面会经常写一下博客,分享一下资料、经验,如果想了解其他项目、技术,可以去B站搜:星汇极客,主页链接:星汇极客

此外,还开发了个人网站,里面有一些资料和源码供下载:个人网站

一、2024江苏省电子设计竞赛(H小车题)

1、题目解析

        一、 任务:设计主控采用TI MSPM0系列MCU控制的小车,能在地图上自动行驶。弧线的四个顶点分别定义为A、B、C和D点。(实际地图没有标有ABCD,初始位置没有要求)

a82288f4d1614acf92bf0e58f0e49f06.jpeg

        二、 要求:

        (1)将小车放在位置A点,小车能自动行驶到B点停车,停车时有声光提示。用时不大于15秒。(20分)。

6d314f26e7b941c5902711cbc6142c78.png

        思路:要求从A点出发,小车跑到B点停下来会有声光。使用编码器控制行驶距离,陀螺仪PID走直线,停下来蜂鸣器响、LED亮。

        注意:停下来时最好黑线在轮子间,蜂鸣器的纸要撕下来不然太小声。

        (2)将小车放在位置A点,小车能自动行驶到B点后,沿半弧线行驶到C点,再由C点自动行驶到D点,最后沿半弧线行驶到A点停车,每经过一个点, 声光提示一次。完成一圈用时不大于30秒。(20分)

2f71286a40c64150bd69616cbea38e8f.png

        思路:从A->B->C->D->A走一圈,AB、CD处用编码器+陀螺仪PID走直线,在BC、DA段用灰度传感器循迹。

        (3)将小车放在位置A点,小车能自动行驶到C点后,沿半弧线行驶到B 点,再由B点自动行驶到D点,最后沿半弧线行驶到A点停车。每经过一个点, 声光提示一次。完成一圈用时不大于40秒。(30分)

8de874379cbb4712be412af20b78cdba.png

        思路:从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小车题省一视频

你也可以关注一下我的公众号获取资源。

42389265a68f459da7f9a2cfb4f64a71.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星汇极客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值