前面我们用延时方式实现了数码管的数字倒计时显示。这一次我们用定时器的方式显示倒计时。区别在于本次使用定时器中断方式来动态改变要显示的数字。而主处理中只需要不断循环显示这个数值就行。
由于处理逻辑很简单,就不绘制流程图了。
MSPM0L系列单片机本身有个通用定时器TIMG,是16位的自动重装定时器,支持向上和向下技术两种模式。同时还带有两个比较捕获单元,做到输出比较、输入捕获、PWM输出、单脉冲输出等功能。
TIMG可以选择BUSCLK、MFCLK、LFCLK作为时钟源,最大8分频时钟,再经过一个8位的预分频器,最终成为定时器的计数时钟。
因为对单片机的外设不是很懂,幸好有Sysconfig这个配置工具,就通过Sysconfig来配置定时器,双击工程区域中的gpio_toggle_output.syscfg文件,如果你安装了Sysconfig,会自动进入配置页面。按照我做的配置就可以实现定时器每10ms产生一次中断。
配置定时器需要的参数:
设置完成后,然后点击编译按钮(工具栏的小锤子)。回到“gpio_toggle_output.c”程序代码窗口,找到“SYSCFG_DL_init();”代码行,寻找这个函数的定义位置,看内部代码:
SYSCONFIG_WEAK void SYSCFG_DL_init(void)
{
SYSCFG_DL_initPower();
SYSCFG_DL_GPIO_init();
/* Module-Specific Initializations*/
SYSCFG_DL_SYSCTL_init();
SYSCFG_DL_TIMER_0_init();
}增加了一个和Timer0有关的处理代码,继续看SYSCFG_DL_TIMER_0_init这个函数的定义:
/*
* Timer clock configuration to be sourced by BUSCLK / (8000000 Hz)
* timerClkFreq = (timerClkSrc / (timerClkDivRatio * (timerClkPrescale + 1)))
* 1000000 Hz = 8000000 Hz / (4 * (7 + 1))
*/
static const DL_TimerG_ClockConfig gTIMER_0ClockConfig = {
.clockSel = DL_TIMER_CLOCK_BUSCLK,
.divideRatio = DL_TIMER_CLOCK_DIVIDE_4,
.prescale = 7U,
};
/*
* Timer load value (where the counter starts from) is calculated as (timerPeriod * timerClockFreq) - 1
* TIMER_0_INST_LOAD_VALUE = (10 ms * 1000000 Hz) - 1
*/
static const DL_TimerG_TimerConfig gTIMER_0TimerConfig = {
.period = TIMER_0_INST_LOAD_VALUE,
.timerMode = DL_TIMER_TIMER_MODE_PERIODIC,
.startTimer = DL_TIMER_STOP,
};
SYSCONFIG_WEAK void SYSCFG_DL_TIMER_0_init(void) {
DL_TimerG_setClockConfig(TIMER_0_INST,
(DL_TimerG_ClockConfig *) &gTIMER_0ClockConfig);
DL_TimerG_initTimerMode(TIMER_0_INST,
(DL_TimerG_TimerConfig *) &gTIMER_0TimerConfig);
DL_TimerG_enableInterrupt(TIMER_0_INST , DL_TIMERG_INTERRUPT_ZERO_EVENT);
DL_TimerG_enableClock(TIMER_0_INST);
}这一大段代码就是我们在SysConfig中通过设置得到的。
接下来我们要在主程序中加入Timer0的中断处理代码了。
// Timer0的中断处理,按照配置,每10ms完成一次中断,累计100次,得到1S的周期
void TIMER_0_INST_IRQHandler(void) {
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMERG_IIDX_ZERO:
// 定时器0完成一次中断
cnt_10ms=(cnt_10ms+1)%100;
if (cnt_10ms==0) {
//DL_GPIO_togglePins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_3_PIN);
// 已经达到1秒钟,计数值减1
curnum--;
if (curnum<0) {
curnum=10;
}
}
break;
case DL_TIMERG_IIDX_LOAD:
case DL_TIMERG_IIDX_CC0_DN:
case DL_TIMERG_IIDX_CC1_DN:
case DL_TIMERG_IIDX_CC0_UP:
case DL_TIMERG_IIDX_CC1_UP:
case DL_TIMERG_IIDX_OVERFLOW:
default:
break;
}
}因为我们设置的定时器的中断周期为10ms,所以这里需要进行累计,达到100次的时候,刚好是1秒的周期。为此需要设置一个变量cnt_10ms,一个用于统计中断次数。里面的而另一个变量curnum是用来设置数码管要显示的数字值的,每经过一次1秒钟,这个数值减1。在主程序中会不断显示这个数值,实现从10 到0 的倒计时。
整个过程就是这样,关于数码管的结构、驱动原理,数码管模块的驱动原理在上一个帖子中已经讲述的很详细了,本帖中就不再叙述了。
主程序代码如下(之前调试用的代码没有删除,被注释掉了):
/*
* Copyright (c) 2023, Texas Instruments Incorporated
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Texas Instruments Incorporated nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ti_msp_dl_config.h"
void delay(uint32_t cnt);
void DispNum(unsigned char num, unsigned char pos);
/* This results in approximately 0.5s of delay assuming 32MHz CPU_CLK */
#define DELAY (16000000) // 500ms
#define ms50 (1600000) // 50ms
#define s1 (32000000) // 1s
#define ms1 (320) // 1ms模式延迟
// 16进制数字显示信史段码(0-9,A-F),段码位=1时,数码管的对应的笔段不会被点亮
unsigned char TAB_SEG[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x77, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0xFF, 0xBF };
// 数码管模块的显示位置,自左向右
unsigned char TAB_POS[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
// 发送数据给数码管模块用的串行时钟,时钟上升沿有效
#define SCLK_0 DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN)
#define SCLK_1 DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN)
// 使数码管唯一寄存器数据被锁存到存储寄存器用的锁存信号,上升沿有效
#define RCLK_0 DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_2_PIN)
#define RCLK_1 DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_2_PIN)
// 发送数据给数码管模块用的串行数据位
#define DIO_0 {DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_3_PIN);}
#define DIO_1 {DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_3_PIN);}
void delay(uint32_t cnt) {
uint32_t d=cnt;
while(d--);
}
// 当前计数值
int curnum = 10;
// 10ms的累计计数,为了实现1S的处理
uint32_t cnt_10ms = 10;
/**
* 显示一位数码管数据
* chr : 显示的数值字符(‘0’-‘9’,‘A’-‘F’)
* pos :显示的位置(最左侧开始:0-7, 对应的位置数据:0x80, 0x40,0x20,0x10,0x08,0x04,0x02,0x01)
* 暂时不考虑小数点的处理。数字后面有小数点的场合,相当于该数字的小数点笔段位清0
*/
void DispNum(unsigned char chr, unsigned char pos) {
unsigned char i, num;
// 根据字符,获取对应的段位码数据
if (chr>=0 && chr<16) {
num=TAB_SEG[chr];
} else if (chr>=' ') {
// 空格,所有笔段都不显示
num = 0xFF;
} else if (chr>='-') {
num = 0xBF;
} else {
return;
}
// 将段位码数据以串行方式发送给数码管显示模块
// 每个字符有八个笔段,小数点通常不用
for(i=0;i<8;i++) {
// 输出笔段对应的数据位
if ((num & 0x80)>0) {
// 笔段数据为1 的,DIO对应GPIO口输出高电平
DIO_1;
} else {
// 笔段数据为0 的,DIO对应GPIO口输出低高电平
DIO_0;
}
// 下一个笔段
num<<=1;
// 发出移位用串行时钟上升沿脉冲
SCLK_0;
delay_cycles(ms1);
SCLK_1;
delay_cycles(ms1);
}
// 发送完笔段数据,发送显示位置数据
num = TAB_POS[pos%8];
for(i=0;i<8;i++) {
// 输出对应的数据位
if ((num & 0x80)>0) {
DIO_1;
} else {
DIO_0;
}
// 下一个数码管位置
num<<=1;
// 发出移位用串行时钟上升沿脉冲
SCLK_0;
delay_cycles(ms1);
SCLK_1;
delay_cycles(ms1);
}
// 发出锁存信号,点亮数码管
RCLK_0;
delay_cycles(ms1);
RCLK_1;
delay_cycles(ms1);
}
int main(void) {
uint32_t i=0, j=0;
/* Power on GPIO, initialize pins as digital outputs */
SYSCFG_DL_init();
// 设置定时器0中断优先级
NVIC_SetPriority(TIMER_0_INST_INT_IRQN, 1);
// 允许定时器0中断
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
// 启动定时器计数
DL_TimerG_startCounter(TIMER_0_INST);
/* Default: LED1 and LED3 ON, LED2 OFF */
DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_2_PIN);
DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN |
GPIO_LEDS_USER_LED_3_PIN |
GPIO_LEDS_USER_TEST_PIN);
DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN | GPIO_LEDS_USER_LED_3_PIN | GPIO_LEDS_USER_TEST_PIN);
while (1) {
// // 循环显示10递减到0 的过程
// for (i=0; i<11; i++) {
// if (i==0) {
// // 因为笔段共用,10的显示不同于其他数字,只能通过动态扫描方式显示
// for (j=0; j<1400; j++) {
// DispNum(1, 6);
// DispNum(0, 7);
// }
// } else {
// // 显示字符9-0
// DispNum(10-i, 7);
// // 保持显示时长为1秒
// delay_cycles(s1);
// }
//
// }
if (curnum==10) {
DispNum(1, 6);
DispNum(0, 7);
} else {
DispNum(curnum, 7);
}
}
}
// Timer0的中断处理,按照配置,每10ms完成一次中断,累计100次,得到1S的周期
void TIMER_0_INST_IRQHandler(void) {
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMERG_IIDX_ZERO:
// 定时器0完成一次中断
cnt_10ms=(cnt_10ms+1)%100;
if (cnt_10ms==0) {
//DL_GPIO_togglePins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_3_PIN);
// 已经达到1秒钟,计数值减1
curnum--;
if (curnum<0) {
curnum=10;
}
}
break;
case DL_TIMERG_IIDX_LOAD:
case DL_TIMERG_IIDX_CC0_DN:
case DL_TIMERG_IIDX_CC1_DN:
case DL_TIMERG_IIDX_CC0_UP:
case DL_TIMERG_IIDX_CC1_UP:
case DL_TIMERG_IIDX_OVERFLOW:
default:
break;
}
}关于中断函数名的问题,见文件“ti_msp_dl_config。h”中的定义
运行效果如下:

因为担心数码管模块需要的工作电流比较大,没有敢使用开发板提供的电源,而是用的外部电源。
我要赚赏金
