电赛比赛实录——问题的产生与解决

///电赛比赛实录——问题的产生与解决

电赛比赛实录——问题的产生与解决

就在今天,电赛测评结束的时候,我两周以来对电赛的各种准备也尘埃落定(不过感觉自己比较划水)。虽然最后结果并没有那么符合我的预期,不过在做这个算是我人生中的第一个电赛项目的时候,我还是学到了很多东西,也同时把高中做机器人时遇到的一些想法等重新回顾了一下。

首先先晒一下我们的成品,比较丑,不要太在意细节啦……

总体概述

因为我的动手能力比较差,所以小车的搭建主要还是依靠我的队友,包括各种模块的选择也都主要是由我的队友来负责,我对他们选择模块只有一个要求,让我的代码能尽量好写一点……(虽然最后还是有不少问题是依靠代码解决的……)

回顾这次比赛,我划水的时间还是比较多的,所以这次比赛的大部分工作还是由我的队友完成的,在这里还是非常感谢我的队友所付出的种种努力。(比心)

回顾一下这次比赛的经历,总的来说就是一个从发现问题到解决问题的过程,我们从组装到调试遇到了很多问题,这些问题既有硬件问题,也有代码逻辑的问题,还有一些设计的问题,其中一些解决了,一些找了一些替代方案,一些看似解决了但在最后测试时仍然出了问题。

在开始之前,我们先来看看这次比赛的题目吧:

比赛题目

激光追车系统(自控类)

一、任务

设计并制作激光追车系统,包括自制遥控小车,自制激光瞄准装置,瞄准装置包括摄像头、激光笔。系统组成如图1所示。

图1

二、要求

1.基本要求

(1)搭载显示屏,实时显示小车转速,单位RPM。

(2)小车速度通过按键可调,至少可分为三级速度,速度区别明显。

(3)要求小车自动沿直线行进指定距离(20-50cm内任意数字),误差小于10%,由按键输入行驶距离。

(4)在(3)的基础上,显示屏实时显示行进距离。

2.发挥部分

(1)小车带有姿态检测功能,能实现原地旋转45°、90°、120°、180°,由按键输入指定旋转角度,并在显示屏上实时显示旋转过程中旋转的角度。

(2)如图1所示,摄像头初始朝向随机摆放,将小车随机放在A区域内任意位置(在摄像头视野内),通过按键启动摄像头,要求摄像头能从起始位置指向小车,以激光笔照射到小车作为指示,激光笔至少连续照射在小车5秒以上。

(3)遥控小车在A区域内行驶,要求摄像头跟随小车,以激光笔照射小车作为指示。

(4)其他

前期设计

说实话,看到题目之后我整个人都是发懵的,小车控制方面倒是还好,有高中的一些基础,知道能通过编码器获取数据,再辅以一定的算法实现小车移动的功能。但是,那个激光追车系统是什么鬼啊???这就直接让我一个初学者搞图像识别了?难度还要机器学习???看到题目之后感觉我某个开关被打开了一样,各种奇怪的脑洞层出不穷……现在回想一下,那滋味……

我来介绍一下我当时开的脑洞吧……(有些太丢人的我就不说了)我当时的想法应该能分为两大类:以追车模块与车辆之间是否需要通信区分。

激光追踪模块设计

第一类: 追车模块与车辆之间存在通信

一、通过小车感受自身位置实现的激光追车

这个想法可以说是开始时最简单的想法了,这个想法的核心就是通过小车的编码器和姿态传感器等模块辅以算法实时计算出小车在场地上的位置,然后将这个位置传递给激光追踪模块, 激光追踪模块再经过另一套算法最终得出伺服电机需要转动的角度,实现激光追车的目的。

然而在后来的进一步思考中发现这个想法并不能实现扩展要求2,即能在任意给定初始条件下将摄像头对准小车,于是后来我脑洞大开地想到加一个gps定位模块来计算初始时的相对位置,后来和学长讨论时认为这样子误差太大而且在室内也没有卫星信号,所以这个方法就被放弃了。

二、通过小车上的信号源确定位置实现的激光追车

