gaoyang9992006 发表于 2025-2-17 15:42:09

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


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


/*************功能说明    **************

本例程基于AI8051U为主控芯片的实验箱V1.1版本进行编写测试。

使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。

单色OLED12864显示屏驱动程序,驱动IC为SSD1306,I2C接口,通过I2C将1024字节的图片数据送到屏幕,传送时不占用CPU时间。

显示图形,汉字,英文,数字.

其中图形显示发送命令和图片数据使用I2C操作,传输数据时不占用CPU时间。做GUI最方便了,可以先操作定义于xdata的1024字节缓存,然后触发SPI DMA即可,523us或943us即可自己动刷完。
本例运行于40MHz, SPI速度为主频4分频(10MHz),每次SPI DMA传输总时间943us,SPI速度为主频2分频(20MHz),每次SPI DMA传输总时间523us。

将要显示的内容放在1024字节的显存中,启动DMA传输即可。

下载时, 选择时钟 40MHz (用户可自行修改频率后重新编译即可).

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


      #include      "AI8051U.h"
      #include      "ASCII6x8.h"
      #include      "HZK16.h"
      #include      "ASCII-10x24.h"
      #include      "picture1.h"
      #include      "picture2.h"

      #include "stdio.h"
      #include "intrins.h"

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

/****************************** 用户定义宏 ***********************************/

#define MAIN_Fosc       40000000UL   //定义主时钟
#define Baudrate      115200L
#define TM            (65536 -(MAIN_Fosc/Baudrate/4))
#define PrintUart       1      //1:printf 使用 UART1; 2:printf 使用 UART2
#define Timer0_Reload   (65536UL -(MAIN_Fosc / 1000))       //Timer 0 中断频率, 1000次/秒


/****************************** IO定义 ***********************************/
/*      定义接口      */
                                                      //GND      AI8051U实验箱 V1.1
                                                      //VCC      3~5V


sbit P_OLED_SCL      =          P3^2;      // II2 的时钟脚
sbit P_OLED_SDA      =   P3^3;      // II2 的数据脚



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

/*************本地常量声明    **************/

#define SLAW      (0x3C<<1)
#define SLAR      (SLAW +1)

/*************本地变量声明    **************/

/*************本地函数声明    **************/

void UartInit(void);


voidLCD_delay_ms(u16 ms)      // 1~65535
{
      u16 i;
      do
      {
                i = MAIN_Fosc / 6000;
                while(--i)      ;
      }while(--ms);
}

/********************** I2C函数 ************************/
void Wait()
{
    while (!(I2CMSST & 0x40));
    I2CMSST &= ~0x40;
}

void Start()
{
    I2CMSCR = 0x01;                         //发送START命令
    Wait();
}

void SendData(char dat)
{
    I2CTXD = dat;                           //写数据到数据缓冲区
    I2CMSCR = 0x02;                         //发送SEND命令
    Wait();
}

void RecvACK()
{
    I2CMSCR = 0x03;                         //发送读ACK命令
    Wait();
}

/*
char RecvData()
{
    I2CMSCR = 0x04;                         //发送RECV命令
    Wait();
    return I2CRXD;
}


void SendACK()
{
    I2CMSST = 0x00;                         //设置ACK信号
    I2CMSCR = 0x05;                         //发送ACK命令
    Wait();
}

void SendNAK()
{
    I2CMSST = 0x01;                         //设置NAK信号
    I2CMSCR = 0x05;                         //发送ACK命令
    Wait();
}
*/

void Stop()
{
    I2CMSCR = 0x06;                         //发送STOP命令
    Wait();
}

//******************************************
void OLED_WriteData(u8 dat)                        //write display data to LCD
{
    Start();                              //发送起始命令
    SendData(SLAW);                         //发送设备地址+写命令
    RecvACK();
    SendData(0x40);                         //设置D/C为1,为数据模式
    RecvACK();
                SendData(dat);
                RecvACK();
    Stop();                                 //发送停止命令
}

