宏定义可以提高编程效率、让代码更简洁,并减少重复代码的写作。

基础的宏定义

我们写代码的时候经常用到绝对值、比较、限幅等,现在将整理如下,你也可以自己建一对.C.H 文件,存放自己常用的一些宏定义。

//====================================================宏定义函数区====================================================
//-------------------------------------------------------------------------------------------------------------------
// @brief       绝对值函数 数据范围是 [-32767,32767]
// @param       dat             需要求绝对值的数
// @return      int             返回绝对值
// Sample usage:                dat = myabs(dat);                               // 将dat变成正数
//-------------------------------------------------------------------------------------------------------------------
#define     myabs(x)            ((x)>=0? (x): (-(x)))

//-------------------------------------------------------------------------------------------------------------------
// @brief       限幅 数据范围是 [-32768,32767]
// @param       x               被限幅的数据
// @param       y               限幅范围(数据会被限制在-y至+y之间)
// @return      int             限幅之后的数据
// Sample usage:                int dat = limit(500,300);                       // 数据被限制在-300至+300之间  因此返回的结果是300
//-------------------------------------------------------------------------------------------------------------------
#define     limit(x,y)          ((x)>(y)? (y): ((x)<-(y)? (-(y)): (x)))

//-------------------------------------------------------------------------------------------------------------------
// @brief       双边限幅 数据范围是 [-32768,32767]
// @param       x               被限幅的数据
// @param       a               限幅范围左边界
// @param       b               限幅范围右边界
// @return      int             限幅之后的数据
// Sample usage:                int dat = limit_ab(500,-300,400);               //数据被限制在-300至+400之间  因此返回的结果是400
//-------------------------------------------------------------------------------------------------------------------
#define     limit_ab(x,a,b)     (((x)<(a))?(a):(((x)>(b))?(b):(x)))
//-------------------------------------------------------------------------------------------------------------------
// @brief       取两个数最大值最小值 数据范围是 [-32768,32767]
// @param       a               数据a
// @param       b               数据b
// @return      int             限幅之后的数据
// Sample usage:                int dat = limit_ab(500,-300,400);               //数据被限制在-300至+400之间  因此返回的结果是400
//-------------------------------------------------------------------------------------------------------------------
#define     max_ab(a,b)         (((a)>=(b))? (a):(b))
#define     min_ab(a,b)         (((a)>=(b))? (b):(a))

//====================================================宏定义函数区====================================================

蜂鸣器

//===========================================BEEP==========================================//
#define BEEP_PIN_ZF     P4_4
#define BEEP_PIN	      P44
#define BEEP_ON      BEEP_PIN = 1
#define BEEP_OFF     BEEP_PIN = 0
//BEEP
typedef struct
{
    uint16 State;
    uint16 Num;
    uint16 Time;
    uint32 Cnt;
}BeepTypedef;
 
extern BeepTypedef Beep;
 
void BeepTick(uint8 n, uint16 time);
void Beep_IntCnt(void);
//===========================================BEEP==========================================//
BeepTypedef  Beep;
/**
*@Name			:BeepTick
*@Description 	:BeepTick,中断里计时
*@Param			:n:响几次;time:间隔
*@Return		:None
*@Sample		:BeepTick(2, 100);
**/
void BeepTick(uint8 n, uint16 time)
{
    if(Beep.State == 'F')
    {
        Beep.State = 'T';
        Beep.Time  = time;
        Beep.Num   = n;
        if(Beep.Num == 0) Beep.Num = 1;
        Beep.Cnt = 1;
        BEEP_OFF;//gpio_low(BEEP_PIN);
    }
}
 
/**
*@Name			:Beep_IntCnt
*@Description 	:Beep_IntCnt蜂鸣器中断
*@Param			:
*@Return		:
*@Sample		:
**/
void Beep_IntCnt(void)
{
    if(Beep.State == 'T')
    {
        Beep.Cnt ++;
        if(Beep.Cnt %Beep.Time == 0) BEEP_PIN = !BEEP_PIN;//gpio_toggle_level(BEEP_PIN);
        if((Beep.Cnt %Beep.Time == 0) && ((Beep.Cnt / Beep.Time) == (Beep.Num*2)))
            Beep.State = 'F';
    }
}
 
