藍橋杯微控制器 超聲波模組和PCA模組

Labghost發表於2020-11-24

原理

超聲波測量的原理

超聲波由一個超聲波模組向前方發出,並在空氣中傳播,在遇到障礙物時發生反射,再被另一個超聲波模組接收。微控制器用定時器測量超聲波從發出到接收的時長,再根據聲速,即可算出超聲波模組到障礙物之間的距離。公式:距離=(聲速 × Δ時間)÷2 。若距離單位為釐米,時間單位為微秒,距離公式為距離=Δ時間 x 0.017

另外,對距離進行微分等其他數學運算,還可以得到速度等其他測量值。測速公式:速度= (本次測量距離 - 上次測量距離 )÷兩次測量時間間隔

CT107D競賽板為例,控制微控制器在P10輸出一段38~41kHz的方波,八個週期,發射模組就會產生一段超聲波。當接收模組接收到超聲波,則會在P11產生一個下降沿。需測量超聲波發出後,到下降沿產生之間的時間長度。下圖為示波器測量的P10 P11的波形圖。
在這裡插入圖片描述

PCA模組的原理

目前藍橋杯微控制器MCU型號為IAP15F2K61S2,它有一個PCA模組可以用來實現定時、測量脈寬、PWM等功能。在使用PCA模組時,注意載入STC15F2K60S2.H的標頭檔案來定義特殊功能暫存器。

使用PCA模組驅動超聲波的優點就在於,它由硬體控制計時,並可以自動捕獲電平的變化,裝載計數的值同時產生中斷。CPU可以在控制發射超聲波後,空閒出來進行數碼管顯示等其他任務。
在這裡插入圖片描述
關於PCA定時器的計數頻率,可以選擇SYSclk/12,這樣時間單位才為微秒,因此CMOD暫存器應該設定為0x01(不要以圖示給出的順序配置CPSn位,請參考資料手冊關於CMOD的說明,來配置暫存器)。
PCA1
對於超聲波模組對應的P11引腳,要使PCA工作在下降沿捕獲模式,須將CAPN0置位,將CAPP0清零。傳送超聲波後,先將陣列暫存器(CL CH)和中斷標誌(CCF0 CF)清零,再開啟PCA模組計時及其中斷。當P11產生下降沿時,陣列暫存器(CLCH)的值裝載到模組的捕獲暫存器(CCAP0LCCAP0H)中,併產生中斷。

中斷處理時要判斷是下降沿捕獲(CCF0==1)還是定時器溢位(CF==1),注意清零中斷標誌、關閉PCA定時器及其中斷標誌。

程式碼

PCA定時器的範例程式

STC-ISP上可以直接獲取PCA定時器的範例程式,在“範例程式 > STC15Fxx/STC15Lxx/STC15Wxx Series > PCA的16位捕獲測量脈寬 > C”。或從STC15系列微控制器使用者手冊,第11章11.9節獲得範例程式。

選手可在比賽中參考該程式碼,編寫超聲波驅動。範例程式如下。

/*---------------------------------------------------------------------*/
/* --- STC MCU Limited ------------------------------------------------*/
/* --- STC15F4K60S4 系列 PCA實現16位捕獲舉例---------------------------*/
/* --- Mobile: (86)13922805190 ----------------------------------------*/
/* --- Fax: 86-0513-55012956,55012947,55012969 ------------------------*/
/* --- Tel: 86-0513-55012928,55012929,55012966-------------------------*/
/* --- Web: www.STCMCU.com --------------------------------------------*/
/* --- Web: www.GXWMCU.com --------------------------------------------*/
/* 如果要在程式中使用此程式碼,請在程式中註明使用了STC的資料及程式        */
/* 如果要在文章中應用此程式碼,請在文章中註明使用了STC的資料及程式        */
/*---------------------------------------------------------------------*/