//******************************************
void      OLED_WriteCMD(u8 cmd)
{
    Start();                              //发送起始命令
    SendData(SLAW);                         //发送设备地址+写命令
    RecvACK();
    SendData(0x00);                         //设置D/C为0,为指令模式
    RecvACK();
                SendData(cmd);
                RecvACK();
    Stop();                                 //发送停止命令
}

//========================================================================
// 函数: void Set_Dot_Addr_LCD(int x,int y)
// 描述: 设置在LCD的真实坐标系上的X、Y点对应的RAM地址
// 参数: x               X轴坐标
//               y               Y轴坐标
// 返回: 无
// 备注: 仅设置当前操作地址,为后面的连续操作作好准备
// 版本:
//      2007/04/10      First version
//========================================================================
void Set_Dot_Addr(u8 x,u8 y)
{
      OLED_WriteCMD((u8)(0xb0 + y));                //设置页0~7
      OLED_WriteCMD((x >> 4)| 0x10);      //设置列0~127 高nibble
      OLED_WriteCMD(x & 0x0f);                        //设置列0~127 低nibble
}


//******************************************
void FillPage(u8 y,u8 color)                        //Clear Page LCD RAM
{
      u8 j;
      Set_Dot_Addr(0,y);
      for(j=0; j<128; j++)      OLED_WriteData(color);
}

//******************************************
void FillAll(u8 color)                        //Clear CSn LCD RAM
{
      u8 i;
      for(i=0; i<8; i++)      FillPage(i,color);
}



//******************************************
void Initialize_OLED(void)      //initialize OLED
{

      LCD_delay_ms(100);


      OLED_WriteCMD(0xAE);   //Set Display Off

      OLED_WriteCMD(0xd5);   //display divide ratio/osc. freq. mode
      OLED_WriteCMD(0x80);   //

      OLED_WriteCMD(0xA8);   //multiplex ration mode:63
      OLED_WriteCMD(0x3F);

      OLED_WriteCMD(0xD3);   //Set Display Offset
      OLED_WriteCMD(0x00);

      OLED_WriteCMD(0x40);   //Set Display Start Line

      OLED_WriteCMD(0x8D);   //Set Display Offset
      OLED_WriteCMD(0x14);

      OLED_WriteCMD(0xA1);   //Segment Remap

      OLED_WriteCMD(0xC8);   //Sst COM Output Scan Direction

      OLED_WriteCMD(0xDA);   //common pads hardware: alternative
      OLED_WriteCMD(0x12);

      OLED_WriteCMD(0x81);   //contrast control
      OLED_WriteCMD(0xCF);

      OLED_WriteCMD(0xD9);            //set pre-charge period
      OLED_WriteCMD(0xF1);

      OLED_WriteCMD(0xDB);   //VCOM deselect level mode
      OLED_WriteCMD(0x40);            //set Vvcomh=0.83*Vcc

      OLED_WriteCMD(0xA4);   //Set Entire Display On/Off

      OLED_WriteCMD(0xA6);   //Set Normal Display

      OLED_WriteCMD(0xAF);   //Set Display On

      FillAll(0);
}


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

void WriteAscii6x8(u8 x,u8 y, u8 number)
{
      u8 const *p;
      u8 i;

      if(x > (128-5))      return;
      if(y >= 8)      return;
      p = (u16)number * 6 + ASCII6x8;

      Set_Dot_Addr(x,y);
      for(i=0; i<6; i++)
      {
                OLED_WriteData(*p);
                p++;
      }
}

//=====================================================
void WriteHZ16(u8 x, u8 y, u16 hz)      //向指定位置写一个汉字, x为横向的点0~127, y为纵向的页0~7, hz为要写的汉字.
{
      u8 const *p;
      u8 i;

      if(x > (128-16))      return;
      if(y > 6)                        return;
      p = hz * 32 + HZK16;
      Set_Dot_Addr(x, y);
      for(i=0; i<16; i++)                {      OLED_WriteData(*p);      p++;}

      Set_Dot_Addr(x, (u8)(y+1));
      for(i=0; i<16; i++)                {      OLED_WriteData(*p);      p++;}
}



