我们在STM32开发过程中,或多或少会用到数码管模块,那么在数码管刷新时,我们又该采用那种方式呢?下面小编就给大家讲解以及展示数码管的刷新吧。

一、工作原理

    在我们日常使用的STM32设备中,绝大部分的STM32产品都是将数码管串联成共阳或共阴的数码管组,所谓共阳就是所有数码管的VCC引脚都接在一个电源上,而共阴数码管就是将所有数码管的GND都接在同一个GND上,如下图是一个共阳数码管的原理图。



数码管实时刷新代码Python 数码管怎么刷新_arm


共阳数码管原理图

    那么正如上图所示,在数码管组中,要让一个数码管显示,就可以分为两个部分,其一是数码管段选,用于控制数码管的显示;其二就是数码管的位选,用于选择数码管的位置。因此,我们想要点亮一个数码管就需要段选与位选,两者缺一不可。但是呢,位选相对于段选更为简单,一般只需要控制一个引脚就可,而段选呢,往往需要很多引脚才能够显示一个数值或字母。     如下图所示,数码管共有两种接法,一种是共阳极接法,另一种是共阴极接法,小编所使用的的STM32F103ZET6开发板采用的就是共阳极接法,因此,小编的段码也只针对于共阳极数码管。(不同的数码管采用的方法不同,这里建议大家看看自己开发板采用何种接法。)

数码管实时刷新代码Python 数码管怎么刷新_stm32_02


    既然一个数码管有八段,那我们在数码管上想要显示一个数值应该如何操作呢?如下图所示,我们需要显示一个数值0需要用到的码段有a、b、c、d、e、f,那么我们就可以得到在数码管上显示0,它的段码应该是0xc0。(这里需要注意的是,从左到右、从Pin0-Pin7依次对应的是a,b,c,d,e,f,g,dp。)

数码管实时刷新代码Python 数码管怎么刷新_数码管_03

二、如何让数码管稳定地显示

    看完上面的原理讲解,可见数码管的显示实际上不就是每一位数码管在肉眼不可见的频率下不间断地轮流刷新,那直接放在主程序的while(1)循环里不就好了嘛。这样子好像确实可以啊。😂😂😂那么好,假如我们就这样干,然后现在问题来了:假如我们的这个项目中含有几个中断,而且总中断服务程序需要的时间有几百甚至几千毫秒,那么我们的数码管还能够正常稳定的刷新嘛?答案是肯定不能了,这得到的结果就会是数码管不断地在频闪刷新。(也就是以肉眼可见的频率先关闭上一次数码管的显示,再刷新本次数码管需要显示的数据)
    既然如此,那难道就没有别的更好的方法刷新数码管了吗?有,我们可以使用定时器来刷新数码管 。定时器刷新数码管,其一,我们可以不用过多地考虑数码管刷新的频率,因为它的刷新频率在一开始就已经设置了;其二,我们不用担心会有其他的东西干扰数码管刷新,因为我们是将数码管放在定时器中断里刷新的,只要配置的中断优先级足够高,就一定不会有其他的进程干扰数码管刷新,这真的很nice啊!😎😎😎
    在这里小编再简略解释一下定时器以及中断服务函数工作的方式,假如你有定时器基础就请略过。定时器它的功能就类似手机上的闹钟,当时间到达我们设置时间时就会有响铃(中断服务函数)产生,一旦响铃关闭后,手机就又会恢复到刚才的状态(主程序)。这个流程就类似下图:



数码管实时刷新代码Python 数码管怎么刷新_stm32_04


中断响应以及相关过程的解释


三、实现代码

1、定义段选、位选、段选存储变量

//储存数码管的段选
unsigned int segbuff[] = {10,10,10,10,10,10};
//											 0	    1		 2   3    4    5    6    7    8    9    -   熄灭
unsigned int segtab[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf,0xff,
//0.        1.        2.          3.        4.        5.       6.       7.        8.         9.      -    熄灭	 
0xc0&0x7f,0xf9&0x7f,0xa4&0x7f,0xb0&0x7f,0x99&0x7f,0x92&0x7f,0x82&0x7f,0xf8&0x7f,0x80&0x7f,0x90&0x7f,0xbf,0xff};

//储存数码管的位选
unsigned int segwei[6] = {0x7fff,0xbfff,0xdfff,0xefff,0xf7ff,0xfbff};