通过在小车上安装信号源,激光追踪模块通过两个分别沿x轴和y轴持续扫描的信号接收器来实现小车在追踪范围内的二维定位,因为我们只需要用到二维的两个角度值(分别对应两个伺服电机)就可以实现激光追车,所以当时认为这种方案也是可行的。但是学长给我们提出了一种更简单的方法,于是这个方案也被放弃了。

第二类: 追车模块与车辆之间不存在通信

本来这里也有几种方案的,但现在我只准备写学长提供的那个方案了……

通过openmv实现的激光追车系统

学长们给我推荐了openmv这样的一个模块,它通过python编程,具有非常强大的图像处理功能和功能完备的IDE(我没有打广告哦)。通过这个模块我们很轻松地实现了激光追车系统(其实主要也是利用例程)。

小车控制系统设计

这一块的设计相对就比较容易了,通过编码器和6050姿态传感器就能非常方便地实现小车的姿态感应以及各种与之相关的控制要求。(虽然最后6050部分没做……)

正式制作与测试

小车的搭建

正如开头所说,这一部分我参与得不多,而且我在这种动手操作方面也确实是比较差,所以在这一块主要仰赖与我的队友,我在这也不再描述了……(我队友似乎也要写他的博客,可能的话到时候在这里放一个链接)

激光追车系统

这一给看似最难部分我们队其实花了最少的时间(氪金就能变强),我们在网上买了openmv配套的云台(还好openmv的板子可以问学校借),然后直接往板子里烧激光追车的例程,然后测试发现效果非常好,只可惜动态追踪效果一般般,但是因为我们也都不会python,所以也不改(懒得改)代码了,在这一方面扣了点分……

然后我把我使用的例程代码贴出来作为参考

#pid.py
from pyb import millis
from math import pi, isnan
 
class PID:
    _kp = _ki = _kd = _integrator = _imax = 0
    _last_error = _last_derivative = _last_t = 0
    _RC = 1/(2 * pi * 20)
    def __init__(self, p=0, i=0, d=0, imax=0):
        self._kp = float(p)
        self._ki = float(i)
        self._kd = float(d)
        self._imax = abs(imax)
        self._last_derivative = float('nan')
 
    def get_pid(self, error, scaler):
        tnow = millis()
        dt = tnow - self._last_t
        output = 0
        if self._last_t == 0 or dt > 1000:
            dt = 0
            self.reset_I()
        self._last_t = tnow
        delta_time = float(dt) / float(1000)
        output += error * self._kp
        if abs(self._kd) > 0 and dt > 0:
            if isnan(self._last_derivative):
                derivative = 0
                self._last_derivative = 0
            else:
                derivative = (error - self._last_error) / delta_time
            derivative = self._last_derivative + \
                                     ((delta_time / (self._RC + delta_time)) * \
                                        (derivative - self._last_derivative))
            self._last_error = error
            self._last_derivative = derivative
            output += self._kd * derivative
        output *= scaler
        if abs(self._ki) > 0 and dt > 0:
            self._integrator += (error * self._ki) * scaler * delta_time
            if self._integrator < -self._imax: self._integrator = -self._imax
            elif self._integrator > self._imax: self._integrator = self._imax
            output += self._integrator
        return output
    def reset_I(self):
        self._integrator = 0
        self._last_derivative = float('nan')
#main.py
import sensor, image, time

from pid import PID
from pyb import Servo

pan_servo=Servo(1)
tilt_servo=Servo(2)

red_threshold  = (13, 49, 18, 61, 6, 47)

pan_pid = PID(p=0.07, i=0, imax=90)
tilt_pid = PID(p=0.05, i=0, imax=90)

sensor.reset() # Initialize the camera sensor.
sensor.set_pixformat(sensor.RGB565) # use RGB565.
sensor.set_framesize(sensor.QQVGA) # use QQVGA for speed.
sensor.skip_frames(10) # Let new settings take affect.
sensor.set_auto_whitebal(False) # turn this off.
clock = time.clock() # Tracks FPS.