void      printf_ASCII_text(u8 x, u8 y, u8 *ptr)      //最快写入10个ASCII码(10*6+9=69个字节)耗时430us@24MHZ
{
      u8c;

      for (;;)
      {
      c = *ptr;
                if(c == 0)                return;      //遇到停止符0结束
                if(c < 0x80)                        //ASCII码
                {
                        WriteAscii6x8(x,y,c);
                        x += 6;
                }
                ptr++;
      }
}

//******************************************
void WriteAscii_10x24(u8 x, u8 y, u8 chr)      //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
{
      u8 const *p;
      u8 i;

      if(x > (128-10))      return;
      if(y >= 6)                        return;
      p = (u16)chr * 30 + ASCII10x24;

      Set_Dot_Addr(x, y);
      for(i=0; i<10; i++)                {      OLED_WriteData(*p);      p++;      }

      Set_Dot_Addr(x, (u8)(y+1));
      for(i=0; i<10; i++)                {      OLED_WriteData(*p);      p++;      }

      Set_Dot_Addr(x, (u8)(y+2));
      for(i=0; i<10; i++)                {      OLED_WriteData(*p);      p++;      }
}

//******************************************
void WriteDot_3x3(u8 x, u8 y)      //向指定位置写一个小数点, x为横向的点0~127, y为纵向的页0~7
{
      if(x > (128-3))      return;
      if(y >= 8)                return;

      Set_Dot_Addr(x, y);
      OLED_WriteData(0x38);
      OLED_WriteData(0x38);
      OLED_WriteData(0x38);
}


//************ 打印ASCII 10x24英文字符串 *************************
void      printf_ascii_10x24(u8 x, u8 y, u8 const *ptr)      //x为横向的点0~127, y为纵向的页0~7, *ptr为要打印的字符串指针, 间隔2点.
{
    u8 c;

      for (;;)
      {
                if(x > (128-10))      return;
                if(y > 5)                        return;
                c = *ptr;
                if(c == 0)                return;      //遇到停止符0结束
                if((c >= '0') && (c <= '9'))                        //ASCII码
                {
                        WriteAscii_10x24(x,y,(u8)(c-'0'));
                        x += 12;
                }
                else if(c == '.')
                {
                        WriteDot_3x3(x,(u8)(y+2));
                        x += 6;
                }
                else if(c == ' ')      //显示空格
                {
                        WriteAscii_10x24(x,y,11);
                        x += 12;
                }
                else if(c == '-')      //显示空格
                {
                        WriteAscii_10x24(x,y,10);
                        x += 12;
                }
                        ptr++;
      }
}




//====================================================================================
u8      xdata DisTmp;      //显示缓冲,将要显示的内容放在显存里,启动DMA即可. 由于LCM DMA有4字节对齐问题,所以这里定位对地址为4的倍数


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


void main(void)
{
      u16      i;

      EAXFR = 1;      //允许访问扩展寄存器
      WTST= 0;
      CKCON = 0;


      P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
      P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
      P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
      P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
      P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
      P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
      P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
      P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
      
      UartInit();
      
      I2C_S1 =1;      //I2C功能脚选择,00:P2.4,P2.3; 01:P1.5,P1.4; 11:P3.2,P3.3
      I2C_S0 =1;
      I2CCFG = 0xc2;//使能I2C主机模式
      I2CPSCR = 0x00; //MSSPEED
      I2CMSST = 0x00;
      EA = 1;
      Initialize_OLED();
      printf("SSD1306 OLED 128×64 \r\n");   //串口打印测试
      
      while(1)
      {
                for(i=0; i<1024; i++)      DisTmp = 0;      //清除显存


                printf_ASCII_text(0, 0, "OLED12864 SSD1306");
                for(i=0; i<8; i++)      WriteHZ16((u8)(i*16),2,i);
                printf_ascii_10x24(0,5,"-12.345 678");
                LCD_delay_ms(3000);

                for(i=0; i<1024; i++)      DisTmp = gImage_picture1;      //将图片装载到显存

                LCD_delay_ms(3000);

                for(i=0; i<1024; i++)      DisTmp = gImage_picture2;      //将图片装载到显存

                LCD_delay_ms(3000);
      }
}


