使用stm32f103c8實現一個簡單的運動會計時器

沒事做突然想寫博客,然後發現沒有刷什麼有價值的題,就寫個做實驗的記錄吧。
首先是構思的思維導圖
思維導圖
可以看到,我用到了按鍵結合中斷作爲輸入,使用定時中斷來計數,同時顯示使用的是oled屏幕,大小爲128*64個像素點。
屏幕圖片源水印淘寶店( ̄▽ ̄)”
oled

ok開始第一步,配置管腳圖

oled管腳*7,一個3v3,一個GND,還有五個GPIO輸出。
這邊用的是串行通信的方法,各個管腳功能分別是
CS:OLED 片選信號。
RST(RES):硬復位 OLED。
DC:命令/數據標誌(0,讀寫命令;1,讀寫數據)。
SCLK:串行時鐘線。在 4 線串行模式下,D0 信號線作爲串行時鐘線 SCLK。
SDIN:串行數據線。在 4 線串行模式下,D1 信號線作爲串行數據線 SDIN。
oled管腳:
CS~PA0
DC/RS~PA2 //這邊的DC管腳在程序裏面被記作了RS,都一樣
RST~PA1
SCLK/D0~PB5
SDIN/D1~PB6

按鍵管腳*5,一個5v,一個GND,3個GPIO輸入。
按鍵功能爲
key0:開始和暫停計時
key1:重置計時
key2:計次,多人跑步的時候記下各個人情況
按鍵管腳:
key0~PB0
key1~PB1
key2~PB2

ok,既然心裏有B數了,那麼就可以開始設置初始化函數了

KEY_INIT(),我設置爲上拉輸入了,管腳改一下,就不多說了。
EXTI_INIT(),從按鍵設置可以看到,應該選中斷線0,1,2,配置管腳爲下降沿觸發,設置NVIC中斷優先級,中斷服務程序先留着。
計時器初始化,這邊用三號定時中斷,TIM3_Int_Init(100,7199),解釋一下,這個板子的時鐘頻率爲72Mhz,我們不需要這麼高的頻率來做定時器中斷,所以將他分頻,72M除一下設置的分頻值7199,得到的結果是1W,也就是現在我們的定時器中斷爲1Whz,我們想要計時器可以精確到0.01s,所以設置爲每數100次觸發一個定時器中斷。然後中斷服務程序就可以用來累加我們的時間了。
oled_init(),這個也就是按照之前的管腳配置做就行了,注意選擇串行通信模式

初始化完成,回過頭來想一下,我覺得中文顯示可以嗦一嗦

先找個軟件來,將中文轉爲16進制的點陣圖,放到oled_font裏面,我用了四個中文字
時間第名,做成了四個數組,一個數組裏面有32個8位的16進制數。這邊用了一個OLED_DrawPoint(x,y,1),x和y爲像素點的位置,1表示這個點要點亮,否則用0。
另外,一個字爲16*16個像素點,這邊寫入的方式爲,先(0,0)->(0,1)->(0,2)……(0,15)->(1,0)->(1,2)…(15,15)。也就是先寫完一列16個像素,再換下一列。但是,我們數組中的數是8位的,也就是隻保存了8個像素點的狀態,所以一列要兩個數來表示。

void OLED_ShowMyChinese(u8 x,u8 y,u8 n)
{
    u8 temp,t,t1;//c語言要先聲明變量,t,t1用來for循環,temp是取數組值
    u8 y0=y;     //保存列開頭位置
    for(t=0;t<32;t++)   //遍歷數組中32個數
    {
        temp=oled_chinese_1616[n][t];    //第n個漢字的第t個數
        for(t1=0;t1<8;t1++)              //遍歷一個數中的八個位
        {
            if(temp&0x80)            //查看temp最高位,看像素點狀態
                OLED_DrawPoint(x,y,1);    //如果最高位是1,就點亮
            else
                OLED_DrawPoint(x,y,0);    //否則暗着
            temp<<=1;                      //左移查看下一個像素
            y++;                     //移動到下一個像素點的物理位置
            if((y-y0)==16)  //這邊是判斷是否寫完一列
            {               //畫完第一個8位數字後y不變回0
                y=y0;   //畫完兩個8位數字後y變回0
                x++;
                break;
            }
        }
    }
}

阿西吧,終於到了最重要的部分了,計時部分開始啦

首先想一下,時間怎麼保存呢?當然是用結構體啦

struct StuTime{
    u8 m,s;//八位的分鐘,秒
    u16 ms;//16位的毫秒
}MyTime={0,0,0};//初始化爲0:0.00

寫在timer.c 這個文件最合適啦。這樣定時中斷服務就可以直接累加時間了。

void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) 
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  
        if(!MyFlag)      //flag用來看有不有暫停計時
            return;
        MyTime.ms+=10;   //累加毫秒
        if(MyTime.ms>=1000)//累加秒
        {
            MyTime.ms%=1000;
            MyTime.s++;
            if(MyTime.s>=60)//累加分鐘
            {
                MyTime.s%=60;
                MyTime.m++;;
            }
        }
    }
}

