打印
[经验分享]

DIY一台史上最“烂”的LED显示器!(连载中……)

[复制链接]
楼主: xuyiyi
手机看帖
扫描二维码
随时随地手机跟帖
21
寂寞男孩| | 2010-3-30 13:33 | 只看该作者 回帖奖励 |倒序浏览
进来围观一下,看一下什么是最烂的

使用特权

评论回复
22
xuyiyi|  楼主 | 2010-3-30 19:27 | 只看该作者
不好意思,这几天太忙了,被客户追在屁股要货,只能暂停半个月,继续

使用特权

评论回复
23
lpydidi| | 2010-3-30 22:13 | 只看该作者
LED显示器也可以搞这么多花样

使用特权

评论回复
24
ypj005| | 2010-4-1 16:56 | 只看该作者
:lol

使用特权

评论回复
25
xuyiyi|  楼主 | 2010-4-18 16:03 | 只看该作者
第一章
  做一个常规的8位LED显示器实验,但省略了驱动电路,设想利于笙泉MA807/MA816单片机强劲的输出口高低电流驱动能力,交叉分时驱动共阴共阳LED显示器,这样,就省略了一半位扫描输出口,在位扫描输出口中,这4个位输出口依次使用1-4个输出口合并输出,一方面提高位输出电流,另一方面可测试实际口驱动超载能力,给正式设计产品时提供参考。

  注: 由于MA807/MA816单片机都是贴片封装, 为方便接线, 故测试时用功能相当的MPC82G516代替。

  上测试图






  这二张为测试MPC82G516的IO口驱动能力,其中最低2位由4根IO口线合并驱动,第3-4位由3根IO口线合并驱动,第5-6位由2根IO口线合并驱动,最高2位由1根IO口线驱动。
  显示一串数据 88888888 , 其中一张为关灯拍摄,更能反映出亮度区别。
  测试源程序为:LED_001.C


MPC82G516键盘显示测试程序.rar (23.62 KB)

小结:
1.  单根IO口作为位驱动,扫描驱动能力略差些,显示亮度有些不足。
2.  2根IO口合并后作为位驱动,扫描驱动能力强多了,显示亮度明显亮许多。
3.  3根和4根IO口合并后作为位驱动,扫描驱动能力更强,靠眼力无法判断显示亮度的区别。
因此,推荐使用2根或3根IO口合并后作为位驱动8位LED显示器,能应付一般使用,当然,如驱动LED显示器的位数少些,单根IO口直接作为位驱动,驱动能力也足够了。


下面这张显示一串数据 76543210 ,  测试源程序为:LED_002.C





使用特权

评论回复
26
xuyiyi|  楼主 | 2010-4-19 07:06 | 只看该作者
继续完善测试程序,加入了通过中断方式动态扫描8位LED显示,并加入了键盘扫描程序,将16个按键键值以2进制型式,在LED显示器最末二位上显示。

    注:楼主位的原理图已作了部分修改
1. 将键盘输入隔离二极管全部反接,以适应16个按键键值能全部独立读入的需求,优化了程序结构。
2. 将键盘输入口改为P3.4, P3.5, 以便将P3.0, P3.1串行口让出来,今后可派点其他用途。

测试程序 LED_003.C :

MPC82G516键盘显示测试程序.rar (39.51 KB)

使用特权

评论回复
27
David_ming| | 2010-4-19 09:19 | 只看该作者
看到程序里似乎有句  code out_D0(void)   有些不解 函数返回值为 code ??

使用特权

评论回复
28
xuyiyi|  楼主 | 2010-4-19 11:23 | 只看该作者
不好意思,老许C语言真的略知一点皮毛,只会东凑西拼抄点书中内容临时组合一下,无法解答你的问题。

  函数指针数组的用法是从书上抄的,原实例前面 函数返回值类型 全部用void, 只是用 Keil C51 编译后 散转子函数表 全部落在 RAM 中,不能自动 在 Code 中生成,将 函数指针数组 前的 函数返回值类型 改为 code , 则自动生成在 Code 中,但通过 函数指针数组 调用的 子函数,在编译时全部显示警告:指针类型不匹配,后将 函数指针数组 中所调用的 子函数返回值类型 全部由 void 改为 Code ,编译一次通过,警告消除。
  我想可能是 Keil C51 编译器太优秀了,语法太严谨了,只有指定 Code 类型,才能将 函数指针数组 编译定位在 Code 中,还请内行的21IC大师解答这一现象。


