STM32初学入门笔记(5):使用STM32CubeMX通过SPI,IIC驱动OLED屏幕

这篇具有很好参考价值的文章主要介绍了STM32初学入门笔记(5):使用STM32CubeMX通过SPI,IIC驱动OLED屏幕。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

随着时代的进步,OLED显示屏成为了继LCD显示屏之后的新一代显示屏技术,OLED具有可视角高,功耗低,厚度薄,耐冲击、振动能力强,像素响应时间低等优点,在嵌入式开发中,OLED显示器也是一个主要的部分,制作OLED显示模块的驱动也是学习STM32路上的重要一部分,本篇将从零开始,一步一步教你编写属于自己的OLED驱动,全部源码放在交流群,有需要的可以入群拿,喜欢的不要忘了点赞以及关注博主哦

交流Q_qun:659512171

目录

一,基础知识:

二,STM32CubeMX配置:

1,新建工程:

2,配置工程:

(1)配置RCC时钟:

(3)配置调试:

(4)配置 IIC / SPI:

        SPI:

        IIC:

(5)配置工程:

(6)生成文件:

三,在 Keil 中编写OLED驱动:

1,新建oled.c,oled.h,oledfont.h:

2,编写程序:

画点,画线:

显示ASCII字符:

显示汉字,图片: 

四,测试驱动: 

效果展示:


一,基础知识:

        SPI,IIC通信的基本原理以及OLED的基本知识

        SPI,IIC:通信:传送门

        下面介绍OLED的基本知识(鉴于OLED数据手册全是英文,初学者或者英语基础不好的朋友读起来很吃力,在这里就不贴数据手册了,有兴趣的可以进群下载,在这里我就分享一些我自己的经验以及理解):

        先来简单介绍一下显示方式,OLED屏幕并不是一个像素一个数据,而是8个像素一个数据,所以实际数据数组(也可以不用数组,后面会讲)总共有64 / 8 = 8行,大小为 128 * 8 = 1024bit。下面给一张图片:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         然后来说一下OLED的数据格式,规定当像素亮的时候表示为1,灭的时候表示为0,则一个包括8像素的数据就可以用一个8位数据来表示,一般使用16进制数。对于字体和图像的取模可以使用取模软件 PCtoLCD2002,这个软件的教程在博客上也有很多,大家可以自行下载,也可以在我的Q群中下载,软件的配置按照下图就可以了

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        其中的左下角的每行显示数据中的点阵大小根据你取的字模横向像素长度来设置,有多长就输入多长,例如:一个图片是64*32的,那么就输入64。索引不用管

        接着来说一下OLED的初始化数据,这一段包含了包括显示方向,亮度,显示模式等等诸多显示相关的重要数据,一般而言使用厂家提供的默认初始化代码就可以,这里贴上:

    OLED_WR_CMD(0xAE);//--display off
	OLED_WR_CMD(0x00);//---set low column address
	OLED_WR_CMD(0x10);//---set high column address
	OLED_WR_CMD(0x40);//--set start line address  
	OLED_WR_CMD(0x20);//--set page address
	OLED_WR_CMD(0x00);
	OLED_WR_CMD(0x81);// contract control
	OLED_WR_CMD(0xFF);//--设置亮度
	OLED_WR_CMD(0xA1);//set segment remap 
	OLED_WR_CMD(0xA6);//--normal / reverse
	OLED_WR_CMD(0xA8);//--set multiplex ratio(1 to 64)
	OLED_WR_CMD(0x3F);//--1/32 duty
	OLED_WR_CMD(0xC8);//Com scan direction
	OLED_WR_CMD(0xD3);//-set display offset
	OLED_WR_CMD(0x00);//
	
	OLED_WR_CMD(0xD5);//set osc division
	OLED_WR_CMD(0x80);//
	
	OLED_WR_CMD(0xD8);//set area color mode off
	OLED_WR_CMD(0x05);//
	
	OLED_WR_CMD(0xD9);//Set Pre-Charge Period
	OLED_WR_CMD(0xF1);//
	
	OLED_WR_CMD(0xDA);//set com pin configuartion
	OLED_WR_CMD(0x12);//
	
	OLED_WR_CMD(0xDB);//set Vcomh
	OLED_WR_CMD(0x30);//
	
	OLED_WR_CMD(0x8D);//set charge pump enable
	OLED_WR_CMD(0x14);//
	
	OLED_WR_CMD(0xAF);//--turn on oled panel

        在这里提供了OLED的初始化程序,后期可以通过向SSD1306的寄存器写入数据来更改设置,在这段语句中,OLED_WR_CMD 函数的定义是:向OLED写入 1 字节的命令。

        另外也要说一下两种通信方式分别是怎么区分指令和数据的:
        SPI:在SPI屏上有一个D/C接口(即Data/Command),SPI屏幕即是通过读取这个脚的电平来区别命令和数据的,D/C为低电平是命令,D/C为高电平时是数据

        IIC:IIC屏幕通过控制字节来区分,命令是0x00,数据是0x40

         下面我们来看一下HAL库提供的SPI/IIC通信函数:

