打印
[其他ST产品]

STM32 HAL 硬件IIC+DMA+简单图形库控制OLED

[复制链接]
楼主: wangtaohui
手机看帖
扫描二维码
随时随地手机跟帖
21
wangtaohui|  楼主 | 2023-6-28 20:22 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
修改OLED_Init函数
void OLED_Init(void)
{
        WriteCmd(0xAE); //display off

        WriteCmd(0x20); //Set Memory Addressing Mode
//        WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
        WriteCmd(0x00); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid

        WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
        WriteCmd(0xc8); //Set COM Output Scan Direction
        WriteCmd(0x00); //---set low column address
        WriteCmd(0x10); //---set high column address
        WriteCmd(0x40); //--set start line address
        WriteCmd(0x81); //--set contrast control register
        WriteCmd(0xff); //亮度调节 0x00~0xff
        WriteCmd(0xa1); //--set segment re-map 0 to 127
        WriteCmd(0xa6); //--set normal display
        WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
        WriteCmd(0x3F); //
        WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
        WriteCmd(0xd3); //-set display offset
        WriteCmd(0x00); //-not offset
        WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
        WriteCmd(0xf0); //--set divide ratio
        WriteCmd(0xd9); //--set pre-charge period
        WriteCmd(0x22); //
        WriteCmd(0xda); //--set com pins hardware configuration
        WriteCmd(0x12);
        WriteCmd(0xdb); //--set vcomh
        WriteCmd(0x20); //0x20,0.77xVcc
        WriteCmd(0x8d); //--set DC-DC enable
        WriteCmd(0x14); //
        WriteCmd(0xaf); //--turn on oled panel
        OLED_CLS();
}

使用特权

评论回复
22
wangtaohui|  楼主 | 2023-6-28 20:23 | 只看该作者
其中 WriteCmd(0x20); WriteCmd(0x00); 两句函数将SSD1306的寻址方式修改为了水平寻址,能够一次性对OLED内部Graphic Display Data RAM (GDDRAM)整体更新。
见firestaradmin大佬的
《STM32 DMA-IIC刷新OLED屏(理论可达42+帧率)》
和Coder_BCM大佬的
《基于STM32F407的 中景园0.96寸OLED(IIC)的程序升级(DMA+IIC + 显存Buffer)》)
若发现行错位,则修改

        WriteCmd(0xd3); //-set display offset
        WriteCmd(0x00 加或减 8的倍数);

使用特权