附:函数指针数组 原程序       

/**********************************************  子程序入口  ****************************************/


/* 低4位LED为共阴,高4位LED为共阳 */
code out_D0(void)
{ P0 = BUFF[0];
  P1 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0xf0;         // 配置P1.3-P1.0为推挽输出口
  P1M1 |= 0x0f;
}

code out_D1(void)
{ P0 = BUFF[1];
  P1 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0x0f;         // 配置P1.7-P1.4为推挽输出口
  P1M1 |= 0xf0;
}

.............................................

code out_D7(void)
{ P0 = ~BUFF[7];
  P2 = 0xff;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P2M0 &= 0x0f;         // 配置P2.7-P2.4为推挽输出口
  P2M1 |= 0xf0;
}


/**********************************************  散转子函数表  ****************************************/


code (*out_list[8])()={
          out_D0 ,   // D0(最低)位显示子程序   
          out_D1 ,   // D1位显示子程序
          out_D2 ,   // D2位显示子程序
          out_D3 ,   // D3位显示子程序
          out_D4 ,   // D4位显示子程序
          out_D5 ,   // D5位显示子程序
          out_D6 ,   // D6位显示子程序
          out_D7};   // D7(最高)位显示子程序

/******************************************************************************************************/

使用特权

评论回复
29
David_ming| | 2010-4-19 13:55 | 只看该作者
问题在于code是修饰函数名还是返回值类型

使用特权

评论回复
30
xuyiyi|  楼主 | 2010-4-19 14:40 | 只看该作者
目前 code 在函数名处的写法,应该属于 函数返回值类型,但函数无法返回 code 型的数值。

如 code 属于强制转换类型,书写上需加圆括号(),我试着给 code 加圆括号(),结果 Keil C51 编译时出错通不过,排除了这一种说法。

用功能上讲,此处 code 的作用属于修饰 函数名,但好像这种写法不太适当。

要么是我们未知的另类写法?只有请高手指点了,老许实在回答这个问题。

使用特权

评论回复
31
xuyiyi|  楼主 | 2010-4-19 14:47 | 只看该作者
试着重写了一下,这样写法编译顺利通过,无任何警告!

从而证实了 code 的作用属于修饰 函数名。

函数指针数组 原程序 如下:      

/**********************************************  子程序入口  ****************************************/


/* 低4位LED为共阴,高4位LED为共阳 */
void out_D0(void)
{ P0 = BUFF[0];
  P1 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0xf0;         // 配置P1.3-P1.0为推挽输出口
  P1M1 |= 0x0f;
}

void out_D1(void)
{ P0 = BUFF[1];
  P1 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0x0f;         // 配置P1.7-P1.4为推挽输出口
  P1M1 |= 0xf0;
}

.............................................

void out_D7(void)
{ P0 = ~BUFF[7];
  P2 = 0xff;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P2M0 &= 0x0f;         // 配置P2.7-P2.4为推挽输出口
  P2M1 |= 0xf0;
}


/**********************************************  散转子函数表  ****************************************/


code void (*out_list[8])()={
          out_D0 ,   // D0(最低)位显示子程序   
          out_D1 ,   // D1位显示子程序
          out_D2 ,   // D2位显示子程序
          out_D3 ,   // D3位显示子程序
          out_D4 ,   // D4位显示子程序
          out_D5 ,   // D5位显示子程序
          out_D6 ,   // D6位显示子程序
          out_D7};   // D7(最高)位显示子程序

/******************************************************************************************************/

使用特权

评论回复
32
xuyiyi|  楼主 | 2010-4-19 14:52 | 只看该作者
我觉得 code  称作 修饰 函数指针数组 类型(注:不是函数返回值类型)。
或者 强制 指定 函数指针数组 在存储器上的地址分配 比较合适。