那麼暫停的方法上面也說了,用一個flag就行了

void MyTime_GO(void)
{
    MyFlag=!MyFlag;
}

機智呀NULL
這個函數將被外部中斷服務0程序調用

void EXTI0_IRQHandler(void)
{
    delay_ms(10);
    MyTime_GO();
    EXTI_ClearITPendingBit(EXTI_Line0);
}

另外三個按鍵功能也是這樣的,所以主要的功能函數都在timer.c這個文件裏面了
重置計時功能

void MyTime_RST(void)
{
    MyFlag=0;   //先把flag設爲0,暫停計時
    MyTime.m=0; //再將時間全重置爲0
    MyTime.s=0; //這樣以後呢,就不多忙活了
    MyTime.ms=0;

}

然後是計次功能,顯示的樣式爲三行,這個oled可以寫四行字,這邊選擇下三行,第一行留給實時的時間。
這邊顯示的一行爲第nn名: nn:nn.nn,這個n就是到時候要替換的數字了。

void MyTime_CNT(void)
{
    u8 static MyCntFlag=1;//給這個函數加個鎖,防止連續觸發
    u8 static MyCnt=0;    //打算用三行來計次這個記錄我這是第幾次計次了,改寫哪行
    u8 MyPageY;
    if(!MyCntFlag)      //查看有不有鎖上
        return ;
    MyCntFlag=0;        //鎖上
    MyPageY=(MyCnt%3+1)*16;     //計算我改寫哪一行,也就是y的值
    //顯示中文了
    OLED_ShowMyChinese(0,MyPageY,2);//顯示我中文字庫的第二個字,就是"第"
    OLED_ShowMyChinese(32,MyPageY,3);//顯示"名",和下面數字和在一起就是‘第n名’了
    OLED_ShowNum(16,MyPageY,MyCnt+1,2,16);
    //顯示分隔符
    OLED_ShowChar(48,MyPageY,':',16,1);
    OLED_ShowChar(80,MyPageY,':',16,1);
    OLED_ShowChar(104,MyPageY,'.',16,1);
    //替換數字,都是兩位的數字
    OLED_ShowNum(64,MyPageY,MyTime.m,2,16);//分鐘
    OLED_ShowNum(88,MyPageY,MyTime.s,2,16);//秒鐘
    OLED_ShowNum(112,MyPageY,MyTime.ms,2,16);//毫秒
    MyCnt++;//記下我寫過這行了,下次寫下一行
    MyCntFlag=1;//記得把鎖打開
}

啊哈,到這邊計時已經實現了,可以暫停和重置還有計次,但是第一行的實時時間還沒有顯示,也沒有看到過刷新GRAM的函數,這樣是不行的,oled啥玩意都不會顯示

所以現在開始搞定最後一個main函數
首先,把七大姨八大叔全部叫來初始化一下,調用一個他們的init函數
然後進入永不退出的while(1)循環啦
第一行實時顯示的樣式爲時 間: nn:nn.nn,同上,n爲要替換的數字
這邊用到了一個GetTime函數,先看一下這個函數,這個函數在timer.c裏面

u32 GetTime(void)
{
    return (u32)((MyTime.m<<24)+(MyTime.s<<16)+(MyTime.ms));
}

還記的我們的時間結構體嗎,8位的分鐘,8位的秒,16位的毫秒,加一起不就可以組成一個32位的數啦,這邊就用一個32位的數返回給要查看時間的同學了,前8位爲分鐘,後面8位爲秒,再最後16位就是毫秒了。

    while(1)
    {
        u8 tmp;
        u32 reVal=GetTime();          //從timer.c獲取時間
        OLED_ShowMyChinese(0,0,0);    //顯示‘時’
        OLED_ShowMyChinese(32,0,1);   //顯示‘間’
        OLED_ShowChar(48,0,':',16,1); //顯示分隔的冒號
        OLED_ShowChar(80,0,':',16,1); 
        OLED_ShowChar(104,0,'.',16,1);
        tmp=(reVal&0xff000000)>>24;//根據前面GetTime的規則取出分鐘
        OLED_ShowNum(64,0,tmp,2,16);//顯示分鐘
        tmp=(reVal&0x00ff0000)>>16;//根據前面GetTime的規則取出秒
        OLED_ShowNum(88,0,tmp,2,16);//顯示秒
        tmp=(reVal&0x0000ffff);//根據前面GetTime的規則取出毫秒
        OLED_ShowNum(112,0,tmp,2,16);//顯示毫秒
        OLED_Refresh_Gram();//刷新oled,注意,整個項目中不要有多個刷新調用
        delay_ms(10);       //否則你的屏幕會亂掉,錯位
                            //這樣看來他的刷新函數應該沒有鎖保護了
    }
 }

好了,到這裏也就寫完了,連個線試一下,然後就可以開始debug了這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章