一步步完成数码管

主要内容:
第一步:产生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清0TCCR0 = (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=122Drv_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初始化为输出0DDRB  |=   (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段数码管

共阴的意思是:
1、8个LED的负极都连接在com引脚
2、8个LED的正极对应8个引脚,编号分别为[Dp,g,f,e,d,c,b,a]

数码管的电压和电流:
点亮1个数码管所需的电流需要查看厂家给的数码管数据手册才知道。
如果没有数据手册,可以预估为10-20mA,电压预估为2V

和普通LED一样、必要的时候需要加限流电阻

在动态扫描中、每个数码管都是点亮几毫秒熄灭几十毫秒,并非一直点亮,所以不加限流电阻也行,除非是大电流数码管。

数码管的驱动芯片是74HC13874HC573,他们的驱动电流足够点亮1个数码管:

2、共阴极数码管的点亮方式:

1、在引脚 a输入高电平,引脚 b-Dp输入低电平,在 com引脚输入低电平。
       那么,段 [a]被点亮,段 [Dp,g,f,e,d,c,b]都不亮。
       也就是说、只有输入状态 为高电平的段会被点亮。
2、在引脚 b、c输入高电平,引脚 a、d-Dp输入低电平,在 com引脚输入低电平。
       那么,段 [b,c]被点亮,段 [a,d,e,f,g,Dp]都不亮。
       这时得到的图像就是数字 '1'的图像,对应的 段码[Dp,g,f,e,d,c,b,a]=0b00000110=0x06
3、在引脚 a、b、g、e、d输入高电平,引脚 c、f、Dp输入低电平,在 com引脚输入低电平。
       那么,段 [a,b,g,e,d]被点亮,段 [c,f,Dp]都不亮。
       这时得到的图像就是数字 '2'的图像,对应的 段码[Dp,g,f,e,d,c,b,a]=0b01011011=0x5B
4、也就是说、 com口为低电平的数码管被 使能,如果它的 段选中有 不为0的段,这个段就会被 点亮

3、段码:

1、 16十六进制数字[0-9,A-F]的段码如下:

static const uint8_t segment_code[17]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,  // 0 - 90x77,0x7c,0x39,0x5e,0x79,0x71,                      // A - F0x00                                                // 全部熄灭
};

最后一个值 0x00就是全部 LED段都不亮,用来熄灭数码管上所有 LED段。
      在电路中、 74HC573的输出和输入保持一致,同时输出电流。

将这些段码赋予PORTB口就可以在74HC573的输出同样的电平数据,使得段[Dp,g,f,e,d,c,b,a]上得到对应的电平,对应的LED段就被点亮。

4、位选:

1、通过拉低某个数码管的 com口、来 使能这个数码管,这称为位选。
8com口都连接在 74HC138上,输出为低电平的引脚上连接的数码管将被选中。

74HC138的输入输出表如下:

在电路中,使用PORTA[2:0]来对应74HC138的输入[A2,A0]
74HC138的输入[A2,A0]映射到PORTA[2:0]就是下面的数组,用来分别使能第0位第7位数码管:

static const uint8_t segment_index[8]= { 0,1,2,3,4,5,6,7 };

5、代码:

下面的代码用来在第0号数码管上、显示数字"5"

Mod_LED_display.c

#include <avr/interrupt.h>
#include "Mod_LED_Displayer.h"// 段码(共阴 == 高电平点亮)
static const uint8_t segment_code[17]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,  // 0 - 90x77,0x7c,0x39,0x5e,0x79,0x71,                      // A - F0x00                                                // 全部熄灭
};// 位码(低电平使能)(使用74HC138选通位选)
// 分别使能第0号到第7号数码管(与之相与(&)来使能)
static const uint8_t segment_index[8]= { 0,1,2,3,4,5,6,7 };// ==========================================================================================================
// LED数码管硬件初始化
//
// ==========================================================================================================
void Mod_LED_display_init(void)
{// 数码位选选使能(74HC138芯片使能)DDRC  |= (1 << DDC7);PORTC |= (1 << PC7 );// 段选控制:PORTB初始化为:输出低电平DDRB  = 0xFF;PORTB = 0x00;// 位选控制:PORTA[2:0]初始化为:输出低电平(选中第0号数码管)DDRA  |=   (1 << DDA0) |(1 << DDA1) |(1 << DDA2);PORTA &= ~((1 << PA0 ) |(1 << PA1 ) |(1 << PA2 ));// LED数码管显示固定的数据 ------------------------// 清除数码管显示PORTB  = segment_code[16];// 修改位选PORTA |= segment_index[7];PORTA &= segment_index[0];  // 第0号数码管// 修改显示PORTB  = segment_code[5];   // 数字'5'
}

