数码之家

 找回密码
 立即注册
搜索
查看: 6176|回复: 31

[C51] 单片机I/O口无序直驱LED数码管程序分享

[复制链接]
发表于 2021-9-11 20:02:56 | 显示全部楼层 |阅读模式
本帖最后由 慕名而来 于 2021-9-11 20:05 编辑

最近坛里有坛友 @iritwq 发帖请教数码管编程问题,帖子:初学C,数码管编程问题求教https://www.mydigit.cn/thread-275008-1-1.html(出处: 数码之家) ,在参与回复的时候发现它的4位数码管与单片机的连接不是用一组完整8位的I/O引脚来驱动8段LED数码,而是无序连接的,自从玩单片机以来也玩过很多的数码管驱动,但是基本都是通过164或595进行串口驱动,极少用到I/O直驱,更没有弄过任意接线的I/O口无序驱动,恰逢这几天没啥玩的,就找出了数码管通过飞线焊了一个4位数码块,编写了程序实物测试了一下,感觉也不难驱动,程序留存于此也分享给用得到的新手。
1.在此特别感谢上述帖子回复中的第32楼的坛友 @1065307738  我在他的代码中学到了将分散的位变量映射到一个变量的各个位的方法,通过这个方法使程序更加简练易懂。

2.硬件电路只用于演示,不讨论单片机I/O口分配的合理性。

原理图:



分享程序:

  1. /**本程序用于测试分散I/O口驱动4位数码管显示**/
  2. #include <STC12C5A60S2.h>
  3. #include <intrins.h>
  4. #define uint unsigned int
  5. #define uchar unsigned char
  6. //----I/O口位功能定义-----------------------------------
  7. sbit da=P1^2;
  8. sbit db=P1^1;
  9. sbit dc=P0^1;
  10. sbit dd=P0^2;
  11. sbit de=P0^3;
  12. sbit df=P1^3;
  13. sbit dg=P1^4;
  14. sbit dp=P0^0;

  15. sbit w1=P0^4;
  16. sbit w2=P0^5;
  17. sbit w3=P0^6;
  18. sbit w4=P0^7;

  19. //不含小数点8段LED字形码0-9+黑屏码、共阳极驱动0亮1熄
  20. uchar duan_ma[11]= {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff};
  21. uchar wei_ma[4]= {0x07,0x0b,0x0d,0x0e}; //字位码
  22. uchar date[4];//显示数据暂存
  23. //====熄屏延时a*1ms延时函数===============================
  24. void delay_1ms (uchar a)        //@12.000MHz
  25. {
  26.     uchar i, j;
  27.     while(a--)
  28.     {
  29.         _nop_();
  30.         _nop_();
  31.         i = 12;
  32.         j = 168;
  33.         do
  34.         {
  35.             while (--j);
  36.         }
  37.         while (--i);
  38.     }
  39. }
  40. //====显示颜色a*10us=====================================
  41. void delay_10us(uchar a)                //@12.000MHz
  42. {
  43.     uchar i;
  44.     while(a--)
  45.     {
  46.         i = 27;
  47.         while (--i);
  48.     }
  49. }
  50. //=======================================================
  51. //将LED字形的8个笔段控制I/O引脚映射到变量的8个位上
  52. void segment_out(uchar dat)
  53. {
  54.     da=(bit)(dat&0x01);
  55.     db=(bit)(dat&0x02);
  56.     dc=(bit)(dat&0x04);
  57.     dd=(bit)(dat&0x08);
  58.     de=(bit)(dat&0x10);
  59.     df=(bit)(dat&0x20);
  60.     dg=(bit)(dat&0x40);
  61.     dp=(bit)(dat&0x80);
  62. }
  63. //=======================================================
  64. //将4个数位控制I/O引脚映射到变量的4个位上
  65. void position_out(uchar dat)
  66. {
  67.     w1=(bit)(dat&0x08);
  68.     w2=(bit)(dat&0x04);
  69.     w3=(bit)(dat&0x02);
  70.     w4=(bit)(dat&0x01);
  71. }
  72. //====数据拆分函数=======================================
  73. //提取出数据的千、百、十、个位的数值存入数组中
  74. void digits_obtain(uint dat)
  75. {
  76.     date[0]=dat/1000;
  77.     date[1]=dat%1000/100;
  78.     date[2]=dat%100/10;
  79.     date[3]=dat%10;
  80. }
  81. //====显示输出函数=======================================
  82. void display_out(uint dat)
  83. {
  84.     uchar i;
  85.     digits_obtain(dat);                //拆分数据
  86.     for(i=0; i<4; i++)
  87.     {
  88.         if(date[0]==0)                //如果第一位为0则屏蔽掉
  89.         {
  90.             date[0]=10;
  91.         }
  92.         else
  93.         {
  94.             segment_out(duan_ma[date[i]]);        //发送段显示码
  95.             position_out(wei_ma[i]);                //发送位显示码
  96.             if(i==1)                                                //点亮百位与十位中间的小数点、最大显示数据为99.99
  97.             {
  98.                 dp=0;
  99.             }
  100.             else
  101.             {
  102.                 dp=1;
  103.             }
  104.         }
  105. //以下为消隐及显示亮度调整部分
  106.         delay_10us(20);                                //显示时间、赋值变化可以改变显示亮度
  107.         segment_out(duan_ma[10]);        //送黑屏码
  108.         delay_1ms(5);                                //熄屏时间
  109.     }
  110. }
  111. //====主函数==============================================
  112. main()
  113. {
  114.     P0M1=0x00;
  115.     P0M0=0xff;                //P0口的8个引脚设定为推挽输出
  116.     P1M1=0x00;
  117.     P1M0=0x1e;                //P1.1、P1.2、P1.3、P1.4设定为推挽输出
  118.     while(1)
  119.     {
  120.         display_out(1234);
  121.     }
  122. }