评论回复
23
wangtaohui|  楼主 | 2023-6-28 20:23 | 只看该作者
使用和验证
添加test.c,该文件源自原例程,有修改。
需要注意的是调用OLED_ShowCHinese函数的文件和oled_font.c文件的编码格式需一致:GBK。当一个为UTF-8,一个为GBK时会显示不出汉字。
/*
        Copyright (c) [2019] [一只程序缘 jiezhuo]
        [https://gitee.com/jiezhuonew/oledlib] is licensed under the Mulan PSL v1.
        You can use this software according to the terms and conditions of the Mulan PSL v1.
        You may obtain a copy of Mulan PSL v1 at:
                http://license.coscl.org.cn/MulanPSL
        THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
        PURPOSE.
        See the Mulan PSL v1 for more details.

        ==========此函数是oledlib图形库的综合测试函数 基本上使用到了里面的所有内容==================

        目前接线说明:
                仅仅使用到0.96寸7引脚oled 使用SPI通信方式(具体见oled_drive.c)
                D0  = SCK  ->        PA4                (屏幕批次不同丝印可能不同)
                D1  = SDA  ->        PA5
                RES = RES  ->        PA6
                DC  = DC   ->        PA7
                CS  = SCS  ->        PA8

        软件模块:
                oled        spi驱动
                beep        蜂鸣器 提示或作为led表示运行状态
                timer3        中断1ms 可为图形库提供时间基准
                uart        可作为串口连接电脑打印调试xinxi
                delay        系统自带的时间设置与提供延时函数

        视频演示信息:
                单片机 --- STM32F103
                oled   --- 采用4线SPI的7引脚0.96寸黑白双色oled

        图形库功能:
                在oledlib文件夹下作出多个c文件的分类 每个c文件的开头都有对该文件的简要说明
                包括打点 画线 矩形 三角形 圆 椭圆 圆角矩形 的边缘与填充
                还包括 字符显示 汉字显示 位图显示
                最后还有 oled模拟串口printf的debug

        字符显示:
                针对于字符有4种大小0-1-2-3 具体参见SetFontSize()
                基于大家的使用习惯
                0是一般大家习惯用的                        此时y只能为0-7对应显示的7大行                 注意:此时x为地基大行 y为第几列
                2 3都是基于1的等比例放大        此时y能为0-6x对应显示的每一小行                此时为正常坐标系 x为第几小行 y为第几列

        中文汉字显示:
                中文汉字的大小为16x16                此时y也只能为0-7对应显示的7大行                注意:此时x为地基大行 y为第几列

        取模说明:
                取模软件采用PCtoLCD2002
                字符 汉字 和 位图的取模方式和设置都在font.c和bmp.c中有注释

        用户调用函数:
                在draw_api.h中提供了所有函数的接口 可在其中查看所有函数的简要功能

        移植说明:
                1.移植图形库仅需要准备好底层驱动 在oled_driver.c中作出修改即可 在该c文件开头有说明
                2.若需要对test.c中的动画效果进行测试 还需要为函数OledTimeMsFunc()提供时间基准
                3.库函数使用的延时函数是DelayMs() 移植后由单片机型号补充调整DelayMs()

        图形库本质:
                该图形库本质为对一数组进行操作 然后刷新整个数组到屏幕中实现效果
                因此相似点阵屏(oled/TFT/大小尺寸/点阵/液晶)也可以使用 根据像素可参见oled_config.h

        详细的注释:
                如果是使用为用途 了解test.c和draw_api.c即可 做出花里胡哨的UI界面
                在本人阅读代码和注释的过程中 发现很多结构和函数都值得学习 仔细理解理解

        GUI和本图形库的关系:
                gui是一个以图形界面来进行人机交互的系统 gui是拥有窗口、菜单、控件、对话框等交互的图形接口
                这个只是一个图形库 类似于c++上的easyx图形库 是没有图形接口的 只提供绘图和显示的功能
                图形库可以算是gui的一部分,也可以自己以图形库继续封装为gui库

        代码容量:
                除去位图 Code=26254 RO-data=3754 RW-data=100 ZI-data=4108

        原作的话:
                为了做这个绘制图形库,我参考了很多其他的算法,花了大量时间去做移植
                优化,其中参考最多的是github中的arduboy游戏机,大部分图形是移植它的,
                现在是最初的初稿版本,还有很多地方需要优化改进。我想要将这个图形库做大,
                后续会加入更多有趣的东西,和模块,代码都是用最简单的方式编写的,都是开源的。
                后续也会加上注释说明,让更多人以单片机和oled屏来入门硬件编程,如果你
                使用了该库,请标明出处。b站关注oximeterg,可获取该库的最新版本和消息。
                注意:绘制填充或实心的图形,影响刷新效率(待优化中)
*/


// #include "test.h"
#include "draw_api.h"
#include "../Delay/Delay.h"
#include "stdlib.h"
#include "stdio.h"
#include "math.h"

void ShowStars(void);
void ShowWatch(void);
void ShowPolygon(void);
void ShowSnow(void);