使用特权

评论回复
33
David_ming| | 2010-4-19 15:17 | 只看该作者
头一次看到这种用法,许工很谦虚哦~

使用特权

评论回复
34
David_ming| | 2010-4-19 15:20 | 只看该作者
void const out_D7(void)这样写也没有警告,应该是修饰函数名,如果修饰函数名,加不加又有什么区别呢

使用特权

评论回复
35
xuyiyi|  楼主 | 2010-4-19 16:41 | 只看该作者
头一次看到这种用法,许工很谦虚哦~
David_ming 发表于 2010-4-19 15:17


不是谦虚,是确实不懂,老许有十几年没搞这玩意了,C语言的二十来条基本指令、类型结构都背不出写不全,写点C程序很吃力,一直要翻书抄书,好在马老师编著的《AVR单片机嵌入式系统原理与应用实践》,这本书不错,里面有大量的C程序实例可供我抄,老许一口气买了三本,呵呵。

使用特权

评论回复
36
David_ming| | 2010-4-19 17:22 | 只看该作者
35# xuyiyi
同意,那本书虽然有点错误,但是很经典

使用特权

评论回复
37
xuyiyi|  楼主 | 2010-4-19 20:20 | 只看该作者
抄书为什么只抄一本呢?多抄几本,把里面的相同点抄出来,一般就不会犯错。
还有,《匠人手记》写的也不错,里面有好多思路值得借签。

使用特权

评论回复
38
chuxh| | 2010-4-19 23:05 | 只看该作者
《匠人手记》确实很不错

使用特权

评论回复
39
xuyiyi|  楼主 | 2010-4-21 19:10 | 只看该作者
本帖最后由 xuyiyi 于 2010-4-21 19:22 编辑

第二章,
  在第一章的基础上,输出驱动电流以软件模拟PWM调制控制,省略了8个LED段限流电阻,以发扬光大天朝偷工减料之山寨文化,精简节省一切可省略之零件,在本LED显示器中,用笙泉MA807/MA816/MPC82G516单片机,不借助任何外围电路,直接驱动LED显示器,去打造一款“史上”(同时也是“世上”)最“烂”的LED显示器。

  另外,扩展了16个常规按键电路,在本实验中,设置了8级亮度调节,将使用其中的二个按键,按1#(S9)键可增加显示亮度,按0#(S1)键可减小显示亮度。

  实验证明,笙泉MA807/MA816/MPC82G516单片机的输出源电流和输出灌电流不是同一级别的,输出源电流要远小于输出灌电流,
  当段输出口串限流电阻时,用灌电流驱动共阴LED数码管时,两个IO口并联即可胜任,而用源电流驱动共阳LED数码管时,起码用三个IO口并联,否则,消隐效果较差。
  当段输出口不串限流电阻,输出电流以软件模拟PWM调制控制时,由于源电流输出能力较差,不太合适,建议使用输出能力强的灌电流输出+软件模拟PWM调制方式,效果较好。否则,消隐效果较差。
  
测试程序 LED_004.C :

MPC82G516键盘显示测试程序.rar

69.17 KB

使用特权

评论回复
40
xuyiyi|  楼主 | 2010-4-21 19:14 | 只看该作者
/*---------------------------------------------------*/
/*                                                                */
/*   MPC82G516键盘显示测试程序 之四       */
/*                                                                */
/*   功能 :使用中断方式动态扫描显示,并加入了  */
/*          键盘扫描程序,将16个按键键值以2进制  */
/*          型式,在LED显示器最末二位上显示。    */
/*          输出驱动电流以软件模拟PWM调制控制, */
/*          省略了8个LED段限流电阻               */
/*                                                                    */
/*          按1#(S9)键可增加显示亮度,按0#(S1))键  */
/*          可减小显示亮度。                          */
/*                                                                         */
/*   CPU  : MPC82G516                                     */
/*   晶振 : 24MHz                                               */
/*   作者 : 许意义                                */  
/*   版本 : V1.0                                                  */
/*   日期 : 2010.4.20                                          */
/*                                                                      */
/*---------------------------------------------------*/