main.c

// ==========================================================================================================
// 主函数
// ==========================================================================================================
#include <avr/io.h>
#include "Mod_LED_Displayer.h"
#include "sys_timer.h"
#include "system.h"
#include "config.h"// ==========================================================================================================
// main函数
// ==========================================================================================================
int main(void)
{// ---------// 关全局中断cli();// 系统初始化 ( 包含Mod_LED_display_init() )sys_init();// 开全局中断sei();// ---------while(1){}return 0;
}

测试结果:

1、现在可以在任一个数码管上、显示任一个十六进制数了。

-------------------------------------------------------------------------------------------------------------------------------------

第三步: 动态扫描

说明:
1、由静态显示到动态扫描经过了以下步骤:

(1). 在第一步的1ms定时中断中、每隔1000ms第0号数码管上显示1个数字,

并依次循环显示16十六进制数,检验所有段码

(2). 在第一步的1ms定时中断中、每隔1000ms切换到下一个数码管,去显示一个固定的数字,

并依次循环切换这8个数码管,检验所有 位码

(3). 让8位数码管动态显示数值01234567

也就是切换到第0号数码管时、显示数字'0',后面的一次类推,每隔1ms切换一个数码管。

(4). 最后、让让8位数码管动态显示任意数值,超过8位的数值当然只能显示低8位(十进制)。

最终的代码经过了几次构造,以方便修改用于控制数码管的IO口。

代码如下:

Mod_LED_display.c

