一步步完成数码管
主要内容:
第一步:产生1ms的时基
第二步:静态显示
第三步:动态扫描
第四步:余晖 重影
第五步:带消息机制的任务函数
第六步:按指定进制显示
-------------------------------------------------------------------------------------------------------------------------------------
开发环境:AVR Studio 4.19 + avr-toolchain-installer-3.4.1.1195-win32.win32.x86
芯片型号:ATmega16
芯片主频:8MHz
-------------------------------------------------------------------------------------------------------------------------------------
第一步: 产生1ms的时基
说明:1、使用定时器0的 CTC中断产生 1ms的时基信号, CTC模式下时自动重装初值、比较方便。
2、使用 OCF0中断、不需要 OC0引脚输出波形。
代码:
Drv_Timer.h中的相关定义:// -------------------
// 定时器中断模式
typedef enum
{
INT_MODE_TOV = 0,
INT_MODE_OCF = 1,
INT_MODE_ICF = 2,
INT_MODE_OCF1A = 3,
INT_MODE_OCF1B = 4
} TIMER_INT_MODE;
// 定时器比较匹配引脚输出模式
typedef enum
{
COM_MODE_NONE = 0,
COM_MODE_TOGGLE = 1,
COM_MODE_CLEAR = 2,
COM_MODE_SET = 3,
} TIMER_COM_MODE;
// 定时器0
typedef enum
{
T0_WGM_NOMAL = 0,
T0_WGM_PHASE_PWM = 1,
T0_WGM_CTC = 2,
T0_WGM_FAST_PWM = 3,
T0_CLK_SOURCE_NONE = 0,
T0_CLK_SOURCE_CLK_1 = 1,
T0_CLK_SOURCE_CLK_8 = 2,
T0_CLK_SOURCE_CLK_64 = 3,
T0_CLK_SOURCE_CLK_256 = 4,
T0_CLK_SOURCE_CLK_1024 = 5,
T0_CLK_SOURCE_T0_FALL = 6,
T0_CLK_SOURCE_T0_RAISE = 7
} TIMER0_MODE;
Drv_Timer.c中的操作函数:
// ==========================================================================================================
// TIMER0 初始化
//
// 参数:wave_mode 工作模式/波形产生模式选择
// OC_mode 比较匹配/PWM输出模式选择
// clk_source 时钟源和预分频选择
//
// 写TCCR0时需要清除bit7=FOC0
//
// 定时器溢出周期 T = ((1.0 / 8000000) * 1000000) * clk_source * 256 ( @ 8MHz )
// ==========================================================================================================
void Drv_Timer0_init(const uint8_t wave_mode, const uint8_t com_mode, const uint8_t clk_source)
{
uint8_t wgm00,wgm01;
wgm00 = wave_mode & 0x01;
wgm01 = (wave_mode & 0x02) >> 1;
// 写TCCR0时需要将bit7=FOC0清0
TCCR0 = (wgm00 << 6)| // 工作模式/波形产生模式选择
(wgm01 << 3)|
((com_mode & 0x03) << 4)| // 比较匹配/PWM输出模式选择
((clk_source & 0x07) << 0); // 时钟源和预分频选择
}
// ==========================================================================================================
// TIMER0 中断使能
//
// 参数:int_mode = INT_MODE_TOV 或 INT_MODE_OCF 或 INT_MODE_ICF
// enable = ENABLE 或 DISABLE
//
// 说明:
// 1、OC0引脚要先配置成比较匹配引脚、再修改数据方向寄存器DDB3
// 2、可以单独使能/禁止一种模式的中断
//
// ==========================================================================================================
void Drv_Timer0_INT_Enable(const uint8_t int_mode, const uint8_t enable)
{
if(INT_MODE_TOV == int_mode)
{
if(DISABLE == enable)
{
TIMSK &= ~(1 << TOIE0);
}
else
{
TIMSK |= (1 << TOIE0);
}
TIFR |= (1 << TOV0);
return ;
}
if(INT_MODE_OCF == int_mode)
{
if(DISABLE == enable)
{
TIMSK &= ~(1 << OCIE0);
}
else
{
TIMSK |= (1 << OCIE0);
}
TIFR |= (1 << OCF0);
}
}
// ==========================================================================================================
// 设置TCNT0和OCR0的值
//
// (1). 在比较匹配下、OCR0需要在TCNT0被设置之后设置
// ==========================================================================================================
void Drv_Timer0_set_TCNT0_OCR0(const uint8_t tcnt0, const uint8_t ocr0)
{
TCNT0 = tcnt0;
OCR0 = ocr0;
}
sys_timer.c中设置定时器0,并在OCF0中断中使用PA1测试定时时间:
#include <avr/interrupt.h>
#include "Drv_Timer.h"
#include "sys_timer.h"
// ==========================================================================================================
// 系统任务定时器
//
// (1). 使用Timer0产生1ms的时标
// 定时周期 T = ((1.0/8000000)*1000000)*64*(124+1) = 1000us = 1ms
//
// ==========================================================================================================
void sys_timer_init(void)
{
// 定时器0初始化:CTC模式、OC0引脚不连接、64预分频
Drv_Timer0_init(T0_WGM_CTC, COM_MODE_NONE, T0_CLK_SOURCE_CLK_64);
// 设置初值:TCNT0=0、OCR0=122
Drv_Timer0_set_TCNT0_OCR0(0, 122);
// 使能OCF0中断
Drv_Timer0_INT_Enable(INT_MODE_OCF, ENABLE);
}
// ==========================================================================================================
// 系统定时器中断
//
// (1). 使用Timer0的CTC中断调度各个任务
//
// ==========================================================================================================
ISR(TIMER0_COMP_vect)
{
PORTA ^= (1 << PA1); // 使用PA1测试定时周期
}
main.c中完成初始化,并设置IO:
// ==========================================================================================================
// 主函数
// ==========================================================================================================
#include <avr/io.h>
#include "Drv_Timer.h"
#include "system.h"
#include "sys_timer.h"
#include "config.h"
// ==========================================================================================================
// main函数
// ==========================================================================================================
int main(void)
{
// ---------
// 关全局中断
cli();
// 系统初始化 ( 包含sys_timer_init() )
sys_init();
DDRA |= (1 << DDA0) | (1 << DDA1);
PORTA &= ~((1 << PA0 ) | (1 << PA1 ));
// OC0/PB3初始化为输出0
DDRB |= (1 << DDB3);
PORTB &= ~((1 << PB3 ));
// 开全局中断
sei();
// ---------
while(1)
{
}
return 0;
}
测试结果:
示波器输出如下:1、 PA1引脚输出方波,周期是 2*1.0ms,引脚电平每隔 1.0ms翻转一次。
使用 OCR0=124、计算得到精确的 1.0ms,但进入中断函数是需要花费时间的。
所以这里使用稍小的 OCR0=122,让从中断产生到进入中断函数为止的时间更精确为 1.0ms
有些计时功能会积累时基的误差、越到后面积累的误差越大,所以这里能精确就尽量做的精确些。
到此、 1.0ms定时完成。
2、 OC0引脚没有波形输出,我们也不需要用到这个引脚,就让他保持普通 IO的特性吧。
-------------------------------------------------------------------------------------------------------------------------------------
第二步: 静态显示
说明:1、这一步需要根据电路图、在指定的数码管上显示指定的符号。
1、数码管驱动电路图:
电路中使用的是共阴极数码管:
1个数码管有8个LED,称为8段数码管。
</