年初的時候,春節(jié)在家閑著無聊的東西生均,留個紀(jì)念。
材料
- STM8S103F3P 單片機一塊
- 4位數(shù)碼管一塊
- PCB板/面包板以及連接線若干
我使用的是某寶上買個STM8S103F核心板面睛,數(shù)碼管為共陰數(shù)碼管棚点。僅在面包板上實驗通過,尚未制成PCB板捷雕。
電路圖
程序
以下代碼均在IAR下測試通過椒丧,由于我并不會使用C,因此代碼可能比較丑陋救巷,見諒壶熏。
控制數(shù)碼管
對于控制數(shù)碼管顯示數(shù)字,我大量使用了硬編碼浦译,因此代碼很難看棒假。頭文件如下:(若實際使用中與上面的線路圖連接不一致,只需要修改此頭文件的相關(guān)定義即可管怠。)
/*
* 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號針腳 對應(yīng)分組
#define LED02_GROUP (GPIOA) // 數(shù)碼管2號針腳 對應(yīng)分組
#define LED03_GROUP (GPIOD) // 數(shù)碼管3號針腳 對應(yīng)分組
#define LED04_GROUP (GPIOB) // 數(shù)碼管4號針腳 對應(yīng)分組
#define LED05_GROUP (GPIOB) // 數(shù)碼管5號針腳 對應(yīng)分組
#define LED06_GROUP (GPIOA) // 數(shù)碼管6號針腳 對應(yīng)分組
#define LED07_GROUP (GPIOD) // 數(shù)碼管7號針腳 對應(yīng)分組
#define LED08_GROUP (GPIOC) // 數(shù)碼管8號針腳 對應(yīng)分組
#define LED09_GROUP (GPIOC) // 數(shù)碼管9號針腳 對應(yīng)分組
#define LED10_GROUP (GPIOC) // 數(shù)碼管10號針腳 對應(yīng)分組
#define LED11_GROUP (GPIOC) // 數(shù)碼管11號針腳 對應(yīng)分組
#define LED12_GROUP (GPIOC) // 數(shù)碼管12號針腳 對應(yīng)分組
// 第一組 針腳定義
#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
/hide1
與show2
/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ù)碼管對應(yīng)位數(shù)
*/
void show2(GPIO_TypeDef * port, unsigned char pin) {
GPIO_WriteHigh(port, pin);
}
/**
* 隱藏數(shù)碼管對應(yīng)位數(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 };
// 關(guān)閉所有位
hide2(LED12_GROUP, LED12);
hide2(LED09_GROUP, LED09);
hide2(LED08_GROUP, LED08);
hide2(LED06_GROUP, LED06);
// 關(guān)閉所有筆畫
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;
}
// 打開對應(yīng)位數(shù)
// 關(guān)閉所有位
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);
}
計算時間的流逝
由于單片機無法得知真實時間的流逝祝拯,因此需要通過單片機的時鐘頻率計算得出對應(yīng)的真實世界的時間。STM8S103默認(rèn)啟動使用內(nèi)部高速時鐘她肯,8分頻佳头,即16MHz / 8 = 2MHz
。那么一個時鐘周期就對應(yīng)真實世界的1 / 2MHz = 0.5 us
晴氨,這個結(jié)果在后面的延時函數(shù)中有用到康嘉。我在這里使用了定時器,來計算時間的流逝籽前,因為在main方法中亭珍,我可能會隨時修改循環(huán)體的代碼,所以我無法準(zhǔn)確計算出一次循環(huán)的準(zhǔn)確時間枝哄,此時采用定時器的中斷肄梨,能夠較為準(zhǔn)確的獲得時間的流逝。由于默認(rèn)啟動為2MHz的時鐘頻率挠锥,因此我設(shè)置的定時器采用1000分頻众羡,自動重載2000,即1 / (2MHz / 1000) *2000 = (1 / (1 / 500)) us * 2000 = 500us *2000 = 1000000us = 1000ms = 1s
蓖租,正好1秒觸發(fā)一次中斷粱侣。
注:可能是我淘寶上買的便宜貨原因羊壹,芯片的內(nèi)部時鐘其實誤差有點大,這樣計算出來的理論時間與實際間隔比較容易有出入齐婴。如果需要比較準(zhǔn)確的計時油猫,可以考慮使用外部晶振。
主程序
代碼注釋比較詳細尔店,不再重復(fù)說明眨攘。
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();
}
// 反轉(zhuǎn)設(shè)置位
void toggleSettle(){
if(settle == MINITES)
settle = HOURS;
else
settle = MINITES;
}
void valueUp(){
dpFlag = 1; // 設(shè)置值 強制顯示
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); // 默認(rèn)啟動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);
}
// 獲得設(shè)置按鍵的狀態(tài) - 進入/退出設(shè)置模式
BitStatus readButtonSet(){
return GPIO_ReadInputPin(GPIOD, GPIO_PIN_4);
}
// 獲得設(shè)值按鍵的狀態(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){
// 判定 設(shè)置鍵 狀態(tài)
if(readButtonSet() == PRESSED){
if(appMode == NORMAL){ // 從正常模式 切換到設(shè)置模式
if(++press_tick > 1000){
// 設(shè)置模式
appMode = SETTLE;
press_tick = 0;
delay_nms(2000); // 延遲2s 給用戶提示情竹,防止按鍵時間過長,進入設(shè)置模式后匀哄,再次退出秦效,延時狀態(tài)下,數(shù)碼管會停止顯示時間
}
}else if(appMode == SETTLE){ // 已經(jīng)在設(shè)置模式涎嚼,則切換時分秒的修改或退出設(shè)置模式
if(++press_tick > 1000){ // 短按30次 會自動認(rèn)為是長按
appMode = NORMAL;
press_tick = 0;
delay_nms(2000); // 延遲2s阱州,給用戶提示,防止按鍵時間過長法梯,退出設(shè)置模式后苔货,再次進入
}else{ //若不為長按,則切換設(shè)置的高低位
toggleSettle();
}
}
}else if(readButtonValue() == PRESSED){ // 是否為 調(diào)整鍵 按下
if(appMode == SETTLE){
if(--press_tick <= 0){ // 控制跳轉(zhuǎn)速率 防止長按狀態(tài)下立哑,跳動過快
valueUp();
press_tick = 500;
}
}
}else{
press_tick = 0; // 清除計數(shù)夜惭,防止多次短按后誤認(rèn)為長按
}
a = hh / 10;
b = hh % 10;
c = mm / 10;
d = mm % 10;
// 判定當(dāng)前程序模式
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){
// 設(shè)置模式下,dpFlag 臨時充當(dāng) 當(dāng)前設(shè)置位的標(biāo)志铛绰,用于閃爍顯示
// 當(dāng)前設(shè)置的數(shù)位 進行閃爍顯示诈茧,即 設(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); // 清除中斷標(biāo)志位
}
測試
此時按線路圖連接,啟動程序这嚣。應(yīng)該就已經(jīng)完成了鸥昏,在正常模式下,長按『設(shè)置鍵』進入設(shè)置模式(按『設(shè)值鍵』無任何效果)疤苹,在設(shè)置模式下互广,正在被設(shè)置位數(shù)(小時數(shù)或分鐘數(shù))會閃爍顯示敛腌,此時可以按『設(shè)值鍵』對該數(shù)字進行加一操作卧土,也可以長按『設(shè)值鍵』進行連續(xù)的加一操作惫皱。同時可以短按『設(shè)置鍵』切換設(shè)置小時數(shù)或分鐘數(shù)。設(shè)置完成后尤莺,長按『設(shè)置鍵』退出設(shè)置模式旅敷,返回正常模式。
需要注意的是颤霎,我使用的數(shù)碼管為沒有小數(shù)點的數(shù)碼管媳谁,在第二位與第三位數(shù)之間是豎著的兩個點,作為時鐘分隔符(分別連接著第三位與第四位的共陰極)友酱。每位數(shù)右下角的四個小數(shù)點均不亮晴音,如果你使用的數(shù)碼管為普通數(shù)碼管,非顯示時間的數(shù)碼管缔杉,在main.c的循環(huán)中锤躁,需要對小數(shù)點的顯示做部分調(diào)整。
這個應(yīng)用我做了將近半個月或详,其中有關(guān)于時間的計算系羞,對我造成了極大的困擾,一直不理解如何比較準(zhǔn)確的計算真實世界的時間流逝霸琴。對于軟延遲而言椒振,假設(shè)MCU時鐘頻率為X MHz,即一個時鐘周期需要(1/X)us梧乘,因此可以通過計算指令的時鐘周期澎迎,獲得準(zhǔn)確的時間。對于定時器而言宋下,假設(shè)MCU時鐘頻率為X MHz嗡善,分頻P,自動重載Y学歧,重復(fù)計數(shù)Z罩引,則一次中斷的時間為((1/(X / P))YZ)us,重復(fù)計數(shù)器好像只有TIM1支持枝笨,其他計數(shù)器的不支持則重復(fù)計數(shù)器為1袁铐。
源代碼
iar的源代碼一份,基于STM8S官方例程修改横浑。源碼中使用的引腳與之前電路圖中繪制的有所不同剔桨。STM8S_StdPeriph_Template