/*这是HAL库的SPI通信示例*/
HAL_SPI_Transmit(&hspi1, data, 1024, 1000);
/*这是HAL库的IIC通信示例*/
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, 1024, CMD, 1000);

        可以看到,在SPI通信中,没有提供从机地址和寄存器地址两个参数,如果你看了我之前写的解析通信协议的那篇文章,你一定知道,这是由于SPI本身不是地址选择从机的缘故,SPI通过片选(CS)选择从机,IIC通过地址选择从机,聪明的你可能又要问:那SPI怎么访问从机的寄存器呢?关于这个问题,我们可以借鉴一下IIC的通信时序(我前面一篇也讲了),在IIC中,地址在起始位之后,数据之前,所以在SPI中也就是在向寄存器发送数据前发送这个寄存器的地址,例如:现在有一个OLED显示屏,我想要调整他的亮度(对于黑白屏即为对比度),由数据手册可以得到亮度寄存器地址是 0x81h,那么我只需要先发送一个命令为 0x81然后再发送一个命令为 0x80(亮度,取值在0x00 ~ 0xFF,即0 ~ 255)就可以实现亮度调整了

        OLED命令的部分说的差不多了,下面来看一下显示数据:

        关于OLED的显示,有三种方式,一种是页面寻址模式,即每个页是独立的,想要显示全屏需要操作完8行,示意图如下

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         此种方式的优点是抗干扰能力强,不会因一个数据错乱而影响全屏,缺点是需要传输多次数据,如果是性能较弱的单片机速度会不够快

         另一种是水平寻址模式,即全屏作为一个整体,在一行输入完后跳转到下一行,结束后再跳到第一行垂直寻址模式与此类似,也是全屏作为一个整体,但是在一列的数据输入完后跳入下一列,结束后跳到第一列,示意图如下:

水平寻址:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

垂直寻址: 

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         此种模式的优点是速度快,一次性传输完所有数据,缺点是抗干扰能力较弱少一个数据会使全屏显示错位

        这几种显示模式由SSD1306的0x20h寄存器决定,默认为0x10页面寻址模式,上面提供的初始化代码就是使用页面寻址模式,如果需要使用水平寻址(0x00)/垂直寻址(0x01),更改寄存器值为对应值即可,要注意的是,如果是页面寻址就需要设置高列和低列,其余的不用(也不用记,后面我会给出三种的初始化)

        还有一点要注意,即是否使用显存,如果使用则代表在STM32上创建一个缓冲数组来存放下一次显示的数据,优点就是操作方便,后期的画点,滚屏等操作会很简单,但缺点就是占用空间较大(其实也不完全算缺点),而不使用显存就是直接把要显示的数据从字库中提取出来或者创建,然后一个一个的写入,优点就是占用小,对低性能,小SRAM的单片机很友好,但缺点就是操作不方便,而且速度进一步降低

        当然,我还需要介绍一下IIC接口OLED和SPI接口OLED的区别,它们两个的区别就是几乎没有区别,因为它们本来用的就是同一个显示屏,只不过引出了不同的接口,做成了两个模块而已。由于只有接口不同,两种屏幕模块只在速度和写入函数上有所不同,体现在驱动程序上就是 OLED_WR_CMD 和 OLED_WR_Data 两个函数不太一样,其他的原理都是一样的

        现在,想必你已经掌握了基础知识(是不是累了?收藏文章休息会接着看吧),可以开敲代码了,这里使用 STM32CubeMX 生成配置文件以减少工作量

二,STM32CubeMX配置:

1,新建工程:

        打开STM32CubeMX,选择 Start My Project from MCU,选择你的MCU,这里我就拿最大众的 F103C8T6 举例:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

2,配置工程:

(1)配置RCC时钟:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        这里输入主频敲击回车后就可以自动配置 

(3)配置调试:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        这里要选好,不然会导致芯片自锁

(4)配置 IIC / SPI:

        为了方便后面的工作,我这里直接把IIC和SPI两个都配置了

        SPI:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         这里如果报错(即红色的叉),调整下面的分频系数 Prescaler 即可

        如果你看过我前面的文章,就会知道还需要配置一些GPIO来实现对从机的选择等操作,这里需要3个,分别是RES,DC,CS:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        这里使用了临近的3个GPIO,都定义为浮空输出,为了方便区分,在下面的 User Label 对它进行命名

        IIC:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        IIC无需进行任何额外设置

