目录
最近买了一些电子元件,打算配合手头上的F28335开发板一起玩一玩。其中有一个四位共阳的数码管,虽然比较简单,但有些细节还是需要注意,所以写一篇东西来记录一下这个过程。
1 要实现的功能
直接利用F28335的GPIO口来驱动控制数码管,将输入的数字显示出来。
2 数码管的原理
2.1 数码管发光显示原理
如图所示为本次所用的数码管的结构尺寸以及电路原理图。由右上角的示意图可以看到,一个数字有8个发光位置,分别是组成数字的A到G段以及组成小数点的DP段,令相应的段发光就可以组成各个数字。而段的发光是通过发光二极管实现的,由下方的这个电路原理图可以看到,每一个数位都有各自的8段发光二极管,而每个数位的8段发光二极管的阳极都是连在一块的,所以这种叫共阳极数码管,而如果二极管的方向调换,各段二极管的阴极连在一块的就叫共阴极数码管。因此,由这个电路图我们可以知道,举个例子,如果我们把12脚接上高电平,而将4/7脚接上低电平,那么最左边一位数字的B/C段就会被点亮,就可以在最左边一位上显示出“1”。
2.2 数码管控制原理
开始之前,首先可以看到,四位数码管一共有12个管脚,其中6/8/9/12脚分别控制着四位数字其中的一位,而剩余的8个管脚分别控制着8段发光二极管的其中一段,为了方便描述,将6/8/9/12脚称为位管脚,而将其余管脚称为段管脚。
在上一部分中,我们搞清楚了怎么让数码管显示数字,但是仔细观察电路原理图可以发现,虽然每个数位都有自己的8段发光二极管,但是各个数位相同的段对应的发光二极管,比如每个数字的A段,它们的阴极是连在一块的。这就意味着,我们不能同时控制2个或以上的数位,因为如果我们同时给2个或以上的位管脚接上高电平,那么这些数位将显示一样的数字。
为了解决这个问题,我们可以采用动态扫描的方式,也就是快速轮流点亮各个数位,当轮换速度超过一定程度时,人眼就无法分辨,就会认为各个数位是同时点亮的。
3 功能实现过程
3.1 GPIO配置
由于四位数码管共有12个管脚,而手头上又没有其他器件,所以这里打算直接用F28335的12个GPIO来驱动控制数码管。
根据数据手册,选取了JP1接口上的1-6脚以及9-14脚作为输出管脚(如果不是接口上针孔之间间隔太密,我也想选1-12脚)。管脚初始化代码如下:
void Init_DT_Gpio()
{
EALLOW;
// configure the corresponding pins to be GPIO
GpioCtrlRegs.GPBMUX1.bit.GPIO40 = 0x00;
GpioCtrlRegs.GPBMUX1.bit.GPIO41 = 0x00;
GpioCtrlRegs.GPBMUX1.bit.GPIO42 = 0x00;
GpioCtrlRegs.GPBMUX1.bit.GPIO43 = 0x00;
GpioCtrlRegs.GPBMUX1.bit.GPIO44 = 0x00;
GpioCtrlRegs.GPBMUX1.bit.GPIO45 = 0x00;
GpioCtrlRegs.GPCMUX2.bit.GPIO80 = 0x00;
GpioCtrlRegs.GPCMUX2.bit.GPIO81 = 0x00;
GpioCtrlRegs.GPCMUX2.bit.GPIO82 = 0x00;
GpioCtrlRegs.GPCMUX2.bit.GPIO83 = 0x00;
GpioCtrlRegs.GPCMUX2.bit.GPIO84 = 0x00;
GpioCtrlRegs.GPCMUX2.bit.GPIO85 = 0x00;
// configure the corresponding pins as output pins
GpioCtrlRegs.GPBDIR.bit.GPIO40 = 1;
GpioCtrlRegs.GPBDIR.bit.GPIO41 = 1;
GpioCtrlRegs.GPBDIR.bit.GPIO42 = 1;
GpioCtrlRegs.GPBDIR.bit.GPIO43 = 1;
GpioCtrlRegs.GPBDIR.bit.GPIO44 = 1;
GpioCtrlRegs.GPBDIR.bit.GPIO45 = 1;
GpioCtrlRegs.GPCDIR.bit.GPIO80 = 1;
GpioCtrlRegs.GPCDIR.bit.GPIO81 = 1;
GpioCtrlRegs.GPCDIR.bit.GPIO82 = 1;
GpioCtrlRegs.GPCDIR.bit.GPIO83 = 1;
GpioCtrlRegs.GPCDIR.bit.GPIO84 = 1;
GpioCtrlRegs.GPCDIR.bit.GPIO85 = 1;
EDIS;
}
3.2 数字显示
经过上面的配置之后,我们就可以通过对GPIO口的操作来实现数字的显示。首先进行相关的宏定义以方便理解和编程:
// macro definition of the output pins of digital tube
#define DT_E GpioDataRegs.GPBDAT.bit.GPIO40
#define DT_D GpioDataRegs.GPBDAT.bit.GPIO41
#define DT_DP GpioDataRegs.GPBDAT.bit.GPIO42
#define DT_C GpioDataRegs.GPBDAT.bit.GPIO43
#define DT_G GpioDataRegs.GPBDAT.bit.GPIO44
#define DT_BIT4 GpioDataRegs.GPBDAT.bit.GPIO45
#define DT_B GpioDataRegs.GPCDAT.bit.GPIO80
#define DT_BIT3 GpioDataRegs.GPCDAT.bit.GPIO81
#define DT_BIT2 GpioDataRegs.GPCDAT.bit.GPIO82
#define DT_F GpioDataRegs.GPCDAT.bit.GPIO83
#define DT_A GpioDataRegs.GPCDAT.bit.GPIO84
#define DT_BIT1 GpioDataRegs.GPCDAT.bit.GPIO85
然后定义了三个函数,分别是 Sel_Bit 、 Sel_Num 和 Output ,其中 Sel_Bit 和 Sel_Num 是根据输入的期望点亮的数位和期望显示的数字来分别对位管脚和段管脚进行操作,而 Output 则是调用 Sel_Bit 和 Sel_Num 来在相应的数位上显示相应的数字。其代码分别如下:
void Sel_Bit(Uint16 bit)
{
switch (bit)
{
case 1:
DT_BIT2 = 0; DELAY_US(10);
DT_BIT3 = 0; DELAY_US(10);
DT_BIT4 = 0; DELAY_US(10);
DT_BIT1 = 1; DELAY_US(10);
break;
case 2:
DT_BIT1 = 0; DELAY_US(10);
DT_BIT3 = 0; DELAY_US(10);
DT_BIT4 = 0; DELAY_US(10);
DT_BIT2 = 1; DELAY_US(10);
break;
...
}
}
void Sel_Num(Uint16 num)
{
switch (num)
{
case 0:
DT_A = 0; DELAY_US(10);
DT_B = 0; DELAY_US(10);
DT_C = 0; DELAY_US(10);
DT_D = 0; DELAY_US(10);
DT_E = 0; DELAY_US(10);
DT_F = 0; DELAY_US(10);
DT_G = 1; DELAY_US(10);
DT_DP = 1; DELAY_US(10);
break;
case 1:
DT_A = 1; DELAY_US(10);
DT_B = 0; DELAY_US(10);
DT_C = 0; DELAY_US(10);
DT_D = 1; DELAY_US(10);
DT_E = 1; DELAY_US(10);
DT_F = 1; DELAY_US(10);
DT_G = 1; DELAY_US(10);
DT_DP = 1; DELAY_US(10);
break;
...
}
}
void Output(Uint16 bit, Uint16 num)
{
Sel_Bit(bit);
Sel_Num(num);
}
其中DELAY_US(10)是延时语句,这是因为不能连续对GPxDAT寄存器进行操作。而为了避免位管脚同时接通,在某个位管脚输出高电平时将其余位管脚输出低电平。
3.3 显示控制
现在我们可以在选定的数位上显示选定的数字了,下面的问题就是将输入的数字转换成各个数位要显示的数字,也就是要分别提取数字的个、十、百、千位。这通过 DT_Show 函数来实现,代码如下:
void DT_Show(Uint16 num)
{
Uint16 remain;
remain = num % 10000;
Bits[0] = remain / 1000;
remain = remain % 1000;
Bits[1] = remain / 100;
remain %= 100;
Bits[2] = remain / 10;
remain %= 10;
Bits[3] = remain;
}
其中Bits数组的元素就是对应数位要显示的数字,Bits[0]为最左位。
3.4 其他代码
主函数中的while循环如下:
while(1)
{
Output(1,Bits[0]);
Output(2,Bits[1]);
Output(3,Bits[2]);
Output(4,Bits[3]);
}
以此来实现数码管的轮流点亮。而输入数字的更新代码放在了定时器中断中:
interrupt void ISRTimer0()
{
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
CpuTimer0Regs.TCR.bit.TIF = 1;
CpuTimer0Regs.TCR.bit.TRB = 1;
if (DT_count == 9999)
DT_count = 0;
else
DT_count++;
DT_Show(DT_count);
}
4 调试修改
硬件连接、代码编写都完成后进行烧录调试,发现显示效果上有很大的问题。虽然能辨认出数码管有在按照输入数字的改变而改变,但是除此之外还有一些不该被点亮的发光二极管被点亮了,导致仿佛有两个数字重叠在同一个数位上。经过仔细辨认,发现最右边一位数位的变化能引起最左边一位数位的相同变化,举个例子,比如1234,本来应该只有最右边一位是4,但实际上能看到最左边一位也显示出一个淡淡的4。
经过思考和调(xia)试(gai),我发现,这是由于各个管脚不是同时变化而造成的,还是用之前的1234举例子,最右边一位显示完4之后应该轮到最左边一位显示1,这个时候先调用 Sel_Bit 函数改变位管脚的输出,由6管脚输出高电平变成12管脚输出高电平,但这个时候,段管脚的电位还没有改变,还是显示“4”的状态,所以就导致最左边一位上也显示出“4”,同时由于经过短暂延时之后段管脚的状态就改变了,所以4的持续时间不长,显示出的效果就是淡淡的。
为了解决这个问题,做了以下三方面的改进,首先是对驱动函数进行修改,利用GPxSET寄存器和GPxCLEAR寄存器的操作代替GPXDAT寄存器的操作,这样可以不进行延时,提高操作的实时性,修改之后的宏定义如下:
#define DT_E_ON GpioDataRegs.GPBCLEAR.bit.GPIO40 = 1
#define DT_E_OFF GpioDataRegs.GPBSET.bit.GPIO40 = 1
#define DT_D_ON GpioDataRegs.GPBCLEAR.bit.GPIO41 = 1
#define DT_D_OFF GpioDataRegs.GPBSET.bit.GPIO41 = 1
#define DT_DP_ON GpioDataRegs.GPBCLEAR.bit.GPIO42 = 1
#define DT_DP_OFF GpioDataRegs.GPBSET.bit.GPIO42 = 1
#define DT_C_ON GpioDataRegs.GPBCLEAR.bit.GPIO43 = 1
#define DT_C_OFF GpioDataRegs.GPBSET.bit.GPIO43 = 1
#define DT_G_ON GpioDataRegs.GPBCLEAR.bit.GPIO44 = 1
#define DT_G_OFF GpioDataRegs.GPBSET.bit.GPIO44 = 1
#define DT_BIT4_ON GpioDataRegs.GPBSET.bit.GPIO45 = 1
#define DT_BIT4_OFF GpioDataRegs.GPBCLEAR.bit.GPIO45 = 1
#define DT_B_ON GpioDataRegs.GPCCLEAR.bit.GPIO80 = 1
#define DT_B_OFF GpioDataRegs.GPCSET.bit.GPIO80 = 1
#define DT_BIT3_ON GpioDataRegs.GPCSET.bit.GPIO81 = 1
#define DT_BIT3_OFF GpioDataRegs.GPCCLEAR.bit.GPIO81 = 1
#define DT_BIT2_ON GpioDataRegs.GPCSET.bit.GPIO82 = 1
#define DT_BIT2_OFF GpioDataRegs.GPCCLEAR.bit.GPIO82 = 1
#define DT_F_ON GpioDataRegs.GPCCLEAR.bit.GPIO83 = 1
#define DT_F_OFF GpioDataRegs.GPCSET.bit.GPIO83 = 1
#define DT_A_ON GpioDataRegs.GPCCLEAR.bit.GPIO84 = 1
#define DT_A_OFF GpioDataRegs.GPCSET.bit.GPIO84 = 1
#define DT_BIT1_ON GpioDataRegs.GPCSET.bit.GPIO85 = 1
#define DT_BIT1_OFF GpioDataRegs.GPCCLEAR.bit.GPIO85 = 1
然后,对切换的动作进行修改,原来是先切换位管脚的电平分布,再切换段管脚的电平分布。现在改成先将位管脚全部拉低,使得数码管全灭,然后切换段管脚的电平分布,最后切换位管脚的电平分布。
最后的话,切换过程完成之后延时10微秒,使得每个数位能持续亮一段时间,避免频繁切换造成的亮度降低。修改之后的 Sel_Bit 、 Sel_Num 和 Output 函数代码如下:
void Sel_Bit(Uint16 bit)
{
switch (bit)
{
case 1:
DT_BIT1_ON;
DT_BIT2_OFF;
DT_BIT3_OFF;
DT_BIT4_OFF;
break;
case 2:
DT_BIT1_OFF;
DT_BIT2_ON;
DT_BIT3_OFF;
DT_BIT4_OFF;
break;
...
}
}
void Sel_Num(Uint16 num)
{
DT_BIT1_OFF;
DT_BIT2_OFF;
DT_BIT3_OFF;
DT_BIT4_OFF;
switch (num)
{
case 0:
DT_A_ON;
DT_B_ON;
DT_C_ON;
DT_D_ON;
DT_E_ON;
DT_F_ON;
DT_G_OFF;
DT_DP_OFF;
break;
case 1:
DT_A_OFF;
DT_B_ON;
DT_C_ON;
DT_D_OFF;
DT_E_OFF;
DT_F_OFF;
DT_G_OFF;
DT_DP_OFF;
break;
...
}
}
void Output(Uint16 bit, Uint16 num)
{
Sel_Num(num);
Sel_Bit(bit);
DELAY_US(10);
}
5 最终效果
数码管效果
6 后语
至此,利用F28335控制四位共阳数码管的小实验就完成了。不过本人水平有限,这个实验各个方面肯定都还有可以改进的地方,欢迎大家批评指正。