请选择 进入手机版 | 继续访问电脑版

 找回密码
 立即注册
查看: 1941|回复: 50

STC32G驱动OLED12864,用普通IO模拟SPI或硬件SPI接口方式+双页显示

[复制链接]
  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 197 天

    [LV.7]常住居民III

    11

    主题

    126

    回帖

    956

    积分

    高级会员

    积分
    956
    发表于 2023-12-25 16:27:36 | 显示全部楼层 |阅读模式
    STC32G驱动OLED12864,
    1, 普通I/O模拟SPI
    2, 硬件SPI接口方式+双页显示

    使用普通IO模拟SPI接口方式来驱动OLED(SSD1306)显示屏,可以不必研究STC有关SPI寄存器的定义和使用方法,只要根据实际应用电路,定义引脚和显示方向即可。
    因此使用非常简单、移植方便,适用于各种CPU型号的单片机。


    先以屠龙刀三核心实验板为例, 后以STC32G12K128_V9.62实验箱为例, 展示驱动显示的实际效果, 如图所示:
    屠龙刀三驱动OLED:

    微信图片_20231225100744.jpg

    微信图片_20231225100734.jpg

    V9.62实验箱驱动OLED: (设置显示方向与屠龙刀三相反)

    微信图片_20231225100802.jpg

    这两者区别仅仅在于I/O接口和显示方向的定义不同, 其余完全相同.



    演示主程序源码(屠龙刀三): 各函数功能测试见注释说明。

    //********************************************************************************
    // 名称: Main.C
    // 基于: STC32G12K128 V9.62 实验箱  / 屠龙刀三核心实验板
    // 实验: OLED 12864 显示屏 SSD1306 驱动
    // 编程: 浦晓明(浦江一水) 2023-12-16
    //********************************************************************************
    #include "STC32G_SYS.H"
    #include "SSD1306.H"
    #include "pic.h"


    /** 全局变量说明 设为全局,便于调试观察... **************/
    //========================================================================
    // 函数: void  delay_ms(unsigned int ms)
    // 描述: 毫秒级延时函数。
    // 参数: ms,要延时的ms数,自动适应主时钟.
    //=====================================================================
    void  delay_ms(unsigned int ms)
    { unsigned int i;
      do{ i = MAIN_Fosc / 6000;
          while(--i);
        } while(--ms);
    }
    /**** 主函数入口 ************************/
    void main(void)
    {
      SYS_Init();                   //系统初始化   
      OLED_Init();                 //初始化
      OLED_CLS();                //清屏
      OLED_Light(0);             //最低亮度(实际暗亮,不灭)
      OLED_Light(0xFF);        //最高亮度
      OLED_Light(0xCF);        //初始亮度(较最高略暗)
      OLED_HZ16(10,1,"单",0,1);  //测试汉字。 文本显示: X水平坐标0..127点(列) Y垂直坐标0..7页(行)
      OLED_HZ16(26,1,"片",0,1);
      OLED_HZ16(42,1,"机",0,1);
      OLED_A16(60,1,'O',0,1);     //8*16字符
      OLED_A08(68,2,'k',0,1);      //5*7字符
      OLED_String(10,4,"ABCDEFG",0,1);  //测试字符串...
      OLED_Str5x7(10,6,"0123456789ABCD",0,1);
      delay_ms(1000);
      //主循环...
      while(1)
      { OLED_BMP(0,0,128,8,OLED12864_IMG0[0],1); //显示BMP图片   图形显示: X水平坐标0..127点(列) Y垂直坐标0..63点(行)
        delay_ms(2000);
        OLED_BMP(0,0,128,8,OLED12864_IMG1[0],0);
        OLED_String(0,0,"STC32",0,1);                        //在图上叠加文字...
        delay_ms(2000);
        OLED_CLS();                                                  //清屏
        OLED_String(5,1,"单片机STC32G",0,1);            //中西文混合显示
        OLED_String(5,3,"单片机STC32G",1,1);            //反显
        OLED_Str5x7(16,6,"STC32G12K128",0,1);        //正显  
        OLED_Str5x7(16,7,"STC32G12K128",1,1);        //反显
        delay_ms(2000);
        OLED_CLS();                                                  //清屏
        OLED_LineH(10,10,108,0);                              //画一水平线(不刷新显示)
        OLED_LineV(10,10,44,0);                                //画一垂直线
        OLED_Line(10,10,118,54,0);                           //两点一线 不同方向画线...
        OLED_Line(10,54,118,10,0);                           //两点一线
        OLED_Line(118,54,118,10,0);                         //两点一线
        OLED_Line(118,54,10,54,0);                          //两点一线
        OLED_Box(0,0,127,63,1);                              //画一方框(立即刷新显示)
        OLED_Circle(64,32,30,1);                              //画一个圆(立即刷新显示)
        delay_ms(2000);
      }  
    }

    工程文件0错误0警告打包,供坛友参考和验证:
    10-TestOLED(STC32G屠龙刀三)A.rar (63.31 KB, 下载次数: 100)
    10-TestOLED(STC32G实验箱V9.62)A.rar (54.08 KB, 下载次数: 72)

    可选用普通I/O模拟SPI或硬件SPI接口方式:


    在SSD1306.C文件中 选择方式定义... 重新编译即可...



    #define UseSPI    1     //0: IO模拟SPI;  1: 使用硬件SPI

    实验双页显示 (见6#楼)






    10-TestOLED(STC32G实验箱V9.62) .rar

    54.1 KB, 下载次数: 46

    10-TestOLED(STC32G屠龙刀三) .rar

    63.3 KB, 下载次数: 55

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

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 197 天

    [LV.7]常住居民III

    11

    主题

    126

    回帖

    956

    积分

    高级会员

    积分
    956
     楼主| 发表于 2023-12-25 16:58:25 | 显示全部楼层
    本帖最后由 浦江一水 于 2023-12-27 10:12 编辑

    有关OLED屏的驱动文件就两个:
    SSD1306.H
    SSD1306.C

    尽可能独立成章,减少交叉包含引用,便于移植。在工程中添加挂上就能用。

    几点思考与说明:
    1,由于采用内存映射显示缓存的方法,占用内存128*64=1024字节,所有显示函数实际是先对内存映射区操作,然后整体刷新屏幕(一次传输1024字节)完成显示。
    考虑合理减少传输占时,在相关操作函数的调用时,增加了mode形参,当mode=1时, 立即刷新屏幕显示, 当mode=0时,仅完成对缓存的操作, 而不立即刷新屏幕显示。
    使用时可根据需要来选择是否立即刷行, 这样可相对提高显示效率,同时也简化源码书写,避免大量显式地使用OLED_Show()之类的刷新函数。
    2,该12864单色显示屏,采用一字节表达纵向8点(下高位)像素的方法。因此,高度64点分为8页(行)显示。故在函数涉及时,文本和图形采用不同的坐标方式:
    文本显示时,X为水平坐标,0..127点(列)  Y为垂直坐标,0..7页(行)。
    图形显示时,X为水平坐标,0..127点(列)  Y为垂直坐标,0..63点(行)。
    (当然文本显示也是可以做成以点为行,即0..63行的,就是显示效率低些。本文只是Demo,仅实现简单应用。)
    3,显示屏的字库和图片数据为外挂文件。
    ASCII.H 包含 6*8点阵和8*16 点阵西文字符。可根据需要扩展。
    HZK16.H 用户需要扩展制作的16点阵小汉字库,可根据需要扩展。目前只包含“单片机”三字。然而构成方法及书写规则已体现。

    SSD1306.H tp 头文件 (工程包里有)

    //********************************************************************************
    // 文件名称: SSD1306.H 显示屏头文件
    // 基于电路: STC32G12K128 V9.62 实验箱
    // 实验功能: 驱动7针 OLED 12864 单色显示屏
    // 整理编程: 浦晓明(浦江一水) 2023-12-22
    // 端口定义: 7针排列..
    //   1.GND ------- 地线
    //   2.VCC ------- 电源 3.3-5V
    //   3.SCK -- P2.5 时钟端
    //   4.SDA -- P2.3 数据端 (MOSI)
    //   5.RES -- P2.4 复位端
    //   6.DC  -- P3.4 数据/命令切换
    //   7.CS  -- P1.1 片选端
    //********************************************************************************
    #ifndef __SSD1306_H
    #define __SSD1306_H


    #include "STC32G.H"
    #include <string.h>
    #include <stdlib.h>


    #include "STC32G_SYS.H"


    /*  OLED Pixel */
    #define WIDTH 128
    #define HEIGHT 64
    #define PAGES   8


    /*  OLED Brightness */
    #define BRIGHTNESS_MIN 1
    #define BRIGHTNESS_MAX 25


    /*  OLED Driver */
    void OLED_Init(void);                                      //初始化
    void OLED_Show(void);                                    //显示(缓存数据刷屏)
    void OLED_Disp(unsigned char On);                  //开关显示
    void OLED_CLS(void);                                      //清屏
    void OLED_Light(unsigned char Level);              //亮度设置
    void OLED_SetXY(unsigned char X,unsigned char Y);             //设定坐标
    void OLED_Point(unsigned char X,unsigned char Y,u8 mode); //画一个点
    void OLED_LineH(unsigned char X,unsigned char Y,unsigned char W,u8 mode);  //画一条水平线
    void OLED_LineV(unsigned char X,unsigned char Y,unsigned char H,u8 mode);  //画一条垂直线
    void OLED_Line( unsigned char X0, unsigned char Y0, unsigned char X1,unsigned char Y1,u8 mode);
    void OLED_Box(unsigned char X,unsigned char Y,unsigned char W,unsigned char H,u8 mode);  //画一个方框
    void OLED_Circle(unsigned char X, unsigned char Y, unsigned char r,u8 mode);                      //画一个圆
    void OLED_BMP(unsigned char X,unsigned char Y,unsigned char W,unsigned char H,unsigned char BMP[],u8 mode);
    void OLED_A08(unsigned char X,unsigned char Y,char Ch, u8 turn,u8 mode);    //OLED显示一个6*8字符
    void OLED_A16(unsigned char X,unsigned char Y,char Ch, u8 turn,u8 mode);    //OLED显示一个8*16字符
    void OLED_HZ16(unsigned char X,unsigned char Y,char *Hz, u8 turn,u8 mode); //OLED显示一个16*16汉字
    void OLED_Str5x7(unsigned char X,unsigned char Y,char *s,u8 turn,u8 mode);  //OLED显示6*8点阵字符串
    void OLED_String(unsigned char X,unsigned char Y,char *s,u8 turn,u8 mode);   //OLED显示16点阵高字符串


    #endif

    SSD1306.C    各函数的实现文件, 算法参考STC论坛资料及众高手坛友算法, 重新简化整理改编。

    //********************************************************************************
    // 文件名称: SSD1306.C  
    // 基于电路: STC32G12K128 V9.62 实验箱  /  屠龙刀三 核心板
    // 实验功能: 驱动7针 OLED 12864 单色显示屏 (模拟IO方式)
    // 整理编程: 浦晓明(浦江一水) 2023-12-22
    // 端口定义: 7针排列..
    //   1.GND ------- 地线
    //   2.VCC ------- 电源 3.3-5V        / 屠龙刀三引脚定义
    //   3.SCK -- P2.5 时钟端
    //   4.SDA -- P2.3 数据端 (MOSI)
    //   5.RES -- P2.4 复位端              P2.0 复位端                       
    //   6.DC  -- P3.4 数据/命令切换       P2.1 数据/命令切换
    //   7.CS  -- P1.1 片选端              P2.2 片选端
    //********************************************************************************
    #include "SSD1306.H"
    #include "ASCII.H"
    #include "HZK16.H"


    //OLED屏引脚定义
    sbit OLED_SCK = P2^5;   //时钟
    sbit OLED_SDA = P2^3;   //数据
    //sbit OLED_RES = P2^4;   //复位 //for STC32G12K128 实验箱V9.62
    //sbit OLED_DC  = P3^4;   //数令
    //sbit OLED_CS  = P1^1;   //片选
    sbit OLED_RES = P2^0;   //复位   //for 屠龙刀三
    sbit OLED_DC  = P2^1;   //数令
    sbit OLED_CS  = P2^2;   //片选

    unsigned char xdata ShowBuf[8][128]; //OLED全局缓存

    //========================================================================
    // 函数名称: delayms(unsigned int ms) 毫秒级延时函数
    // 函数功能: OLED驱动用的延时
    //========================================================================
    void delayms(unsigned int ms)
    { unsigned int xdata i;
      do
      { i = MAIN_Fosc / 6000;
        while(--i);   //6T per loop 每循环6指令周期
      } while(--ms);
    }
    //========================================================================
    // 函数名称: OLED_WR_CMD(unsigned char Cmd)  
    // 函数功能: OLED_写命令字
    //========================================================================
    void OLED_WR_CMD(unsigned char Cmd)
    { unsigned char i;
      OLED_SCK= 0;    //时钟低;
      OLED_DC = 0;    //写命令
      OLED_CS = 0;    //片选中
      for(i=0;i<8;i++)
      { if(Cmd&0x80)OLED_SDA=1;
        else        OLED_SDA=0;
        Cmd <<= 1;    //左移位
        OLED_SCK = 1;
        OLED_SCK = 0;
      }
      OLED_CS = 1;    //片不选
    }
    //========================================================================
    // 函数名称: OLED_WR_DAT(unsigned char Dat)  
    // 函数功能: OLED_写数据字
    //========================================================================
    void OLED_WR_DAT(unsigned char Dat)
    { unsigned char i;
      OLED_SCK= 0;    //时钟低;
      OLED_DC = 1;    //写数据
      OLED_CS = 0;    //片选中
      for(i=0;i<8;i++)
      { if(Dat&0x80)OLED_SDA=1;
        else        OLED_SDA=0;
        Dat <<= 1;    //左移位
        OLED_SCK = 1;
        OLED_SCK = 0;
      }
      OLED_CS = 1;    //片不选
    }
    //========================================================================
    // 函数名称: OLED_Disp(unsigned char OnOff)
    // 函数功能: OLED开关显示 参数: 1:开 0:关
    //========================================================================
    void OLED_Disp(unsigned char On)  //开关显示
    {
      if(On==1)
      { OLED_WR_CMD(0x8D); //电荷泵使能
        OLED_WR_CMD(0x14); //开启电荷泵
        OLED_WR_CMD(0xAF); //点亮屏幕
      } else
      { OLED_WR_CMD(0x8D); //电荷泵使能
        OLED_WR_CMD(0x10); //关闭电荷泵
        OLED_WR_CMD(0xAF); //关闭屏幕        
      }
    }
    //========================================================================
    // 函数名称: OLED_Init() 显示屏初始化函数
    //========================================================================
    void OLED_Init(void)
    {
      OLED_SCK = 0;                       //时钟端低
      OLED_SDA = 1; delayms(100); //数据端高
      OLED_RES = 1; delayms(200);
      OLED_RES = 0; delayms(200); //复位
      OLED_RES = 1;

      OLED_WR_CMD(0xAE);//--turn off oled panel
      OLED_WR_CMD(0x00);//--set low column address
      OLED_WR_CMD(0x10);//--set high column address
      OLED_WR_CMD(0x40);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
      OLED_WR_CMD(0x81);//--set contrast control register
      OLED_WR_CMD(0xCF);//--Set SEG Output Current Brightness 亮度...
    //OLED_WR_CMD(0xA0);//--Set SEG/Column Mapping     0xA0左右反转 0xA1正常
    //OLED_WR_CMD(0xC0);//--Set COM/Row Scan Direction 0xC0上下反转 0xC8正常
      OLED_WR_CMD(0xA1);//--Set SEG/Column Mapping     0xA0左右反转 0xA1正常
      OLED_WR_CMD(0xC8);//--Set COM/Row Scan Direction 0xC0上下反转 0xC8正常
      OLED_WR_CMD(0xA6);//--set normal display
      OLED_WR_CMD(0xA8);//--set multiplex ratio(1 to 64)
      OLED_WR_CMD(0x3f);//--1/64 duty
      OLED_WR_CMD(0xD3);//--set display offset        Shift Mapping RAM Counter (0x00~0x3F)
      OLED_WR_CMD(0x00);//--not ofset
      OLED_WR_CMD(0xd5);//--set display clock divide ratio/oscillator frequency
      OLED_WR_CMD(0x80);//--set divide ratio, Set Clock as 100 Frames/Sec
      OLED_WR_CMD(0xD9);//--set pre-charge period
      OLED_WR_CMD(0xF1);//--Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
      OLED_WR_CMD(0xDA);//--set com pins hardware configuration
      OLED_WR_CMD(0x12);
      OLED_WR_CMD(0xDB);//--set vcomh
      OLED_WR_CMD(0x40);//--Set VCOM Deselect Level
      OLED_WR_CMD(0x20);//--Set Page Addressing Mode (0x00/0x01/0x02)
      OLED_WR_CMD(0x00);//
      OLED_WR_CMD(0x8D);//--set Charge Pump enable/disable
      OLED_WR_CMD(0x14);//--set(0x10) disable
      OLED_WR_CMD(0xA4);//--Disable Entire Display On (0xa4/0xa5)
      OLED_WR_CMD(0xA6);//--Disable Inverse Display On (0xa6/a7)
      OLED_WR_CMD(0xAF);
      OLED_Disp(1);     //开显示
    }
    //========================================================================
    // 函数名称: OLED_SetXY
    // 函数功能: OLED设置显示位置
    // 入口参数: X-水平坐标 0..127,Y-垂直坐标0..63
    //========================================================================
    void OLED_SetXY(unsigned char X, unsigned char Y)
    {
            OLED_WR_CMD((u8)(0xB0+Y));
            OLED_WR_CMD((u8)(((0xF0&X)>>4)|0x10));
            OLED_WR_CMD((u8)(((0x0f&X)|0x01)));
    }
    //========================================================================

    // 函数名称: OLED_Show
    // 函数功能: OLED刷新显示(将显示缓存的数据刷到显示屏显示出来)
    //========================================================================
    void OLED_Show(void)     
    { unsigned char xdata i,n;
      for(i=0;i<8;i++)       //8行(页)循环...
      { OLED_WR_CMD((u8)(0xB0+i)); //设置行起始地址
        OLED_WR_CMD(0x00);   //设置低列起始地址
        OLED_WR_CMD(0x10);   //设置高列起始地址
        for(n=0;n<128;n++)   //每行(页)128点(列)
        OLED_WR_DAT(ShowBuf[n]); //写数据...
      }
    }
    //========================================================================
    // 函数名称: OLED_CLS 清屏函数
    // 函数功能: OLED刷新显示(将显示缓存的0数据刷到显示屏显示出来)
    //========================================================================
    void OLED_CLS(void)     
    {
      memset(ShowBuf,0,128*8); //清空缓存
      OLED_Show();             //刷新显示
    }
    //========================================================================
    // 函数名称: OLED_Light(unsigned char num)
    // 函数功能: OLED亮度级设置
    //========================================================================
    void OLED_Light(unsigned char Level)
    {
        OLED_WR_CMD(0x81);  //
        OLED_WR_CMD(Level); //  
        OLED_WR_CMD(0xDB);  //--set vcomh
        OLED_WR_CMD(0x20);  //Set VCOM Deselect Level   
    }


    //========================================================================
    // 函数名称: OLED_Point
    // 函数功能: OLED显示一个点
    // 入口参数: X:水平点 0..127 Y:垂直点0..63
    //========================================================================
    void OLED_Point(unsigned char X,unsigned char Y,u8 mode)
    {
      ShowBuf[Y/8][X] |= 1<<(Y%8);  //垂直8点,高位在下,低位在上.
      if(mode)OLED_Show();
    }
    //========================================================================
    // 函数名称: OLED_BMP
    // 函数功能: OLED缓存写入BMP格式图片
    // 入口参数: X:水平起点 0..127 Y:垂直起点0..63  W: 宽度 H:高度 *BMP图片数组
    //========================================================================
    void OLED_BMP(unsigned char X, unsigned char Y,unsigned char W, unsigned char H,unsigned char BMP[],u8 mode)
    { unsigned int xdata num=0;
      unsigned char i,l;
      for( i=0;i<H;i++ )
      { for( l=0;l<W;l++)ShowBuf[Y+i][X+l]=BMP[num++]; }
      if(mode)OLED_Show();
    }
    //========================================================================

    // 画一条水平线
    //========================================================================
    void OLED_LineH(unsigned char X,unsigned char Y,unsigned char W,u8 mode)
    { unsigned char i;
      for(i=0;i<W;i++)OLED_Point((u8)(X+i),Y,0);
      if(mode)OLED_Show();  
    }  
    //========================================================================
    // 画一条垂直线
    //========================================================================
    void OLED_LineV(unsigned char X,unsigned char Y,unsigned char H,u8 mode)
    { unsigned char i;
      for(i=0;i<H;i++)OLED_Point(X,(u8)(Y+i),0);
      if(mode)OLED_Show();  
    }
    //========================================================================
    // 函数名称: OLED_BuffShowPoint
    // 函数功能: OLED显示一条线
    // 入口参数: X0起点 Y0起点 X1终点 Y1终点
    //========================================================================
    void OLED_Line( unsigned char X0, unsigned char Y0, unsigned char X1,unsigned char Y1,u8 mode)
    { unsigned char x,y;
      if(X0>X1){ x=X0;X0=X1;X1=x; y=Y0;Y0=Y1;Y1=y; }  //为从左到右而交换坐标.
      if(X0!=X1)
      { for(x=0;x<(X1-X0);x++ )
        { if(Y1>Y0)OLED_Point((u8)(X0+x),(u8)(Y0+(u16)(Y1-Y0)*(u16)x/(u16)(X1-X0)),0);
          else     OLED_Point((u8)(X0+x),(u8)(Y0-(u16)(Y0-Y1)*(u16)x/(u16)(X1-X0)),0);
        }
      }
      else
      { if(Y0>Y1){ for(y=Y1; y<=Y0; y++ )OLED_Point(X0,y,0); }
        else     { for(y=Y0; y<=Y1; y++ )OLED_Point(X0,y,0); }
      }
      if(mode)OLED_Show();  
    }
    //========================================================================
    // 画一个方框
    //========================================================================
    void OLED_Box(unsigned char X,unsigned char Y,unsigned char W,unsigned char H,u8 mode)
    {
      OLED_LineH(X,Y,W,0);   OLED_LineV(X,Y,H,0);
      OLED_LineH(X,(u8)(Y+H),W,0); OLED_LineV((u8)(X+W),Y,H,0);
      if(mode)OLED_Show();
    }  
    //========================================================================
    // 函数名称: OLED_Circle
    // 函数功能: OLED显示一个圆形
    // 入口参数: X点  Y点  r:半径
    //========================================================================
    void OLED_Circle(unsigned char X,unsigned char Y,unsigned char r,u8 mode)
    { int a, b, di;
      a = 0; b = r;
      di = 3 - (r << 1);        //判断下个点位置的标志
      while (a <= b)
      { OLED_Point((u8)(X+a),(u8)(Y-b),0);  //5
        OLED_Point((u8)(X+b),(u8)(Y-a),0);  //0
        OLED_Point((u8)(X+b),(u8)(Y+a),0);  //4
        OLED_Point((u8)(X+a),(u8)(Y+b),0);  //6
        OLED_Point((u8)(X-a),(u8)(Y+b),0);  //1
        OLED_Point((u8)(X-b),(u8)(Y+a),0);  //3
        OLED_Point((u8)(X-a),(u8)(Y-b),0);  //2
        OLED_Point((u8)(X-b),(u8)(Y-a),0);  //7
        a++;
        //使用Bresenham算法画圆
        if (di < 0)di += 4 * a + 6;
        else
        { di += 10 + 4 * (a - b); b--;}
      }
      if(mode)OLED_Show();
    }
    //========================================================================
    // 函数名称: OLED_A08
    // 函数功能: OLED显示一个5*7字符
    // 入口参数: X: 水平坐标列 0..127  Y: 垂直坐标行(页) 0..7
    //     turn: 0:正显示 1:反显示  mode: 1:立即刷新显示 0:仅写缓存
    //========================================================================
    void OLED_A08(unsigned char X,unsigned char Y,char Ch,u8 turn,u8 mode)     //OLED显示一个5*7字符
    { unsigned char i;
      for( i=0;i<6;i++ )
      { if(turn==0)            
        ShowBuf[Y][X+i]= ASC8[Ch-' '];
        else
        ShowBuf[Y][X+i]=~ASC8[Ch-' '];
      }
      if(mode)OLED_Show();
    }
    //========================================================================
    // 函数名称: OLED_A16
    // 函数功能: OLED显示一个8*16字符
    // 入口参数: X: 水平坐标列 0..127  Y: 垂直坐标行(页) 0..7
    //     turn: 0:正显示 1:反显示  mode: 1:立即刷新显示 0:仅写缓存
    //========================================================================
    void OLED_A16(unsigned char X,unsigned char Y,char Ch,u8 turn,u8 mode)     //OLED显示一个8*16字符
    { unsigned char i,j;
      for( j=0;j<2;j++ ){
      for( i=0;i<8;i++ )
      { if(turn==0)            
        ShowBuf[Y+j][X+i]= ASC16[Ch-' '][i+j*8];
        else
        ShowBuf[Y+j][X+i]=~ASC16[Ch-' '][i+j*8];
      } }
      if(mode)OLED_Show();
    }
    //========================================================================
    // 函数名称: OLED_HZ16
    // 函数功能: OLED显示一个16*16汉字
    // 入口参数: X: 水平坐标列 0..127  Y: 垂直坐标行(页) 0..7
    //     turn: 0:正显示 1:反显示  mode: 1:立即刷新显示 0:仅写缓存
    //========================================================================
    void OLED_HZ16(unsigned char X,unsigned char Y,char *Hz,u8 turn,u8 mode)
    { unsigned char i,j;
      unsigned int m;
      //查找字库汉字表
      for(m=0;m<TotalHZ16;m++)
      { if((Hz[0]==HZ16M[m][0])&&(Hz[1]==HZ16M[m][1]))break; }
      if(m>=TotalHZ16)return; //查无汉字...返回.
      //显示汉字...传输点阵字模32字节
      for( j=0;j<2;j++ ){
      for( i=0;i<16;i++ )
      { if(turn==0)            
        ShowBuf[Y+j][X+i]= HZ16[m][i+j*16];
        else
        ShowBuf[Y+j][X+i]=~HZ16[m][i+j*16];
      } }
      if(mode)OLED_Show();
    }
    //========================================================================
    // 函数名称: OLED_String
    // 函数功能: OLED显示5*7点阵字符串
    // 入口参数: X: 水平坐标列 0..127  Y: 垂直坐标行(页) 0..7
    //     turn: 0:正显示 1:反显示  mode: 1:立即刷新显示 0:仅写缓存
    //========================================================================
    void OLED_Str5x7(unsigned char X,unsigned char Y,char *s,u8 turn,u8 mode)
    {
      while(*s != '\0') //字符串不为空,循环
      { OLED_A08(X,Y,*s,turn,0); X+=6; s++; }      
      if(mode)OLED_Show();
    }
    //========================================================================
    // 函数名称: OLED_String
    // 函数功能: OLED显示字符串。
    // 入口参数: X: 水平坐标列 0..127  Y: 垂直坐标行(页) 0..7
    //     turn: 0:正显示 1:反显示  mode: 1:立即刷新显示 0:仅写缓存
    //========================================================================
    void OLED_String(unsigned char X,unsigned char Y,char *s,u8 turn,u8 mode)
    { char hz[2];
       while(*s != '\0')       //字符串不为空,循环
      { if ((unsigned char)*s < 0x80)     //根据输入数据的大小判断是字符还是汉字,
        { OLED_A16(X,Y,*s,turn,0);
          X+=8; s++;
        }
        else
        { hz[0] = *s ; hz[1] = *(s+1) ;
          OLED_HZ16(X,Y,hz,turn,0);
          X+=16; s+=2;
        }
        if(X>127){ X=0; Y+=2; }   //行
      }      
      if(mode)OLED_Show();

    }

    //===== SSD1306.C END ===================================================================




    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    慵懒
    10 小时前
  • 签到天数: 177 天

    [LV.7]常住居民III

    22

    主题

    1304

    回帖

    3188

    积分

    论坛元老

    积分
    3188
    发表于 2023-12-25 17:39:47 | 显示全部楼层
    之前是过用软件SPI刷屏,,可能也是大一点 320*240 彩屏,,,慢很多,,明显能看到刷屏的轨迹(也可能自己写的程序太烂)后来换成了硬件SPI,舒服多了。后来基本上涉及大数据量发送的都只用硬件SPI了,,剩下一些小数据量的传感器通讯,如果用的是硬件SPI的引脚位置,就还用硬件SPI,,如果不是,才用软件SPI。。
    参考例程并不是对技术参 考手册的补充,而是对技术参 考手册的解释。
    技术参 考手册不应该需要参考例程作为补充,而是解释成了参考例程的样子
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 197 天

    [LV.7]常住居民III

    11

    主题

    126

    回帖

    956

    积分

    高级会员

    积分
    956
     楼主| 发表于 2023-12-25 18:17:06 | 显示全部楼层
    楼上朋友,言之有理。谢谢浏览和回复。
    具体应用肯定是要视具体情况而定的,满足实际需求是最主要的。
    本例只是一个练习,探求另外一种驱动方式。若用SPI模式,官方例程和高手例程,早就写好了,替代读写字节函数。
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    慵懒
    10 小时前
  • 签到天数: 177 天

    [LV.7]常住居民III

    22

    主题

    1304

    回帖

    3188

    积分

    论坛元老

    积分
    3188
    发表于 2023-12-25 23:39:56 | 显示全部楼层
    浦江一水 发表于 2023-12-25 18:17
    楼上朋友,言之有理。谢谢浏览和回复。
    具体应用肯定是要视具体情况而定的,满足实际需求是最主要的。
    本例 ...

    我的意思是说,,不用太在意软件上怎么去模拟这些通讯方式,,,多研究研究显示框架.实现个什么效果,比这个强多了
    参考例程并不是对技术参 考手册的补充,而是对技术参 考手册的解释。
    技术参 考手册不应该需要参考例程作为补充,而是解释成了参考例程的样子
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 197 天

    [LV.7]常住居民III

    11

    主题

    126

    回帖

    956

    积分

    高级会员

    积分
    956
     楼主| 发表于 2023-12-27 20:36:02 | 显示全部楼层
    本帖最后由 浦江一水 于 2023-12-28 09:00 编辑

    非常感谢 神农鼎大明狐的认可和鲜花鼓励, 更加激发了学习兴趣...


    这个OLED显示12864屏,价格便宜,接口简单, STC单片机最小系统屠龙刀三实验板, 配上个小显示屏, 练习编程思路, 感觉还是很不错的.  

    思考了一下, 1024字节的显示缓存, 还是很小的, 能否扩大一点, 探索一下, 弄个双页显示玩玩,  看看效果是否在视觉上过得去....
    也为以后做项目, 积累一点基础, 于是乎, 在以上原有程序的基础上, 稍加了变化, 实现双页显示, 试试看.....


    主程序:   (实验内容见注释...)



    //********************************************************************************
    // 名称: Main.C
    // 基于: STC32G12K128 V9.62 实验箱  / 屠龙刀三核心实验板
    // 实验: OLED 12864 显示屏 SSD1306 驱动 __ 实现双页切换显示
    // 编程: 浦晓明(浦江一水) 2023-12-16
    //********************************************************************************
    #include "STC32G_SYS.H"
    #include "SSD1306.H"
    #include "pic.h"

    /** 全局变量说明 设为全局,便于调试观察... **************/

    //========================================================================
    // 函数: void  delay_ms(unsigned int ms)
    // 描述: 毫秒级延时函数。
    // 参数: ms,要延时的ms数,自动适应主时钟.
    //=====================================================================
    void  delay_ms(unsigned int ms)
    { unsigned int i;
      do{ i = MAIN_Fosc / 6000;
          while(--i);
        } while(--ms);
    }
    /**** 主函数入口 ************************/
    void main(void)
    {
      SYS_Init();                 //系统初始化
      OLED_Init();                //初始化
      OLED_CLS(0);                //清屏第一页
      OLED_CLS(1);                //清屏第二页
      OLED_HZ16(10,3,"单",0,0,1); //在第一页测试单个汉字
      OLED_HZ16(26,3,"片",0,0,1); //16*16点阵
      OLED_HZ16(42,3,"机",0,0,1);
      OLED_A16(60,3,'S',0,0,0);   //在第一页测试单个字符
      OLED_A16(68,3,'T',0,0,0);   //8*16字符
      OLED_A16(76,3,'C',0,0,1);
      OLED_A08(86,3,'O',0,0,0);   //6*8字符
      OLED_A08(92,3,'K',0,0,1);
      OLED_String(10,1,"单片机ABCDEFG",0,1,1); //在第二页测试字符串算法...
      OLED_Str5x7(10,4,"0123456789ABCD",0,1,1);
      OLED_Show(0);               //立即显示第一页  
      OLED_MovePage(1);           //移动到显示第二页
      OLED_MovePage(0);           //移动到显示第一页
      OLED_BMP(0,0,128,8,OLED12864_IMG0[0],0,1);   //在第一页显示第一幅图片
      OLED_BMP(0,0,128,8,OLED12864_IMG1[0],1,1);   
    //在第二页显示第二幅图片
      OLED_Show(0);               //立即显示第一页  
      OLED_Show(1);               //立即显示第二页
      OLED_Light(0);              //测试最低亮度
      OLED_Light(0xFF);           //测试最高亮度
      OLED_Light(0xCF);           //返回初始亮度
      OLED_Point(95,20,1,0);      //在第二页上画图写字
      OLED_Point(97,22,1,0);
      OLED_Point(99,24,1,1);      //画三个点
      OLED_LineH(92,0,16,1,1);    //画一水平线
      OLED_LineV(92,0,16,1,1);    //画一垂直线
      OLED_Line(92,0,108,16,1,1); //画斜线
      OLED_Box(110,0,16,16,1,1);  //画方框
      OLED_String(0,0,"STC32",0,1,1); //图上叠加字
      //主循环...
      while(1)
      {
        //左右移动显示两页...
        OLED_MovePage(0);
        delay_ms(2000);
        OLED_MovePage(1);
        delay_ms(2000);
      }  
    }


    程序运行于屠龙刀三实验板, (移植到STC32G实验箱V9.62,很简单,只要改变引脚定义即可)




    具体函数实现, 可见工程文件, 不占版面贴出...

    10-TestOLED(STC32G屠龙刀三)双页显示.rar (59.04 KB, 下载次数: 63)





    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    571

    主题

    1万

    回帖

    1万

    积分

    管理员

    积分
    14753
    发表于 2024-1-1 17:37:15 | 显示全部楼层
    用 DMA 来支持 SPI 加上了 ?
    下面这个思路用上没有 ?

    截图202401011736165904.jpg

    截图202401011737076442.jpg




    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 197 天

    [LV.7]常住居民III

    11

    主题

    126

    回帖

    956

    积分

    高级会员

    积分
    956
     楼主| 发表于 2024-1-2 12:44:00 | 显示全部楼层
    感谢神农鼎管理员的关注和指点。
    还要继续学习STC强大而灵活高效的DMA功能...
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    12 小时前
  • 签到天数: 176 天

    [LV.7]常住居民III

    11

    主题

    108

    回帖

    777

    积分

    高级会员

    积分
    777
    发表于 2024-3-7 10:59:11 | 显示全部楼层
    浦江一水 发表于 2023-12-25 16:58
    有关OLED屏的驱动文件就两个:
    SSD1306.H
    SSD1306.C

    你好,你的程序好像是模拟SPI驱动(屠龙刀三),没有硬件SPI驱动啊,不知哪里没搞对,望指点!
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 197 天

    [LV.7]常住居民III

    11

    主题

    126

    回帖

    956

    积分

    高级会员

    积分
    956
     楼主| 发表于 2024-3-7 12:58:55 | 显示全部楼层
    楼上朋友您好,谢谢您对本帖的浏览。
    关于硬件SPI驱动的方法,源程序中是包含有的,可能是我没有说明清楚。
    本实验的初衷是用模拟IO的方法实现驱动,这样的好处是不必关注和研究MCU有关硬件SPI寄存器的定义用法,比较适合移植,可用在恰巧端口IO是没有硬件SPI功能的场合,提供一种灵活的方法。
    后来有坛友认为,要讲究速度,应该用硬件SPI的方法。
    屠龙刀三实验板的显示屏端口,是具有硬件SPI功能的,那么当然是应该首先用硬件SPI方法的。
    其实模拟IO和硬件SPI的区别在于基本字节读写函数的不同,不是非常复杂难用的。
    所有我立马就补充改写了源程序,可使用硬件SPI驱动方法来驱动显示。
    源程序打包在一楼的底部,又上传了一下,并给出了补充说明。

    如果您下载了 "10-TestOLED(STC32G屠龙刀三) .rar"文件包(默认已经是硬件SPI方式的),并解压后....
    在SSD1306.C的中, 开始的地方, 有一行宏定义:

    #define UseSPI    1     //0: IO模拟SPI;  1: 使用硬件SPI

    1的意思就是用硬件SPI方式, 0就是用模拟IO方式, 已经默认1
    重新编译一下, 再下载执行, 就可以了.  编译软件会根据这个定义, 选用不同的编译语句(即条件编译) 。

    其实,原理是重新编写了两个基本函数:
    也就是说, 新的 SSD1306.C 既可以用于模拟方式,又可以用于硬件方式. 比较灵活.

    源程序如下, 再逐行补充一下说明...

    //========================================================================
    // 函数名称: OLED_WR_CMD(unsigned char Cmd)  
    // 函数功能: OLED_写命令字
    //========================================================================
    void OLED_WR_CMD(unsigned char Cmd)
    {
    #if(UseSPI==0)                           //如果UseSPI定义为0, 使用模拟IO方式
      unsigned char i;                         //模拟方式需要这个变量i (计数器)
    #endif  
      OLED_SCK= 0;                          //时钟低;
      OLED_DC = 0;                           //写命令
      OLED_CS = 0;                           //片选中
    #if(UseSPI)                                   //如果UseSPI定义为1, 使用硬件SPI方式.
      //SPI_WriteByte(Cmd);              //写一个字节...
      SPDAT = Cmd;                          //写入到寄存器SPDAT中...
      while(SPIF == 0);                       //标志位为零等待....
      SPIF = 1;                                   //清SPIF标志  
      WCOL = 1;                                //清WCOL标志
    #else                                            //否则就是模拟IO方式...
      for(i=0;i<8;i++)                          //循环8次,将字节数据的每一位从IO口移位输出....
      { if(Cmd&0x80)OLED_SDA=1;   //如果最高位是1, 端口给出高电平
        else                 OLED_SDA=0;   //如果最高位是0, 端口给出低电平
        Cmd <<= 1;                             //左移位 //
        OLED_SCK = 1;                        //时钟脉冲 , 推出一位输出...
        OLED_SCK = 0;
      }
    #endif
      OLED_CS = 1;                           //片不选
    }
    //========================================================================
    // 函数名称: OLED_WR_DAT(unsigned char Dat)  
    // 函数功能: OLED_写数据字
    //========================================================================
    void OLED_WR_DAT(unsigned char Dat)
    {
    #if(UseSPI==0)
      unsigned char i;
    #endif  
      OLED_SCK= 0;    //时钟低;
      OLED_DC = 1;    //写数据
      OLED_CS = 0;    //片选中
    #if(UseSPI)
      SPDAT = Dat;
      while(SPIF == 0);
      SPIF = 1;   //清SPIF标志
      WCOL = 1;   //清WCOL标志
    #else
      for(i=0;i<8;i++)
      { if(Dat&0x80)OLED_SDA=1;
        else        OLED_SDA=0;
        Dat <<= 1;    //左移位
        OLED_SCK = 1;
        OLED_SCK = 0;
      }
    #endif  
      OLED_CS = 1;    //片不选
    }

    写数据函数与写命令函数,绝大部分语句是相同的, 就不再重复注释了。
    写命令和写数据函数不同之处在于DC端口电平不同:DC=0写命令,DC=1写数据。
    可能有人认为, 没有必要用两个函数,只要写一个写字节函数就可以了。 没错,这是看个人习惯了。
    如果用一个写字节函数,必然要带一个形式参数,再根据这一形参标识,在函数内部加以区别。
    个人认为分开写,源程序的可读性较好。

    我也是STC32G12K128单片机的新手,以上说明,供参考。若有不清楚或有错误之处, 尽管提问或指出,共同学习。
    回复 支持 反对 送花

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-6-25 19:18 , Processed in 0.113610 second(s), 68 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

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