#include "REG_MPC82G516.h"       

#define   PT0H    3

#define   LED_Bright_Level            8                  // LED亮度调节级数
#define   LED_Bright_Preference         6                  // 预置LED亮度级数          
#define   LED_Bright_max         1            // LED最大亮度(数值越小亮度越大,但不能小于1)

#define   CtKmin     LED_Bright_max           // Ct值最小,LED亮度最大
#define   CtKmax     CtKmin+LED_Bright_Level  // Ct值最大,LED亮度最小
#define   CtK_Preference  CtKmin+(LED_Bright_Level-LED_Bright_Preference)  // 预置Ct值
  
bit       LED_bit;
volatile unsigned char Ci;
volatile unsigned char CtK=CtK_Preference;

unsigned char code TAB1[19]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,
                             0x77,0x7c,0x39,0x5e,0x79,0x71,0x40,0,0xff};  // 0123456789abCdEF-8.

unsigned char code TAB2[8]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};    // 键扫描位地址


unsigned char BUFF[8];       // 显示缓冲区

unsigned char KEY[2];        // 输入的键值缓存区,高在前,低在后,P3.4、P3.5为键盘扫描输入口

unsigned char KEY_bak[2];           // 上一次输入的键值缓存区备份,用于键盘消抖动


/**********************************************  子程序入口  ****************************************/


/* 低4位LED为共阴,高4位LED为共阳 */
void out_D0(void)
{ P0 = 0;
  P1 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0xf0;         // 配置P1.3-P1.0为推挽输出口
  P1M1 |= 0x0f;
  P0 = BUFF[0];
}

void out_D1(void)
{ P0 = 0;
  P1 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0x0f;         // 配置P1.7-P1.4为推挽输出口
  P1M1 |= 0xf0;
  P0 = BUFF[1];
}

void out_D2(void)
{ P0 = 0;
  P2 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P2M0 &= 0xf0;         // 配置P2.3-P2.0为推挽输出口
  P2M1 |= 0x0f;
  P0 = BUFF[2];
}

void out_D3(void)
{ P0 = 0;
  P2 = 0;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P2M0 &= 0x0f;         // 配置P2.7-P2.4为推挽输出口
  P2M1 |= 0xf0;
  P0 = BUFF[3];
}

void out_D4(void)
{ P0 = 0xff;
  P1 = 0xff;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0xf0;         // 配置P1.3-P1.0为推挽输出口
  P1M1 |= 0x0f;
  P0 = ~BUFF[4];
}

void out_D5(void)
{ P0 = 0xff;
  P1 = 0xff;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P1M0 &= 0x0f;         // 配置P1.7-P1.4为推挽输出口
  P1M1 |= 0xf0;
  P0 = ~BUFF[5];
}

void out_D6(void)
{ P0 = 0xff;
  P2 = 0xff;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P2M0 &= 0xf0;         // 配置P2.3-P2.0为推挽输出口
  P2M1 |= 0x0f;
  P0 = ~BUFF[6];
}

void out_D7(void)
{ P0 = 0xff;
  P2 = 0xff;
  P0M0 = 0;                   // 配置P0为推挽输出口
  P0M1 = 0xff;
  P2M0 &= 0x0f;         // 配置P2.7-P2.4为推挽输出口
  P2M1 |= 0xf0;
  P0 = ~BUFF[7];
}

void off_outLED(void)  reentrant
{
  P0M0 = 0xff;         // 配置P0为输入口
  P0M1 = 0;
  P1M0 = 0xff;         // 配置P1为输入口
  P1M1 = 0;
  P2M0 = 0xff;         // 配置P2为输入口
  P2M1 = 0;
}

void ms20(void)
{ volatile unsigned int Ia;
  for (Ia=0; Ia<20000; Ia++) { ; }
}


/**********************************************  散转子函数表  ****************************************/