/**
*@Name			:BeepParams_Init
*@Description 	:BeepParams_Init
*@Param			:
*@Return		:
*@Sample		:
**/
void BeepParams_Init(void)
{
    Beep.State = 'F';
    Beep.Cnt = 0;
    Beep.Num = 0;
    Beep.Time = 0;
}

按键

//===========================KEY================================//
typedef enum
{
    nopress = 0,
    onepress,//短按一下
    holdpress,//一直按着
} KeyStateEnum;

extern KeyStateEnum KeyUp, KeyDown, KeyLeft, KeyRight, KeyCenter, KeyNext, KeyLast;

#define KEY_UP      P70
#define KEY_DOWN    P41
#define KEY_LEFT    P36
#define KEY_RIGHT   P37
#define KEY_CENTER  P72
#define KEY_NEXT    P66
#define KEY_LAST    P46

void KeyScanInt(void);

很好看懂,按键有三种状态,没按下、点按、长按,根据自己按键的数目进行修改就行。

//===========================================KEY======================================//
KeyStateEnum KeyUp, KeyDown, KeyLeft, KeyRight, KeyCenter, KeyNext, KeyLast;
/**
*@Name            :KeyScan
*@Description     :KeyScan//在中断里扫描
*@Param            :None
*@Return        :None
*@Sample        :KeyScan();
**/
void KeyScanInt(void)
{
    static uint8 KeyPressNum = 0;
    static uint16 KeyPressTime = 0;
    static uint8 SomeKeyPress_Flag = 0;//0 松开 1 有按下  2:消抖后 3:长按
    #define AlwaysPressTime 1600//一直按检测时间
    #define debouncing  5
    if(SomeKeyPress_Flag == 0 && (KEY_UP == 0 || KEY_DOWN == 0 || KEY_CENTER == 0 ||\
        KEY_LEFT == 0 || KEY_RIGHT == 0 || KEY_NEXT == 0 || KEY_LAST == 0))
    {
         SomeKeyPress_Flag = 1;
    }
    if(SomeKeyPress_Flag > 0)
    {
        KeyPressTime ++;
        //计时5ms消抖
        if(SomeKeyPress_Flag == 1 && KeyPressTime >= debouncing)
        {
            SomeKeyPress_Flag = 2;
            if(KEY_UP == 0)
                KeyPressNum = 1;//检测按键按下
            else if(KEY_DOWN == 0)
                KeyPressNum = 2;
            else if(KEY_LEFT == 0)
                KeyPressNum = 3;
            else if(KEY_RIGHT == 0)
                KeyPressNum = 4;
            else if(KEY_CENTER == 0)
                KeyPressNum = 5;
            else if(KEY_NEXT == 0)
                KeyPressNum = 6;
            else if(KEY_LAST == 0)
                KeyPressNum = 7;
        }
        //按一下就松开的状态
        if((KEY_UP == 1 && KEY_DOWN == 1 && KEY_LEFT == 1 && KEY_RIGHT == 1  && KEY_NEXT == 1 && \
                KEY_LAST == 1 && KEY_CENTER == 1) && KeyPressTime < AlwaysPressTime && SomeKeyPress_Flag == 2)
        {
            SomeKeyPress_Flag = 0;//按键松开了
            if(KeyPressNum == 1)
                KeyUp = onepress;
            else if(KeyPressNum == 2)
                KeyDown = onepress;
            else if(KeyPressNum == 3)
                KeyLeft = onepress;
            else if(KeyPressNum == 4)
                KeyRight = onepress;
            else if(KeyPressNum == 5)
                KeyCenter = onepress;
            else if(KeyPressNum == 6)
                KeyNext = onepress;
            else if(KeyPressNum == 7)
                KeyLast = onepress;
            KeyPressTime = 0;
            SomeKeyPress_Flag = 0;
//            BeepTick(1,50);
        }
        //长按不松开的状态
        if(KeyPressTime >= AlwaysPressTime && SomeKeyPress_Flag == 2)
        {
            if(KeyPressNum == 1)      KeyUp = holdpress;
            else if(KeyPressNum == 2) KeyDown = holdpress;
            else if(KeyPressNum == 3) KeyLeft = holdpress;
            else if(KeyPressNum == 4) KeyRight = holdpress;
            else if(KeyPressNum == 5) KeyCenter = holdpress;
            else if(KeyPressNum == 6) KeyNext = holdpress;
            else if(KeyPressNum == 7) KeyLast = holdpress;
//            BeepTick(2,50);
            if(KEY_UP == 1 && KEY_DOWN == 1 && KEY_LEFT == 1 && KEY_RIGHT == 1 && \
                KEY_CENTER == 1 && KEY_NEXT == 1 && KEY_LAST == 1)
            {
                SomeKeyPress_Flag = 0;
                KeyPressTime = 0;
                KeyUp = nopress;
                KeyDown = nopress;
                KeyLeft = nopress;
                KeyRight = nopress;
                KeyNext = nopress;
                KeyLast = nopress;
                KeyCenter = nopress;
            }
        }
    }
}

