STM8S制作數(shù)字時鐘

年初的時候,春節(jié)在家閑著無聊的東西,留個紀念。

材料

  • STM8S103F3P 單片機一塊
  • 4位數(shù)碼管一塊
  • PCB板/面包板以及連接線若干

我使用的是某寶上買個STM8S103F核心板,數(shù)碼管為共陰數(shù)碼管。僅在面包板上實驗通過,尚未制成PCB板。

電路圖

數(shù)字時鐘電路圖

程序

以下代碼均在IAR下測試通過,由于我并不會使用C,因此代碼可能比較丑陋,見諒。

控制數(shù)碼管

對于控制數(shù)碼管顯示數(shù)字,我大量使用了硬編碼,因此代碼很難看。頭文件如下:(若實際使用中與上面的線路圖連接不一致,只需要修改此頭文件的相關定義即可。)

/*
 * tube.h
 *
 *  Created on: 2016年2月3日
 *      Author: forDream
 */

#ifndef PROJECT_STM8S_STDPERIPH_TEMPLATE_DRIVE_TUBE_H_
#define PROJECT_STM8S_STDPERIPH_TEMPLATE_DRIVE_TUBE_H_
#define GPIO_MODE GPIO_MODE_OUT_PP_LOW_FAST
// 分組定義
#define LED01_GROUP (GPIOA) // 數(shù)碼管1號針腳 對應分組
#define LED02_GROUP (GPIOA) // 數(shù)碼管2號針腳 對應分組
#define LED03_GROUP (GPIOD) // 數(shù)碼管3號針腳 對應分組
#define LED04_GROUP (GPIOB) // 數(shù)碼管4號針腳 對應分組
#define LED05_GROUP (GPIOB) // 數(shù)碼管5號針腳 對應分組
#define LED06_GROUP (GPIOA) // 數(shù)碼管6號針腳 對應分組

#define LED07_GROUP (GPIOD) // 數(shù)碼管7號針腳 對應分組
#define LED08_GROUP (GPIOC) // 數(shù)碼管8號針腳 對應分組
#define LED09_GROUP (GPIOC) // 數(shù)碼管9號針腳 對應分組
#define LED10_GROUP (GPIOC) // 數(shù)碼管10號針腳 對應分組
#define LED11_GROUP (GPIOC) // 數(shù)碼管11號針腳 對應分組
#define LED12_GROUP (GPIOC) // 數(shù)碼管12號針腳 對應分組

// 第一組 針腳定義
#define LED01 GPIO_PIN_2 // 數(shù)碼管 01 針腳
#define LED02 GPIO_PIN_1 //
#define LED03 GPIO_PIN_5
#define LED04 GPIO_PIN_5
#define LED05 GPIO_PIN_4
#define LED06 GPIO_PIN_3

// 第二組
#define LED07 GPIO_PIN_2 // 數(shù)碼管 07 針腳 分組B
#define LED08 GPIO_PIN_7
#define LED09 GPIO_PIN_6
#define LED10 GPIO_PIN_5
#define LED11 GPIO_PIN_4
#define LED12 GPIO_PIN_3

void initTube();
void showNum(int digital, int number,int dp);
void showDP(int isShow);
void reverseDP();
void testTube();
#endif /* PROJECT_STM8S_STDPERIPH_TEMPLATE_DRIVE_TUBE_H_ */

此處為共陰數(shù)碼管,若為共陽,則倒置show1/hide1show2/hide2函數(shù)即可。

/*
 * tube.c
 *
 *  Created on: 2016年2月3日
 *      Author: forDream
 */
#include "tube.h"
#include "stm8s.h"

/**
 * 顯示具體筆畫
 */
void show1(GPIO_TypeDef * port, unsigned char pin) {
    GPIO_WriteLow(port, pin);
}
/**
 * 隱藏具體筆畫
 */
void hide1(GPIO_TypeDef * port, unsigned char pin) {
    GPIO_WriteHigh(port, pin);
}
/**
 * 顯示數(shù)碼管對應位數(shù)
 */
