找回密码
 立即注册
查看: 319|回复: 3

AI8051U试验箱数码管驱动学习笔记,自己实现I2C驱动OLED

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2025-04-29 11:42:39

11

主题

18

回帖

157

积分

注册会员

积分
157
发表于 2025-2-17 15:42:09 | 显示全部楼层 |阅读模式

AI8051U试验箱数码管驱动学习笔记,自己实现I2C驱动OLED
官方使用的是SPI驱动的SSD1306的OLED屏幕示例,于是我琢磨了一下,参考I2C驱动EEPROM的示例写基础函数, 一次点亮了屏幕

  1. /*************  功能说明    **************
  2. 本例程基于AI8051U为主控芯片的实验箱V1.1版本进行编写测试。
  3. 使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。
  4. edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。
  5. 单色OLED12864显示屏驱动程序,驱动IC为SSD1306,I2C接口,通过I2C将1024字节的图片数据送到屏幕,传送时不占用CPU时间。
  6. 显示图形,汉字,英文,数字.
  7. 其中图形显示发送命令和图片数据使用I2C操作,传输数据时不占用CPU时间。做GUI最方便了,可以先操作定义于xdata的1024字节缓存,然后触发SPI DMA即可,523us或943us即可自己动刷完。
  8. 本例运行于40MHz, SPI速度为主频4分频(10MHz),每次SPI DMA传输总时间943us,SPI速度为主频2分频(20MHz),每次SPI DMA传输总时间523us。
  9. 将要显示的内容放在1024字节的显存中,启动DMA传输即可。
  10. 下载时, 选择时钟 40MHz (用户可自行修改频率后重新编译即可).
  11. ******************************************/
  12.         #include        "AI8051U.h"
  13.         #include        "ASCII6x8.h"
  14.         #include        "HZK16.h"
  15.         #include        "ASCII-10x24.h"
  16.         #include        "picture1.h"
  17.         #include        "picture2.h"
  18.         #include "stdio.h"
  19.         #include "intrins.h"
  20. //************************************************************************************************
  21. /****************************** 用户定义宏 ***********************************/
  22. #define MAIN_Fosc       40000000UL   //定义主时钟
  23. #define Baudrate        115200L
  24. #define TM              (65536 -(MAIN_Fosc/Baudrate/4))
  25. #define PrintUart       1        //1:printf 使用 UART1; 2:printf 使用 UART2
  26. #define Timer0_Reload   (65536UL -(MAIN_Fosc / 1000))       //Timer 0 中断频率, 1000次/秒
  27. /****************************** IO定义 ***********************************/
  28. /*        定义接口        */
  29.                                                         //GND        AI8051U实验箱 V1.1
  30.                                                         //VCC        3~5V
  31. sbit P_OLED_SCL        =          P3^2;        // II2 的时钟脚
  32. sbit P_OLED_SDA        =   P3^3;        // II2 的数据脚
  33. /*****************************************************************************/
  34. /*************  本地常量声明    **************/
  35. #define SLAW        (0x3C<<1)
  36. #define SLAR        (SLAW +1)
  37. /*************  本地变量声明    **************/
  38. /*************  本地函数声明    **************/
  39. void UartInit(void);
  40. void  LCD_delay_ms(u16 ms)        // 1~65535
  41. {
  42.         u16 i;
  43.         do
  44.         {
  45.                 i = MAIN_Fosc / 6000;
  46.                 while(--i)        ;
  47.         }while(--ms);
  48. }
  49. /********************** I2C函数 ************************/
  50. void Wait()
  51. {
  52.     while (!(I2CMSST & 0x40));
  53.     I2CMSST &= ~0x40;
  54. }
  55. void Start()
  56. {
  57.     I2CMSCR = 0x01;                         //发送START命令
  58.     Wait();
  59. }
  60. void SendData(char dat)
  61. {
  62.     I2CTXD = dat;                           //写数据到数据缓冲区
  63.     I2CMSCR = 0x02;                         //发送SEND命令
  64.     Wait();
  65. }
  66. void RecvACK()
  67. {
  68.     I2CMSCR = 0x03;                         //发送读ACK命令
  69.     Wait();
  70. }
  71. /*
  72. char RecvData()
  73. {
  74.     I2CMSCR = 0x04;                         //发送RECV命令
  75.     Wait();
  76.     return I2CRXD;
  77. }
  78. void SendACK()
  79. {
  80.     I2CMSST = 0x00;                         //设置ACK信号
  81.     I2CMSCR = 0x05;                         //发送ACK命令
  82.     Wait();
  83. }
  84. void SendNAK()
  85. {
  86.     I2CMSST = 0x01;                         //设置NAK信号
  87.     I2CMSCR = 0x05;                         //发送ACK命令
  88.     Wait();
  89. }
  90. */
  91. void Stop()
  92. {
  93.     I2CMSCR = 0x06;                         //发送STOP命令
  94.     Wait();
  95. }
  96. //******************************************
  97. void OLED_WriteData(u8 dat)                        //write display data to LCD
  98. {
  99.     Start();                                //发送起始命令
  100.     SendData(SLAW);                         //发送设备地址+写命令
  101.     RecvACK();
  102.     SendData(0x40);                         //设置D/C为1,为数据模式
  103.     RecvACK();
  104.                 SendData(dat);
  105.                 RecvACK();
  106.     Stop();                                 //发送停止命令
  107. }
  108. //******************************************
  109. void        OLED_WriteCMD(u8 cmd)
  110. {
  111.     Start();                                //发送起始命令
  112.     SendData(SLAW);                         //发送设备地址+写命令
  113.     RecvACK();
  114.     SendData(0x00);                         //设置D/C为0,为指令模式
  115.     RecvACK();
  116.                 SendData(cmd);
  117.                 RecvACK();
  118.     Stop();                                 //发送停止命令
  119. }
  120. //========================================================================
  121. // 函数: void Set_Dot_Addr_LCD(int x,int y)
  122. // 描述: 设置在LCD的真实坐标系上的X、Y点对应的RAM地址
  123. // 参数: x                 X轴坐标
  124. //                 y                 Y轴坐标
  125. // 返回: 无
  126. // 备注: 仅设置当前操作地址,为后面的连续操作作好准备
  127. // 版本:
  128. //      2007/04/10      First version
  129. //========================================================================
  130. void Set_Dot_Addr(u8 x,u8 y)
  131. {
  132.         OLED_WriteCMD((u8)(0xb0 + y));                //设置页0~7
  133.         OLED_WriteCMD((x >> 4)  | 0x10);        //设置列0~127 高nibble
  134.         OLED_WriteCMD(x & 0x0f);                        //设置列0~127 低nibble
  135. }
  136. //******************************************
  137. void FillPage(u8 y,u8 color)                        //Clear Page LCD RAM
  138. {
  139.         u8 j;
  140.         Set_Dot_Addr(0,y);
  141.         for(j=0; j<128; j++)        OLED_WriteData(color);
  142. }
  143. //******************************************
  144. void FillAll(u8 color)                        //Clear CSn LCD RAM
  145. {
  146.         u8 i;
  147.         for(i=0; i<8; i++)        FillPage(i,color);
  148. }
  149. //******************************************
  150. void Initialize_OLED(void)        //initialize OLED
  151. {
  152.         LCD_delay_ms(100);
  153.         OLED_WriteCMD(0xAE);     //Set Display Off
  154.         OLED_WriteCMD(0xd5);     //display divide ratio/osc. freq. mode
  155.         OLED_WriteCMD(0x80);     //
  156.         OLED_WriteCMD(0xA8);     //multiplex ration mode:63
  157.         OLED_WriteCMD(0x3F);
  158.         OLED_WriteCMD(0xD3);     //Set Display Offset
  159.         OLED_WriteCMD(0x00);
  160.         OLED_WriteCMD(0x40);     //Set Display Start Line
  161.         OLED_WriteCMD(0x8D);     //Set Display Offset
  162.         OLED_WriteCMD(0x14);
  163.         OLED_WriteCMD(0xA1);     //Segment Remap
  164.         OLED_WriteCMD(0xC8);     //Sst COM Output Scan Direction
  165.         OLED_WriteCMD(0xDA);     //common pads hardware: alternative
  166.         OLED_WriteCMD(0x12);
  167.         OLED_WriteCMD(0x81);     //contrast control
  168.         OLED_WriteCMD(0xCF);
  169.         OLED_WriteCMD(0xD9);            //set pre-charge period
  170.         OLED_WriteCMD(0xF1);
  171.         OLED_WriteCMD(0xDB);     //VCOM deselect level mode
  172.         OLED_WriteCMD(0x40);            //set Vvcomh=0.83*Vcc
  173.         OLED_WriteCMD(0xA4);     //Set Entire Display On/Off
  174.         OLED_WriteCMD(0xA6);     //Set Normal Display
  175.         OLED_WriteCMD(0xAF);     //Set Display On
  176.         FillAll(0);
  177. }
  178. //******************************************
  179. void WriteAscii6x8(u8 x,u8 y, u8 number)
  180. {
  181.         u8 const *p;
  182.         u8 i;
  183.         if(x > (128-5))        return;
  184.         if(y >= 8)        return;
  185.         p = (u16)number * 6 + ASCII6x8;
  186.         Set_Dot_Addr(x,y);
  187.         for(i=0; i<6; i++)
  188.         {
  189.                 OLED_WriteData(*p);
  190.                 p++;
  191.         }
  192. }
  193. //=====================================================
  194. void WriteHZ16(u8 x, u8 y, u16 hz)        //向指定位置写一个汉字, x为横向的点0~127, y为纵向的页0~7, hz为要写的汉字.
  195. {
  196.         u8 const *p;
  197.         u8 i;
  198.         if(x > (128-16))        return;
  199.         if(y > 6)                        return;
  200.         p = hz * 32 + HZK16;
  201.         Set_Dot_Addr(x, y);
  202.         for(i=0; i<16; i++)                {        OLED_WriteData(*p);        p++;}
  203.         Set_Dot_Addr(x, (u8)(y+1));
  204.         for(i=0; i<16; i++)                {        OLED_WriteData(*p);        p++;}
  205. }
  206. void        printf_ASCII_text(u8 x, u8 y, u8 *ptr)        //最快写入10个ASCII码(10*6+9=69个字节)耗时430us@24MHZ
  207. {
  208.         u8  c;
  209.         for (;;)
  210.         {
  211.         c = *ptr;
  212.                 if(c == 0)                return;        //遇到停止符0结束
  213.                 if(c < 0x80)                        //ASCII码
  214.                 {
  215.                         WriteAscii6x8(x,y,c);
  216.                         x += 6;
  217.                 }
  218.                 ptr++;
  219.         }
  220. }
  221. //******************************************
  222. void WriteAscii_10x24(u8 x, u8 y, u8 chr)        //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
  223. {
  224.         u8 const *p;
  225.         u8 i;
  226.         if(x > (128-10))        return;
  227.         if(y >= 6)                        return;
  228.         p = (u16)chr * 30 + ASCII10x24;
  229.         Set_Dot_Addr(x, y);
  230.         for(i=0; i<10; i++)                {        OLED_WriteData(*p);        p++;        }
  231.         Set_Dot_Addr(x, (u8)(y+1));
  232.         for(i=0; i<10; i++)                {        OLED_WriteData(*p);        p++;        }
  233.         Set_Dot_Addr(x, (u8)(y+2));
  234.         for(i=0; i<10; i++)                {        OLED_WriteData(*p);        p++;        }
  235. }
  236. //******************************************
  237. void WriteDot_3x3(u8 x, u8 y)        //向指定位置写一个小数点, x为横向的点0~127, y为纵向的页0~7
  238. {
  239.         if(x > (128-3))        return;
  240.         if(y >= 8)                return;
  241.         Set_Dot_Addr(x, y);
  242.         OLED_WriteData(0x38);
  243.         OLED_WriteData(0x38);
  244.         OLED_WriteData(0x38);
  245. }
  246. //************ 打印ASCII 10x24英文字符串 *************************
  247. void        printf_ascii_10x24(u8 x, u8 y, u8 const *ptr)        //x为横向的点0~127, y为纵向的页0~7, *ptr为要打印的字符串指针, 间隔2点.
  248. {
  249.     u8 c;
  250.         for (;;)
  251.         {
  252.                 if(x > (128-10))        return;
  253.                 if(y > 5)                        return;
  254.                 c = *ptr;
  255.                 if(c == 0)                return;        //遇到停止符0结束
  256.                 if((c >= '0') && (c <= '9'))                        //ASCII码
  257.                 {
  258.                         WriteAscii_10x24(x,y,(u8)(c-'0'));
  259.                         x += 12;
  260.                 }
  261.                 else if(c == '.')
  262.                 {
  263.                         WriteDot_3x3(x,(u8)(y+2));
  264.                         x += 6;
  265.                 }
  266.                 else if(c == ' ')        //显示空格
  267.                 {
  268.                         WriteAscii_10x24(x,y,11);
  269.                         x += 12;
  270.                 }
  271.                 else if(c == '-')        //显示空格
  272.                 {
  273.                         WriteAscii_10x24(x,y,10);
  274.                         x += 12;
  275.                 }
  276.                         ptr++;
  277.         }
  278. }
  279. //====================================================================================
  280. u8        xdata DisTmp[1024];        //显示缓冲,将要显示的内容放在显存里,启动DMA即可. 由于LCM DMA有4字节对齐问题,所以这里定位对地址为4的倍数
  281. //******************************************
  282. void main(void)
  283. {
  284.         u16        i;
  285.         EAXFR = 1;        //允许访问扩展寄存器
  286.         WTST  = 0;
  287.         CKCON = 0;
  288.         P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  289.         P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  290.         P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  291.         P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  292.         P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  293.         P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  294.         P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  295.         P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  296.         
  297.         UartInit();
  298.         
  299.         I2C_S1 =1;      //I2C功能脚选择,00:P2.4,P2.3; 01:P1.5,P1.4; 11:P3.2,P3.3
  300.         I2C_S0 =1;
  301.         I2CCFG = 0xc2;  //使能I2C主机模式
  302.         I2CPSCR = 0x00; //MSSPEED[13:6]
  303.         I2CMSST = 0x00;
  304.         EA = 1;
  305.         Initialize_OLED();
  306.         printf("SSD1306 OLED 128×64 \r\n");     //串口打印测试
  307.         
  308.         while(1)
  309.         {
  310.                 for(i=0; i<1024; i++)        DisTmp[i] = 0;        //清除显存
  311.                 printf_ASCII_text(0, 0, "  OLED12864 SSD1306");
  312.                 for(i=0; i<8; i++)        WriteHZ16((u8)(i*16),2,i);
  313.                 printf_ascii_10x24(0,5,"-12.345 678");
  314.                 LCD_delay_ms(3000);
  315.                 for(i=0; i<1024; i++)        DisTmp[i] = gImage_picture1[i];        //将图片装载到显存
  316.                 LCD_delay_ms(3000);
  317.                 for(i=0; i<1024; i++)        DisTmp[i] = gImage_picture2[i];        //将图片装载到显存
  318.                 LCD_delay_ms(3000);
  319.         }
  320. }
  321. /******************** 串口打印函数 ********************/
  322. void UartInit(void)
  323. {
  324. #if(PrintUart == 1)
  325.     S1_S1 = 0;      //UART1 switch to, 0x00: P3.0 P3.1, 0x40: P3.6 P3.7, 0x80: P1.6 P1.7, 0xC0: P4.3 P4.4
  326.     S1_S0 = 0;
  327.         SCON = (SCON & 0x3f) | 0x40;
  328.         T1x12 = 1;      //定时器时钟1T模式
  329.         S1BRT = 0;      //串口1选择定时器1为波特率发生器
  330.         TL1  = TM;
  331.         TH1  = TM>>8;
  332.         TR1 = 1;        //定时器1开始计时
  333. //        SCON = (SCON & 0x3f) | 0x40;
  334. //        T2L  = TM;
  335. //        T2H  = TM>>8;
  336. //        AUXR |= 0x15;   //串口1选择定时器2为波特率发生器
  337. #else
  338.         S2_S = 1;       //UART2 switch to: 0: P1.2 P1.3,  1: P4.2 P4.3
  339.     S2CFG |= 0x01;  //使用串口2时,W1位必需设置为1,否则可能会产生不可预期的错误
  340.         S2CON = (S2CON & 0x3f) | 0x40;
  341.         T2L  = TM;
  342.         T2H  = TM>>8;
  343.         AUXR |= 0x14;              //定时器2时钟1T模式,开始计时
  344. #endif
  345. }
  346. void UartPutc(unsigned char dat)
  347. {
  348. #if(PrintUart == 1)
  349.         SBUF = dat;
  350.         while(TI==0);
  351.         TI = 0;
  352. #else
  353.         S2BUF  = dat;
  354.         while(S2TI == 0);
  355.         S2TI = 0;    //Clear Tx flag
  356. #endif
  357. }
  358. char putchar(char c)
  359. {
  360.         UartPutc(c);
  361.         return c;
  362. }
