打印
[STC单片机]

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

[复制链接]
223|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
官方使用的是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);


void  LCD_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
{
        u8  c;

        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[1024];        //显示缓冲,将要显示的内容放在显存里,启动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[13:6]
        I2CMSST = 0x00;
        EA = 1;
        Initialize_OLED();
        printf("SSD1306 OLED 128×64 \r\n");     //串口打印测试
       
        while(1)
        {
                for(i=0; i<1024; i++)        DisTmp[i] = 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[i] = gImage_picture1[i];        //将图片装载到显存

                LCD_delay_ms(3000);

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

                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:49 | 只看该作者
运行的效果

使用特权

评论回复
板凳
STCMCUNT015| | 2025-2-18 08:45 | 只看该作者
高主任威武

使用特权

评论回复
地板
gaoyang9992006|  楼主 | 2025-2-18 08:59 | 只看该作者

谢谢

使用特权

评论回复
5
gaoyang9992006|  楼主 | 2025-2-18 09:00 | 只看该作者
原版的自带的通过DMA显示图片功能,我给补充了一个函数,可以实现,如下:
//显示图片函数
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[i * 128 + j]); // 逐字节发送图像数据
        }
    }
}

使用特权

评论回复
6
gaoyang9992006|  楼主 | 2025-2-18 09:01 | 只看该作者
实现图片的测试代码部分如下:
        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);

        }

使用特权

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

本版积分规则

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

2003

主题

16099

帖子

212

粉丝