void show2(GPIO_TypeDef * port, unsigned char pin) {
    GPIO_WriteHigh(port, pin);
}
/**
 * 隱藏數(shù)碼管對應位數(shù)
 */
void hide2(GPIO_TypeDef * port, unsigned char pin) {
    GPIO_WriteLow(port, pin);
}
void showA(int isShow) {
    if (isShow)
        show1(LED11_GROUP, LED11);
    else
        hide1(LED11_GROUP, LED11);
}

void showB(int isShow) {
    if (isShow)
        show1(LED07_GROUP, LED07);
    else
        hide1(LED07_GROUP, LED07);
}
void showC(int isShow) {
    if (isShow)
        show1(LED04_GROUP, LED04);
    else
        hide1(LED04_GROUP, LED04);
}
void showD(int isShow) {
    if (isShow)
        show1(LED02_GROUP, LED02);
    else
        hide1(LED02_GROUP, LED02);
}
void showE(int isShow) {
    if (isShow)
        show1(LED01_GROUP, LED01);
    else
        hide1(LED01_GROUP, LED01);
}
void showF(int isShow) {
    if (isShow)
        show1(LED10_GROUP, LED10);
    else
        hide1(LED10_GROUP, LED10);
}
void showG(int isShow) {
    if (isShow)
        show1(LED05_GROUP, LED05);
    else
        hide1(LED05_GROUP, LED05);
}
void showDP(int isShow) {
    if (isShow)
        show1(LED03_GROUP, LED03);
    else
        hide1(LED03_GROUP, LED03);
}
void reverseDP(){
        GPIO_WriteReverse(LED03_GROUP, LED03);
}

volatile static int tag=0;

void initTube(){
    if (tag == 0) {
        tag = -1;
        /*
        GPIO_DeInit(LED01_GROUP);
        GPIO_DeInit(LED02_GROUP);
        GPIO_DeInit(LED03_GROUP);
        GPIO_DeInit(LED04_GROUP);
        GPIO_DeInit(LED05_GROUP);
        GPIO_DeInit(LED06_GROUP);
        GPIO_DeInit(LED07_GROUP);
        GPIO_DeInit(LED08_GROUP);
        GPIO_DeInit(LED09_GROUP);
        GPIO_DeInit(LED10_GROUP);
        GPIO_DeInit(LED11_GROUP);
        GPIO_DeInit(LED12_GROUP);
        */

        GPIO_Init(LED01_GROUP, LED01, GPIO_MODE);
        GPIO_Init(LED02_GROUP, LED02, GPIO_MODE);
        GPIO_Init(LED03_GROUP, LED03, GPIO_MODE);
        GPIO_Init(LED04_GROUP, LED04, GPIO_MODE);
        GPIO_Init(LED05_GROUP, LED05, GPIO_MODE);
        GPIO_Init(LED06_GROUP, LED06, GPIO_MODE);

        GPIO_Init(LED07_GROUP, LED07, GPIO_MODE);
        GPIO_Init(LED08_GROUP, LED08, GPIO_MODE);
        GPIO_Init(LED09_GROUP, LED09, GPIO_MODE);
        GPIO_Init(LED10_GROUP, LED10, GPIO_MODE);
        GPIO_Init(LED11_GROUP, LED11, GPIO_MODE);
        GPIO_Init(LED12_GROUP, LED12, GPIO_MODE);
    }
}
void showNum(int digital, int number,int dp) {
    unsigned char digitalMap[] = { 0, 12, 9, 8, 6 };
    // 關閉所有位
    hide2(LED12_GROUP, LED12);
    hide2(LED09_GROUP, LED09);
    hide2(LED08_GROUP, LED08);
    hide2(LED06_GROUP, LED06);
    // 關閉所有筆畫
    showA(0);
    showB(0);
    showC(0);
    showD(0);
    showE(0);
    showF(0);
    showG(0);
        showDP(dp);
    // 顯示筆畫
    switch (number) {
        case 0:
        showA(1);
        showB(1);
                showC(1);
        showD(1);
        showE(1);
        showF(1);
        break;
    case 1:
        showB(1);
        showC(1);
        break;
    case 2:
        showA(1);
        showB(1);
        showG(1);
        showE(1);
                showD(1);
        break;
    case 3:
        showA(1);
        showB(1);
        showG(1);
        showC(1);
        showD(1);
        break;
    case 4:
        showF(1);
        showG(1);
        showB(1);
        showC(1);
        break;
    case 5:
        showA(1);
        showF(1);
        showG(1);
        showC(1);
        showD(1);
        break;
    case 6:
        showA(1);
        showF(1);
        showE(1);
        showD(1);
        showC(1);
        showG(1);
        break;
    case 7:
        showA(1);
        showB(1);
        showC(1);
        break;
    case 8:                
        showA(1);
        showB(1);
                showC(1);
        showD(1);
        showE(1);
        showF(1);
        showG(1);
        break;
    case 9:
        showA(1);
        showB(1);
        showC(1);
        showD(1);
        showF(1);
        showG(1);
        break;
    }
    // 打開對應位數(shù)
    // 關閉所有位
    switch (digital) {
    case 1:
        show2(LED12_GROUP, LED12);
        break;
    case 2:
        show2(LED09_GROUP, LED09);
        break;
    case 3:
        show2(LED08_GROUP, LED08);
        break;
    case 4:
        show2(LED06_GROUP, LED06);
        break;
    }
}