/**
*@Name            :KeyParams_Init
*@Description     :KeyParams_Init
*@Param            :None
*@Return        :None
*@Sample        :KeyParams_Init();
**/
void KeyParams_Init(void)
{
    KeyUp = nopress;
    KeyDown = nopress;
    KeyCenter = nopress;
    KeyLeft = nopress;
    KeyRight = nopress;
    KeyNext = nopress;
    KeyLast = nopress;
}

电机

宏定义

//========================MOTOR=================================//
#define   Motor_state     1 //1是一路IO控制方向一路PWM控制转速;0是两路PWM控制
#define   PWM_FRE        17000    //电机频率
#define     PWM_R0      PWMB_CH3_P76
#define     PWM_R1      PWMB_CH1_P74
#define     PWM_L1        PWMB_CH4_P77
#define     PWM_L0        PWMB_CH2_P75
#define   DIR_R     P76
#define   DIR_L    P75
void  MotorWrite(void);

电机驱动部分,pwm_l、pwm_r为左右电机驱动占空比;
因常见电机驱动有两种,一种是一路IO控制方向一路PWM控制转速;另一种是两路PWM控制。故将其整合,根据实际的硬件,更改参数和引脚即可


//===========================================MOTOR==========================================//
//statement
short pwm_l = 0;// 0 ~ 1000 
short pwm_r = 0;

void MotorWrite(void)
{
    //限幅
    pwm_l = limit(pwm_l,1000);
    pwm_r = limit(pwm_r,1000);
    #if !Motor_state
        if(pwm_l >= 0)
        {
            pwm_duty( PWM_L0,0);                            
            pwm_duty( PWM_L1,  pwm_l);            
        }
        else
        {
            pwm_duty( PWM_L0, -pwm_l);                            
            pwm_duty( PWM_L1, 0);            
        }    
        if(pwm_r >= 0)
        {
            pwm_duty( PWM_R0,0);                            
            pwm_duty( PWM_R1, pwm_r);
        }
        else
        {
            pwm_duty( PWM_R0, -pwm_r);                            
            pwm_duty( PWM_R1, 0);        
        }    
  }
#else 
        if(pwm_l >= 0)
        {
            DIR_L = 1;
            pwm_duty( PWM_L1,  pwm_l);                                        
        }
        else
        {
            DIR_L = 0;
            pwm_duty( PWM_L1, -pwm_l);                            
        }    
        if(pwm_r >= 0)
        {
            DIR_R = 0;                            
            pwm_duty( PWM_R1, pwm_r);
        }
        else
        {
            DIR_R = 1;                            
            pwm_duty( PWM_R1, -pwm_r);        
        }
    #endif
}

舵机

//========================SERVO=================================//
#define PWM_SERVO            PWMA_CH3P_P24
#define    SERVO_MID            6650
#define SERVO_ADDMAX    780
#define SERVO_FRE            50        //舵机频率Hz
//===========================================SERVO==========================================//
short ServoOut = 0;
short ServoAdd = 0;
void ServoWrite(void)
{                     
    ServoAdd=limit(ServoAdd,SERVO_ADDMAX);
    ServoOut = (int32)(SERVO_MID + ServoAdd);
    pwm_duty(PWM_SERVO,ServoOut);
}

