STM32控制遥控小车

在两周四次课的时间里从0学习到能独立做出一个能超声波避障,能蓝牙遥控的小车, 收获真的很大, 同时也领略到偏硬件方向编程的乐趣(C语言不老!)


我的小车

车正在避障

主要功能点在GPIO的编程, TIM定时器的初步使用, USART串口通讯

编译平台为KEIL, 使用STM32开发板

main.c

用来将各个模块进行初始化,(但是初始化的具体操作放在各自的.c文件中)
while(1)循环中定时探测前方障碍距离

    bluetooth_init(); 
    SysTick_Init();//延时初始化
    echo_init();
    
    
    while(1)
    {
        if(go_flag)//处于前进状态才去发送超声波判断距离
          if(get_length()<0.005)
          {
            turn(1);    
            Delay_ms(1500);
            gogogo(1);
          }
    }

TIM3(暂时无用)和USART(蓝牙信号指令的接收)的中断函数我也放在了main.c中

void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}
void USART1_IRQHandler(void)
{
    int order ; 
    if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
    {
          order = USART_ReceiveData(USART1);
            switch(order)
            {
                case 0x31://前进 
                    go_flag=1;
                    gogogo(1);
                    break;
                case 0x32://倒车 
                    gogogo(-1);
                    break;
                case 0x33://加速 
                    changeSpeed(-1);
                    break;
                case 0x34://减速 
                    changeSpeed(1);
                    break;
                case 0x35://左转 
                    turn(1);
                    break;
                case 0x36://右转 
                    turn(2);
                    break;
                case 0x37://停车 
                    gogogo(0);
                go_flag=0;
                    break;
            }
        
    }
}

这里发现了一个KEIL的bug,我通过断点调试了一整天才发现的!!
如果把USART_IRQHandler中的order变量设置为.c的全局变量,则每次进入中断前,order就会被自动设置为0x32,然后跑到case2里执行一遍,然后再中断一次,以正常方式进行,编译器简直有毒--个人怀疑可能是编译器的优化做的太过度了

echo.c

int toofar=0;
int psc = 71;   //分频系数  72 000 000 /72 =1us , *1000 0=10ms(1.5米)意味足够远,不予考虑 
/* 初始化模块的 GPIO 以及初始化定时器 TIM2*/
void echo_init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    TIM_TimeBaseInitStructer.TIM_Period=9999;// 定时周期为 10000
    TIM_TimeBaseInitStructer.TIM_Prescaler=psc; 
    TIM_TimeBaseInitStructer.TIM_ClockDivision=0; 
    TIM_TimeBaseInitStructer.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_TimeBaseInitStructer.TIM_RepetitionCounter = 0  ;
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructer);

    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
    NVIC_InitTypeDef NVIC_InitStructer;
    NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructer.NVIC_IRQChannelSubPriority=2;
    NVIC_InitStructer.NVIC_IRQChannel=TIM2_IRQn;
    NVIC_InitStructer.NVIC_IRQChannelCmd=ENABLE;
    NVIC_Init(&NVIC_InitStructer);
    TIM_Cmd(TIM2,DISABLE);// 关闭定时器使能




    GPIO_InitTypeDef GPIO_InitStructer;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    /*TRIG 信号 */
    GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
    GPIO_InitStructer.GPIO_Pin=GPIO_Pin_8;
    GPIO_Init(GPIOB, &GPIO_InitStructer);
    /*ECOH 信号 */
    GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
    GPIO_InitStructer.GPIO_Pin=GPIO_Pin_9;
    GPIO_Init(GPIOB, & GPIO_InitStructer);
}

初始化函数不解释,但是注意一下psc写成全局变量是为了方便修改使用者决定的什么才是"足够短",需要避障的

    
double get_length(void)
{
    double length=0;
    long int t;
    double time; 
        GPIO_SetBits(GPIOB,GPIO_Pin_8); 
        Delay_us(20); 
        GPIO_ResetBits(GPIOB,GPIO_Pin_8);  
        while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==RESET);
        TIM_Cmd(TIM2,ENABLE);// 回响信号到来,开启定时器计数
        while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==SET);
        TIM_Cmd(TIM2,DISABLE);// 关闭定时器
        if(!toofar) //没有完成一次计数周期,即距离不是很远 
        {
            t=TIM_GetCounter(TIM2);// 获取 TIM2 寄存器中的计数值
            time = t/1000000; //往返时间,单位s 
            length =  340*time/2; //单程长度,单位m 
            TIM2->CNT=0; // 将 TIM2 计数寄存器的计数值清零
        }
        else
        {
            toofar = 0; 
            length =99; //足够远,不用考虑 
            TIM2->CNT=0; // 将 TIM2 计数寄存器的计数值清零
        }
    return length;
}

get_length()用来打开超声波模块,最后返回一个距离值

void TIM2_IRQHandler(void) // 中断,当距离足够远时,设置toofar = 1,不必再计算 
{
    if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
      {
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);// 清除中断标志
        toofar = 1; 
      }
}

这个中断函数非常重要
比如,如果距离足够长,导致计数器完成了一个计数周期,开始了新的计数周期,那么读出来的getCounter就会比原来小一整个周期,所以如果完成了一个周期的计数,就算他够远,设置toofar = 1