def find_max(blobs):
    max_size=0
    for blob in blobs:
        if blob[2]*blob[3] > max_size:
            max_blob=blob
            max_size = blob[2]*blob[3]
    return max_blob


while(True):
    clock.tick() # Track elapsed milliseconds between snapshots().
    img = sensor.snapshot() # Take a picture and return the image.

    blobs = img.find_blobs([red_threshold])
    if blobs:
        max_blob = find_max(blobs)
        pan_error = max_blob.cx()-img.width()/2
        tilt_error = max_blob.cy()-img.height()/2

        print("pan_error: ", pan_error)

        img.draw_rectangle(max_blob.rect()) # rect
        img.draw_cross(max_blob.cx(), max_blob.cy()) # cx, cy

        pan_output=pan_pid.get_pid(pan_error,1)/2
        tilt_output=tilt_pid.get_pid(tilt_error,1)
        print("pan_output",pan_output)
        pan_servo.angle(pan_servo.angle()+pan_output)
        tilt_servo.angle(tilt_servo.angle()-tilt_output)

小车控制系统

原本的计划是用传感器获取所有数据,再辅以PID算法来完成项目中的各种操作,但是后来由于种种原因(根本原因:菜),所以在部分项目上放弃了通过传感器+PID的操作方式,而是采用预先测量数据然后采取按比例延时的方式来完成目标。虽然在测试的时候表现良好,但是在正式测试的时候却出现了种种问题。在下一个环节中将会对这些问题进行分析。

以下的部分给出主要的核心代码以及部分注释。