(5)配置工程:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         这里按照图中配置就好了

(6)生成文件:

        点击右上角 GENERATE CODE 生成文件,然后等待进度条跑完后点击 Open Project(前提是keil是工程文件默认打开程序)进入keil

三,在 Keil 中编写OLED驱动:

1,新建oled.c,oled.h,oledfont.h:

        注意!“.c” 文件一定要保存到文件的Core中的Src文件夹,位置如图:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

 如果是 “.h” 文件,则要保存到Core目录中的Inc文件夹(主要是看着整洁,而且Keil中有这两个路径)

        可以通过右击左边的工程目录中的文件夹图标,然后在菜单中选择 Add New Item Group 来新建:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         按照这个模式,再新建 oled.h 和 oledfont.h 即可,下面对这3个文件进行简单说明:

        oled.c:关键程序都写在这里,比如函数的定义,是大脑

        oled.h:函数的声明以及宏的定义都在这里,保证了oled.c的函数可以在主程序中调用,是神经系统

        oledfont.h:字库,基本的英文ASCII库和自己取模的汉字字模都在这里,是记忆库

        上述的3个文件共同实现了对OLED屏幕的驱动,但现在只是刚刚建好,所以下面就要为它们 “注入灵魂”,写程序

2,编写程序:

        我一般习惯于先把头文件结构写好,这样在编写源文件的时候就可以只调用头文件(这一部分是给C语言基础不好的朋友们写的,基础好的可以直接跳过)

oled.h:

#ifndef __OLED_H__
#define __OLED_H__

#include "i2c.h"
#include "spi.h"
#include "main.h"

//在下面写函数声明


#endif

        上面给出了头文件中的一般结构,在C语言中是很基础的东西,在此不做赘述

oledfont.h:

#ifndef __OLEDFONT_H__
#define __OLEDFONT_H__

//在下面写字库


#endif

        好了,头文件结构都写好了,接下来总算该写程序了,下面我就边讲边贴代码,一个函数一个函数的讲解

        首先,引用头文件:

#include "oled.h"
#include "oledfont.h"

如果这里报错就检查一下前面头文件是否写对了,以及两个头文件是否在Keil的路径中

        然后,按照一般的程序,现在就该写初始化函数了,但是在这里面,还需要先把写命令和写数据的函数写好,这里先在头文件中定义几个宏方便调用RES,DC,CS引脚:

#define OLED_RES_Clr HAL_GPIO_WritePin(RES_GPIO_Port, RES_Pin, GPIO_PIN_RESET);
#define OLED_RES_Set HAL_GPIO_WritePin(RES_GPIO_Port, RES_Pin, GPIO_PIN_SET);

#define OLED_DC_Clr HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_RESET);
#define OLED_DC_Set HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET);

#define OLED_CS_Clr HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
#define OLED_CS_Set HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);

将上面的宏定义添加到oled.h或者是oled.c的靠上部分(至少位置在写命令和写数据函数之前,不然都会调用错误),我一般习惯于写到头文件里,然后就可以回到oled.c中编写函数了

unsigned char GRAM[1024]; //定义显存,适用于SRAM较大的单片机
/*页寻址显存:
unsigned char GRAM[8][128];
*/

//写命令
void OLED_WR_CMD(unsigned char cmd){
  OLED_DC_Clr; //把 D/C 引脚拉低以表示命令
  HAL_SPI_Transmit(&hspi1,&cmd,1,1000);
  /*IIC:
	HAL_I2C_Mem_Write(&hi2c1 ,0x78,0x00,I2C_MEMADD_SIZE_8BIT,&cmd,1,1000);
  */
}

//写数据
void OLED_WR_Data(unsigned char* data){
  /*SPI:*/
  OLED_DC_Set; //把 D/C 引脚拉高以表示数据
  HAL_SPI_Transmit(&hspi1,data,1024,1000);
  /*IIC:
	HAL_I2C_Mem_Write(&hi2c1 ,0x78,0x40,I2C_MEMADD_SIZE_8BIT,data,1024,1000);
  */
}

/*页寻址:
//void OLED_WR_Data(unsigned char* data[8]){
//  OLED_DC_Set; //把 D/C 引脚拉高以表示数据
//  for(int i=0;i<8;i++){
    SPI:
//    HAL_SPI_Transmit(&hspi1,data[i],128,1000);
    IIC:
	  HAL_I2C_Mem_Write(&hi2c1 ,0x78,0x40,I2C_MEMADD_SIZE_8BIT,data[i],128,1000);
//  }
  
//}
*/

/*
如果不使用显存,则使用以下代码:
void OLED_WR_Data(unsigned char data){
  OLED_DC_Set; //把 D/C 引脚拉高以表示数据
  HAL_SPI_Transmit(&hspi1,&data,1,1000);
}
*/