//本示例在Keil開發環境下請選擇Intel的8058晶片型號進行編譯
//若無特別說明,工作頻率一般為11.0592MHz


#include "reg51.h"
#include "intrins.h"

#define FOSC    11059200L

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;

sfr P0M1 = 0x93;
sfr P0M0 = 0x94;
sfr P1M1 = 0x91;
sfr P1M0 = 0x92;
sfr P2M1 = 0x95;
sfr P2M0 = 0x96;
sfr P3M1 = 0xb1;
sfr P3M0 = 0xb2;
sfr P4M1 = 0xb3;
sfr P4M0 = 0xb4;
sfr P5M1 = 0xC9;
sfr P5M0 = 0xCA;
sfr P6M1 = 0xCB;
sfr P6M0 = 0xCC;
sfr P7M1 = 0xE1;
sfr P7M0 = 0xE2;

sfr P_SW1       = 0xA2;             //外設功能切換暫存器1

#define CCP_S0 0x10                 //P_SW1.4
#define CCP_S1 0x20                 //P_SW1.5

sfr CCON        =   0xD8;           //PCA控制暫存器
sbit CCF0       =   CCON^0;         //PCA模組0中斷標誌
sbit CCF1       =   CCON^1;         //PCA模組1中斷標誌
sbit CR         =   CCON^6;         //PCA定時器執行控制位
sbit CF         =   CCON^7;         //PCA定時器溢位標誌
sfr CMOD        =   0xD9;           //PCA模式暫存器
sfr CL          =   0xE9;           //PCA定時器低位元組
sfr CH          =   0xF9;           //PCA定時器高位元組
sfr CCAPM0      =   0xDA;           //PCA模組0模式暫存器
sfr CCAP0L      =   0xEA;           //PCA模組0捕獲暫存器 LOW
sfr CCAP0H      =   0xFA;           //PCA模組0捕獲暫存器 HIGH
sfr CCAPM1      =   0xDB;           //PCA模組1模式暫存器
sfr CCAP1L      =   0xEB;           //PCA模組1捕獲暫存器 LOW
sfr CCAP1H      =   0xFB;           //PCA模組1捕獲暫存器 HIGH
sfr CCAPM2      =   0xDC;           //PCA模組2模式暫存器
sfr CCAP2L      =   0xEC;           //PCA模組2捕獲暫存器 LOW
sfr CCAP2H      =   0xFC;           //PCA模組2捕獲暫存器 HIGH
sfr PCA_PWM0    =   0xf2;           //PCA模組0的PWM暫存器
sfr PCA_PWM1    =   0xf3;           //PCA模組1的PWM暫存器
sfr PCA_PWM2    =   0xf4;           //PCA模組2的PWM暫存器

BYTE cnt;                           //儲存PCA計時溢位次數
DWORD count0;                       //記錄上一次的捕獲值
DWORD count1;                       //記錄本次的捕獲值
DWORD length;                       //儲存訊號的時間長度(count1 - count0)