复制代码


实物显示图片:









本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

打赏

参与人数 5家元 +180 收起 理由
jf201006 + 20 謝謝分享
iritwq + 20 謝謝分享
兔包公 + 20 程序万变不离其中,抓的老鼠就是好猫.
家睦 + 100
杨雪飞 + 20 謝謝分享

查看全部打赏

发表于 2021-9-11 22:34:57 | 显示全部楼层
本帖最后由 广东梁百万 于 2021-9-11 23:03 编辑

下面这个是我年初针对STC8G1K17   SOP-20 +  LCD1602写的程序(部分),就是用不是同一组的I/O口拼成一组8位数据口给LCD1602的D0~D7.

重点留意  bdata 这个数据类型   ,跟着把端口P1^2定义成LCD_D0 , 再在程序里对LCD_D0 进行位操作
那位1065307738大神用的  DA=(bit)(Dat&0x01); 进行位操作比我的更简洁,但下面程序的作法是我自己摸索出来的,所以我还是习惯了用我自己的方法:lol:


   sbit  LCD_D0  =  P1^2;
   sbit  LCD_D1  =  P1^3 ;
   sbit  LCD_D2  =  P3^2 ;
   sbit  LCD_D3  =  P3^3 ;
   sbit  LCD_D4  =  P3^4 ;
   sbit  LCD_D5  =  P3^5 ;
   sbit  LCD_D6  =  P3^6 ;
   sbit  LCD_D7  =  P3^7;
   sbit  LCD_RS  =  P1^0;   //LCD1602 的 RS端  ,1-数据,0-指令
   sbit  LCD_EN  =  P1^1 ;  //LCD1602 的 E端   ,先从低变高,再变低,以确认输入数据或命令



         
         unsigned char bdata LCD1602RAM2;  //LCD1602的写入缓冲    ********* 重点留意  bdata 这个数据类型

         sbit LCD1602RAM2_D0= LCD1602RAM2^0;
         sbit LCD1602RAM2_D1= LCD1602RAM2^1;
         sbit LCD1602RAM2_D2= LCD1602RAM2^2;
         sbit LCD1602RAM2_D3= LCD1602RAM2^3;
         sbit LCD1602RAM2_D4= LCD1602RAM2^4;
         sbit LCD1602RAM2_D5= LCD1602RAM2^5;
         sbit LCD1602RAM2_D6= LCD1602RAM2^6;
         sbit LCD1602RAM2_D7= LCD1602RAM2^7;