void demo(void)
{
    int i,j;
    //demo演示
    ClearScreen();
    DrawBitmap(0,0,Panda,128,64);
    UpdateScreen();
    DelayMs(1000);

    //字符
    ClearScreen();
    SetFontSize(1);
    DrawString(0,0,"test");
    UpdateScreen();
    DelayMs(1000);

    SetFontSize(2);
    DrawString(0,8,"test");
    UpdateScreen();
    DelayMs(1000);

    SetFontSize(3);
    DrawString(0,24,"test");
    UpdateScreen();
    DelayMs(1000);

    //汉字测试
    ClearScreen();
    OLED_ShowCHinese(0,0,"一只程序缘");
    UpdateScreen();
    DelayMs(1000);
    OLED_ShowCHinese(2,0,"一只程序缘一只程序缘一只程序缘一只程序缘");
    UpdateScreen();
    DelayMs(1000);

    //数字测试
    ClearScreen();
    for(i=0; i<300; i++)
    {
        SetFontSize(0);
        DrawNum(0,96,i,4);
        SetFontSize(1);
        DrawNum(0,16,i,4);
        SetFontSize(2);
        DrawNum(0,24,i,4);
        SetFontSize(3);
        DrawNum(0,40,i,4);
        UpdateScreen();
    }

    //串口debug
    DelayMs(100);
    ClearScreen();
    for(i=0; i<256; i++)
    {
        OledPrintf("num:%d cha:%c hex:%x\r\n",i,i,i);
    }
    DelayMs(100);

    //划线
    ClearScreen();
    for(i=0; i<20; i++)
    {
        DrawLine(0,0,i*10,63);
        UpdateScreen();
    }
    for(i=0; i<20; i++)
    {
        DrawLine(128,0,128-i*10,63);
        UpdateScreen();
    }
    DelayMs(100);

    //矩形
    for(j=0; j<2; j++)
    {
        if(j==0)
            ClearScreen();
        for(i=0; i<31; i+=2)
        {
            DrawRect2(i*2,i,128-i*4,64-2*i);//画矩形外框
            UpdateScreen();
        }
        if(j==0)
            ClearScreen();
        DelayMs(100);
        for(i=31; i>0; i-=2)
        {
            DrawFillRect2(i*2,i,128-i*4,64-2*i);//画实心矩形
            UpdateScreen();
        }
        SetDrawColor(pix_black);//划线颜色
        SetFillcolor(pix_black);//填充颜色
    }
    SetDrawColor(pix_white);
    SetFillcolor(pix_white);

    //圆角矩形
    for(j=0; j<2; j++)
    {
        if(j==0)
            ClearScreen();
        for(i=0; i<25; i+=2)
        {
            DrawRoundRect(i*2,i,128-i*4,64-2*i,8);
            UpdateScreen();
        }
        if(j==0)
            ClearScreen();
        DelayMs(100);
        for(i=25; i>2; i-=2)
        {
            DrawfillRoundRect(i*2,i,128-i*4,64-2*i,8);
            UpdateScreen();
        }
        SetDrawColor(pix_black);
        SetFillcolor(pix_black);
    }
    SetDrawColor(pix_white);
    SetFillcolor(pix_white);
    DelayMs(100);

    //椭圆
    ClearScreen();
    DrawEllipse(63,31,63,31);
    UpdateScreen();
    DelayMs(500);
    ClearScreen();
    DrawEllipse(63,31,16,31);
    UpdateScreen();
    DelayMs(500);
    ClearScreen();
    DrawFillEllipse(63,31,63,31);
    UpdateScreen();
    DelayMs(500);
    ClearScreen();
    DrawFillEllipse(63,31,16,31);
    UpdateScreen();
    DelayMs(500);

    //圆
    ClearScreen();
    DrawCircle(63,31,30);
    UpdateScreen();
    DelayMs(500);
    ClearScreen();
    DrawFillCircle(63,31,30);
    UpdateScreen();
    DelayMs(500);

    //三角形
    ClearScreen();
    DrawTriangle(5,10,100,30,60,50);
    UpdateScreen();
    DelayMs(500);
    ClearScreen();
    DrawFillTriangle(5,10,100,30,60,50);
    UpdateScreen();
    DelayMs(500);

    //绘制图形图片 位图
    ClearScreen();
    DrawBitmap(0,0,BmpTest1,16,16);
    UpdateScreen();
    DelayMs(500);
    DrawBitmap(16,0,BmpTest2,32,32);
    UpdateScreen();
    DelayMs(500);
    DrawBitmap(48,0,BmpTest3,64,64);
    UpdateScreen();
    DelayMs(500);

    //圆弧
    ClearScreen();
    for(i=0; i<360; i++)
    {
        DrawArc(63,31,30,0,i);        //画圆弧
        UpdateScreen();
                DelayMs(25);
        ClearScreen();
    }
    DrawCircle(63,31,30);                //画圆
    UpdateScreen();
    DelayMs(100);

    for(i=0; i<10; i++)
    {
        DrawFillCircle(63,31,i);        //在中心填充圆
        DrawCircle(63,31,30);
        UpdateScreen();
                DelayMs(25);
        ClearScreen();
    }

    //绕点
    for(i=0; i<720; i++) //转720度2圈
    {
        TypeXY temp;
        SetAngle(i);                                        //设置角度
        SetRotateCenter(63,31);                        //设置圆心
        temp=GetRotateXY(63,31+30);                //讲已知坐标旋转角度
        DrawFillCircle(temp.x,temp.y,5);//画出算出的位置
        DrawCircle(63,31,30);                        //画出轨迹
        DrawFillCircle(63,31,10);                //填充中心
        UpdateScreen();                                        //更新屏幕
        ClearScreen();
    }

    //向右边平移
    for(i=0; i<95; i++)
    {
        TypeXY temp;                                        //其实就是上面继续旋转
        SetAngle(720+i);                                //画图的时候x+偏移量
        SetRotateCenter(63+i,31);
        temp=GetRotateXY(63+i,31+30);
        DrawFillCircle(temp.x,temp.y,5);
        DrawCircle(63+i,31,30);
        DrawFillCircle(63+i,31,10);
        UpdateScreen();
        ClearScreen();
    }

    //动画
    ShowStars();        //星空动画
    ShowWatch();        //时钟动画
    ShowPolygon();        //多边形动画
//        ShowSnow();                //下雪动画
    DelayMs(100);

    //结束
    ClearScreen();
    SetFontSize(2);
    DrawString(8,16," Show End ");        //显示字符串
    UpdateScreen();
    DelayMs(1000);

    ClearScreen();
    DrawBitmap(0,10,Like,128,40);        //三连图片
    UpdateScreen();
        DelayMs(1000);
}