void testTube(){
    showA(1);
    showB(1);
    showC(1);
    showD(1);
    showE(1);
    showF(1);
    showG(1);
    showDP(1);
    show2(LED12_GROUP, LED12);
    show2(LED09_GROUP, LED09);
    show2(LED08_GROUP, LED08);
    show2(LED06_GROUP, LED06);
}

計算時間的流逝

由于單片機無法得知真實時間的流逝,因此需要通過單片機的時鐘頻率計算得出對應的真實世界的時間。STM8S103默認啟動使用內(nèi)部高速時鐘,8分頻,即16MHz / 8 = 2MHz。那么一個時鐘周期就對應真實世界的1 / 2MHz = 0.5 us,這個結果在后面的延時函數(shù)中有用到。我在這里使用了定時器,來計算時間的流逝,因為在main方法中,我可能會隨時修改循環(huán)體的代碼,所以我無法準確計算出一次循環(huán)的準確時間,此時采用定時器的中斷,能夠較為準確的獲得時間的流逝。由于默認啟動為2MHz的時鐘頻率,因此我設置的定時器采用1000分頻,自動重載2000,即1 / (2MHz / 1000) *2000 = (1 / (1 / 500)) us * 2000 = 500us *2000 = 1000000us = 1000ms = 1s,正好1秒觸發(fā)一次中斷。

注:可能是我淘寶上買的便宜貨原因,芯片的內(nèi)部時鐘其實誤差有點大,這樣計算出來的理論時間與實際間隔比較容易有出入。如果需要比較準確的計時,可以考慮使用外部晶振。

主程序

代碼注釋比較詳細,不再重復說明。

main.h

#ifndef PROJECT_STM8S_STDPERIPH_TEMPLATE_MAIN_H_
#define PROJECT_STM8S_STDPERIPH_TEMPLATE_MAIN_H_
typedef enum {NORMAL, SETTLE} AppMode;
typedef enum {HOURS,MINITES} PartOfSettle;
void add1Sec();
void toggleDP();
void delay_nms(int ms);
void changeMode(AppMode mode);
AppMode currentMode();
void toggleSettle();
#endif /* PROJECT_STM8S_STDPERIPH_TEMPLATE_MAIN_H_ */

main.c

/* Includes ------------------------------------------------------------------*/
#include "stm8s.h"
#include "stm8s_it.h"
#include "tube.h"
#include "main.h"
/* Private defines -----------------------------------------------------------*/
#define PRESSED RESET // 按鍵按下