舵机中值(SERVO_MID )就是在该占空比下,轻轻推车,车将沿着直线行驶。

获取非常简单。可以定义一个变量,int SERVO_MID_tset=650;

在主循环里放pwm_duty(PWM_SERVO,SERVO_MID_tset);

通过不断改变SERVO_MID_tset的值,最终获取到满意的SERVO_MID。

改变SERVO_MID_tset的值的方式有很多种,最简单的就是在代码上修改,然后烧录。不断尝试。

最有效的是写一个按键菜单的人机交互,实时修改SERVO_MID_tset。

编码器

//========================ENCODER=================================//
#define ENCODER_L_TIM		    CTIM0_P34
#define ENCODER_L_DIR   		P63
#define ENCODER_R_TIM			  CTIM1_P35
#define ENCODER_R_DIR   		P73
#define RealSpeedK 		      8.696//0.2142								// 100/115(1024)470(4096)
 
//statement
typedef struct
{
    float SpeedNow;
    float SpeedLast;
    float Acc;
    float Distance;
	float Distance_Min;
}WheelTypedef;
 
extern WheelTypedef   Wheel, Wheel_L, Wheel_R;
 
extern int32 Encoder_L_Cnt;
extern int32 Encoder_R_Cnt;
extern int32 EncoderAll_Cnt;
 
void EncoderRead(void);
uint8 EncoderShow(void);
//======================================ENCODER======================================//
WheelTypedef   Wheel, Wheel_L, Wheel_R;
int32 Encoder_L_Cnt = 0;
int32 Encoder_R_Cnt = 0;
int32 EncoderAll_Cnt = 0;
 
/**
*@Name			:EncoderRead
*@Description 	:EncoderRead//速度单位为cm/s,距离单位为m
*@Param			:None
*@Return		:None
*@Sample		:EncoderRead();
**/
void EncoderRead(void)
{
    //获取并清空计数值
    Encoder_L_Cnt = ctimer_count_read(ENCODER_L_TIM);
    Encoder_R_Cnt = ctimer_count_read(ENCODER_R_TIM);
    EncoderAll_Cnt += (Encoder_L_Cnt + Encoder_R_Cnt)/2;
    ctimer_count_clean(ENCODER_L_TIM);
    ctimer_count_clean(ENCODER_R_TIM);
		if(ENCODER_L_DIR==1)Encoder_L_Cnt = - Encoder_L_Cnt;
	  if(ENCODER_R_DIR==0)Encoder_R_Cnt = - Encoder_R_Cnt;
    //计算速度
    Wheel_L.SpeedNow = (float)(Encoder_L_Cnt * RealSpeedK);
    Wheel_R.SpeedNow = (float)(Encoder_R_Cnt * RealSpeedK);
    //计算加速度
    Wheel_L.Acc = Wheel_L.SpeedNow - Wheel_L.SpeedLast;
    Wheel_R.Acc = Wheel_R.SpeedNow - Wheel_R.SpeedLast;
    //速度滤波
    if (myabs(Wheel_L.Acc) > 80.0f)
        Wheel_L.SpeedNow = Wheel_L.SpeedNow * 0.5 + Wheel_L.SpeedLast * 0.5;
    else
        Wheel_L.SpeedNow = Wheel_L.SpeedNow * 0.9 + Wheel_L.SpeedLast * 0.1;
    if (myabs(Wheel_R.Acc) > 80.0f)
        Wheel_R.SpeedNow = Wheel_R.SpeedNow * 0.5 + Wheel_R.SpeedLast * 0.5;
    else
        Wheel_R.SpeedNow = Wheel_R.SpeedNow * 0.9 + Wheel_R.SpeedLast * 0.1;
    //速度限幅
//    Wheel_L.SpeedNow = limit(Wheel_L.SpeedNow, 500);
//    Wheel_R.SpeedNow = limit(Wheel_R.SpeedNow, 500);
    //速度更新
    Wheel_L.SpeedLast = Wheel_L.SpeedNow;
    Wheel_R.SpeedLast = Wheel_R.SpeedNow;
    //距离计算
    Wheel.Distance += (float)(((Wheel_L.SpeedNow + Wheel_R.SpeedNow)/2.0f)/100000.0f);
    //总体速度计算
    Wheel.SpeedNow = (Wheel_L.SpeedNow + Wheel_R.SpeedNow)/2.0f;
    //总体速度更新
    Wheel.SpeedLast = Wheel.SpeedNow;
}