上面我写了页寻址,水平寻址/垂直寻址的程序,后面就不再写了,要修改只需对显存的操作进行更改,例如:

for(int i=0;i<8;i++){
    for(int t=0;t<128;t++){
        GRAM[i*128 + t] = 0x00;
    }
}
//可以化成
for(int i=0;i<8;i++){
    for(int t=0;t<128;t++){
        GRAM[i][t] = 0x00;
    }
}

此后对于显存的操作就不再一一展示,参照上面的程序更改即可

        下面就是初始化函数了,这里使用的是水平寻址模式

void OLED_Init(void){
  HAL_Delay(200); //延时防止卡死
  /*使用水平寻址模式*/
	OLED_RES_Clr;
	HAL_Delay(80);
	OLED_RES_Set; //复位OLED
	OLED_WR_CMD(0xAE); //display off
	OLED_WR_CMD(0x20);	//Set Memory Addressing Mode	
	OLED_WR_CMD(0x00);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	OLED_WR_CMD(0xB0);	//Set Page Start Address for Page Addressing Mode,0-7
	OLED_WR_CMD(0xC8);	//Set COM Output Scan Direction
	OLED_WR_CMD(0x00); //---set low column address
	OLED_WR_CMD(0x10); //---set high column address
	OLED_WR_CMD(0x40); //--set start line address
	OLED_WR_CMD(0x81); //--set contrast control register
	OLED_WR_CMD(0xFF); //亮度调节 0x00~0xff
	OLED_WR_CMD(0xA1); //--set segment re-map 0 to 127
	OLED_WR_CMD(0xA6); //--set normal display
	OLED_WR_CMD(0xA8); //--set multiplex ratio(1 to 64)
	OLED_WR_CMD(0x3F); //
	OLED_WR_CMD(0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	OLED_WR_CMD(0xD3); //-set display offset
	OLED_WR_CMD(0x00); //-not offset
	OLED_WR_CMD(0xD5); //--set display clock divide ratio/oscillator frequency
	OLED_WR_CMD(0xF0); //--set divide ratio
	OLED_WR_CMD(0xD9); //--set pre-charge period
	OLED_WR_CMD(0x22); //
	OLED_WR_CMD(0xDA); //--set com pins hardware configuration
	OLED_WR_CMD(0x12);
	OLED_WR_CMD(0xDB); //--set vcomh
	OLED_WR_CMD(0x20); //0x20,0.77xVcc
	OLED_WR_CMD(0x8D); //--set DC-DC enable
	OLED_WR_CMD(0x14); //
	OLED_WR_CMD(0xAF); //--turn on oled panel
  /*使用页面寻址模式
	OLED_WR_CMD(0xAE);//--display off
	OLED_WR_CMD(0x00);//---set low column address
	OLED_WR_CMD(0x10);//---set high column address
	OLED_WR_CMD(0x40);//--set start line address  
	OLED_WR_CMD(0xB0);//--set page address
	OLED_WR_CMD(0x81); // contract control
	OLED_WR_CMD(0xFF);//--128   
	OLED_WR_CMD(0xA1);//set segment remap 
	OLED_WR_CMD(0xA6);//--normal / reverse
	OLED_WR_CMD(0xA8);//--set multiplex ratio(1 to 64)
	OLED_WR_CMD(0x3F);//--1/32 duty
	OLED_WR_CMD(0xC8);//Com scan direction
	OLED_WR_CMD(0xD3);//-set display offset
	OLED_WR_CMD(0x00);//
	
	OLED_WR_CMD(0xD5);//set osc division
	OLED_WR_CMD(0x80);//
	
	OLED_WR_CMD(0xD8);//set area color mode off
	OLED_WR_CMD(0x05);//
	
	OLED_WR_CMD(0xD9);//Set Pre-Charge Period
	OLED_WR_CMD(0xF1);//
	
	OLED_WR_CMD(0xDA);//set com pin configuartion
	OLED_WR_CMD(0x12);//
	
	OLED_WR_CMD(0xDB);//set Vcomh
	OLED_WR_CMD(0x30);//
	
	OLED_WR_CMD(0x8D);//set charge pump enable
	OLED_WR_CMD(0x14);//
	
	OLED_WR_CMD(0xAF);//--turn on oled panel
  */
}

        初始化函数无需过多深入研究,就是很无聊的一个寄存器一个寄存器的配置,没有什么可以学习的,直接使用厂家提供的就可以了

        现在,我们成功的将OLED初始化了,但就像一个场地,如果都是人,还怎么做实验?所以我们需要一个函数来 “清场”

/*
功能描述:清屏
*/
void OLED_Clear(void){
	for(int n=0;n<1024;n++){
		GRAM[n]=0x00;
	}
}

        现在,你的oled.c中已经具备了发送命令/数据、初始化以及清屏的能力,但这只是准备好了场地,要想实现显示,还需要后面的函数:

画点,画线:

        画点可以说是最基础的一个函数了,有了画点函数,就相当于有了画线,画圆,画矩形,画三角等等函数,只需要利用一些数学知识,把OLED显示屏看作一个坐标系,代入数学函数即可画出各种图案,各位可以自己探索,这里只给出最基本的画点,画线函数:

/*
功能描述:在OLED中画点
参数:x:x坐标;y:y坐标;mode:1,反白显示;0,正常显示
*/
void OLED_DrawPoint(int x,int y,int mode){
  int line_y,pixel_y,temp; //定义临时变量
  if(x>127||y>63){
    return;
  } //判断数据合法性
  line_y = y / 8;  //计算对应显存行数
  pixel_y = y % 8; //计算对应行的像素
  temp = 0x01 << pixel_y; //通过移位得到数据
  if(mode==0){
    GRAM[line_y*128 + x] |= temp; //通过或运算来更新显存
  }else{
    GRAM[line_y*128 + x] &= temp; //通过与运算来更新显存
  }
}

/*
功能描述:在OLED中画线
参数:x1,y1:起始坐标;x2,y2:终止坐标;mode:1,反白显示;0,正常显示
*/
void OLED_DrawLine(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2,int mode)
{
	unsigned char i = 0;                //先计算增量Δy和Δx
	char DeltaY = 0,DeltaX = 0;
	float k = 0,b = 0;									//考虑到斜率有小数的情况,所以b也写成浮点型
	if(x1>x2) {													//保持Δx为正,方便后面使用
		i = x2;x2 = x1;x1 = i;
		i = y2;y2 = y1;y1 = i;
		i = 0;
	}
	if (y1 <= y2){
		DeltaY = y2 - y1;
		DeltaX = x2 - x1;
		if(DeltaX == 0) {										//斜率k不存在时的画法
			if(y1 <= y2) {
					for(y1 = y1; y1<=y2; y1++) {
						OLED_DrawPoint(x1,y1,mode);
					}
				}else if(y1 > y2) { 
					for(y2 = y2; y2<=y1; y2++) {
						OLED_DrawPoint(x1,y2,mode);
					}
				}
		}else if(DeltaY == 0) {								//斜率k为0时的画法
			for(x1 = x1; x1<=x2; x1++) {
				OLED_DrawPoint(x1,y1,mode);
			}	
		}else	{															//斜率正常存在时的画法
			k = ((float)DeltaY)/((float)DeltaX);		//计算斜率
			b = y2 - k * x2;												//计算截距
			if(k > -1 && k < 1) {
				for(x1 = x1; x1 <= x2; x1++) {
					OLED_DrawPoint(x1,(int)(k * x1 + b),mode);
				}
			}else if (k >= 1 || k <= -1){
				for(y1 = y1; y1<=y2; y1++) {
					OLED_DrawPoint((int)((y1 - b) / k),y1,mode);
				}
			}
		}
	}else if(y1 > y2){
		int m = y1;
		y1 = y2;
		y2 = m;
		DeltaY = y2 - y1;
		DeltaX = x2 - x1;
		if(DeltaX == 0) {										//斜率k不存在时的画法
			if(y1 <= y2) {
					for(y1 = y1; y1<=y2; y1++) {
						OLED_DrawPoint(x1,y1,mode);
					}
				}else if(y1 > y2) { 
					for(y2 = y2; y2<=y1; y2++) {
						OLED_DrawPoint(x1,y2,mode);
					}
				}
		}else	{															//斜率正常存在时的画法
			k = ((float)DeltaY)/((float)DeltaX);		//计算斜率
			b = y2 - k * x2;												//计算截距
			int n = y1;
			if(k > -1 && k < 1) {
				for(x1 = x1; x1 <= x2; x1++) {
					OLED_DrawPoint(x1,(int)(y2 - (k * x1 + b) + n),mode);
				}
			}else if (k >= 1 || k <= -1){
				for(y1 = y1; y1<=y2; y1++) {
					OLED_DrawPoint((int)(y1 - b) / k,y2 - y1 + n,mode);
				}
			}
		}
	}
}
显示ASCII字符:

        现在,oled.c中已经具备画图的能力了,但是很明显,我们现代人不能和老祖先一样只用图像交流,所以我们还需要文字,需要文字就需要字库,下面给出两个尺寸ASCII字库(6*8和8*16尺寸),放在oledfont.h中,当然,你也可以直接使用系统字库,CSDN上也是有相关教程的,不过其实大同小异

PS:因为字库有点多,所以就挂上网盘下载链接,12k大小,很快就下好了

OLED字库https://pan.baidu.com/s/1Fi_6sSSdaICBMJB5QH1lQg?pwd=ao0d提取码 ao0d

         现在字库有了,下一步就是提取数据到显存使用,为了方便使用,我将此功能分为三个部分,基础的单字符提取,数字显示,字符串显示,下面是代码:

/*
功能描述:显示一个字符
参数:x,y:坐标;chr:字符;Size:尺寸12(6*8)、16(8*16);mode:1,反白显示;0,正常显示
*/
void OLED_ShowChar(int x,int y,char chr,int Size,int mode)
{
	if (mode == 0){
		unsigned char c=0,i=0;	
		c=chr-' ';                       //得到偏移后的值
		if(x>127){x=0;y=y+2;}
		if(Size ==16){
			for(i=0;i<8;i++){
				GRAM[y*128 + x + i] = F8X16[c*16+i];
			}
			for(i=0;i<8;i++){
				GRAM[(y+1) * 128 + x + i] = F8X16[c*16+i+8];
			}
		}else {	
			for(i=0;i<6;i++){
        GRAM[y*28 + x + i] = F6x8[c*6+i];
			}
		}
	}else if(mode == 1){
		unsigned char c=0,i=0;	
		c=chr-' ';
		if(x>127){x=0;y=y+2;}
		if(Size ==16){
			for(i=0;i<8;i++){
				GRAM[y*128 + x + i] = ~F8X16[c*16+i];
			}
			for(i=0;i<8;i++){
				GRAM[(y+1) * 128 + x + i] = ~F8X16[c*16+i+8];
			}
		}else {	
			for(i=0;i<6;i++){
        GRAM[y* 128 + x + i] = ~F6x8[c*6+i];
			}
		}
	}
}

/*m^n*/
int oled_pow(int x,int y)
{
	int result=1;	 
	for(int n = y;n > 0;n--){
		result *= x;
	}
	return result;
}

/*
功能描述:显示数字
参数:x,y:起点坐标;size:字体大小:num:数值(0~4294967295);mode:1,反白显示;0,正常显示
*/
void OLED_ShowNum(int x,int y,int num,int size,int mode)
{         	
	int t,te;
	int enshow,len=0;
	int temp = num;
	do {
		len++;
		temp /= 10;
	}while(temp != 0);
	for(t=0;t<len;t++)
	{
		te=(num/oled_pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(te==0){
				OLED_ShowChar(x+(size/2)*t,y,' ',size,mode);
				continue;
			}else {
				enshow=1;
			} 
		}
	 	OLED_ShowChar(x+(size/2)*t,y,te+'0',size,mode); 
	}
}

/*
功能描述:显示一个字符串
参数:x,y:起点坐标;*char:文本,用""引出;Char_Size:尺寸16/12;mode:1,反白显示;0,正常显示
*/
void OLED_ShowString(int x,int y,char *chr,int Char_Size,int mode)
{
	unsigned char j=0;
	while (chr[j]!='\0')
	{		
		OLED_ShowChar(x,y,chr[j],Char_Size,mode);
		x+=8;
		if(x>120){
			if (Char_Size == 16){
				x=0;y+=2;
			}else{
				x=0;y+=1;
			}
		}
		j++;
	}
}
显示汉字,图片: 

        现在好了,有文字,能画图,应该可以了吧,但我们中国人怎么能不显示汉字呢?而且现在只能画图,要是想要显示一张图片该怎么做呢?所以还需要两个字库和函数分别存储,显示汉字和图片(不同尺寸的要分开,同种尺寸可以放在一起)

char Hzk16[][16]={
{0x00,0x00,0x1E,0x10,0x10,0x90,0xF0,0x9F,0x90,0x90,0x90,0x90,0x1E,0x00,0x00,0x00},
{0x80,0x80,0x84,0x42,0x41,0x42,0x24,0x28,0x10,0x08,0x04,0x03,0x00,0x00,0x00,0x00},/*"岁",0*/

{0x00,0x00,0x80,0x00,0x00,0xE0,0x02,0x04,0x18,0x00,0x00,0x00,0x40,0x80,0x00,0x00},
{0x10,0x0C,0x03,0x00,0x00,0x3F,0x40,0x40,0x40,0x40,0x40,0x78,0x00,0x01,0x0E,0x00},/*"心",1*/
};

char BMP32[][128]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xA0,0x80,0x10,0x48,0xA4,0xD4,0xC8,0xC0,0xE0,0xE0,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x0B,0x00,0x18,0x31,0x33,0x0B,0x03,0x03,0x07,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x66,0x66,0x24,0x24,0xA7,0x27,0x24,0x24,0xA4,0xE6,0x00,0x00,0x00,0xFC,0xFC,0x00,0x04,0xFE,0x00,0x00,0xFE,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x06,0x02,0x02,0x02,0x03,0x03,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x01,0x03,0x00,0x06,0x07,0x06,0x06,0x06,0x06,0x00,0x01,0x00,0x00,0x00,0x00,0x00},
/*"F:\JiJin\Pictures\Saved Pictures\LOGO.bmp",0*/
};

        这个是我用PCtoLCD取的字模,大家可以自己取模,同样是放在oledfont.h中,当然,汉字也可以直接使用系统字库,这样就可以把汉字显示融合到显示字符串的函数里了,但本文作为演示,只使用单独字模。如果你仔细观察图片和汉字的字模会发现,他们两个简直就是一家人,除了尺寸不太一样,其他都是一样的,这也就是我把它们俩放在一起讲的原因,显示汉字实际上就是显示图片(其实对于除画图外的所有显示都是),现在有了前面程序的经验,可能你已经会写这个函数了,下面把我的函数贴上:

/*
功能描述:显示汉字
参数:x,y:起点坐标;no:对应数组位置;mode:1,反白显示;0,正常显示
*/
void OLED_ShowChinese(int x,int y,int no,int mode)
{
	for (int i = 0; i < 2; i++) {
		for(int t=0;t<16;t++)
		{
			if (mode == 0){
				GRAM[y * 128 + x + t] = Hzk16[2*no+i][t];
			}else if(mode == 1){
				GRAM[y * 128 + x + t] = ~Hzk16[2*no+i][t];
			}
		}
		y++;
	}
}

/*
功能描述:显示显示BMP图片
参数:x,y:起点坐标;no:对应数组位置;mode:1,反白显示;0,正常显示
*/
void OLED_ShowBMP(int x,int y,int no,int mode)
{
  int BMP_High = 32;
  int BMP_Long = 32;
  int temp;
  if(BMP_High%8 != 0){
    temp = BMP_High/8 + 1;
  }else{
    temp = BMP_High/8;
  }
	for(int i=0;i < temp;i++){
		for(int t=0;t<BMP_Long;t++)
		{
			GRAM[i * 128 + x + t] = BMP32[4*no+i][t];
		}
	}
}

        呼~,终于写完了,但是这就可以了吗?想想我们还差什么?聪明的你应该已经注意到了,我们全程都在操作显存,但是目前显存还在STM32中没有发出去!所以我们需要一个函数发送显存(如果在每个函数后面加上发送会一卡一卡的,所以单独写出)