volatile AppMode appMode = NORMAL;
volatile PartOfSettle settle = MINITES;
volatile int hh,mm,ss;
volatile int dpFlag=1;

void delay_1ms(){
  for(int i=0;i<400;i++) asm("nop");
}
// 延遲n毫秒
void delay_nms(int ms) {
  for(int i=0;i<ms;i++)
    delay_1ms();
}
// 反轉設置位
void toggleSettle(){
  if(settle == MINITES)
    settle = HOURS;
  else
    settle = MINITES;
}
void valueUp(){
  dpFlag = 1; // 設置值 強制顯示
  if(settle == HOURS){
    if(++hh > 23) hh = 0;
  }else{
    if(++mm > 59) mm = 0;
  }
}
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
// 初始化定時器,用于計算時間
void initTim(){
  TIM1_TimeBaseInit(999, TIM1_COUNTERMODE_UP, 2000, 0); // 默認啟動16MHz,8分頻,即2MHz。定時器1秒一次中斷
  TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
  TIM1_ARRPreloadConfig(ENABLE);
  TIM1_SetCounter(0x0000);
  TIM1_Cmd(ENABLE);
  enableInterrupts();
}
/**
* 計數(shù)時間+1秒
*/
void add1Sec(){
  dpFlag ^= 1; // 一秒鐘,數(shù)碼管中時間的分割點跳動一次
  if(++ss>59){
    ss = 0;
    if(++mm>59){
      mm = 0;
      if(++hh>23)
        hh = 0;
    }
  }
}
// 初始化按鍵狀態(tài) -- 置為上拉輸出無中斷模式
void initButton(){
  GPIO_Init(GPIOD, GPIO_PIN_6, GPIO_MODE_IN_PU_NO_IT);
  GPIO_Init(GPIOD, GPIO_PIN_4, GPIO_MODE_IN_PU_NO_IT);
}
// 獲得設置按鍵的狀態(tài) - 進入/退出設置模式
BitStatus readButtonSet(){
  return GPIO_ReadInputPin(GPIOD, GPIO_PIN_4);
}
// 獲得設值按鍵的狀態(tài) - 調(diào)整時鐘數(shù)字
BitStatus readButtonValue(){
  return GPIO_ReadInputPin(GPIOD, GPIO_PIN_6);
}
void main(void)
{
    initButton();
    hh=00;
    mm=00;
    ss=00;
    initTube();
    initTim();
    int a,b,c,d;
    int press_tick = 0;
    while(1){
      // 判定 設置鍵 狀態(tài)
      if(readButtonSet() == PRESSED){
        if(appMode == NORMAL){ // 從正常模式 切換到設置模式
          if(++press_tick > 1000){
            // 設置模式
            appMode = SETTLE;
            press_tick = 0;
            delay_nms(2000); // 延遲2s 給用戶提示,防止按鍵時間過長,進入設置模式后,再次退出,延時狀態(tài)下,數(shù)碼管會停止顯示時間
          }  
        }else if(appMode == SETTLE){ // 已經(jīng)在設置模式,則切換時分秒的修改或退出設置模式
          if(++press_tick > 1000){ // 短按30次 會自動認為是長按
            appMode = NORMAL;
            press_tick = 0;
            delay_nms(2000); // 延遲2s,給用戶提示,防止按鍵時間過長,退出設置模式后,再次進入
          }else{ //若不為長按,則切換設置的高低位
            toggleSettle();
          }
        }
      }else if(readButtonValue() == PRESSED){ // 是否為 調(diào)整鍵 按下
        if(appMode == SETTLE){
          if(--press_tick <= 0){ // 控制跳轉速率 防止長按狀態(tài)下,跳動過快
            valueUp();
            press_tick = 500;
          }
        }
      }else{
        press_tick = 0; // 清除計數(shù),防止多次短按后誤認為長按
      }
      a = hh / 10;
      b = hh % 10;
      c = mm / 10;
      d = mm % 10;
      // 判定當前程序模式
      if(appMode == NORMAL){        
        showNum(1,a,dpFlag);
        //delay_nms(7);
        showNum(2,b,dpFlag);
        //delay_nms(7);
        showNum(3,c,dpFlag); 
        //delay_nms(7);
        showNum(4,d,dpFlag);
        //delay_nms(7);
      }else if(appMode == SETTLE){
        // 設置模式下,dpFlag 臨時充當 當前設置位的標志,用于閃爍顯示
        // 當前設置的數(shù)位 進行閃爍顯示,即 設置小時數(shù)時,小時數(shù)閃爍,否則為分鐘數(shù)閃爍
        if(settle == HOURS){
          if(dpFlag){
            showNum(1,a,1);
            showNum(2,b,1);
          }
          showNum(3,c,1);
          showNum(4,d,1);
        }else if(settle == MINITES){
          showNum(1,a,1);
          showNum(2,b,1);
          if(dpFlag){
            showNum(3,c,1);
            showNum(4,d,1);
          }
        }
      }
    }
}

