前面我们用延时方式实现了数码管的数字倒计时显示。这一次我们用定时器的方式显示倒计时。区别在于本次使用定时器中断方式来动态改变要显示的数字。而主处理中只需要不断循环显示这个数值就行。
由于处理逻辑很简单,就不绘制流程图了。
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”中的定义
运行效果如下:
因为担心数码管模块需要的工作电流比较大,没有敢使用开发板提供的电源,而是用的外部电源。