//ADC.c #include "adc.h" #include "delay.h" #include "stm32f4xx_adc.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" typedef unsigned char u8; typedef unsigned int u32; typedef unsigned short u16; //初始化ADC void Adc_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟 //先初始化ADC1通道5 IO口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 通道5 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不带上下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始� RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOF时钟 //GPIOF9,F10初始化设置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 GPIO_SetBits(GPIOB,GPIO_Pin_1); RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE); //ADC1复位 RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); //复位结束 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//预分频4分频。ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz ADC_CommonInit(&ADC_CommonInitStructure);//初始化 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//关闭连续转换 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐 ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中 也就是只转换规则序列1 ADC_Init(ADC1, &ADC_InitStructure);//ADC初始化 ADC_Cmd(ADC1, ENABLE);//开启AD转换器 } //获得ADC值 //ch: @ref ADC_channels //通道值 0~16取值范围为:ADC_Channel_0~ADC_Channel_16 //返回值:转换结果 u16 Get_Adc(u8 ch) { //设置指定ADC的规则组通道,一个序列,采样时间 ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_480Cycles ); //ADC1,ADC通道,480个周期,提高采样时间可以提高精确度 ADC_SoftwareStartConv(ADC1); //使能指定的ADC1的软件转换启动功能 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束 return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果 } //获取通道ch的转换值,取times次,然后平均 //ch:通道编号 //times:获取次数 //返回值:通道ch的times次转换结果平均值 u16 Get_Adc_Average(u8 ch,u8 times) { u32 temp_val=0; u8 t; for(t=0;t<times;t++) { temp_val+=Get_Adc(ch); delay_ms(5); } return temp_val/times; } float GetGP2Y(void) { u32 AD_PM; double pm; GPIO_ResetBits(GPIOB,GPIO_Pin_1); delay_us(280); AD_PM = Get_Adc(ADC_Channel_0); //PA0 delay_us(40); GPIO_SetBits(GPIOB,GPIO_Pin_1); delay_us(9680); pm = 0.17*AD_PM-0.1; //转换公式 // printf("%f\n",pm); return pm; } 
//ds18b20.c #include "ds18b20.h" #include "delay.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" typedef unsigned char u8; typedef unsigned int u32; typedef unsigned short u16; //复位DS18B20 void DS18B20_Rst(void) { DS18B20_IO_OUT(); //SET PG11 OUTPUT DS18B20_DQ_OUT=0; //拉低DQ delay_us(750); //拉低750us DS18B20_DQ_OUT=1; //DQ=1 delay_us(15); //15US } //等待DS18B20的回应 //返回1:未检测到DS18B20的存在 //返回0:存在 u8 DS18B20_Check(void) { u8 retry=0; DS18B20_IO_IN();//SET PG11 INPUT while (DS18B20_DQ_IN&&retry<200) { retry++; delay_us(1); }; if(retry>=200)return 1; else retry=0; while (!DS18B20_DQ_IN&&retry<240) { retry++; delay_us(1); }; if(retry>=240)return 1; return 0; } //从DS18B20读取一个位 //返回值:1/0 u8 DS18B20_Read_Bit(void) // read one bit { u8 data; DS18B20_IO_OUT();//SET PG11 OUTPUT DS18B20_DQ_OUT=0; delay_us(2); DS18B20_DQ_OUT=1; DS18B20_IO_IN();//SET PG11 INPUT delay_us(12); if(DS18B20_DQ_IN)data=1; else data=0; delay_us(50); return data; } //从DS18B20读取一个字节 //返回值:读到的数据 u8 DS18B20_Read_Byte(void) // read one byte { u8 i,j,dat; dat=0; for (i=1;i<=8;i++) { j=DS18B20_Read_Bit(); dat=(j<<7)|(dat>>1); } return dat; } //写一个字节到DS18B20 //dat:要写入的字节 void DS18B20_Write_Byte(u8 dat) { u8 j; u8 testb; DS18B20_IO_OUT();//SET PG11 OUTPUT; for (j=1;j<=8;j++) { testb=dat&0x01; dat=dat>>1; if (testb) { DS18B20_DQ_OUT=0;// Write 1 delay_us(2); DS18B20_DQ_OUT=1; delay_us(60); } else { DS18B20_DQ_OUT=0;// Write 0 delay_us(60); DS18B20_DQ_OUT=1; delay_us(2); } } } //开始温度转换 void DS18B20_Start(void)// ds1820 start convert { DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0x44);// convert } //初始化DS18B20的IO口 DQ 同时检测DS的存在 //返回1:不存在 //返回0:存在 u8 DS18B20_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//使能GPIOG时钟 //GPIOG9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化 DS18B20_Rst(); return DS18B20_Check(); } //从ds18b20得到温度值 //精度:0.1C //返回值:温度值 (-550~1250) short DS18B20_Get_Temp(void) { u8 temp; u8 TL,TH; short tem; DS18B20_Start (); // ds1820 start convert DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0xbe);// convert TL=DS18B20_Read_Byte(); // LSB TH=DS18B20_Read_Byte(); // MSB if(TH>7) { TH=~TH; TL=~TL; temp=0;//温度为负 }else temp=1;//温度为正 tem=TH; //获得高八位 tem<<=8; tem+=TL;//获得底八位 tem=(double)tem*0.625;//转换 if(temp)return tem; //返回温度值 else return -tem; }
//encoder.c #include "encoder.h" void encoder_left_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//开启TIM4时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);//开启GPIOB时钟 GPIO_PinAFConfig(GPIOD,GPIO_PinSource12,GPIO_AF_TIM4);//PB0引脚复用 GPIO_PinAFConfig(GPIOD,GPIO_PinSource13,GPIO_AF_TIM4);//PB1引脚服用 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13; //GPIOB0,GPIOB1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOD,&GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 65535; //设置下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIMx时钟频率除数的预分频值 不分频 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //设置定时器为编码器模式 IT1 IT2为上升沿和下降沿都计数四倍频 TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_BothEdge,TIM_ICPolarity_BothEdge); TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 0; //输入滤波器 TIM_ICInit(TIM4, &TIM_ICInitStructure); TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清楚所有标志位 TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //允许中断更新 TIM4->CNT = 0; TIM_Cmd(TIM4, ENABLE); //使能TIM3 } void encoder_right_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//开启TIM4时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);//开启GPIOB时钟 GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM4);//PB0引脚复用 GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM4);//PB1引脚服用 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //GPIOB0,GPIOB1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOC,&GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 65535; //设置下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIMx时钟频率除数的预分频值 不分频 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //设置定时器为编码器模式 IT1 IT2为上升沿和下降沿都计数四倍频 TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,TIM_ICPolarity_BothEdge,TIM_ICPolarity_BothEdge); TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 0; //输入滤波器 TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_ClearFlag(TIM3, TIM_FLAG_Update); //清楚所有标志位 TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //允许中断更新 TIM4->CNT = 0; TIM_Cmd(TIM3, ENABLE); //使能TIM3 } void TIM4_IRQHandler(void) { if(TIM4->SR&0X0001)//溢出中断 { } TIM4->SR&=~(1<<0);//清除中断标志位 } void TIM3_IRQHandler(void) { if(TIM3->SR&0X0001)//溢出中断 { } TIM3->SR&=~(1<<0);//清除中断标志位 } //参数说明:左边:3,右边:4 int Read_Encoder(u8 TIMX) { int Encoder_TIM; switch(TIMX) { case 2: Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break; case 3: Encoder_TIM= (short)TIM3 -> CNT; TIM3 -> CNT=0;break; case 4: Encoder_TIM= (short)TIM4 -> CNT; TIM4 -> CNT=0;break; default: Encoder_TIM=0; } return Encoder_TIM; }
//motro.c #include "stm32f4xx_gpio.h" #include "delay.h" #include "pwm.h" #include "led.h" #include "usart.h" #include "stm32f4xx_tim.h" typedef unsigned char u8; void Action_Right_Turn (void) { TIM_SetCompare1(TIM14,175); GPIO_SetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,225); GPIO_ResetBits(GPIOC,GPIO_Pin_2); } void Action_Left_Turn (void) { TIM_SetCompare1(TIM14,80); GPIO_SetBits(GPIOC,GPIO_Pin_2); //GPIO_ResetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,270); GPIO_ResetBits(GPIOC,GPIO_Pin_1); } void Go_speed_M (void) { TIM_SetCompare1(TIM14,275); GPIO_ResetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,185); GPIO_ResetBits(GPIOC,GPIO_Pin_2); } void Go_speed_L (void) { TIM_SetCompare1(TIM14,325); GPIO_ResetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,225); GPIO_ResetBits(GPIOC,GPIO_Pin_2); } void Go_speed_H (void) { TIM_SetCompare1(TIM14,265); GPIO_ResetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,165); GPIO_ResetBits(GPIOC,GPIO_Pin_2); } void Back (void) { TIM_SetCompare1(TIM14,150); GPIO_SetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,210); GPIO_SetBits(GPIOC,GPIO_Pin_2); } void stop(void) { TIM_SetCompare1(TIM14,500); GPIO_ResetBits(GPIOC,GPIO_Pin_1); TIM_SetCompare1(TIM13,500); GPIO_ResetBits(GPIOC,GPIO_Pin_2); }
//pwm.c #include "pwm.h" #include "led.h" #include "usart.h" #include "stm32f4xx_tim.h" typedef unsigned char u8; typedef unsigned int u32; typedef unsigned short u16; void TIM14_PWM_Init(u32 arr,u32 psc)//psc:分频系数 arr:重装载值 { //此部分需手动修改IO口设置 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE); //TIM14时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能PORTF时钟 GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GPIOF9复用为定时器14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //GPIOF9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化PF9 TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM14,&TIM_TimeBaseStructure);//初始化定时器14 //初始化TIM14 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低 TIM_OC1Init(TIM14, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC1 TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable); //使能TIM14在CCR1上的预装载寄存器 TIM_ARRPreloadConfig(TIM14,ENABLE);//ARPE使能 //TIM_CtrlPWMOutputs(TIM14,DISABLE); TIM_Cmd(TIM14, ENABLE); //使能TIM14 } void TIM13_PWM_Init(u32 arr,u32 psc)//psc:分频系数 arr:重装载值 { //此部分需手动修改IO口设置 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE); //TIM14时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能PORTF时钟 GPIO_PinAFConfig(GPIOF,GPIO_PinSource8,GPIO_AF_TIM13); //GPIOF9复用为定时器14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIOF9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化PF9 TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM13,&TIM_TimeBaseStructure);//初始化定时器14 //初始化TIM14 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低 TIM_OC1Init(TIM13, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC1 TIM_OC1PreloadConfig(TIM13, TIM_OCPreload_Enable); //使能TIM14在CCR1上的预装载寄存器 TIM_ARRPreloadConfig(TIM13,ENABLE);//ARPE使能 TIM_Cmd(TIM13, ENABLE); //使能TIM14 } void TIM11_PWM_Init(u32 arr,u32 psc)//psc:分频系数 arr:重装载值 { //此部分需手动修改IO口设置 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM11,ENABLE); //TIM14时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能PORTF时钟 GPIO_PinAFConfig(GPIOF,GPIO_PinSource7,GPIO_AF_TIM11); //GPIOF9复用为定时器14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //GPIOF9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化PF9 TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM11,&TIM_TimeBaseStructure);//初始化定时器14 //初始化TIM14 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低 TIM_OC1Init(TIM11, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC1 TIM_OC1PreloadConfig(TIM11, TIM_OCPreload_Enable); //使能TIM14在CCR1上的预装载寄存器 TIM_ARRPreloadConfig(TIM11,ENABLE);//ARPE使能 TIM_Cmd(TIM11, ENABLE); //使能TIM14 } void TIM10_PWM_Init(u32 arr,u32 psc)//psc:分频系数 arr:重装载值 { //此部分需手动修改IO口设置 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10,ENABLE); //TIM14时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能PORTF时钟 GPIO_PinAFConfig(GPIOF,GPIO_PinSource6,GPIO_AF_TIM10); //GPIOF9复用为定时器14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //GPIOF9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化PF9 TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM10,&TIM_TimeBaseStructure);//初始化定时器14 //初始化TIM14 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低 TIM_OC1Init(TIM10, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC1 TIM_OC1PreloadConfig(TIM10, TIM_OCPreload_Enable); //使能TIM14在CCR1上的预装载寄存器 TIM_ARRPreloadConfig(TIM10,ENABLE);//ARPE使能 TIM_Cmd(TIM10, ENABLE); //使能TIM14 } 
//rpm.c #include "rpm.h" #include "lcd.h" #include "sys.h" void show_rpm(void) { int encoder_left=0; int encoder_right=0; char printstr[30]={0}; encoder_left=Read_Encoder(3); encoder_right=Read_Encoder(4); sprintf(printstr,"Left :%d RPM",encoder_left/1560*600); LCD_ShowString(30,50,200,16,16,printstr); sprintf(printstr,"Right :%d RPM",encoder_right/1560*600); LCD_ShowString(30,70,200,16,16,printstr); }