复制代码

截图202502171542046501.jpg

1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2025-04-29 11:42:39

11

主题

18

回帖

157

积分

注册会员

积分
157
发表于 2025-2-17 15:42:27 | 显示全部楼层
特此分享进来,方便不知道怎么下手的朋友下手
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2025-04-29 11:42:39

11

主题

18

回帖

157

积分

注册会员

积分
157
发表于 2025-2-17 16:33:01 | 显示全部楼层
刚才又研究了一下显示图片,写了一个显示图片的函数
  1. //显示图片函数
  2. void OLED_DisplayImage(const u8 *image)
  3. {
  4.     u8 i, j;
  5.     for(i = 0; i < 8; i++)  // SSD1306 有 8 页
  6.     {
  7.         Set_Dot_Addr(0, i); // 设置起始地址
  8.         
  9.         for(j = 0; j < 128; j++) // 每页有 128 列
  10.         {
  11.             OLED_WriteData(image[i * 128 + j]); // 逐字节发送图像数据
  12.         }
  13.     }
  14. }
复制代码

测试效果很棒
  1. void main(void)
  2. {
  3.         u16        i;
  4.         EAXFR = 1;        //允许访问扩展寄存器
  5.         WTST  = 0;
  6.         CKCON = 0;
  7.         P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  8.         P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  9.         P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  10.         P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  11.         P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  12.         P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  13.         P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  14.         P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  15.        
  16.         UartInit();
  17.        
  18.         I2C_S1 =1;      //I2C功能脚选择,00:P2.4,P2.3; 01:P1.5,P1.4; 11:P3.2,P3.3
  19.         I2C_S0 =1;
  20.         I2CCFG = 0xc2;  //使能I2C主机模式
  21.         I2CPSCR = 0x00; //MSSPEED[13:6]
  22.         I2CMSST = 0x00;
  23.         EA = 1;
  24.         Initialize_OLED();
  25.         printf("SSD1306 OLED 128×64 \r\n");     //串口打印测试
  26.        
  27.         while(1)
  28.         {
  29.                 printf_ASCII_text(0, 0, "  OLED12864 SSD1306");
  30.                 for(i=0; i<8; i++)        WriteHZ16((u8)(i*16),2,i);
  31.                 printf_ascii_10x24(0,5,"-12.345 678");
  32.                 LCD_delay_ms(3000);
  33.                 OLED_DisplayImage(gImage_picture1);
  34.                 LCD_delay_ms(3000);
  35.                 OLED_DisplayImage(gImage_picture2);
  36.                 LCD_delay_ms(3000);
  37.                 FillAll(0);
  38.         }
  39. }
复制代码
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:430
  • 最近打卡:2025-05-02 05:41:25
已绑定手机

0

主题

43

回帖

503

积分

高级会员

积分
503
发表于 2025-2-21 17:44:18 | 显示全部楼层
回复

使用道具 举报 送花

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

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 06:57 , Processed in 0.211408 second(s), 69 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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