//-----------------------------------------------------------------------


                 //LCD1602数据写入
                        void LCD1602SZ()
                   {
                         LCD_RS=1;
                        LCD_EN=1;
                   LCD_D0=LCD1602RAM2_D0;
                   LCD_D1=LCD1602RAM2_D1;
                   LCD_D2=LCD1602RAM2_D2;
                   LCD_D3=LCD1602RAM2_D3;
                   LCD_D4=LCD1602RAM2_D4;
                   LCD_D5=LCD1602RAM2_D5;
                   LCD_D6=LCD1602RAM2_D6;
                   LCD_D7=LCD1602RAM2_D7;
                    LCD_EN=0;  //E,下降沿,让LCD读取
                         Delay60us();  //延时80us

                        }


                         //LCD1602指令写入
                        void LCD1602ZL()
                        {
                        LCD_RS=0;
                        LCD_EN=1;
                   LCD_D0=LCD1602RAM2_D0;
                   LCD_D1=LCD1602RAM2_D1;
                   LCD_D2=LCD1602RAM2_D2;
                   LCD_D3=LCD1602RAM2_D3;
                   LCD_D4=LCD1602RAM2_D4;
                   LCD_D5=LCD1602RAM2_D5;
                   LCD_D6=LCD1602RAM2_D6;
                   LCD_D7=LCD1602RAM2_D7;
                    LCD_EN=0;                 //E,下降沿,让LCD读取
                        Delay60us();  //延时80us

                        }





                        //LCD1602初始化
                        void LCD1602CCH()
                        {
                        
                        LCD1602RAM2=0;
                        delay_ms(15);          //延时15毫秒
                        LCD1602RAM2=0x38;  //;16行*2,5*8
                        LCD1602ZL();         //写入指令
                   delay_ms(5);          //延时5毫秒
                   LCD1602RAM2=0x38;  //;16行*2,5*8
                        LCD1602ZL();         //写入指令
                   delay_ms(5);          //延时5毫秒
                   LCD1602RAM2=0x08; // 关显示
                   LCD1602ZL();         //写入指令
                   delay_ms(1);          //延时1毫秒
                  LCD1602RAM2=0x01;        //清屏
                   LCD1602ZL();         //写入指令
                   delay_ms(5);          //延时5毫秒
                   LCD1602RAM2=0x06;        //;地址计数器AC自动加1,光标右移 *******
                   delay_ms(1);          //延时1毫秒
                   LCD1602RAM2=0x0C;        //开显示
                  LCD1602ZL();         //写入指令
                   delay_ms(1);          //延时1毫秒
                  
                   }



                // LCD1602全屏显示程序,16字*2行


                  void LCD1602SC()
                   {
                   unsigned char i=16;          //每行的个数计数
                   unsigned char x=0;                 //行数
                   unsigned char y=0;                //列数
                  
                   LCD1602RAM2=0x80;   //这个是LCD第1行的地址
                   LCD1602ZL(); //以地址/指令形式发送到LCD1602

                   while(i--)
                   {
                   LCD1602RAM2=LCD1602RAM1[0][y];
                   LCD1602SZ(); //以数据形式发送到LCD1602
                   y++;
                   };

               LCD1602RAM2=0xC0;   //这个是LCD第2行的地址
                   LCD1602ZL(); //以地址/指令形式发送到LCD1602
                   i=16        ;                 //每行的个数计数
                   y=0;        //每行的个数
                   while(i--)
                   {
                   LCD1602RAM2=LCD1602RAM1[1][y];
                   LCD1602SZ(); //以数据形式发送到LCD1602
                   y++;
                   };
                  
                   }








回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-12 10:01:20 | 显示全部楼层
广东梁百万 发表于 2021-9-11 22:34
下面这个是我年初针对STC8G1K17   SOP-20 +  LCD1602写的程序(部分),就是用不是同一组的I/O口拼成一组8 ...