#include <avr/interrupt.h>
#include "Mod_LED_Displayer.h"// 段码(共阴 == 高电平点亮)
static const uint8_t segment_code[17]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,  // 0 - 90x77,0x7c,0x39,0x5e,0x79,0x71,                      // A - F0x00                                                // 全部熄灭
};// 位码(低电平使能)(使用74HC138选通位选)
// 分别使能第0号到第7号数码管(与之相与(&)来使能)
static const uint8_t segment_index[8]= { 0,1,2,3,4,5,6,7 };// 数码管控制结构
typedef struct
{uint8_t *seg_index; // 位选端口的数据uint8_t *seg_code;  // 段选端口的数据uint8_t  index;     // 使能第index数码管uint8_t  data[sizeof(segment_index)];   // 送给[0:7]号数码管去显示的8个数据
}T_SEG_LED_DISPLAY_CTRL,*pT_SEG_LED_DISPLAY_CTRL;static T_SEG_LED_DISPLAY_CTRL LED_display_ctrl = { .seg_index = (uint8_t *)(&PORTA),.seg_code  = (uint8_t *)(&PORTB),.index = 0,.data  = { 0,1,2,3,4,5,6,7 }};
// 使用指针是为了在代码中使用'->'操作符来替代'.'操作符,更直观
static pT_SEG_LED_DISPLAY_CTRL p_LED_display_ctrl = &LED_display_ctrl;// ==========================================================================================================
// LED数码管硬件初始化
//
// ==========================================================================================================
void Mod_LED_display_init(void)
{// -----------------------------// 数码管位选使能(74HC138芯片使能)DDRC  |= (1 << DDC7);PORTC |= (1 << PC7 );// 段选控制:PORTB初始化为:输出低电平DDRB  = 0xFF;PORTB = 0x00;// 位选控制:PORTA[2:0]初始化为:输出低电平(选中第0号数码管)DDRA  |=   (1 << DDA0) |(1 << DDA1) | (1 << DDA2);PORTA &= ~((1 << PA0 ) |(1 << PA1 ) | (1 << PA2 ));
}// ==========================================================================================================
//      LED数码管显示数据的刷新
//
// (1). 在系统定时器中每隔1ms刷新1次
//
// ==========================================================================================================
void Mod_LED_display_update(void)
{
#if 1// 熄灭当前数码管、用来避免余晖*p_LED_display_ctrl->seg_code = segment_code[sizeof(segment_code) - 1];
#endif// 切换到下1个数码管p_LED_display_ctrl->index++;if(p_LED_display_ctrl->index > (sizeof(segment_index) - 1)){p_LED_display_ctrl->index = 0;}// 修改位选、修改显示*p_LED_display_ctrl->seg_index |= segment_index[sizeof(segment_index) - 1];*p_LED_display_ctrl->seg_index &= segment_index[p_LED_display_ctrl->index];*p_LED_display_ctrl->seg_code   = segment_code[p_LED_display_ctrl->data[segment_index[p_LED_display_ctrl->index]]];
}// ==========================================================================================================
// LED数码管显示(十进制)
//
// ==========================================================================================================
void Mod_LED_display(uint32_t data)
{uint8_t temp  = 0;uint8_t index = 0;for(index = 0; index < sizeof(segment_index); index++){// 取出最低位的数据if(0 == data) { temp = 0; }else          { temp = data % 10UL; }// 保存最低位的数据p_LED_display_ctrl->data[sizeof(segment_index) - index - 1] = temp;// 丢弃最低位的数据if(0 != data) { data = data / 10UL; }}
}// ==========================================================================================================
// LED数码管显示(进制)
//
// 1、可以显示十六进制、以及十六进制以内的进制
//
// ==========================================================================================================
void Mod_LED_display_hex(uint32_t data, uint8_t hex)
{uint8_t temp  = 0;uint8_t index = 0;if((hex > 16) || (hex < 2)) { return ; }for(index = 0; index < sizeof(segment_index); index++){if(0 == data) { temp = 0; }else          { temp = data % hex; }p_LED_display_ctrl->data[sizeof(segment_index) - index - 1] = temp;if(0 != data) { data = data / hex; }}
}

sys_timer.c中,每隔1ms刷新一次数码管:

// ==========================================================================================================
//      系统定时器中断(中断周期=1ms)
//
// (1). 使用Timer0的CTC中断调度各个任务
//
// ==========================================================================================================
ISR(TIMER0_COMP_vect)
{Mod_LED_display_update();
}

main.c如下:

// ==========================================================================================================
// 主函数
// ==========================================================================================================
#include <avr/io.h>
#include "Mod_LED_Displayer.h"
#include "sys_timer.h"
#include "system.h"
#include "config.h"// ==========================================================================================================
// main函数
// ==========================================================================================================
int main(void)
{// ---------// 关全局中断cli();// 系统初始化sys_init();// 开全局中断sei();Mod_LED_display(123456789);
//  Mod_LED_display_hex(65536, 15);// -----------------------while(1){}return 0;
}

测试结果:

1、显示中没有余晖(重影),不亮的段都是白色。

2、操作函数中还提供了一个根据指定的进制来保存和显示数字的函数Mod_LED_display_hex()

比如Mod_LED_display_hex(65536, 15);就是将十进制数65536按照15进制保存、并显示在数码管上。

数码管上将显示15进制数14641,可以在线进行进制转换、来验证结果:http://tool.oschina.net/hexconvert。

在线转换的结果:

3、这里对数码管的操作是一种三段式的操作:初始化 + 前台更新 + 后台API

-------------------------------------------------------------------------------------------------------------------------------------

第四步: 余晖 (重影)

余晖的来源:

1、如果现在正在第0号数码管上显示数字'E'('E'的段码是0x79),然后切换到第1号数码管上去显示数字'1',中间不先将第0号数码管熄灭。

其过程是:修改位码、从第0号数码管切换到第1号数码管,修改段码、将段码由'E'的段码0x79改为'1'的段码0x06

切换到第1号数码管只修改了位码,此时的段码依然是'E'的段码0x79,此时第1号数码管就会显示数字'E'

接着把段码改为'1'的段码0x06,此时、第1号数码管才显示数字'1'

结果是、第1号数码管会先显示一小段时间(几个时钟周期)的数字'E'、然后才显示数字'1'(1ms)

显示数字'1'的时候、只有段[b,c]被点亮。

显示数字'E'的时候、只有段[a,d,e,f,g]会被点亮。

所以、第1号数码管显示数字'1'之前,段[a,d,e,f,g]会被点亮一段时间,然后才去点亮段[b,c],这里有一先一后的顺序。

但对于眼睛来说,由于时间太短,眼睛会认为显示几个时钟周期的数字'E'和显示1ms数字'1'是同时发生的。

看的的结果就是、第1号数码管显示数字'1'的时候、其他的段[a,d,e,f,g]是微亮的,这就是余晖

显示数字'E'的时间越久、第1号数码管来自第0号数码管的余晖就越明显。

另外、驱动电流越大,数码管越量,余晖也会越亮。

2、上面先修改位选、再修改段选。

如果先修改段选、再修改位选、一样会有余晖产生,如果不先熄灭数码管、让段码为0x00的话。

只是、第1号数码管的余晖来自第2号数码管,而不是第0号数码管。

因为数码管还没有从第1号切换到第2号之前,段码要先改成了第2号数码管要显示的段码。

3、所以在切换数码管之前,需要熄灭数码管,将段码先修改为熄灭显示的段码 0x00

4、或者、可以关闭数码管,也就是将所有数码管的com口都拉高,熄灭所有数码管。

在段码修改完毕后,再设置下一个数码管的位码,来使能下一个数码管。

上面的电路中、74HC138不能禁止所有数码管,所以只能使用熄灭显示的段码0x00

使用三极管控制数码管的com口的电路,可以禁止所有数码管。

测试代码:

说明:
1、如果屏蔽下面用于避免余晖的代码,就会有余晖。
2、但为了让余晖更明显,可以在切换到下一个数码管之前,延时几百微妙。
3、下面的代码使用#if 0来使能余晖的产生,并加入延时:

// ==========================================================================================================
//      LED数码管显示数据的刷新
//
// (1). 在系统定时器中每隔1ms刷新1次
//
// ==========================================================================================================
void Mod_LED_display_update(void)
{// 切换到下1个数码管p_LED_display_ctrl->index++;if(p_LED_display_ctrl->index > (sizeof(segment_index) - 1)){p_LED_display_ctrl->index = 0;}// 修改位选、修改显示*p_LED_display_ctrl->seg_index |= segment_index[sizeof(segment_index) - 1];*p_LED_display_ctrl->seg_index &= segment_index[p_LED_display_ctrl->index];// 切换数码管后,延时一段时间,看看余晖for(volatile int j = 0; j < 20; j++) {}// 修改段码*p_LED_display_ctrl->seg_code   = segment_code[p_LED_display_ctrl->data[segment_index[p_LED_display_ctrl->index]]];
}

测试结果:

0、从左到右分别是第0号数码管到第7号数码管。
1、在第3号数码管上、可以明显的看到来自第2号数码管(显示数字'0')的余晖。
第5、6、7号数码管上、也有明显的余晖。
2、第0号数码管上没有余晖,因为它的上一个数码管(第7号)显示的是数字'1',其余晖被第0号数码管自己显示的数字'0'覆盖了。
同样的、第1、2、4号数码管上的余晖也是被覆盖了。
3、延时用的for循环中、有个volatile,用来避免编译器优化变量j
如果被优化掉,这段代码就没了,因为这段代码对AVR-GCC编译器来说、没有什么意义。

-------------------------------------------------------------------------------------------------------------------------------------

第五步: 引入消息机制的任务函数

使用如下结构、得到另一种形式的任务函数:

这种任务函数自带两个部分:消息查询(从消息队列中查询消息)、消息发生(将该任务自己产生的消息发生到消息队列)。

任务完全由任务函数本身来控制,与其他任务之间的通信都通过消息队列来进行,这样的独立性更高。

消息队列成为一个公共的资源地,只提供保存消息的功能,类似一个水池,每个任务都可以从中取得消息,也可以将消息放入其中。

每个任务函数都由三段组成:消息查询任务正文消息发送

数码管显示任务、只查询消息、不产生消息。

完整代码:

// ==========================================================================================================
// Copyright (c) 2016 Manon.C <codingmanon.163.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ------------------------------------
// 本文定义了Atmega16下的8段LED数码管驱动
//
// ------------------------------------
// 包含:
//
// ==========================================================================================================
#include "Mod_LED_Displayer.h"// 段码(共阴 == 高电平点亮)
static const uint8_t segment_code[17]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,  // 0 - 90x77,0x7c,0x39,0x5e,0x79,0x71,                      // A - F(与之相与来得到某1位数码管的数值)0x00                                                // 全部熄灭(与之相或来熄灭当前选中的数码管的显示)
};// 位码(低电平使能)(使用74HC138选通位选)
// 分别使能第0号到第7号数码管(与之相与(&)来使能)
static const uint8_t segment_index[8]= { 0,1,2,3,4,5,6,7 };typedef struct
{uint8_t *seg_index; // 位选端口的数据uint8_t *seg_code;  // 段选端口的数据uint8_t  index;     // 使能第index数码管uint8_t  data_part[_countof(segment_index)];   // 送给[0:7]号数码管去显示的8个数据uint32_t data;      // 用于显示的数据,它将被拆分到data_part[]去保存
}T_SEG_LED_DISPLAY_CTRL,*pT_SEG_LED_DISPLAY_CTRL;static T_SEG_LED_DISPLAY_CTRL LED_display_ctrl = {  .seg_index = (uint8_t *)(&PORTA),.seg_code  = (uint8_t *)(&PORTB),.index = 0,.data_part = { 0,0,0,0,0,0,0,0 },.data = 0};
static pT_SEG_LED_DISPLAY_CTRL p_LED_display_ctrl = &LED_display_ctrl;// ==========================================================================================================
// LED数码管硬件初始化
//
// ==========================================================================================================
void Mod_LED_display_init(void)
{// 数码管位选使能(74HC138芯片使能):输出高电平DDRC  |= (IO_OUTPUT << DDC7);PORTC |= (IO_OUTPUT << PC7 );// 段选控制:PORTB初始化为:输出低电平DDRB  = 0xFF;PORTB = 0x00;// 位选控制:PORTA[2:0]初始化为:输出低电平(选中第0号数码管)DDRA  |=   (IO_OUTPUT << DDA0) |(IO_OUTPUT << DDA1) | (IO_OUTPUT << DDA2);PORTA &= ~((1 << PA0 ) |(1 << PA1 ) | (1 << PA2 ));
}// ==========================================================================================================
// LED数码管显示数据的刷新
//
// 参数:static index    将1个8位十进制数字拆分到数组的8个单元中,index是当前数组元素的下标
//                       每次只拆分1位,数据中超过8位的部分会自动被截断
//
// 查询消息:EVENT_SEG_UPDATE
// 消息参数:32位数值
// 发送消息:无
//
// 说明:
// (0). 在系统定时器或任务调度器中定时刷新(被作为1个任务去调度)
// (1). 需要拆分一个8位数,拆分每1位(加上刷新显示)耗时170us(包含进出任务函数的时间)
// (2). 实际上、每当data更新时、都需要重新计算8位数组p_LED_display_ctrl->data_part内的值
// (3). 如果data是3位数(如120),就只需要计算3次,也就会出现3次耗时约为170us的计算。
//      data=0后就不再进行耗时的计算,而是直接给数组p_LED_display_ctrl->data_part中对应的元素赋0值。
// (4). 如果data为0或没有变化,就不需要计算,此时本任务函数耗时11.32us
//
// ==========================================================================================================
void task_Mod_LED_display_update(void)
{uint8_t temp = 0;volatile static uint8_t index = _countof(segment_index);// --------------------------------------------------------------------// 查询消息if(TRUE == sys_event_peek(EVENT_SEG_UPDATE, &p_LED_display_ctrl->data)){index = 0;  // 如果得到更新的数据、就启动数据拆分}// ---------------------------------------------------// 正文// ---------------------------------------------------// 拆分8位十进制数据到data_part[](从第0号数据位置开始计算)if(index < _countof(segment_index)){if(0 == p_LED_display_ctrl->data) { temp = 0; }else                              { temp = p_LED_display_ctrl->data % 10UL; }p_LED_display_ctrl->data_part[_countof(segment_index) - 1 - index] = temp;if(0 != p_LED_display_ctrl->data) { p_LED_display_ctrl->data = p_LED_display_ctrl->data / 10UL; }index++;}// --------// 刷新显示// 熄灭当前数码管*p_LED_display_ctrl->seg_code = segment_code[_countof(segment_code) - 1];// 切换到下1个数码管p_LED_display_ctrl->index++;if(p_LED_display_ctrl->index > (_countof(segment_index) - 1)){p_LED_display_ctrl->index = 0;}// 修改位选、修改显示*p_LED_display_ctrl->seg_index |= segment_index[_countof(segment_index) - 1];*p_LED_display_ctrl->seg_index &= segment_index[p_LED_display_ctrl->index];*p_LED_display_ctrl->seg_code   = segment_code[p_LED_display_ctrl->data_part[segment_index[p_LED_display_ctrl->index]]];// ---------------------------------------------------// 发送消息
}// ==========================================================================================================
// LED数码管显示(十进制)
//
// 说明:
// (1): 如有必要,可将显示的格式做成事件EVENT_SEG_FORMAT、带消息:MSG_SEG_HEX(按照16进制显示结果)
//                                                                MSG_SEG_DEC(按照10进制显示结果)
//                                                                MSG_SEG_OCT(按照08进制显示结果)
//                                                                MSG_SEG_BIN(按照02进制显示结果)
//
// ==========================================================================================================
void Mod_LED_display(uint32_t data)
{sys_event_post(EVENT_SEG_UPDATE, data);
}