/
//星空动画
void ShowStars(void)
{
    int i;
    int count=0;
    int fps=60;
    typedef struct START
    {
        short x;
        short y;
        short speed;
        unsigned char speedcount;
        unsigned char isexist;
    } Star;

    Star star[128]= {0};
    srand(2);
    for(i=0; i<128; i++)
    {
        if(star[i].isexist==0)
        {
            //设置128个()星星的初始信息
            star[i].x=rand()%127;                //随机生成初始x坐标
            star[i].y=rand()%64;                //随机生成y的坐标
            star[i].speedcount=0;
            star[i].speed=rand()%8+1;        //1-8的数
            star[i].isexist=1;
        }
    }
    while(1)
    {
        if(FrameRateUpdateScreen(fps)==1)        //在此函数中定时刷新屏
        {
            count++;
            if(count>=fps*10)                           //10秒钟时间到达之后跳出循环结束动画
                return;
        }

        //此段函数一直在运行
        //依次画出128个星星
        for(i=0; i<128; i++)
        {
            //如果这一个星星已经移动到退出屏幕界面
            //则在最左侧重新生成一颗新星星
            if(star[i].isexist==0)
            {
                star[i].x=0;
                star[i].y=rand()%64;
                star[i].speed=rand()%6+1;
                star[i].speedcount=0;
                star[i].isexist=1;
            }
            else
            {
                star[i].speedcount++;
                if(star[i].x>=124)                        //标记已经退出屏幕
                    star[i].isexist=0;

                //清除上一个时刻画的星星(的尾巴) 不管有没有操作 都进行清除操作
                SetDrawColor(pix_black);
                DrawLine(star[i].x,star[i].y,star[i].x,star[i].y);

                SetDrawColor(pix_white);
                if(star[i].speedcount==star[i].speed)        //运行时间到了一定的长度
                {
                    star[i].speedcount=0;                                //复位运行时间并向右移一格
                    star[i].x+=1;                                                //总之星星的结束需要在这经历124次
                }                                                                                //只不过有的更快 就能移动更快
                //从头到尾画出整条星星 不管星星是否已经变化
                DrawLine(star[i].x, star[i].y, star[i].x+(6/star[i].speed)-1, star[i].y);
            }
        }
    }
}