/*
功能描述:发送显存
*/
void OLED_GRAM_Transmit(void){
  OLED_WR_Data(GRAM);
}

        这下结束了……吗?没有,再想想,在C语言中,写好函数后需要干什么?没错,就是函数声明,接着只需要在头文件 oled.h 中声明一下函数,然后主程序调用就可以测试了

void OLED_WR_CMD(unsigned char cmd);
void OLED_WR_Data(unsigned char* data);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DrawPoint(int x,int y,int mode);
void OLED_DrawLine(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2,int mode);
void OLED_ShowChar(int x,int y,char chr,int Size,int mode);
int oled_pow(int x,int y);
void OLED_ShowNum(int x,int y,int num,int size,int mode);
void OLED_ShowString(int x,int y,char *chr,int Char_Size,int mode);
void OLED_ShowChinese(int x,int y,int no,int mode);
void OLED_ShowBMP(int x,int y,int no,int mode);
void OLED_GRAM_Transmit(void);

四,测试驱动: 

        声明完后,在main.c中调用oled.h,如下图

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        这里要写在USER CODE BEGIN Includes 和 USER CODE END Includes 之间,这样下次生成配置文件就不会删掉,

        然后在初始化区域初始化OLED显示屏以及清屏,主循环写入测试程序:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        程序都要写在用户代码区,然后编译,下载……咦,怎么报错了?

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         如果出现这种情况,就在魔术棒里配置一下

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

         配置完以后,再次下载,成功下载入单片机

        硬件连接:

         SPI:

                        OLED     -------    STM32

                         GND                    GND

                         VCC                     3.3v

                      D0(SCLK)        SCLK(PA5)

                      D1(SDA)          MOSI(PA7)

                       RES                     RES(PA4)

                        DC                      DC(PB0)

                        CS                      CS(PB1)

        IIC:

                  OLED     -------    STM32

                    GND                 GND

                    VCC                  3.3v

                    SCL               SCL(PB6)

                    SDA               SDA(PB7)