2、GPIO引脚的初始化

    GPIO初始化时,我们需要打开GPIOE与GPIOG时钟、选择GPIO引脚、设置GPIO频率、设置GPIO模式就好。但是由于数码管的段选与位选需要用到的引脚不相同,于是下面初始化函数中就需要有两组GPIO引脚的初始化。

/*
	* @brief intialivation of digital tube(初始化数码管)
	* @param none
	* @retval none
*/
void seg_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; 
	//打开GPIOE与GPIOG时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_GPIOE,ENABLE);
	
	/* 数码管GPIOE的初始化  */
	//选择GPIO的引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15|GPIO_Pin_14|GPIO_Pin_13|GPIO_Pin_12|GPIO_Pin_11|GPIO_Pin_10; 
	//选择GPIO的频率
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	//选择GPIO的模式为推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	//初始换
	GPIO_Init(GPIOE,&GPIO_InitStructure);
	
	/* 数码管GPIOG的初始化  */
	//选择GPIO的引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
	//选择GPIO的频率
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	//选择GPIO的模式为推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	//初始化
	GPIO_Init(GPIOG,&GPIO_InitStructure);
}

3、消影

/*
	* @brief trun of all digital tube(关闭所有的数码管) 
	* @param none
	* @retval none
*/
void turn_off_all_smg(void)
{
	GPIO_Write(GPIOE,0x7FFF);//选中所有的数码管
	GPIO_Write(GPIOG,0xff); //关闭所有的数码管
}

4、数码管刷新

/*
	* @brief Refresh digital tube(刷新数码管)
	* @param none
	* @retval none
*/
void refresh_segs(void)
{
	//用于记录刷新数码管的位置
	static unsigned char segaddr = 0;
	
	turn_off_all_smg();//消影 即消除上一次显示数据对这一次的影响
	
	GPIO_Write(GPIOE,segwei[segaddr]);//选中数码管位置
	GPIO_Write(GPIOG,segtab[segbuff[segaddr]]); //写入数码管的数值	
	if(++segaddr == 6) segaddr = 0;//一共只有6个数码管,因此加到6就好
	
}

5、定时器初始化

    在这里我们将定时器配置的是毫秒级定时器,而且中断优先级相对居中。

/**************************
函数功能:中断嵌套NVIC的初始化
函数参数:无
函数返回值:无
**************************/
static void NVICInit(void){
	NVIC_InitTypeDef NVIC_InitStructure;
/************************中断嵌套NVIC设置*************************/
	//指定中断通道
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
	//设置抢占式优先级 数字越小优先级越高
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	//设置响应式优先级  数字越小优先级越高
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	
	//NVIC初始化
	NVIC_Init(&NVIC_InitStructure);
}

/**************************
函数功能:定时器的配置函数
函数参数:无
函数返回值:无
**************************/
static void timeBaseInit(void){
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	//使能定时器6的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);

/*********************定时器的初始化********************************/	
	//自动重装载值为1000 也就是说定时器在1000个时钟脉冲之后才会触发定时器的中断程序
	TIM_TimeBaseStructure.TIM_Period = 1000-1;
	//定时器的预分频系数为72  也就是说定时器会得到主频(72MHz)分频72次之后的频率
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1;
	//定时器的时钟分割TDTS = Tck_tim(就是不再对定时器的频率进行分频)
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	//将定时器设置成向上计数模式
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStructure);
	
	//清除TIM4的中断处理位
	TIM_ClearFlag(TIM4,TIM_FLAG_Update);
	
	//使能更新TIM4的中断源
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
	
	//设置使能中断
	TIM_Cmd(TIM4,ENABLE);
}


/*********************************
 * 函数功能:定时器4的初始化
 * 函数参数:无
 * 函数返回值:无
 ********************************/
void time4Init(void)
{
    timeBaseInit();
	NVICInit();

}

6、中断服务函数

/************************定时器的中断服务函数***********************/
void TIM4_IRQHandler(void)
{ 
	//如果中断没有复位就更新中断 也就是复位
	if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
	{
        refresh_segs();
		TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
	}
}

四、使用说明

显示10,就给segbuff数组赋值为segbuff[0]=1;segbuff[1]=0;至于到底应该在哪个位置给segbuff数组赋值就看自己程序的需求了。

五、附录

共阴极数码管的段选数值:

//   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
   unsigned int segbuff[] = 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black  -     H    J    K    L    N    o   P    U     t    G    Q    r   M    y
    0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
    0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46};    //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1