消息管理和任务调度,详见:《A005-软件结构-从前后台到调度器》。

-------------------------------------------------------------------------------------------------------------------------------------

第六步: 按指定进制显示

改进:
1、可指定按照 二进制十六进制显示数据
2、进一步在 消息机制下整理了任务函数

Drv_Sys.c

// ==========================================================================================================
// 设置数据格式(范围:[2,16])
//
// ==========================================================================================================
void Drv_sys_set_digital_format(uint8_t format)
{if((format >= 2) && (format <= 16)){sys_event_post(EVENT_DIGITAL_FORMAT, format);}
}

Mod_LED_display.c

// ==========================================================================================================
// Copyright (c) 2016 Manon.C <codingmanon@163.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ------------------------------------
// 本文定义了Atmega16下的8段LED数码管驱动
// 文中对应的驱动电路:http://blog.csdn.net/manon_des_source/article/details/51783675
//
// ------------------------------------
// 包含:
//
// ==========================================================================================================
#include "Mod_LED_Displayer.h"// 段码(共阴 == 高电平点亮)
static const uint8_t segment_code[17]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,  // 0 - 90x77,0x7c,0x39,0x5e,0x79,0x71,                      // A - F(与之相与来得到某1位数码管的数值)0x00                                                // 全部熄灭(与之相或来熄灭当前选中的数码管的显示)
};// 位码(低电平使能)(使用74HC138选通位选)
// 分别使能第0号到第7号数码管(与之相与(&)来使能)
static const uint8_t segment_index[8]= { 0,1,2,3,4,5,6,7 };typedef struct
{uint8_t *seg_index; // 位选端口的数据uint8_t *seg_code;  // 段选端口的数据uint8_t  index;     // 使能第index数码管uint8_t  format;    // 数据的显示格式、范围:[2,16]uint8_t  set_format;// 需要设置新的显示格式uint8_t  set_data;  // 有新的显示数据uint8_t  data_part[_countof(segment_index)];  // 送给[0:7]号数码管去显示的8个数据uint8_t  data_index;// 拆分data到data_part[]时、作为data_part的下标使用uint32_t data;      // 用于显示的数据,它将被拆分到data_part[]去保存uint32_t data_copy; // 数据备份、重置format时需要使用
}T_SEG_LED_DISPLAY_CTRL,*pT_SEG_LED_DISPLAY_CTRL;static T_SEG_LED_DISPLAY_CTRL LED_display_ctrl = {  .seg_index = (uint8_t *)(&PORTA),.seg_code  = (uint8_t *)(&PORTB),.index = 0,.format     = 10,   // 默认使用十进制格式显示.set_format = FALSE,.set_data   = FALSE,.data_part  = { 0,0,0,0,0,0,0,0 },.data_index = 0,.data       = 0,.data_copy  = 0};
static pT_SEG_LED_DISPLAY_CTRL p_LED_display_ctrl = &LED_display_ctrl;// ==========================================================================================================
// LED数码管硬件初始化
//
// ==========================================================================================================
void Mod_LED_display_init(void)
{// 数码管位选使能(74HC138芯片使能):输出高电平DDRC  |= (IO_OUTPUT << DDC7);PORTC |= (IO_OUTPUT << PC7 );// 段选控制:PORTB初始化为:输出低电平DDRB  = 0xFF;PORTB = 0x00;// 位选控制:PORTA[2:0]初始化为:输出低电平(选中第0号数码管)DDRA  |=   (IO_OUTPUT << DDA0) |(IO_OUTPUT << DDA1) | (IO_OUTPUT << DDA2);PORTA &= ~((1 << PA0 ) |(1 << PA1 ) | (1 << PA2 ));
}// ==========================================================================================================
//   拆分8位数据到data_part[](从第0号数据位置开始计算)
//
// 说明:
// (1). 每次只拆分1位,数据中超过8位的部分会自动被截断,所以二进制格式时只会截取低8位
// (2). 实际上、每当data更新时、都需要重新计算8位数组p_LED_display_ctrl->data_part内的值
// (3). 如果data是3位数(如120),就只需要计算3次,也就会出现3次较为耗时的计算
//      data=0后就不再进行耗时的计算,而是直接给数组p_LED_display_ctrl->data_part中对应的元素赋0值。
// (4). 如果data为0或没有变化,就不需要计算
//
// ==========================================================================================================
bool Mod_LED_display_set_data(uint8_t format)
{uint8_t temp = 0;if(p_LED_display_ctrl->data_index < _countof(segment_index)){if(0 == p_LED_display_ctrl->data) { temp = 0; }else                              { temp = p_LED_display_ctrl->data % format; }p_LED_display_ctrl->data_part[_countof(segment_index) - 1 - p_LED_display_ctrl->data_index] = temp;if(0 != p_LED_display_ctrl->data) { p_LED_display_ctrl->data = p_LED_display_ctrl->data / format; }p_LED_display_ctrl->data_index++;return TRUE;}return FALSE;
}// ==========================================================================================================
// LED数码管显示数据的刷新
//
// ==========================================================================================================
void Mod_LED_display_update(void)
{// 熄灭当前数码管*p_LED_display_ctrl->seg_code = segment_code[_countof(segment_code) - 1];// 切换到下1个数码管p_LED_display_ctrl->index++;if(p_LED_display_ctrl->index > (_countof(segment_index) - 1)){p_LED_display_ctrl->index = 0;}// 修改位选、修改显示*p_LED_display_ctrl->seg_index |= segment_index[_countof(segment_index) - 1];*p_LED_display_ctrl->seg_index &= segment_index[p_LED_display_ctrl->index];*p_LED_display_ctrl->seg_code   = segment_code[p_LED_display_ctrl->data_part[segment_index[p_LED_display_ctrl->index]]];
}// ==========================================================================================================
// LED数码管刷新任务(硬件接口)
//
// 查询消息:EVENT_SEG_UPDATE、EVENT_DATA_DORMAT
// 消息参数:32位数值
// 发送消息:无
//
// 说明:
// (1). 在系统定时器或任务调度器中定时刷新(被作为1个任务去调度)
//
// ==========================================================================================================
void task_Mod_LED_display(void)
{uint32_t temp = 0;// ------------------------------------------------// 消息查询if(TRUE == sys_event_peek(EVENT_SEG_UPDATE, &temp)){p_LED_display_ctrl->set_data   = TRUE;  // 如果得到更新的数据、就启动数据拆分p_LED_display_ctrl->data_index = 0;     // 如果正在拆分过程中、又一次需要拆分,就需要重新设置.data_index为0p_LED_display_ctrl->data       = temp;p_LED_display_ctrl->data_copy  = temp;}if(TRUE == sys_event_peek(EVENT_DATA_FORMAT, &temp)){p_LED_display_ctrl->set_format = TRUE;p_LED_display_ctrl->format     = temp;}// -------------------------------------// 任务正文// -------------------------------------// 拆分数据if(TRUE == p_LED_display_ctrl->set_data){p_LED_display_ctrl->set_data = Mod_LED_display_set_data(p_LED_display_ctrl->format);}if(TRUE == p_LED_display_ctrl->set_format){p_LED_display_ctrl->set_format = FALSE;p_LED_display_ctrl->set_data   = TRUE;p_LED_display_ctrl->data_index = 0;p_LED_display_ctrl->data = p_LED_display_ctrl->data_copy;}// --------// 刷新显示Mod_LED_display_update();// ----------------------// 消息发送
}// ==========================================================================================================
// LED数码管显示(上层接口)
//
// ==========================================================================================================
void Mod_LED_display(uint32_t data)
{sys_event_post(EVENT_SEG_UPDATE, data);
}

比如、按照 二进制显示的计数器:

00
0
00
0
00
0
00
0
00
0
0

B001-Atmega16-数码管相关推荐

  1. 基于FPGA的复杂的数字时钟设计(代码)

    复杂的数字时钟设计 一.设计要求: 1.正常显示功能 正常显示时,六位数码管显示日期.时间以及闹钟.对于日期来说,前两位显示年份的后两位(如2020年 显示20),中间两位显示月份,最后两位显示日.对 ...

  2. 【Proteus仿真】ATMEGA16 ADC转化数码管显示

    [Proteus仿真]ATMEGA16 ADC转化数码管显示 注意这是ATMEGA16AVR型号的单片机,不能在Keil平台上进行开发和编译的. Proteus仿真 所使用的编译软件ICCAVR 不懂 ...

  3. FPGA控制的数码显示电路

    实现的功能:8个数码管轮流显示8个数字,若频率合适,可以实现8个数码管同时被点亮的视觉效果 1.CL5461AS CL5461AS 数码管管脚图如下图所示,它将四个数码显示管的a-g 及小数点dp 管 ...

  4. [原创].七段数码管驱动,Verilog版本

    我以前在艾米电子写的驱动.贴在博客之目的:一.时常记记,以防忘记:二.分享给大家.也许是工作比较忙之缘故吧,新近的博文啰嗦的话语少了许多,直接贴上代码,大家有什么不明白的,留言即可. 版本1 顶层例化 ...

  5. 西南交大计算机组成原理考试大纲,西南交大计算机组成原理实验二七段LED数码管显示译码器的设计.docx...

    //译码模块 module xianshi(input [3:0]xs,output reg [6:0]l); always @ (xs) begin case(xs) 4'b0000: l<= ...

  6. 51单片机按键控制数码管0~9_7种常见的51单片机时钟电路图

    在MCS-51单片机片内有一个高增益的反相放大器,反相放大器的输入端为XTAL1,输出端为XTAL2,由该放大器构成的振荡电路和时钟电路一起构成了单片机的时钟方式. 电子学习资料大礼包​mp.weix ...

  7. 基于FPGA的数字钟——(三)时钟显示模块(数码管)

    基于FPGA的数字钟--(三)数码管显示模块 一.硬件原理 本设计中使用 6 个共阳数码管,可以显示 6 个数字(包含小数点) .电路用 PNP管来反向驱动并且控制列扫描信号来选择哪个数码管.而且所有 ...

  8. 七段LED数码管显示译码器设计

    包含按键输入并显示模块,输入为四位16进制信号,同步在led数码管显示,如果没有输入,则自动脉冲计数 module led(comscan,clr,k,kon,kc,o,comscanout); in ...

  9. 51单片机学习笔记-2数码管显示

    2 数码管显示 [toc] 注:笔记主要参考B站江科大自化协教学视频"51单片机入门教程-2020版 程序全程纯手打 从零开始入门". 注:工程及代码文件放在了本人的Github仓 ...

  10. FPGA 驱动数码管动态显示(VerilogVivado)

    FPGA 驱动数码管动态显示 前言 一.数码管驱动原理 二.设计思路 三.实现代码 四.hex8_tb文件 五.上板测试 1.74HC595时序图 2. HC595_Driver设计 3.HC595_ ...

最新文章

  1. 102. Binary Tree Level Order Traversal
  2. 直接定址表03 - 零基础入门学习汇编语言74
  3. HDU 4873 ZCC Loves Intersection(JAVA、大数、推公式)
  4. [数学]点、线、面分割问题
  5. IEPLUS(IE增强性插件)
  6. Python help 函数 - Python零基础入门教程
  7. php在四线城市待遇如何,月薪5000元在四线城市算什么水平,丢人吗?
  8. 《如何搭建小微企业风控模型》第五节 特征工程(上)
  9. Android应用程序开发(第三版)-课后习题解答
  10. MATLAB使用cic滤波器,基于Matlab的CIC滤波器设计
  11. 乐虎 尖叫_如果您想更快地尖叫! 为什么政府技术需要(很多)更好的治理
  12. TransModeler 交通仿真软件
  13. 原生js实现canvas粒子特效
  14. 学习coreldraw
  15. Spring Boot 集成 Activiti7(工作流)
  16. 用IMU监测力量训练中的杠铃速度,帮助科学界和体育界评估杠杆运动学
  17. 服务器机箱装系统蓝屏,电脑安装系统,容易出现蓝屏、死机等5大问题,装机达人给你支招...
  18. composer [InvalidArgumentException] Could not find package laravel/laravel with stability stable.
  19. Java实验报告(一)
  20. GPT生成精美Echarts图表

热门文章

  1. csp之模拟试题——2019 CSP——J
  2. ubuntu与centos对比
  3. 《core JAVA for the impatient》阅读笔记(5) 泛型编程
  4. 论文被 scoop(抢先发表)是怎样一种体验?
  5. ubuntu下docker添加国内镜像
  6. 指掌易COO许铭:助力企业安全兼顾员工隐私保护
  7. Android手机截图代码(针对root设备,可截取任意屏幕)
  8. 解决 Can not construct instance of `xxxxx`
  9. UILabelUITextView文本嵌入图片处理
  10. 怎么在终端卸载mysql_mysql 的卸载、再安装与常用命令