//时钟动画
//钟 小时 分钟 秒,
void RoundClock(int hours,int minute,int sec)
{
    unsigned char i=0;
    TypeXY hourspoint,minutepoint,secpoint,tmp1,tmp2;

    //时针
    SetRotateValue(63,31,hours*30+(minute*30)/60,1);
    hourspoint=GetRotateXY(63-14,31);
    DrawLine(63,31,hourspoint.x,hourspoint.y);
    //分针
    SetRotateValue(63,31,minute*6+(sec*6)/60,1);
    minutepoint=GetRotateXY(63-21,31);
    DrawLine(63,31,minutepoint.x,minutepoint.y);
    //秒针
    SetRotateValue(63,31,sec*6,1);
    secpoint=GetRotateXY(63-28,31);
    DrawLine(63,31,secpoint.x,secpoint.y);
    //表盘
    for(i=0; i<12; i++)
    {
        SetRotateValue(63,31,i*30,1);
        tmp1=GetRotateXY(63-29,31);
        tmp2=GetRotateXY(63-24,31);
        DrawLine(tmp1.x,tmp1.y,tmp2.x,tmp2.y);
    }
    DrawFillCircle(63,31,2);
    DrawCircle(63,31,30);
    UpdateScreen();
        DelayMs(25);
    ClearScreen();
}

void ShowWatch(void)
{
    int i,j,z;
    int count=0;

    for(i=0; i<12; i++)
        for(j=0; j<60; j++)
            for(z=0; z<60; z++)
            {
                RoundClock(i,j,z);
                count++;
                if(count>=800)
                    return;
            }
}

/
//多边形动画
void ShowPolygon(void)
{
    int x0=63,y0=31;                //正多边形的外接圆的圆心
    unsigned char i =0,j;
    int n=1,r=31;                        //画正n边形 大小半径31
    int v=1,count=0;                //每个相邻的多边形隔1 画count次后退出
    int x[30],y[30];

    while(1)
    {
        ClearScreen();
        for(i=0; i<n; i++)
        {
            x[i]=r*cos(2*3.1415926*i/n)+x0;
            y[i]=r*sin(2*3.1415926*i/n)+y0;
        }
        for(i=0; i<=n-2; i++)
        {
            for(j=i+1; j<=n-1; j++)
                DrawLine(x[i],y[i],x[j],y[j]);
        }
        n+=v;
        if(n==20||n==0)
            v=-v;

        UpdateScreen();
                DelayMs(25);                //延时停顿100ms
        if(++count==90)
        {
            count=0;
            return ;
        }
    }
}

//下雪的函数 和星空类似
void ShowSnow(void)
{
    int a[66],i,num=0;
    int count=0;
    int fps=60;
    struct Snow
    {
        short x;
        short y;
        short speed;
    } snow[100];

    srand(1);
    for(i=0; i<66; i++)
        a[i]=(i-2)*10;
    ClearScreen();
    while(1)
    {
        if(FrameRateUpdateScreen(fps)==1)        //在此函数中定时刷新屏
        {
            count++;
            if(count>=fps*10)                           //10秒钟时间到达之后跳出循环结束动画
                return;
        }
        if(num!=100)
        {
            snow[num].speed=1+rand()%4;
            i=rand()%66;
            snow[num].x=a[i];
            snow[num].y=0;
            num++;
        }
        for(i=0; i<num; i++)
        {
            snow[i].y+=snow[i].speed;
            DrawPixel(snow[i].x,snow[i].y+1);
            DrawPixel(snow[i].x+1,snow[i].y);
            DrawPixel(snow[i].x,snow[i].y);
            DrawPixel(snow[i].x-1,snow[i].y);
            DrawPixel(snow[i].x,snow[i].y-1);
            if(snow[i].y>63)
            {
                snow[i].y=0;
            }
        }
        UpdateScreen();
                DelayMs(25);
        ClearScreen();
    }
}