Bluetooth.c

#include "bluetooth.h"
void bluetooth_init()
{
     GPIO_InitTypeDef GPIO_InitStructure;
     USART_InitTypeDef U;
     NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); 
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    
     GPIO_InitStructure.GPIO_Pin =GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
         GPIO_Init(GPIOA, &GPIO_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn ;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure); 
    
    U.USART_BaudRate = 9600;
    U.USART_WordLength = USART_WordLength_8b;
    U.USART_StopBits = USART_StopBits_1;
    U.USART_Parity = USART_Parity_No;
    U.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    U.USART_Mode = USART_Mode_Rx;
    USART_Init(USART1,&U);
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    USART_Cmd(USART1,ENABLE);
 } 

这里面只有关于蓝牙引脚的初始化

car.c

int carSpeedPeriod = 0;

void car_init(int i)
{
    //和转方向相关的引脚 
     GPIO_InitTypeDef GPIO_InitStructure;
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
     GPIO_InitStructure.GPIO_Pin =GPIO_Pin_1| GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出? 
     GPIO_Init(GPIOD, &GPIO_InitStructure);
     //和timer相关的引脚
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); 
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//抄呼吸灯 
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(GPIOA,&GPIO_InitStructure);
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;    
      GPIO_Init(GPIOB,&GPIO_InitStructure);
    
    TIM_TimeBaseInitTypeDef T;
    
    carSpeedPeriod+= i;
    T.TIM_Period  = carSpeedPeriod;
    T.TIM_Prescaler = 719;
    T.TIM_ClockDivision = 0;
    T.TIM_CounterMode = TIM_CounterMode_Up;
    T.TIM_RepetitionCounter = 0 ;
    TIM_TimeBaseInit(TIM3,&T);
    TIM_Cmd(TIM3,ENABLE);
    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
    
    TIM_OCInitTypeDef O;
    O.TIM_OCMode = TIM_OCMode_PWM2;
    O.TIM_Pulse =5;
    O.TIM_OutputState = TIM_OutputState_Enable;
    O.TIM_OCPolarity=TIM_OCPolarity_Low;
    TIM_OC1Init(TIM3,&O);
    TIM_OC2Init(TIM3,&O);
    TIM_OC3Init(TIM3,&O);
    TIM_OC4Init(TIM3,&O);
      
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn ;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure); 
       } 

控制电机的各个引脚的初始化
为什么有全局变量carSpeedPeriod呢? 因为加减速的时候需要先读出原来的转速值,然后自增/减操作

注意电机的转向并不是完全向前哦! 因为电机是对称的,所以另一侧的电机要反着转

       
// 控制车子的运作方向       
void gogogo(int direction)
{
    //前进 
    if(direction == 1)
    {
        carSpeedPeriod = 0;
        car_init(30);
        GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_SET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_SET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_RESET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_RESET);
    }
    //停车 
    else if(direction == 0)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,DISABLE); 
        TIM_Cmd(TIM3,DISABLE);
    }
    //后退 
    else if(direction == -1)
    {
        carSpeedPeriod = 0;
        car_init(30);
        GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_RESET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_RESET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_SET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_SET);
    }
}
    
//转向,没有考虑回正 
void turnx(int direction)
{
    //左转 
    if(direction == 1)
    {
        GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_SET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_SET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_SET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_SET);
    }
    //右转 
    if(direction == 2)
    {
        GPIO_WriteBit(GPIOD, GPIO_Pin_1,Bit_RESET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_3,Bit_RESET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_2,Bit_RESET);
        GPIO_WriteBit(GPIOD, GPIO_Pin_4,Bit_RESET);
    }
}
void turn(int direction)
{
    //左转 
    if(direction == 1)
    {
        turnx(1); 
        Delay_ms(1000);
        gogogo(1);//回轮 
    }
    //右转 
    else if(direction == 2)
    {
        turnx(2);
        Delay_ms(1000);
        gogogo(1);
    }   
}
//改变速度,通过改变周期 
void changeSpeed(int i)
{
    if(carSpeedPeriod > 2)//不能再小了 
    car_init(i*2);
}

这些操作都是通过重新注册GPIO引脚和TIM定时器的配置完成的,难度不大,但是一定要细心

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 姓名:周崇杰 学号:16140120059 专业:机械设计制造及其自动化 转载自:http://blog.csd...
    CJbaby阅读 3,470评论 0 3
  • 交通灯控制设计 一、选题背景 每个城市的交通就犹如人体的血管,人类生命的持续需要心脏为血液提供动力,依靠血液来在人...
    Rik_personal阅读 1,618评论 0 0
  • 什么是嵌入式 IEEE(Institute of Electrical and Electronics Engin...
    Leon_Geo阅读 3,682评论 1 20
  • 山村里家家都有大片的山地,种庄稼又全靠人工,只靠自家几个单薄的劳力,种那些洋芋(土豆)、小麦、玉米之类的大庄稼是很...
    糖果乔阅读 266评论 0 0
  • “我是将来要成为王的男人。”8岁的男孩眉清目秀,稚气未脱,这番豪情壮语倒令我失笑。“冰,这个世界早已经没有王了。”...
    匪玉阅读 379评论 16 15