多谢你的经验分享,以前有了解过bdata数据类型,多是在ILI9431彩屏驱动等例程中用到,但我没有用过,如果不是借鉴坛友的方法我自己写的话就是如下结构了:
  1. void zi_xing(uchar c)//字形生成函数
  2. {
  3. switch(c)
  4. {
  5. case 0:               
  6. {
  7. da=0;db=0;dc=0;dd=0;de=0;df=0;dg=1;//0
  8. }
  9. break;
  10. case 1:               
  11. {
  12. da=1;db=0;dc=0;dd=1;de=1;df=1;dg=1;//1
  13. }
  14. break;
  15. case 2:               
  16. {
  17. da=0;db=0;dc=1;dd=0;de=0;df=1;dg=0;//2
  18. }
  19. break;
  20. case 3:               
  21. {
  22. da=0;db=0;dc=0;dd=0;de=1;df=1;dg=0;//3
  23. }
  24. break;
  25. case 4:               
  26. {
  27. da=1;db=0;dc=0;dd=1;de=1;df=0;dg=0;//4
  28. }
  29. break;
  30. case 5:               
  31. {
  32. da=0;db=1;dc=0;dd=0;de=1;df=0;dg=0;//5
  33. }
  34. break;
  35. case 6:               
  36. {
  37. da=0;db=0;dc=0;dd=0;de=0;df=1;dg=0;//6
  38. }
  39. break;
  40. case 7:               
  41. {
  42. da=0;db=0;dc=0;dd=1;de=1;df=1;dg=1;//7
  43. }
  44. break;
  45. case 8:               
  46. {
  47. da=0;db=0;dc=0;dd=0;de=0;df=0;dg=0;//8
  48. }
  49. break;
  50. case 9:               
  51. {
  52. da=0;db=0;dc=0;dd=0;de=1;df=0;dg=0;//9
  53. }
  54. break;
  55. case 10:               
  56. {
  57. da=1;db=1;dc=1;dd=1;de=1;df=1;dg=1;//黑屏
  58. }
  59. break;
  60. default:
  61. break;
  62. }
复制代码


字形生成函数的代码非常直观,虽然省掉了字模数组但看起来还是繁琐些,调用也是一样的:zi_xing(date);//其中date对应显示数据拆分后的某一位数据。
回复 支持 1 反对 0

使用道具 举报

发表于 2021-9-12 19:10:02 | 显示全部楼层
都是大神呀,膜拜中。。。。。。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-12 19:59:34 | 显示全部楼层
iritwq 发表于 2021-9-12 19:10
都是大神呀,膜拜中。。。。。。

只是喜欢玩单片机,因为C编程是看例程学习的所以一直很外行,只会改编程序不会起头写程序。
回复 支持 反对

使用道具 举报

发表于 2021-9-14 23:19:52 | 显示全部楼层
广东梁百万 发表于 2021-9-11 22:34
下面这个是我年初针对STC8G1K17   SOP-20 +  LCD1602写的程序(部分),就是用不是同一组的I/O口拼成一组8 ...

这是基本常识了,keilC51的变前面可以加data idata xdata bdata,各修饰有各自的用处,keilC51的帮助有说明的。
但是不建议这样写,这样只能用在keilC51上面,换了IDE或芯片内核就用不了,建议使用共用体union,标准C编译器都支持。
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2021-9-15 08:54:31 | 显示全部楼层
595953427@qq 发表于 2021-9-14 23:19
这是基本常识了,keilC51的变前面可以加data idata xdata bdata,各修饰有各自的用处,keilC51的帮助有说 ...

希望大神能够给出用共用体union的代码,很想学习这种方法先谢了!
回复 支持 反对

使用道具 举报

发表于 2021-9-15 09:09:02 | 显示全部楼层
慕名而来 发表于 2021-9-15 08:54
希望大神能够给出用共用体union的代码,很想学习这种方法先谢了!

http://c.biancheng.net/cpp/html/2932.html我在这里看到一个介绍,看的云里雾里的,感觉不到这种方法有什么用处


通过前面的讲解,我们知道结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员。在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union),它的定义格式为:
union 共用体名{
    成员列表
};

共用体有时也被称为联合或者联合体,这也是 Union 这个单词的本意。
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

共用体也是一种自定义类型,可以通过它来创建变量,例如:
union data{
    int n;
    char ch;
    double f;
};
union data a, b, c;
上面是先定义共用体,再创建变量,也可以在定义共用体的同时创建变量:
union data{
    int n;
    char ch;
    double f;
} a, b, c;
如果不再定义新的变量,也可以将共用体的名字省略:
union{
    int n;
    char ch;
    double f;
} a, b, c;
共用体 data 中,成员 f 占用的内存最多,为 8 个字节,所以 data 类型的变量(也就是 a、b、c)也占用 8 个字节的内存,请看下面的演示:
#include <stdio.h>
union data{
    int n;
    char ch;
    short m;
};
int main(){
    union data a;
    printf("%d, %d\n", sizeof(a), sizeof(union data) );
    a.n = 0x40;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.ch = '9';
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.m = 0x2059;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.n = 0x3E25AD54;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
   
    return 0;
}
运行结果:
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
这段代码不但验证了共用体的长度,还说明共用体成员之间会相互影响,修改一个成员的值会影响其他成员。
回复 支持 1 反对 0

使用道具 举报

发表于 2021-9-15 09:20:04 | 显示全部楼层
595953427@qq 发表于 2021-9-14 23:19
这是基本常识了,keilC51的变前面可以加data idata xdata bdata,各修饰有各自的用处,keilC51的帮助有说 ...