void main()
{
    P0M0 = 0x00;
    P0M1 = 0x00;
    P1M0 = 0x00;
    P1M1 = 0x00;
    P2M0 = 0x00;
    P2M1 = 0x00;
    P3M0 = 0x00;
    P3M1 = 0x00;
    P4M0 = 0x00;
    P4M1 = 0x00;
    P5M0 = 0x00;
    P5M1 = 0x00;
    P6M0 = 0x00;
    P6M1 = 0x00;
    P7M0 = 0x00;
    P7M1 = 0x00;

    ACC = P_SW1;
    ACC &= ~(CCP_S0 | CCP_S1);      //CCP_S0=0 CCP_S1=0
    P_SW1 = ACC;                    //(P1.2/ECI, P1.1/CCP0, P1.0/CCP1, P3.7/CCP2)
    
//  ACC = P_SW1;
//  ACC &= ~(CCP_S0 | CCP_S1);      //CCP_S0=1 CCP_S1=0
//  ACC |= CCP_S0;                  //(P3.4/ECI_2, P3.5/CCP0_2, P3.6/CCP1_2, P3.7/CCP2_2)
//  P_SW1 = ACC;  
//  
//  ACC = P_SW1;
//  ACC &= ~(CCP_S0 | CCP_S1);      //CCP_S0=0 CCP_S1=1
//  ACC |= CCP_S1;                  //(P2.4/ECI_3, P2.5/CCP0_3, P2.6/CCP1_3, P2.7/CCP2_3)
//  P_SW1 = ACC;  

    CCON = 0;                       //初始化PCA控制暫存器
                                    //PCA定時器停止
                                    //清除CF標誌
                                    //清除模組中斷標誌
    CL = 0;                         //復位PCA暫存器
    CH = 0;
    CCAP0L = 0;
    CCAP0H = 0;
    CMOD = 0x09;                    //設定PCA時鐘源為系統時鐘,且使能PCA計時溢位中斷
    CCAPM0 = 0x21;                  //PCA模組0為16位捕獲模式(上升沿捕獲,可測從高電平開始的整個週期),且產生捕獲中斷
//  CCAPM0 = 0x11;                  //PCA模組0為16位捕獲模式(下降沿捕獲,可測從低電平開始的整個週期),且產生捕獲中斷
//  CCAPM0 = 0x31;                  //PCA模組0為16位捕獲模式(上升沿/下降沿捕獲,可測高電平或者低電平寬度),且產生捕獲中斷

    CR = 1;                         //PCA定時器開始工作
    EA = 1;

    cnt = 0;
    count0 = 0;
    count1 = 0;

    while (1);
}

void PCA_isr() interrupt 7
{
    if (CCF0)
    {
        CCF0 = 0;
        if (CF && ((CCAP0H & 0x80) == 0))
        {
            CF = 0;
            cnt++;
        }
        count0 = count1;            //備份上一次的捕獲值
        ((BYTE *)&count1)[3] = CCAP0L;  //儲存本次的捕獲值
        ((BYTE *)&count1)[2] = CCAP0H;
        ((BYTE *)&count1)[1] = cnt;
        ((BYTE *)&count1)[0] = 0;
        length = count1 - count0;   //計算兩次捕獲的差值,即得到時間長度
        ((BYTE *)&length)[0] = 0;
    }
    if (CF)
    {
        CF = 0;
        cnt++;                      //PCA計時溢位次數+1
    }
}

超聲波測量的參考程式碼

對上述範例程式進行改寫,同時還須參照資料手冊配置暫存器。
實際測試達到了270cm,與感測器的精度有關。

#include <STC15F2K60S2.H>
#include <intrins.h>

#ifndef u8
#define u8 unsigned char
#endif

#ifndef u16
#define u16 unsigned int
#endif

#ifndef u32
#define u32 unsigned long
#endif

//定義超聲波模組引腳
sbit Trig = P1^0;	//傳送端
sbit Echo = P1^1;	//接收端

u8 code font[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
u8 code y4=0x80,y5=0xa0,y6=0xc0,y7=0xe0;
u8 dis[8]={0xc7,0xff,0xff,0xff,0xff,0xff,0xff,0xff};

bit trig_sign=1,echo_sign=0,time_out_sign=0;
u16 trig_cnt=1000;
u16 len,len_t;

void PCA_init();
void trig_len();
void echo_len();

void dis_smg();
/********************延時函式********************/
void delay100us()		//@12.000MHz
{
	unsigned char i, j;

	i = 2;
	j = 39;
	do
	{
		while (--j);
	} while (--i);
}
void delay12us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	_nop_();
	i = 33;
	while (--i);
}
/********************初始化函式******************/
void PCA_init(){
	P_SW1 &= 0xcf;	//(P1.2/ECI, P1.1/CCP0, P1.0/CCP1, P3.7/CCP2)
	CCON = 0;       //初始化PCA控制暫存器
					//PCA定時器停止
					//清除溢位中斷標誌
					//清除捕獲中斷標誌
	CL = 0;         //清零陣列暫存器
	CH = 0;
	CMOD = 0x01;    //設定PCA時鐘源為SYSclk/12,允許溢位中斷
	CCAPM0 = 0x10;  //PCA模組0為下降沿觸發,關閉中斷。
}

