1. 程式人生 > >STM32控制3路超聲波感測器

STM32控制3路超聲波感測器

使用STM32定時器輸入捕獲模組控制3路超聲波感測器
本次使用的超聲波感測器是常見HC-SR04,該感測器常常使用在小型機器人和智慧小車的避障系統中。
這裡寫圖片描述
在上圖中,5v和GND為模組提供電能,Trig用於觸發模組測距,Echo用於接受返回電平訊號。
其操作時序圖如下:
這裡寫圖片描述
如上圖所示,STM32給Trig引腳一個超過10us的高電平,就可以使能模組內部的測距電路,模組會迴圈發出8個40kHz脈衝,發射出超聲波,然後通過檢測Echo引腳的高電平時間就可以測量出模組與障礙物之間的距離。其計算公式可表示如下:

distance=340Echo
2

模組驅動程式如下:
void HC_SR04_Init(void)
{
    /*初始化GPIO*/
   GPIO_InitTypeDef  GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef         NVIC_InitStructure;
    TIM_ICInitTypeDef        TIM_ICInitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能定時器2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6; //Trig:PA4,PA5,PA6 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_DOWN; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3; //Echo: PA2,PA3對應TIM2的通道3,4 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_NOPULL; GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_TIM2); GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_TIM2); GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//Enable GPIOB's Clock GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PB3 refers to TIM2's channel 2 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_NOPULL; GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_TIM2); GPIO_Init(GPIOB, &GPIO_InitStructure);//initialize GPIOB /初始化TIM2*/ TIM_TimeBaseStructure.TIM_Period = 0xffffffff; //感測器最大探測距離4000mm,一般不會溢位 TIM_TimeBaseStructure.TIM_Prescaler =84-1; //設定TIM2時鐘頻率為1MHz TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //初始化TIM2輸入捕獲 TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_BothEdge; //上升和下降都觸發輸入捕獲 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x00; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_3; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_BothEdge; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x00; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_BothEdge; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x00; TIM_ICInit(TIM2, &TIM_ICInitStructure); //設定輸入捕獲中斷 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,ENABLE);//使能中斷 TIM_Cmd(TIM2,ENABLE); dist.overflow=0; }

值得注意的是,這裡的輸入捕獲的邊緣極性設定為上升和下降沿都捕獲,效率比較高,也有大神開始使用上升沿捕獲,然後在中斷中將捕獲記性設定為下降沿捕獲,我試過這種辦法,程式容易卡死,所以沒有采用。
中斷程式可以這樣編寫:

void TIM2_IRQHandler(void)
{
    static uint16_t cnt_f,cnt_l,cnt_r;

   if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
     {
         dist.overflow++;
         TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
//       printf("overflow\n");
     }

     if(TIM_GetITStatus(TIM2, TIM_IT_CC2)!=RESET)
     {
       cnt_f++;
         if(cnt_f%2==1)//檢測到了上升電平
         {
              TIM2->CNT=0;//計數器清零
         }
         else//檢測到了下降沿電平,讀取距離值
         {
             dist.cnt=TIM2->CNT;
             TIM2->CNT=0;
             dist.f_distance=(dist.overflow*0xffffffff+dist.cnt)*0.17f;
             printf("F_dis=%.2fmm\n",dist.f_distance);
             dist.overflow=0;
         }
         if(cnt_f>65535)cnt_f=0;
         TIM_ClearITPendingBit(TIM2,  TIM_IT_CC2);
     }

     if(TIM_GetITStatus(TIM2, TIM_IT_CC3)!=RESET)
     {
       cnt_l++;
         if(cnt_l%2==1)//檢測到了上升電平
         {
              TIM2->CNT=0;
         }
         else
         {
             dist.cnt=TIM2->CNT;
             TIM2->CNT=0;
             dist.l_distance=(dist.overflow*0xffffffff+dist.cnt)*0.17f;
             printf("L_dis=%.2fmm\n",dist.l_distance);
             dist.overflow=0;
         }
         if(cnt_l>65535)cnt_l=0;
         TIM_ClearITPendingBit(TIM2,  TIM_IT_CC3);
     }

     if(TIM_GetITStatus(TIM2, TIM_IT_CC4)!=RESET)
     {
       cnt_r++;
         if(cnt_r%2==1)
         {
              TIM2->CNT=0;
         }
         else
         {
             dist.cnt=TIM2->CNT;
             TIM2->CNT=0;
             dist.r_distance=(dist.overflow*0xffffffff+dist.cnt)*0.17f;
             printf("R_dis=%.2fmm\n",dist.r_distance);
             dist.overflow=0;
         }
         if(cnt_r>65535)cnt_r=0;
         TIM_ClearITPendingBit(TIM2,  TIM_IT_CC4);
     }
}

這樣設計的好處是中斷程式設計簡單,可以保證實時性。
觸發模組進行測量,通過給模組的Trig引腳一個超過10us高電平訊號就可以觸發模組進行距離測量,程式程式碼如下:

/*
function:啟動距離測量
choice: 0:左邊超聲波模組
        1:中間超聲波模組
        2:右邊超聲波模組
*/
void Get_Distance(uint8_t choice)
{
    switch(choice)
    {
      case 0:
       GPIO_ResetBits(GPIOA,GPIO_Pin_4);
         GPIO_SetBits(GPIOA,GPIO_Pin_4);
         Delay_us(15);
         GPIO_ResetBits(GPIOA,GPIO_Pin_4);  
       break;
    case 1:
             GPIO_ResetBits(GPIOA,GPIO_Pin_5);
         GPIO_SetBits(GPIOA,GPIO_Pin_5);
         Delay_us(15);
         GPIO_ResetBits(GPIOA,GPIO_Pin_5);  
       break;
        case 2:
             GPIO_ResetBits(GPIOA,GPIO_Pin_6);
         GPIO_SetBits(GPIOA,GPIO_Pin_6);
         Delay_us(15);
         GPIO_ResetBits(GPIOA,GPIO_Pin_6);  
       break;
    }
}