初学者真是举步维艰呀,复制的代码一个没动,编译时到处出问题

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2021-9-15 12:29:42 | 显示全部楼层
iritwq 发表于 2021-9-15 09:20
初学者真是举步维艰呀,复制的代码一个没动,编译时到处出问题

你写的不对,不是这样的。
回复 支持 反对

使用道具 举报

发表于 2021-9-15 12:40:58 | 显示全部楼层

  1. #include "reg52.h"

  2. typedef union
  3. {
  4.     unsigned char val;
  5.     struct
  6.     {
  7.         unsigned char bit_0 : 1;
  8.         unsigned char bit_1 : 1;
  9.         unsigned char bit_2 : 1;
  10.         unsigned char bit_3 : 1;
  11.         unsigned char bit_4 : 1;
  12.         unsigned char bit_5 : 1;
  13.         unsigned char bit_6 : 1;
  14.         unsigned char bit_7 : 1;
  15.     } bit_n;
  16. } un_8;

  17. un_8 un8;

  18. void main(void)
  19. {
  20.     un8.val = 0xFF;
  21.     un8.bit_n.bit_0 = 0;
  22.     un8.bit_n.bit_1 = 1;
  23.     un8.bit_n.bit_2 = 0;
  24.     un8.bit_n.bit_3 = 1;
  25.     un8.bit_n.bit_4 = 0;
  26.     un8.bit_n.bit_5 = 1;
  27.     un8.bit_n.bit_6 = 0;
  28.     un8.bit_n.bit_7 = 1;
  29.     while (1)
  30.     {
  31.         un8.val++;
  32.     }
  33. }
复制代码


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2021-9-15 12:41:43 | 显示全部楼层
iritwq 发表于 2021-9-15 09:20
初学者真是举步维艰呀,复制的代码一个没动,编译时到处出问题

你看一下11楼,我没有写注释,应该能看懂吧。
回复 支持 反对

使用道具 举报

发表于 2021-9-15 12:52:56 | 显示全部楼层
本帖最后由 595953427@qq 于 2021-9-15 13:13 编辑

仔细看一下有没有使用bdata的区别。
bdata是好用,但是也有限制的,bdata只有16个字节。
16个字节,每个字节8位,一共128位,你在程序里面定义的bit型变量也包含在这128位里面。
bdata虽好,数量有限,且用且珍惜。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2021-9-15 12:58:12 | 显示全部楼层
还是vs code看着舒服。
墙裂建议使用vs code,使用结构体,共用体的时候代码提示会非常方便。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2021-9-15 13:02:04 | 显示全部楼层
慕名而来 发表于 2021-9-12 10:01
多谢你的经验分享,以前有了解过bdata数据类型,多是在ILI9431彩屏驱动等例程中用到,但我没有用过,如果 ...

你这个代码排版也太不友好了,建议使用vs code,按下快捷键alt + shift + F,会给你整理好排版。
回复 支持 反对

使用道具 举报

发表于 2021-9-15 13:17:33 | 显示全部楼层
同样的代码使用Keil4编译结果和Keil5编译结果相差这么大?

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2021-9-15 15:10:26 | 显示全部楼层
iritwq 发表于 2021-9-15 09:09
http://c.biancheng.net/cpp/html/2932.html我在这里看到一个介绍,看的云里雾里的,感觉不到这种方法有 ...

共用体还有个作用很方便,就是要把float类型的数据保存到eeprom,或者UART通讯的时候传输float类型的数据,用共用体会很方便。
回复 支持 反对

使用道具 举报

发表于 2021-9-15 15:11:43 | 显示全部楼层
iritwq 发表于 2021-9-15 09:20
初学者真是举步维艰呀,复制的代码一个没动,编译时到处出问题

你这样肯定是编译不通过的,变量名不可以使用关键字。data在keilC51里面是关键字,不能用作变量名。
回复 支持 反对

使用道具 举报

发表于 2021-9-15 17:08:53 | 显示全部楼层
595953427@qq 发表于 2021-9-15 15:11
你这样肯定是编译不通过的,变量名不可以使用关键字。data在keilC51里面是关键字,不能用作变量名。 ...

改成这样编译倒是通过了,但是不知道怎样才能出来运行的结果

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复 支持 反对

使用道具 举报

发表于 2021-9-15 23:56:36 | 显示全部楼层
iritwq 发表于 2021-9-15 17:08
改成这样编译倒是通过了,但是不知道怎样才能出来运行的结果

你在单片机上用过printf吗?需要配合串口使用的,通过串口打印。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-6-5 19:42 , Processed in 0.483601 second(s), 14 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表