以上部分为主要一些功能的代码实现,已经添加了一些注释,其余部分理解难度应该也不大了。还有部分比较简单的核心代码,或者是在之前的博文中出现过类似的代码,这一部分代码也就不在这里再次给出了。

比赛结果与问题分析

至于比赛结果,正如前文中也提到过的,出现了一些比较致命的问题,导致最后的得分并没有预期中的那么高。不过从这次比赛中我也吸取了不少教训。

首先就是目标达成方式的问题了,因为我们采用的是预测量的方式来完成的,所以就导致了我们的项目在任何外界参数发生改变的时候都不能很好的完成要求。我们的测试场地和比赛场地完全一致,所以可以排除是场地的影响。至于我的分析,问题主要出在电池的电压不同。因为测试的时候电压是处在消耗了一半作用的情况下,而在实际测试之前,我们把电池充满了,所以导致了电机的转速比测试的时候更快,所以也就产生了非常大的误差。

以上就是这次校内比赛的全部实录了,这是我的第一次比赛,从中学到了很多经验,希望以后我能做得更好。

发布者 | 2019-07-02T03:11:06+08:00 十二月 23rd, 2018|学习笔记, 比赛实录|0条评论

关于作者

坚强大概——并不是指的的结果,而是迈向某个目标的过程吧。

发表评论

博客作者

坚强大概——并不是指的的结果,而是迈向某个目标的过程吧。

标签云

博客统计

  • 4,039 点击次数
召唤伊斯特瓦尔