使用特权

评论回复
24
wangtaohui|  楼主 | 2023-6-28 20:24 | 只看该作者
在main.c文件中包含必要的头文件
//...
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "../Delay/Delay.h"
#include "../oledlib/draw_api.h"
/* USER CODE END Includes */
//...

使用特权

评论回复
25
wangtaohui|  楼主 | 2023-6-28 20:24 | 只看该作者
添加初始化函数并测试demo
//...
  /* USER CODE BEGIN 2 */
  delay_init();
  InitGraph();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        void demo(void);
        demo();
  }
  /* USER CODE END 3 */
//...

使用特权

评论回复
26
wangtaohui|  楼主 | 2023-6-28 20:25 | 只看该作者
效果如下


使用特权

评论回复
27
wangtaohui|  楼主 | 2023-6-28 20:25 | 只看该作者
结论
相比于普通的硬件IIC控制屏幕,改用DMA驱动后,刷新率得到了很大提升。其速度提升的原理为《STM32 DMA-IIC刷新OLED屏(理论可达42+帧率)》

使用特权

评论回复
28
星辰大海不退缩| | 2023-6-30 09:30 | 只看该作者
看来还是HAL库更方便配置

使用特权

评论回复
29
Undshing| | 2023-7-1 23:08 | 只看该作者
hal库配置起来就是快

使用特权

评论回复
30
Jacquetry| | 2023-7-6 23:48 | 只看该作者
显示效果很不错

使用特权

评论回复
31
mmbs| | 2023-7-9 10:01 | 只看该作者
使用HAL库函数,配置DMA控制器以实现DMA传输。 可以将DMA配置为从内存到外设的传输模式,以便在发送数据时减少CPU的负载。

使用特权

评论回复
32
hearstnorman323| | 2023-7-9 14:38 | 只看该作者
要使用HAL硬件IIC和DMA控制OLED,并使用简单图形库进行操作

使用特权

评论回复
33
cemaj| | 2023-7-9 16:57 | 只看该作者
使用 DMA 控制器,控制数据传输,从而减少单片机的工作负担,提高数据传输速度和效率。

使用特权

评论回复
34
MessageRing| | 2023-7-9 22:52 | 只看该作者
DMA会提高多少帧啊?

使用特权

评论回复
35
xiaoyaodz| | 2023-7-10 22:13 | 只看该作者
使用 HAL 硬件 IIC 函数库,控制 IIC 总线的数据传输,例如发送数据、接收数据等。

使用特权

评论回复
36
modesty3jonah| | 2023-7-11 22:32 | 只看该作者
实现DMA,在最初的时候,换用硬件IIC极其的方便,换到HAL库的接口函数就好了

使用特权

评论回复
37
lihuami| | 2023-7-14 13:59 | 只看该作者
使用HAL库来控制通过硬件IIC接口连接的OLED液晶屏,并结合DMA传输和简单图形库,可以实现高效的图形显示。

使用特权

评论回复
38
公羊子丹| | 2024-2-9 07:25 | 只看该作者

一般PCB是V-CUT,

使用特权

评论回复
39
万图| | 2024-2-9 08:28 | 只看该作者

可对电路进行一个防护效果

使用特权

评论回复
40
帛灿灿| | 2024-2-9 11:27 | 只看该作者

不会达到该电压

使用特权

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

本版积分规则