void Timer0Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;			//定時器時鐘12T模式
	TMOD &= 0xF0;			//設定定時器模式
	TL0 = 0x18;				//設定定時初值
	TH0 = 0xFC;				//設定定時初值
	TF0 = 0;				//清除中斷標誌
	TR0 = 1;				//定時器0開始計時
	
	ET0 = 1;				//使能定時器0中斷
}
/****************中斷處理函式********************/
void PCA_isr() interrupt 7		//PCA中斷處理函式
{	
	//捕獲成功
	if (CCF0){
		len_t = (CCAP0H<<8)|CCAP0L;		//儲存本次的捕獲值
		echo_sign = 1;
		CR = 0;							//PCA定時器停止工作
		CCAPM0 &= 0xfe;					//關閉中斷
	}
	//超時
	else if (CF){
		time_out_sign = 1;
		CR = 0;							//PCA定時器停止工作
		CCAPM0 &= 0xfe;					//關閉中斷
	}
	CCF0 = 0;							//清理中斷標誌
	CF = 0;
}

void T0_isr() interrupt 1		//T0中斷處理函式,每1000ms發射一次超聲波
{
	if(--trig_cnt == 0) {
		trig_cnt = 1000;
		trig_sign = 1;
	}
}
/*********************主函式*********************/
void main() {
	Trig = 0;
	Timer0Init();
	PCA_init();	 
	EA = 1;
	while(1) {
		dis_smg();
		if(trig_sign) trig_len();
		if(echo_sign | time_out_sign) echo_len();
	}
}

/*************************************************
*函式:trig_len()
*功能:發射超聲波,開啟PCA計時及中斷
*************************************************/
void trig_len() {
	u8 i=8;
	
	//產生八個週期7kHz方波訊號
	while(i--){
		Trig = 1;
		delay12us();
		Trig = 0;
		delay12us();
	}
	CL = 0;							//計時器清零
	CH = 0;
	CCF0 = 0;						//清標誌(開啟前必須清標誌)
	CF = 0;
	CCAPM0 |= 0x01;					//開啟中斷
	CR = 1;							//PCA定時器開始工作
	
	trig_sign = 0;
}

/*************************************************
*函式:echo_len()
*功能:計算距離,產生數碼管字模
*************************************************/
void echo_len() {
	u8 i;
	
	if(echo_sign){
		//計算距離
		len=len_t *0.017 *10;	//保留1位小數
		//生成字模
		dis[3]=font[len/10000];
		dis[4]=font[len/1000%10];
		dis[5]=font[len/100%10];
		dis[6]=font[len/10%10]&0x7f;
		dis[7]=font[len%10];
		//消零
		for(i=3;dis[i]==font[0];i++) dis[i]=0xff;
	}else if(time_out_sign){
		//超時設定
		len=9999;
		dis[3]=font[9];
		dis[4]=font[9];
		dis[5]=font[9];
		dis[6]=font[9]&0x7f;
		dis[7]=font[9];
	}
	time_out_sign = 0;
	echo_sign = 0;
}

/*************************************************
*函式:dis_smg()
*功能:數碼管顯示函式
*************************************************/
void dis_smg() {
	u8 i;

	for(i=0;i<8;i++){
		P2&=0x1f;
		P0=1<<i;
		P2|=y6;
		P2&=0x1f;
		P0=dis[i];
		P2|=y7;
		delay100us();
		P0=0xff;
	}
}