ADC

//========================ADC=================================//
#define ADC1            ADC_P05
#define ADC2            ADC_P06
#define ADC3            ADC_P15
#define ADC4            ADC_P14

#define ADC5            ADC_P10
#define ADC6            ADC_P03
#define ADC7            ADC_P04
#define ADC8            ADC_P01

#define ADC9            ADC_P11
#define ADC10            ADC_P13

初始化

运行首先要初始化

写一个总的初始化函数,将所有用到的初始化包含在里面

void Decices_Init(void)
{    
    //adc 
    adc_init(ADC1,ADC_SYSclk_DIV_2);
    adc_init(ADC2,ADC_SYSclk_DIV_2);
    adc_init(ADC3,ADC_SYSclk_DIV_2);
    adc_init(ADC4,ADC_SYSclk_DIV_2);
    adc_init(ADC5,ADC_SYSclk_DIV_2);
    adc_init(ADC6,ADC_SYSclk_DIV_2);                                                                                    
    adc_init(ADC7,ADC_SYSclk_DIV_2);
    adc_init(ADC8,ADC_SYSclk_DIV_2);
    adc_init(ADC9,ADC_SYSclk_DIV_2);
    adc_init(ADC10,ADC_SYSclk_DIV_2);                                                                                    
    //servo
    pwm_init(PWM_SERVO,SERVO_FRE,SERVO_MID);
    ServoAdd = 0;
    //motor
    pwm_init(PWM_L1, PWM_FRE, 0);                                                
    pwm_init(PWM_R1, PWM_FRE, 0);    
    gpio_mode(P7_5, GPO_PP);
    gpio_mode(P7_6, GPO_PP);        
    pwm_l = 0;
    pwm_r = 0;        
    //encoder  
    ctimer_count_init(ENCODER_L_TIM);//使用方向编码器
    ctimer_count_init(ENCODER_R_TIM);
    //BEEP
    gpio_mode(BEEP_PIN_ZF,GPO_PP);
    BEEP_OFF;
}

系列文章目录
文章分为三个层次

速通版是希望通过简化的步骤搭建出寻迹小车,进而了解整个智能车是如何实现的,快速上手,为后续参与智能车竞赛做基础。

如果只是为了完成学校智能车初期培训,做出能简单循迹的小车,可以看这个速通版。

全程引导篇是讲了做出能够完赛的智能车的整个过程,大部分文章只是简单点拨一下,但是附上了相关的文章链接,方便根据自己的情况深入了解。

全程引导篇,能够带你比较系统地了解整个智能车的制作过程,推荐备赛初期或者有车模之后学习。

详细讲解篇是全程引导篇的补充,由于全程引导篇是引导性质,文章内容只是点拨,缺乏相应的原理或代码讲解,因此写详细讲解篇作为补充。

详细讲解篇会渗透在全程引导篇中。


速通版
智能小车速通版——手把手教程


全程引导篇


智能车入门——IDE安装以及库函数选用

智能车入门——编程语言(c)的学习

智能车入门——简单驱动常用模块

智能车入门——车模器件篇

智能车入门——模块化编程

智能车入门——跑车前的零碎知识

智能车入门——电磁循迹原理与实现

智能车入门——实现低速完赛


详细讲解篇


智能车入门补充篇

智能车入门补充篇——逐飞开源库介绍

智能车入门补充篇——常见宏定义和使用

智能车入门补充篇——模块化编程

智能车入门补充篇——电感值处理、转向控制与巡线

智能车入门补充篇——元素识别

智能车模块详解——按键及按键调参

智能车模块详解——数据存储与读写(eeprom/flash)

智能车入门——I/O、PWM

智能车入门——中断

智能车入门——编码器

智能车入门——陀螺仪

智能车入门——摄像头和CCD

智能车入门——舵机

(更新中)