效果展示:

stm32驱动spi屏幕,STM32,# STM32CubeMX开发,stm32,笔记,嵌入式硬件,单片机

        到这里,你就拥有了一个属于你自己的最基本的OLED驱动程序,当然,还有很多效果如滚屏,缩放没有做出,以后有机会我再出一篇讲讲。

        既然都看到这里了,不给个关注吗?文章来源地址https://www.toymoban.com/news/detail-758964.html

到了这里,关于STM32初学入门笔记(5):使用STM32CubeMX通过SPI,IIC驱动OLED屏幕的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • HAL库&STM32cubeMX工程软件模拟IIC,改改引脚即可直接使用

    c文件如下 h文件如下

    2024年02月14日
    浏览(23)
  • STM32硬件IIC实验(STM32CubeMx配置)

    IIC:Inter Integrated Circuit,集成电路总线,是一种 同步 串行 半双工 通信总线。 在这里贴一下硬件IIC和软件IIC的区别: 从图中可以看出两者的区别,硬件IIC比软件IIC的用法会比较复杂,但是这里如果不关注底层的实现去使用STM32CubeMx进行IIC的配置,再使用特定函数就能够实现

    2024年02月12日
    浏览(18)
  • STM32 HAL库 STM32CubeMX -- I2C(IIC)

    I2C 通讯协议(Inter - Integrated Circuit) 也就是IIC; 由Phiilps 公司开发的,它引脚少,硬件实现简单,可扩展性强,不需要USART、CAN 等通讯协议的外部收发设备。 I2C协议分为物理层和协议层。 物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输

    2023年04月16日
    浏览(18)
  • STM32单片机初学4-IIC通信(软件模拟)

    IIC ( Inter-Integrated Circuit )又称I2C(习惯读“I方C”),是 IIC Bus简称,中文名为 集成电路总线 ,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。适用于IC间的短距离数据传输。 最初的IIC通信速

    2024年02月05日
    浏览(27)
  • STM32 HAL库 STM32CubeMX -- SPI

    SPI (Serial Peripheral Interface)协议,即串行外围设备接口,是一种高速全双工的通信总线。 它被广泛地使用在ADC、LCD 等设备与MCU 间,要求通讯速率较高的场合。 SPI 通讯使用3 条总线及片选线, 3 条总线分别为SCK、MOSI、MISO,片选线为SS(CS) ,它们的作用介绍如下: (1) SS( Slav

    2024年02月13日
    浏览(25)
  • STM32单片机初学5-IIC通信驱动OLED屏幕

    在我上篇文章(STM32-软件模拟IIC通信)讲解了软件模拟IIC通信。这篇文章详将细讲解利用软件模拟IIC来控制0.96寸的OLED屏幕(如下图),使其显示字符串。本文将不再对IIC通信原理做详细讲解,所以对IIC通信原理不熟悉的话可以参考我上篇文章(点击上面的链接直接跳转)。

    2023年04月10日
    浏览(16)
  • STM32软件模拟实现IIC写入和读取AT24C02(STM32CubeMx配置)

    IIC:Inter Integrated Circuit,集成电路总线,是一种 同步 串行 半双工 通信总线。 在使用IIC时分为硬件IIC以及软件IIC,下图为两者的区别: 在使用IIC前先来了解一下IIC总线结构图,即下图: 从图中可以看出IIC有两个双向信号线,一根是数据线SDA,一根是时钟线SCL,并且都接上拉

    2024年02月04日
    浏览(18)
  • 【STM32 CubeMX】SPI HAL库编程

    STM32 CubeMX 是一款由 STMicroelectronics 提供的图形化配置工具,用于生成 STM32 微控制器的初始化代码和项目框架。在 STM32 开发中,使用 CubeMX 可以大大简化初始化过程,并帮助开发者快速构建应用程序。其中,SPI(串行外设接口)是一种常用的通信协议,它在连接外部设备时非常

    2024年02月19日
    浏览(12)
  • STM32F429IGT6使用CubeMX配置SPI通信(W25Q256芯片)

    1、硬件电路 需要系统性的看一下W25Q256芯片手册  2、设置RCC,选择高速外部时钟HSE,时钟设置为180MHz 3、配置SPI 4、生成工程配置   5、读写流程图 5、相关代码 6、实验现象 没有问题!

    2024年02月12日
    浏览(17)
  • 【STM32】入门(十):STM32CubeMx下载、安装、使用

    【STM32】STM32单片机总目录 STM32CubeMx依赖 java 环境,需要先下载安装java SE,下载地址: https://www.java.com/zh-CN/download/ STM32CubeMx下载地址:https://www.st.com/zh/development-tools/stm32cubemx.html 下载前,需要输入 邮箱 ,在邮箱里点击下载连接,即可下载 下载的软件: 双击exe,点击安装,等

    2024年01月24日
    浏览(20)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包