/******************** 串口打印函数 ********************/
void UartInit(void)
{
#if(PrintUart == 1)
    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
    S1_S0 = 0;
      SCON = (SCON & 0x3f) | 0x40;
      T1x12 = 1;      //定时器时钟1T模式
      S1BRT = 0;      //串口1选择定时器1为波特率发生器
      TL1= TM;
      TH1= TM>>8;
      TR1 = 1;      //定时器1开始计时

//      SCON = (SCON & 0x3f) | 0x40;
//      T2L= TM;
//      T2H= TM>>8;
//      AUXR |= 0x15;   //串口1选择定时器2为波特率发生器
#else
      S2_S = 1;       //UART2 switch to: 0: P1.2 P1.3,1: P4.2 P4.3
    S2CFG |= 0x01;//使用串口2时,W1位必需设置为1,否则可能会产生不可预期的错误
      S2CON = (S2CON & 0x3f) | 0x40;
      T2L= TM;
      T2H= TM>>8;
      AUXR |= 0x14;            //定时器2时钟1T模式,开始计时
#endif
}

void UartPutc(unsigned char dat)
{
#if(PrintUart == 1)
      SBUF = dat;
      while(TI==0);
      TI = 0;
#else
      S2BUF= dat;
      while(S2TI == 0);
      S2TI = 0;    //Clear Tx flag
#endif
}

char putchar(char c)
{
      UartPutc(c);
      return c;
}





gaoyang9992006 发表于 2025-2-17 15:42:27

特此分享进来,方便不知道怎么下手的朋友下手

gaoyang9992006 发表于 2025-2-17 16:33:01

刚才又研究了一下显示图片,写了一个显示图片的函数
//显示图片函数
void OLED_DisplayImage(const u8 *image)
{
    u8 i, j;
    for(i = 0; i < 8; i++)// SSD1306 有 8 页
    {
      Set_Dot_Addr(0, i); // 设置起始地址
      
      for(j = 0; j < 128; j++) // 每页有 128 列
      {
            OLED_WriteData(image); // 逐字节发送图像数据
      }
    }
}
测试效果很棒
void main(void)
{
        u16        i;

        EAXFR = 1;        //允许访问扩展寄存器
        WTST= 0;
        CKCON = 0;


        P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
        P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
        P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
        P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
        P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
        P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
        P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
        P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
       
        UartInit();
       
        I2C_S1 =1;      //I2C功能脚选择,00:P2.4,P2.3; 01:P1.5,P1.4; 11:P3.2,P3.3
        I2C_S0 =1;
        I2CCFG = 0xc2;//使能I2C主机模式
        I2CPSCR = 0x00; //MSSPEED
        I2CMSST = 0x00;
        EA = 1;
        Initialize_OLED();
        printf("SSD1306 OLED 128×64 \r\n");   //串口打印测试
       
        while(1)
        {
                printf_ASCII_text(0, 0, "OLED12864 SSD1306");
                for(i=0; i<8; i++)        WriteHZ16((u8)(i*16),2,i);
                printf_ascii_10x24(0,5,"-12.345 678");
                LCD_delay_ms(3000);
                OLED_DisplayImage(gImage_picture1);
                LCD_delay_ms(3000);
                OLED_DisplayImage(gImage_picture2);
                LCD_delay_ms(3000);
                FillAll(0);

        }
}

lcg 发表于 2025-2-21 17:44:18

{:qiang:}
页: [1]
查看完整版本: AI8051U试验箱数码管驱动学习笔记,自己实现I2C驱动OLED