[STC单片机] AI8051试验箱数码管驱动学习笔记,自己实现I2C驱动OLED

[复制链接]
 楼主| gaoyang9992006 发表于 2025-2-17 15:48 | 显示全部楼层 |阅读模式
官方使用的是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. }


游客,如果您要查看本帖隐藏内容请回复

工程放附件里了。感兴趣的下载看。

本帖子中包含更多资源

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

×
 楼主| gaoyang9992006 发表于 2025-2-17 15:49 | 显示全部楼层
运行的效果

本帖子中包含更多资源

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

×
STCMCUNT015 发表于 2025-2-18 08:45 | 显示全部楼层
高主任威武
 楼主| gaoyang9992006 发表于 2025-2-18 08:59 | 显示全部楼层
 楼主| gaoyang9992006 发表于 2025-2-18 09:00 | 显示全部楼层
原版的自带的通过DMA显示图片功能,我给补充了一个函数,可以实现,如下:
  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. }
 楼主| gaoyang9992006 发表于 2025-2-18 09:01 | 显示全部楼层
实现图片的测试代码部分如下:
  1.         while(1)
  2.         {
  3.                 printf_ASCII_text(0, 0, "  OLED12864 SSD1306");
  4.                 for(i=0; i<8; i++)        WriteHZ16((u8)(i*16),2,i);
  5.                 printf_ascii_10x24(0,5,"-12.345 678");
  6.                 LCD_delay_ms(3000);
  7.                 OLED_DisplayImage(gImage_picture1);
  8.                 LCD_delay_ms(3000);
  9.                 OLED_DisplayImage(gImage_picture2);
  10.                 LCD_delay_ms(3000);
  11.                 FillAll(0);

  12.         }
laodacxy 发表于 2025-5-21 09:28 | 显示全部楼层
好好好
zy9914202 发表于 2025-7-29 16:46 | 显示全部楼层
这个可以显示动画吗?
STCMCUNT015 发表于 2025-7-29 17:29 | 显示全部楼层
zy9914202 发表于 2025-7-29 16:46
这个可以显示动画吗?

Ai8051U 视频级刷彩屏, 及其他组合效果,不做电视,做智能设备的 TFT显示足够了 - 单片机论坛,单片机技术交流论坛 - 21ic电子技术开**坛  https://bbs.21ic.com/icview-3464698-1-1.html
ys8081 发表于 2025-8-2 11:02 | 显示全部楼层
可以用最新的AiCube-ISP-V6.95Z 及以上版本软件,含 AiCube 图形化开发工具  :
自动生成STC单片机的各功能模块程序,及主程序框架,
含:外部中断,定时器,串行口,延时子程序,
       ADC, I2C/SPI, TFT-接口,DMA,DMA-P2P, ...
最新的 AiCube-ISP 从 STCAI 官 方 网站下载即可
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:如果你觉得我的分享或者答复还可以,请给我点赞,谢谢。

2045

主题

16350

帖子

222

粉丝
快速回复 在线客服 返回列表 返回顶部