code void (*out_list[8])()={
          out_D0 ,   // D0(最低)位显示子程序   
          out_D1 ,   // D1位显示子程序
          out_D2 ,   // D2位显示子程序
          out_D3 ,   // D3位显示子程序
          out_D4 ,   // D4位显示子程序
          out_D5 ,   // D5位显示子程序
          out_D6 ,   // D6位显示子程序
          out_D7};   // D7(最高)位显示子程序

                                 
/**********************************************  中断程序入口  ****************************************/


void time0() interrupt 1  using 1    // 定时中断0, 模拟PWM控制LED亮度
{ static unsigned char Ct;
                              // 自动重装定时器0 定时常数,定时时间20us,晶振频率24MHz  
  if (LED_bit == 1)                     // 执行扫描显示
    {
          if ((Ct++) >= CtK)
        { Ct = 0;
                  (*out_list[Ci])();
            }
          else
            {
                  off_outLED();
            }
    }

}

void time1() interrupt 3    // 定时中断1
{ unsigned char Cj[2];
  TL1 = 0x30;                   // 设定定时器1 中断定时时间1ms,晶振频率24MHz   
  TH1 = 0xf8;

  LED_bit = 0;                                  // 关扫描显示
  off_outLED();
  if (++Ci >= 8)
    { Ci = 0;       
          KEY_bak[0] = KEY[0];
          KEY_bak[1] = KEY[1];
          KEY[0] = 0;
          KEY[1] = 0;
        }
  P0    = 0xff;
  P0M0  = 0;                // 配置P0为准双向端口
  P0    = TAB2[Ci];     // 输出键扫描位地址
  Cj[1] = P3;           // 读入键值
  P0M0  = 0xff;                // 配置P0为输入口
  Cj[1] = (~(Cj[1] >> 4)) & 0x03;
  Cj[0] = 0;
  *((unsigned int *)KEY) |= (*((unsigned int *)Cj)) << (Ci<<1);
  LED_bit = 1;                        // 开扫描显示
}
  

/**********************************************  主程序入口  ****************************************/


void main(void)
{
  off_outLED();

  BUFF[0] = TAB1[17];
  BUFF[1] = TAB1[16];
  BUFF[2] = TAB1[15];
  BUFF[3] = TAB1[14];
  BUFF[4] = TAB1[13];
  BUFF[5] = TAB1[12];
  BUFF[6] = TAB1[11];
  BUFF[7] = TAB1[10];

  TMOD = 0x12;        // 设定T0为模式2:8 位自动加载, T1为模式1:16位定时器
  TL0 = 0xd8;                   // 自动重装定时器0 定时常数,定时时间20us,晶振频率24MHz  
  TH0 = 0xd8;  
  TL1 = 0x30;                   // 设定定时器1 中断定时时间1ms,晶振频率24MHz   
  TH1 = 0xf8;

  PT0  = 1;                          // 设定定时器0 为最高中断级
  IPH |= 1<<PT0H;

  ET0 = 1;                          // 开定时器0 中断
  ET1 = 1;                          // 开定时器1 中断
  EA  = 1;            // 开中断总使能
  TR0 = 1;            // 开定时器0
  TR1 = 1;            // 开定时器1

  while(1)
    { BUFF[0] = TAB1[CtKmax-CtK];
          if (((KEY[1]&0x01) != 0) && ((KEY_bak[1]&0x01) != 0))    // '+'
            { if (CtK >= CtKmax)  CtK = CtKmax;
                    else  CtK++;
                  do{ BUFF[0] = TAB1[CtKmax-CtK];
                    }while(((KEY[1]&0x01) != 0) && ((KEY_bak[1]&0x01) != 0));  
                }
          if (((KEY[1]&0x02) != 0) && ((KEY_bak[1]&0x02) != 0))    // '-'
            { if (CtK <= CtKmin)  CtK = CtKmin;
                    else  CtK--;
                  do{ BUFF[0] = TAB1[CtKmax-CtK];
                    }while(((KEY[1]&0x02) != 0) && ((KEY_bak[1]&0x02) != 0));
                }
          ms20();
    }

}


/**********************************************  主程序结束  ****************************************/

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则