目录

  • 前言
  • 一、建立工程
  • 二、编写和移植
  • 前期准备
  • 驱动部分修改
  • 三、使用和验证
  • 结论



(2022年1月22日重制)本文主要是移植带简单图形库的程序,如果只是实现DMA控制,建议看【0.96寸 OLED屏实现1500Fps的帧率】STM32 软件、硬件SPI、I2C驱动总结

本文参考例程 提取码: sch3

前言

本文将介绍在STM32 HAL库下实现硬件IIC+DMA控制0.96寸 OLED屏,以及OLED图形库的移植。


HAL_I2C_Mem_Write解析_c语言

一、建立工程

平台:野火指南者STM32F103VET6 + STM32Cube MX + Keil MDK 5

(示例)

1.选择所用MCU。

HAL_I2C_Mem_Write解析_单片机_02


2.选择启用晶振

HAL_I2C_Mem_Write解析_嵌入式_03


3.选择Debug方式

HAL_I2C_Mem_Write解析_单片机_04


4.设置时钟树

HAL_I2C_Mem_Write解析_嵌入式_05


5.选择合适的引脚开启I2C

HAL_I2C_Mem_Write解析_c语言_06


6.加入I2Cx_TX的DMA

HAL_I2C_Mem_Write解析_单片机_07


7.开启12Cx_event_interrupt

HAL_I2C_Mem_Write解析_c语言_08


8.配置完后建立工程

HAL_I2C_Mem_Write解析_嵌入式_09

二、编写和移植

本节主要介绍移植标准库编写的oledlib图形库过程。
oledlib库原作者:MjGame 原作例程开源地址:MjGame 的例程 本文使用的oledlib库修订者:一只程序缘 本文所要移植库的开源地址:一只程序缘 的例程

git clone https://gitee.com/jiezhuonew/oledlib.git

(示例):

前期准备

1.先导入所需的文件
所用Delay文件参考《无需另配定时器在STM32 HAL下实现微秒级延时(兼容FreeRTOS)》

HAL_I2C_Mem_Write解析_c语言_10


HAL_I2C_Mem_Write解析_HAL_I2C_Mem_Write解析_11


2.将delay.h改为我们所用的Delay.h文件

HAL_I2C_Mem_Write解析_c语言_12


HAL_I2C_Mem_Write解析_嵌入式_13


修改u16为unsigned short或uint16_t

HAL_I2C_Mem_Write解析_HAL_I2C_Mem_Write解析_14


HAL_I2C_Mem_Write解析_单片机_15

驱动部分修改

  1. 在oled_config.h中添加#include “main.h”,并修改TRANSFER_METHOD
  2. HAL_I2C_Mem_Write解析_单片机_16

  3. 在oled_driver.h和oled_driver.c中注释掉标准库相关的东西:
  4. HAL_I2C_Mem_Write解析_HAL_I2C_Mem_Write解析_17


  5. HAL_I2C_Mem_Write解析_嵌入式_18

  6. 此时编译不再弹出错误。
  7. HAL_I2C_Mem_Write解析_嵌入式_19

  8. 在oled_driver.c修改I2C_WriteByte函数
/**
		 @brief  I2C_WriteByte,向OLED寄存器地址写一个byte的数据
		 @param  addr:寄存器地址
						 data:要写入的数据
		 @retval 无
	*/
	void I2C_WriteByte(uint8_t addr, uint8_t data)
	{
//		while (I2C_GetFlagStatus(I2CX, I2C_FLAG_BUSY));

//		I2C_GenerateSTART(I2CX, ENABLE);//开启I2C1
//		while (!I2C_CheckEvent(I2CX, I2C_EVENT_MASTER_MODE_SELECT)); /*EV5,主模式*/

//		I2C_Send7bitAddress(I2CX, OLED_ADDRESS, I2C_Direction_Transmitter);//器件地址 -- 默认0x78
//		while (!I2C_CheckEvent(I2CX, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

//		I2C_SendData(I2CX, addr);//寄存器地址
//		while (!I2C_CheckEvent(I2CX, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

//		I2C_SendData(I2CX, data);//发送数据
//		while (!I2C_CheckEvent(I2CX, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

//		I2C_GenerateSTOP(I2CX, ENABLE);//关闭I2C1总线
		extern I2C_HandleTypeDef hi2c1;
		HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS, addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 10);
	}
  1. 在oled_driver.c修改OLED_CLS函数
void OLED_CLS(void)//清屏
{
//	unsigned char m, n;
//	for (m = 0; m < 8; m++)
//	{
//		WriteCmd(0xb0 + m); //page0-page1
//		WriteCmd(0x00);   //low column start address
//		WriteCmd(0x10);   //high column start address
//		for (n = 0; n < 128; n++)
//		{
//			WriteDat(0x00);
//		}
//	}

	extern unsigned char ScreenBuffer[SCREEN_PAGE_NUM][SCREEN_COLUMN];

    uint16_t i;
    for (i = 0; i < SCREEN_PAGE_NUM * SCREEN_COLUMN; ++i)
    {
        ScreenBuffer[0][i] = 0;
    }
	OLED_FILL(ScreenBuffer[0]);
}
  1. 在oled_driver.c修改OLED_FILL函数
void OLED_FILL(unsigned char BMP[])
{
//	uint8_t i, j;
	unsigned char *p;
	p = BMP;

//	for (i = 0; i < 8; i++)
//	{
//		WriteCmd(0xb0 + i); //page0-page1
//		WriteCmd(0x00);   //low column start address
//		WriteCmd(0x10);

//		for (j = 0; j < 128; j++)
//		{
//			WriteDat(*p++);
//		}
//	}
	extern I2C_HandleTypeDef hi2c1;
    while(hi2c1.State != HAL_I2C_STATE_READY);
    HAL_I2C_Mem_Write_DMA(&hi2c1, OLED_ADDRESS, 0x40, I2C_MEMADD_SIZE_8BIT, p, SCREEN_PAGE_NUM * SCREEN_PAGEDATA_NUM);
}
  1. 注意到
//请将此函数放入1ms中断里,为图形提供时基
//系统时间基准主要用于FrameRateUpdateScreen()中固定帧率刷新屏幕
void OledTimeMsFunc(void)
{
	if(OledTimeMs != 0x00)
	{ 
		OledTimeMs--;
	}
}

故到stm32f1xx_it.c中声明并添加此函数
若未使用FreeRTOS则在滴答定时器中断中添加,反之在对应定时器中断中添加:

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  OledTimeMsFunc();
  /* USER CODE END SysTick_IRQn 1 */
}
  1. 修改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();
}

其中 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的倍数);

三、使用和验证

添加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();
    }
}

在main.c文件中包含必要的头文件

//...
#include "main.h"

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

添加初始化函数并测试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 */
//...

效果如下

HAL_I2C_Mem_Write解析_stm32_20


HAL_I2C_Mem_Write解析_stm32_21


HAL_I2C_Mem_Write解析_HAL_I2C_Mem_Write解析_22


结论

相比于普通的硬件IIC控制屏幕,改用DMA驱动后,刷新率得到了很大提升。其速度提升的原理为《STM32 DMA-IIC刷新OLED屏(理论可达42+帧率)》