#ifdef USE_FULL_ASSERT

/**
  * @brief  Reports the name of the source file and the source line number
  *   where the assert_param error has occurred.
  * @param file: pointer to the source file name
  * @param line: assert_param error line source number
  * @retval : None
  */
void assert_failed(u8* file, u32 line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}
#endif

定時器中斷

在stm8s_it.c中找到INTERRUPT_HANDLER(TIM1_UPD_OVF_TRG_BRK_IRQHandler, 11)的方法簽名,修改如下:

INTERRUPT_HANDLER(TIM1_UPD_OVF_TRG_BRK_IRQHandler, 11)
{
  /* In order to detect unexpected events during development,
     it is recommended to set a breakpoint on the following instruction.
  */
  add1Sec(); // 增加一秒時間
  TIM1_ClearFlag(TIM1_FLAG_UPDATE); // 清除中斷標志位
}

測試

此時按線路圖連接,啟動程序。應該就已經(jīng)完成了,在正常模式下,長按『設置鍵』進入設置模式(按『設值鍵』無任何效果),在設置模式下,正在被設置位數(shù)(小時數(shù)或分鐘數(shù))會閃爍顯示,此時可以按『設值鍵』對該數(shù)字進行加一操作,也可以長按『設值鍵』進行連續(xù)的加一操作。同時可以短按『設置鍵』切換設置小時數(shù)或分鐘數(shù)。設置完成后,長按『設置鍵』退出設置模式,返回正常模式。

需要注意的是,我使用的數(shù)碼管為沒有小數(shù)點的數(shù)碼管,在第二位與第三位數(shù)之間是豎著的兩個點,作為時鐘分隔符(分別連接著第三位與第四位的共陰極)。每位數(shù)右下角的四個小數(shù)點均不亮,如果你使用的數(shù)碼管為普通數(shù)碼管,非顯示時間的數(shù)碼管,在main.c的循環(huán)中,需要對小數(shù)點的顯示做部分調(diào)整。

這個應用我做了將近半個月,其中有關于時間的計算,對我造成了極大的困擾,一直不理解如何比較準確的計算真實世界的時間流逝。對于軟延遲而言,假設MCU時鐘頻率為X MHz,即一個時鐘周期需要(1/X)us,因此可以通過計算指令的時鐘周期,獲得準確的時間。對于定時器而言,假設MCU時鐘頻率為X MHz,分頻P,自動重載Y,重復計數(shù)Z,則一次中斷的時間為((1/(X / P))YZ)us,重復計數(shù)器好像只有TIM1支持,其他計數(shù)器的不支持則重復計數(shù)器為1。

源代碼

iar的源代碼一份,基于STM8S官方例程修改。源碼中使用的引腳與之前電路圖中繪制的有所不同。STM8S_StdPeriph_Template

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容