STM32的HAL库开发---OLED

目录

一、OLED显示屏介绍

二、OLED显示屏驱动原理

三、OLED驱动芯片简介

四、字符显示原理

五、OLED基本驱动步骤

六、OLED编程实战

一、OLED显示屏介绍

常见显示屏:LCD显示屏、点阵显示屏、OLED显示屏。LCD显示屏最常见,技术成熟。

OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机激光显示(Organic Electroluminesence Display)。

OLED显示屏就是利用有机发光二极管制成的显示屏,只要在正负极上加上正确的电压就会发光。

LCD有背光层,显示黑色不那么明显,而OLED就是把所有的发光二极管都关闭,就是很明显的黑色。

ATK_OLED模块是一块小尺寸(0.96寸)、高亮、自带升压电路的高性能OLED显示模块,分辨率为128 * 64,采用SSD1306驱动IC,该芯片内部集成DCDC升压,仅需3.3V供电。0.96英寸指的是对角线0.96英寸。

模块支持8位6800并口、8位8080并口、4线SPI以及IIC等4种通信接口。通过BS1和BS2选择通信接口。

更改通信接口需要将BS1和BS2的焊盘更改高低电平。

通过BS1和BS2选择

引脚说明:

1、CS:OLED片选信号(低电平有效)

2、WR:向OLED写入数据

3、RD:向OLED读取数据

4、D7:0:8位双向数据线

5、RST(RES):硬复位OLED(低电平有效)

6、DC:数据/命令标志(0,命令;1,数据)这个有可能还叫RS,低电平为命令,高电平为数据。

二、OLED显示屏驱动原理

OLED屏驱动的核心是:驱动OLED驱动芯片

通过驱动IC的列地址和行地址,就可以锁定一个点,如果想让这个点亮,就置1,灭就置0。

如何驱动屏幕:

1、选择驱动芯片时序 8080时序,根据时序实现数据写入/读取,通过时序就可以跟驱动IC建立通信关系了。

2、初始化时许,由厂家提供,用于初始化屏幕

3、实现画点函数、读点函数(可选)

SSD1306工作时序(8080****时序):

并口总线时序,常用于屏幕驱动IC的访问,由Intel提出,也叫英特尔总线。

总要进行读和写:读数据、读命令、写数据、写命令。

8080并口读/写的过程
1、设置DC为高(数据)/低(命令)DC引脚 根据写入/读取的数据类型
2、拉低片选CS引脚 选中SSD1306
3、设置RD/WR为低 根据是读数据还是写数据

4、拉高RD/WR,出现上升沿,在RD的上升沿,使数据锁存到数据线D7:0上,在WR的上升沿,使数据写入到SSD1306里。

写时许:

首先是设置DC引脚电平,根据是数据还是命令进行设置,然后拉低CS线,写数据所以将WR线拉低,然后准备数据,对数据线进行操作,然后将WR引脚再拉到,也就是打算写的时候将WR拉低,准备好数据后,将WR拉高,这样就出现了上升沿,这时候数据就写入到SSD1306里边。然后释放CS和DC信号线,在这个过程中,由于没有读数据,所以RD信号线可以保持高电平。

其中第一行就是操作DC引脚的,根据参数cmd看是数据还是命令。其他就是正常按上面说的步骤操作就可以。其中oled_data_out(data);是对数据线进行操作,具体函数如下:

其中GPIO的ODR寄存器是控制输出的,数据线有8位,由PC0~PC7控制,首先将ODR寄存器与OXRR00与,将低8位都清零,然后再使用数据data与OX00FF与得到数据的低8位,将这低8位与ODR寄存器或操作,即可实现数据位控制。

三、OLED驱动芯片简介

用于控制OLED的显示功能和效果。SSD1306指令比较多,下面是常用的。

1、设置显示开关0XAE和0XAF

常见的是先发送0XAE关闭显示,然后发送厂家给的配置,将OLED屏幕参数配置好,然后再发送0XAF开启屏幕显示。

2、设置页地址

B0对应页0,B1对应页1,B7对应页7

3、设置列地址L4

从0X00~0X0F,使用字节的低4位,设置8位列地址的第四位。

4、设置列地址H4

从0X10~0X1F,使用字节的低四位,设置8位列地址的高四位。

其中页地址、列地址低4位,列地址高4位三个字节连着发送。至于为啥列地址用2个字节传输而不用一个地址传输是厂家规定的。这三个字节就是设置SSD1306的GRAM写入地址。

三种设置内存地址模式:页地址模式,水平地址模式和垂直地址模式。

GRAM:图形显示数据RAM是一个位映射静态RAM,保存要显示的位模式。内存大小为128 * 64位,可分为8页,从页0到页7,用于黑白128 * 64点阵显示。

可以理解为FIFO,与物理位置有映射关系,也就是在FIFO中的特定位置,写入数据,对应屏幕的特定位置有变化。

这里GRAM大小为128*64位,一个字节8位, 64位相当于64行,8行为一页,总共有8页。也可以理解为,数据线总共8位,那么一位控制一个点,也就是一个字节的数据D0-D7可以一下控制列上的8个点,也就是8行,64行分为8页。

对于行分为SEG127~SEG0,总共128段,有一个映射关系。

发送0XA1,即可将列地址127映射到SEG0上面。发送0XC8,从COM63向COM0扫描。

一个屏幕分为8页,第一页是COM0-COM7,原点在左上角。

页地址模式:默认为页地址模式,发送0X20为内存地址模式,发送0X02为页地址模式。

对GRAM进行操作时,列地址指针会自动递增。当列地址指针到达列结束地址时,重置为开始地址,但页地址指针不变。用户必须设置新的页面和列地址,以便访问下一页GRAM内容。

图中蓝色的为数据线8条控制的,例如如果点亮SEG3的最上面那个点,数据的低位在控制上面,高位控制下面,发送0X01即可。由于0XB0~0XB7分别对应页0到页7,这个是页2所以发送0XB2,然后是第三列,也就是列地址为0X03,而列地址由两个字节决定,第一个字节的低4位决定列地址的低4位,所以先发送0X03,然后列地址的高4位有第二个字节控制,高四位为0,所以发送0X10。

这里注意如果数据线写入0X01确实可以让最上面那个亮,但是他会把下面的7个都关闭, 因为被覆盖了。解决办法就是在写入之前把左右位都弄清楚,不能想点亮那个直接就写那个,会影响其他位。8080是可以读的,但是SPI和IIC是不可以读的。在可读的状态下,先把那个字节读出来,然后将要改的位设置了,再写进去,这样非常慢。

还有一种方法就是建立一个OLED的GRAM,单片机内部需要128*8个字节,每次修改的时候,只是修改单片机上的GRAM(实际上就是SRAM),在修改完之后,一次性把单片机内部的GRAM写入到OLED的GRAM。每一次都是把单片机里建立的GRAM进行修改,然后把整个单片机GRAM都写进驱动芯片里。

代码实现:

首先创建一个二维数组,其中128代表列数,8代表页数,可以理解位当1270,操作的是屏幕最右上角的8个点。

首先看循环的第一行,i最开始从0开始,也就是设置操作第0页,下面两个表示设置列地址从第0列开始,然后使用循环将一页的数据都发送过去,发送完一页之后,发送第二页,直到将8页数据全部发送完成。

这里给出了想设置OLED某一位具体操作GRAM数组中哪个,可以看到如果想点亮第一行第2列的点,那么y就是0,因为1/8等于0,操作第0页。

画点函数程序:

画点函数主要就是对GRAM数据进行准确赋值,首先程序判断一下点坐标是否超出范围。

然后先根据y坐标计算页数,和对应字节里边的第几位。

这个图是一个字节与8个位置对应,通过对8取余数,即可得到。然后判断这个点是点亮还是熄灭,熄灭就是把这个位设置为0,也就是与上非,如果是点亮就是设置成1,也就是或运算。

四、字符显示原理

**1、ASCII字符:**基于拉丁字母的一套电脑编码系统,共128个字符:33个控制字符 和 95个显示字符。

95个显示字符以空格开始,以~结尾。0~31是控制字符,127也是控制字符,也就是总共33个控制字符。

2、内码

计算机使用的二进制字符编码,就叫内码,简称编码。

由于ASCII编码不能满足中国需求,进而出现了GB2312编码,里边包含6000多个汉字,一个汉字两个字节。GBK是对GB2312的编码扩充。

UNICODE是国际标准编码,支持各国的编码。其中UTF-8就是UNICODE的实现方式之一。

3、如何将字符显示在OLED上?

显示字符,必须先有其点阵数据,点阵数据的集合,叫做字库

单片机根据点阵数据按取模方向进行描点还原,就能显示字符

ASCII字符宽度 = 汉字宽度的一半

例子:字符A的显示,这个A是16*16的,扫描方式选择从上到下,从左到右,高位在前。这里的16*16不是长和宽,长是16,宽是8,可以理解为16*8的,总共16字节数据。

代码:

首先看循环的第一行,取出字符点阵数据的第一个字节,一个16*16的字符,总共有16个字节,扫描方式按从上到下,从左到右,高位在前,那取出来的第一个字节就是最左上角的竖着8个点。

因为要一个一个点画,所以一个一个点取数据,将temp&0X80就是先取最高位数据,与运算之后是1,就是需要画点。

然后将第一个字节左移一位,也就是为下一次画点做准备,取8位中的第二位。同时y是纵坐标也要加1,因为画点要向下移动一个点。然后第一个字节8位,也就是对应8个点,所以用一个for循环表示。一个字符16个字节,也是用一个for循环表示,这里还有一个if判断,意思就是当一个字符的一列两个字节都画完之后,就要换下一列,也就是x++。

4、ASCII字库制作(16*16)

使用PCtoLCD2002.exe软件制作ASCII字库!

1、第1步,设置字体大小

2、第2步,设置字模选项

首先设置阴码,就是共阴极,在某一处是1,就是点亮。然后设置位为逐列式,就是设置好一列之后,进行下一列。顺向就是高位在前。也就是第一位为字节的第8位。设置成输出C51格式。

第3步,输入ASCII字符集(95个)

第4步,生成字模

生成字模之后,即可看到下面出现95行数据,一行代表一个字符,16*16的,也就是一行16个字节数据。这些数据就可以放到一个大的数组里边,也就是字库。

五、OLED基本驱动步骤

目标:用最简单代码,点亮OLED屏,实现任意位置画点

1,确定IO连接关系,看开发板OLED接口原理图

2,初始化IO口,初始化连接OLED的各个IO口

3、编写8080接口函数,oled wr byte

4、编写OLED初始化函数,编写oled init函数,完成初始化序列配置,厂家会提供。

5、编写OLED画点函数,编写oled draw point函数,实现OLED任意位置画点

六、OLED编程实战

1,在OLED屏幕上画一个点(不建立OLED_GRAM)

oled.h头文件

cpp 复制代码
#ifndef __OLED_H
#define __OLED_H

#include "stm32f1xx.h"


/* OLED模式设置
 * 0: 4线串行模式  (模块的BS1,BS2均接GND)
 * 1: 并行8080模式 (模块的BS1,BS2均接VCC)
 */
#define OLED_MODE       1   /* 默认使用8080并口模式 */

/******************************************************************************************/
/* OLED SPI模式引脚 定义 */
/* 注意:这里仅定义了 OLED 4线SPI模式驱动时的 引脚定义. 8位并口访问, 由于引脚太多,就不单独定义了. */

#define OLED_SPI_RST_PORT               GPIOG
#define OLED_SPI_RST_PIN                GPIO_PIN_15
#define OLED_SPI_RST_CLK_ENABLE()       do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0)   /* PG口时钟使能 */

#define OLED_SPI_CS_PORT                GPIOD
#define OLED_SPI_CS_PIN                 GPIO_PIN_6
#define OLED_SPI_CS_CLK_ENABLE()        do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)   /* PD口时钟使能 */

#define OLED_SPI_RS_PORT                GPIOD
#define OLED_SPI_RS_PIN                 GPIO_PIN_3
#define OLED_SPI_RS_CLK_ENABLE()        do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)   /* PD口时钟使能 */

#define OLED_SPI_SCLK_PORT              GPIOC
#define OLED_SPI_SCLK_PIN               GPIO_PIN_0
#define OLED_SPI_SCLK_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define OLED_SPI_SDIN_PORT              GPIOC
#define OLED_SPI_SDIN_PIN               GPIO_PIN_1
#define OLED_SPI_SDIN_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

/******************************************************************************************/

/* OLED SPI模式相关端口控制函数 定义 
 * 注意:OLED_RST/OLED_CS/OLED_RS,这三个是和80并口模式共用的,即80模式也必须实现这3个函数!
 */
#define OLED_RST(x)     do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RST引脚 */

#define OLED_CS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_CS_PORT, OLED_SPI_CS_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_CS_PORT, OLED_SPI_CS_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置CS引脚 */
#define OLED_RS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RS引脚 */
                              
#define OLED_SCLK(x)    do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置SCLK引脚 */
#define OLED_SDIN(x)    do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置SDIN引脚 */

/* OLED 80并口模式WR,RD端口控制函数 定义 */
#define OLED_WR(x)      do{ x ? \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET) :  \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_RESET); \
                        } while (0)     /* 设置WR引脚 */

#define OLED_RD(x)      do{ x ? \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RD引脚 */

/* 命令/数据 定义 */
#define OLED_CMD        0       /* 写命令 */
#define OLED_DATA       1       /* 写数据 */

void oled_init(void);
void oled_draw_point(uint8_t x,uint8_t y);
void oled_wr_byte(uint8_t data ,uint8_t cmd);
void oled_clear(void);
#endif

oled.c源文件

cpp 复制代码
#include "./BSP/OLED/oled.h"
#include "./SYSTEM/delay/delay.h"

/**
 * @brief       初始化OLED(SSD1306)
 * @param       无
 * @retval      无
 */
void oled_init(void)
{
	//对GPIO口进行初始化
    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_GPIOC_CLK_ENABLE();     /* 使能PORTC时钟 */
    __HAL_RCC_GPIOD_CLK_ENABLE();     /* 使能PORTD时钟 */
    __HAL_RCC_GPIOG_CLK_ENABLE();     /* 使能PORTG时钟 */


    /* PC0 ~ 7 设置 */
    gpio_init_struct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;                
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(GPIOC, &gpio_init_struct);                /* PC0 ~ 7 设置 */

    gpio_init_struct.Pin = GPIO_PIN_3|GPIO_PIN_6;           /* PD3, PD6 设置 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(GPIOD, &gpio_init_struct);                /* PD3, PD6 设置 */
    
    gpio_init_struct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(GPIOG, &gpio_init_struct);                /* WR/RD/RST引脚模式设置 */

	
	//对WR RD CS RS/DC引脚进行操作
    OLED_WR(1);
    OLED_RD(1);

    OLED_CS(1);
    OLED_RS(1);

	//复位时序  手册里边的
    OLED_RST(0);
    delay_ms(100);
    OLED_RST(1);

	//oled参数配置
    oled_wr_byte(0xAE, OLED_CMD);   /* 关闭显示 */
    oled_wr_byte(0xD5, OLED_CMD);   /* 设置时钟分频因子,震荡频率 */
    oled_wr_byte(80, OLED_CMD);     /* [3:0],分频因子;[7:4],震荡频率 */
    oled_wr_byte(0xA8, OLED_CMD);   /* 设置驱动路数 */
    oled_wr_byte(0X3F, OLED_CMD);   /* 默认0X3F(1/64) */
    oled_wr_byte(0xD3, OLED_CMD);   /* 设置显示偏移 */
    oled_wr_byte(0X00, OLED_CMD);   /* 默认为0 */

    oled_wr_byte(0x40, OLED_CMD);   /* 设置显示开始行 [5:0],行数. */

    oled_wr_byte(0x8D, OLED_CMD);   /* 电荷泵设置 */
    oled_wr_byte(0x14, OLED_CMD);   /* bit2,开启/关闭 */
    oled_wr_byte(0x20, OLED_CMD);   /* 设置内存地址模式 */
    oled_wr_byte(0x02, OLED_CMD);   /* [1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; */
    oled_wr_byte(0xA1, OLED_CMD);   /* 段重定义设置,bit0:0,0->0;1,0->127; */
    oled_wr_byte(0xC8, OLED_CMD);   /* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
    oled_wr_byte(0xDA, OLED_CMD);   /* 设置COM硬件引脚配置 */
    oled_wr_byte(0x12, OLED_CMD);   /* [5:4]配置 */

    oled_wr_byte(0x81, OLED_CMD);   /* 对比度设置 */
    oled_wr_byte(0xEF, OLED_CMD);   /* 1~255;默认0X7F (亮度设置,越大越亮) */
    oled_wr_byte(0xD9, OLED_CMD);   /* 设置预充电周期 */
    oled_wr_byte(0xf1, OLED_CMD);   /* [3:0],PHASE 1;[7:4],PHASE 2; */
    oled_wr_byte(0xDB, OLED_CMD);   /* 设置VCOMH 电压倍率 */
    oled_wr_byte(0x30, OLED_CMD);   /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */

    oled_wr_byte(0xA4, OLED_CMD);   /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
    oled_wr_byte(0xA6, OLED_CMD);   /* 设置显示方式;bit0:1,反相显示;0,正常显示 */
    oled_wr_byte(0xAF, OLED_CMD);   /* 开启显示 */
	oled_clear();
}

//清屏函数
void oled_clear(void)
{
	 uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        oled_wr_byte (0xb0 + i, OLED_CMD); /* 设置页地址(0~7) */
		
		//设置列起始地址
        oled_wr_byte (0x00, OLED_CMD);     /* 设置显示位置---列低地址 */
        oled_wr_byte (0x10, OLED_CMD);     /* 设置显示位置---列高地址 */

		//发送128个0  就是把一页都设置成0
        for (n = 0; n < 128; n++)
        {
            oled_wr_byte(0x00, OLED_DATA);
        }
    }
}

void oled_draw_point(uint8_t x,uint8_t y)
{
	//计算点对应的页数 8行为1页
	uint8_t page_num = y / 8;
	
	//使用页地址模式
	//oled_wr_byte(0X20,OLED_CMD);
	
	//设置页 页从0XB0开始到0XB7  B0对应第0页
	oled_wr_byte(0xB0 + page_num,OLED_CMD);
	
	//设置列地址的低4位 前面与操作是取列地址低4位 后面那个是厂家规定的起始地址
	oled_wr_byte((x & 0X0F) + 0X00 ,OLED_CMD);
	
	//设置列地址的高4位 前面与操作是取列地址高4位 然后右移4位 因为厂家规定高四位从ox10~0x1F 后面那个是厂家规定的起始地址
	oled_wr_byte(((x & 0XF0) >> 4) + 0X10 ,OLED_CMD);
	
	//发送一字节数据 使用y%8的意思是计算处点亮这个点对应8位数据中的哪一位  然后通过移位操作即可得到要发送的数据
	oled_wr_byte( 1 << (y % 8) ,OLED_DATA);
}

//连接的是PC0~PC7
//对数据线D0~D7进行操作 首先将原来ODR的低8位清除  然后将数据的低8位设置进ODR寄存器
void oled_data_out(uint8_t data)
{
	GPIOC->ODR = (GPIOC->ODR & 0XFF00) | (data & 0X00FF);
}

//写一个字节
void oled_wr_byte(uint8_t data ,uint8_t cmd)
{
	//设置RS位 根据是命令还是数据进行设置
	OLED_RS(cmd);
	
	//拉低片选
	OLED_CS(0);
	
	//写数据 拉低WR引脚
	OLED_WR(0);
	
	oled_data_out(data);
	
	//准备好数据后 拉高WR引脚 产生上升沿
	OLED_WR(1);
	
	//释放片选
	OLED_CS(1);
	
	//释放RS/DC引脚 恢复默认
	OLED_RS(1);
}

main.c主函数

cpp 复制代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/OLED/oled.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
	sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    led_Init();                         /* LED初始化 */
	oled_init();
	oled_draw_point(0,0);
    while(1)
    { 
		LED0(1);
		LED1(0);
		delay_ms(500);
		
		LED0(0);
		LED1(1);
		delay_ms(500);
    }
}

2,在OLED屏幕上显示一个字符'A'(建立OLED_GRAM)

程序里边主要是建立一个GRAM数组,为8*128的,8代表有8页,128代表有128列,一页中的一列有8个点,整好一个字节表示。

程序里边oled_refresh函数思路就是把整个GRAM数组都发送给驱动IC,然后让OLED显示。

oled_clear函数思路就是一直发送0 ,相当于发送8*128个0。

oled_draw_point函数是画一个点的,参数为画点的坐标,通过y坐标与8取整,即可得到是第几页。然后这个函数只是设置一个点,也就是操作一个字节里边的一位,通过y坐标与8取余数,即可得到对应8位中的第几位,然后进行位操作,对GRAM进行设置。

oled_show_char函数是显示一个字符,16*16字符有16个字节数据,一列两个数据。对16个数据依次遍历,一位一位设置,通过调用前面的oled_draw_point函数进行设置,只需要知道x,y坐标,还有是否点亮。是否点亮通过位操作,看某位是0还是1,x表示列,当一列的16个点都设置完成,x++,y是横坐标,从最上边开始描点,依次向下,8个点为一个字节。

oled.h头文件

cpp 复制代码
#ifndef __OLED_H
#define __OLED_H

#include "stm32f1xx.h"


/* OLED模式设置
 * 0: 4线串行模式  (模块的BS1,BS2均接GND)
 * 1: 并行8080模式 (模块的BS1,BS2均接VCC)
 */
#define OLED_MODE       1   /* 默认使用8080并口模式 */

/******************************************************************************************/
/* OLED SPI模式引脚 定义 */
/* 注意:这里仅定义了 OLED 4线SPI模式驱动时的 引脚定义. 8位并口访问, 由于引脚太多,就不单独定义了. */

#define OLED_SPI_RST_PORT               GPIOG
#define OLED_SPI_RST_PIN                GPIO_PIN_15
#define OLED_SPI_RST_CLK_ENABLE()       do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0)   /* PG口时钟使能 */

#define OLED_SPI_CS_PORT                GPIOD
#define OLED_SPI_CS_PIN                 GPIO_PIN_6
#define OLED_SPI_CS_CLK_ENABLE()        do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)   /* PD口时钟使能 */

#define OLED_SPI_RS_PORT                GPIOD
#define OLED_SPI_RS_PIN                 GPIO_PIN_3
#define OLED_SPI_RS_CLK_ENABLE()        do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)   /* PD口时钟使能 */

#define OLED_SPI_SCLK_PORT              GPIOC
#define OLED_SPI_SCLK_PIN               GPIO_PIN_0
#define OLED_SPI_SCLK_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define OLED_SPI_SDIN_PORT              GPIOC
#define OLED_SPI_SDIN_PIN               GPIO_PIN_1
#define OLED_SPI_SDIN_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

/******************************************************************************************/

/* OLED SPI模式相关端口控制函数 定义 
 * 注意:OLED_RST/OLED_CS/OLED_RS,这三个是和80并口模式共用的,即80模式也必须实现这3个函数!
 */
#define OLED_RST(x)     do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RST引脚 */

#define OLED_CS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_CS_PORT, OLED_SPI_CS_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_CS_PORT, OLED_SPI_CS_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置CS引脚 */
#define OLED_RS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RS引脚 */
                              
#define OLED_SCLK(x)    do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置SCLK引脚 */
#define OLED_SDIN(x)    do{ x ? \
                                  HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_RESET); \
                        }while(0)       /* 设置SDIN引脚 */

/* OLED 80并口模式WR,RD端口控制函数 定义 */
#define OLED_WR(x)      do{ x ? \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET) :  \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_RESET); \
                        } while (0)     /* 设置WR引脚 */

#define OLED_RD(x)      do{ x ? \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); \
                        }while(0)       /* 设置RD引脚 */

/* 命令/数据 定义 */
#define OLED_CMD        0       /* 写命令 */
#define OLED_DATA       1       /* 写数据 */

void oled_init(void);
void oled_draw_point(uint8_t x,uint8_t y,uint8_t dot);
void oled_wr_byte(uint8_t data ,uint8_t cmd);
void oled_clear(void);
void oled_show_char(uint8_t x,uint8_t y);
void oled_refresh(void);
#endif

oled.c源文件

cpp 复制代码
#include "./BSP/OLED/oled.h"
#include "./SYSTEM/delay/delay.h"

//建立OLED的GRAM数组
static uint8_t oled_gram[8][128];

//OLED刷新函数  也就是把gram数组发送过去
void oled_refresh(void)
{
	uint8_t i,n;
	for(i = 0;i < 8;i++)
	{
		
		//首先设置页数  然后把一页的数据都发送过去
		oled_wr_byte (0xb0 + i, OLED_CMD); /* 设置页地址(0~7) */
		
		//设置列起始地址 从最左边开始
        oled_wr_byte (0x00, OLED_CMD);     /* 设置显示位置---列低地址 */
        oled_wr_byte (0x10, OLED_CMD);     /* 设置显示位置---列高地址 */

		//发送128个0  就是把一页都设置成0
        for (n = 0; n < 128; n++)
        {
            oled_wr_byte(oled_gram[i][n],OLED_DATA );
        }
	}
}

/**
 * @brief       初始化OLED(SSD1306)
 * @param       无
 * @retval      无
 */
void oled_init(void)
{
	//对GPIO口进行初始化
    GPIO_InitTypeDef gpio_init_struct;
    __HAL_RCC_GPIOC_CLK_ENABLE();     /* 使能PORTC时钟 */
    __HAL_RCC_GPIOD_CLK_ENABLE();     /* 使能PORTD时钟 */
    __HAL_RCC_GPIOG_CLK_ENABLE();     /* 使能PORTG时钟 */


    /* PC0 ~ 7 设置 */
    gpio_init_struct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;                
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(GPIOC, &gpio_init_struct);                /* PC0 ~ 7 设置 */

    gpio_init_struct.Pin = GPIO_PIN_3|GPIO_PIN_6;           /* PD3, PD6 设置 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(GPIOD, &gpio_init_struct);                /* PD3, PD6 设置 */
    
    gpio_init_struct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;        /* 中速 */
    HAL_GPIO_Init(GPIOG, &gpio_init_struct);                /* WR/RD/RST引脚模式设置 */

	
	//对WR RD CS RS/DC引脚进行操作
    OLED_WR(1);
    OLED_RD(1);

    OLED_CS(1);
    OLED_RS(1);

	//复位时序  手册里边的
    OLED_RST(0);
    delay_ms(100);
    OLED_RST(1);

	//oled参数配置
    oled_wr_byte(0xAE, OLED_CMD);   /* 关闭显示 */
    oled_wr_byte(0xD5, OLED_CMD);   /* 设置时钟分频因子,震荡频率 */
    oled_wr_byte(80, OLED_CMD);     /* [3:0],分频因子;[7:4],震荡频率 */
    oled_wr_byte(0xA8, OLED_CMD);   /* 设置驱动路数 */
    oled_wr_byte(0X3F, OLED_CMD);   /* 默认0X3F(1/64) */
    oled_wr_byte(0xD3, OLED_CMD);   /* 设置显示偏移 */
    oled_wr_byte(0X00, OLED_CMD);   /* 默认为0 */

    oled_wr_byte(0x40, OLED_CMD);   /* 设置显示开始行 [5:0],行数. */

    oled_wr_byte(0x8D, OLED_CMD);   /* 电荷泵设置 */
    oled_wr_byte(0x14, OLED_CMD);   /* bit2,开启/关闭 */
    oled_wr_byte(0x20, OLED_CMD);   /* 设置内存地址模式 */
    oled_wr_byte(0x02, OLED_CMD);   /* [1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; */
    oled_wr_byte(0xA1, OLED_CMD);   /* 段重定义设置,bit0:0,0->0;1,0->127; */
    oled_wr_byte(0xC8, OLED_CMD);   /* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
    oled_wr_byte(0xDA, OLED_CMD);   /* 设置COM硬件引脚配置 */
    oled_wr_byte(0x12, OLED_CMD);   /* [5:4]配置 */

    oled_wr_byte(0x81, OLED_CMD);   /* 对比度设置 */
    oled_wr_byte(0xEF, OLED_CMD);   /* 1~255;默认0X7F (亮度设置,越大越亮) */
    oled_wr_byte(0xD9, OLED_CMD);   /* 设置预充电周期 */
    oled_wr_byte(0xf1, OLED_CMD);   /* [3:0],PHASE 1;[7:4],PHASE 2; */
    oled_wr_byte(0xDB, OLED_CMD);   /* 设置VCOMH 电压倍率 */
    oled_wr_byte(0x30, OLED_CMD);   /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */

    oled_wr_byte(0xA4, OLED_CMD);   /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
    oled_wr_byte(0xA6, OLED_CMD);   /* 设置显示方式;bit0:1,反相显示;0,正常显示 */
    oled_wr_byte(0xAF, OLED_CMD);   /* 开启显示 */
	oled_clear();
}

//清屏函数
void oled_clear(void)
{
	 uint8_t i, n;

    for (i = 0; i < 8; i++)
    {
        oled_wr_byte (0xb0 + i, OLED_CMD); /* 设置页地址(0~7) */
		
		//设置列起始地址 从最左边开始
        oled_wr_byte (0x00, OLED_CMD);     /* 设置显示位置---列低地址 */
        oled_wr_byte (0x10, OLED_CMD);     /* 设置显示位置---列高地址 */

		//发送128个0  就是把一页都设置成0
        for (n = 0; n < 128; n++)
        {
            oled_wr_byte(0x00, OLED_DATA);
        }
    }
}

void oled_draw_point(uint8_t x,uint8_t y,uint8_t dot)
{
		
	//画点超出范围
	if(x > 127 || y > 63) return;
	
	//计算点对应的页数 8行为1页
	uint8_t page_num = y / 8;
	
	uint8_t temp;
	
	//设置点亮8位中的哪一位 因为一个字节8位 整好控制一页中的一列 一位对应一个点
	uint8_t bit;
	
	//对8取余数 即可得到对应字节第几位
	bit = y % 8;
	
	//临时调整的那一位值
	temp = (1 << bit);
	
	//点亮就设置为1  不点亮设置为0  进行位操作即可
	if(dot)
		oled_gram[page_num][x] |= temp;
	else
		oled_gram[page_num][x] &= ~temp;
}


/* 16*16大小,字符A的点阵数据数组:*/
uint8_t oled_ascii_1608[]=
{
  0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,
  0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04
} ;


void oled_show_char(uint8_t x,uint8_t y)
{
	uint8_t y0 = y;
	
	//设置第几个字节
	uint8_t t;
	
	//设置一个字节里边的第几位
	uint8_t t0;
	
	//存储一个字节的数据
	uint8_t temp;
	//显示一个16*16的需要16个字节数
	for(t = 0;t < 16;t++)
	{
		//取字库中的数据 一个数据包括8个点
		temp = oled_ascii_1608[t];
		
		//对8个点轮流设置
		for(t0 = 0;t0 < 8;t0++)
		{
			//这个点需要点亮
			if(temp & 0X80)
				oled_draw_point(x,y,1);
			else
				oled_draw_point(x,y,0);
			
			//处理完一个点处理下一个点
			temp <<= 1;
			y++;
			
			//处理完一列数据 16*16 一列16个点
			if((y - y0) == 16)
			{
				//开始第二列 纵坐标回到最上边
				y = y0;
				x++;
				break;
			}
		}
	}
}

//连接的是PC0~PC7
//对数据线D0~D7进行操作 首先将原来ODR的低8位清除  然后将数据的低8位设置进ODR寄存器
void oled_data_out(uint8_t data)
{
	GPIOC->ODR = (GPIOC->ODR & 0XFF00) | (data & 0X00FF);
}

//写一个字节
void oled_wr_byte(uint8_t data ,uint8_t cmd)
{
	//设置RS位 根据是命令还是数据进行设置
	OLED_RS(cmd);
	
	//拉低片选
	OLED_CS(0);
	
	//写数据 拉低WR引脚
	OLED_WR(0);
	
	oled_data_out(data);
	
	//准备好数据后 拉高WR引脚 产生上升沿
	OLED_WR(1);
	
	//释放片选
	OLED_CS(1);
	
	//释放RS/DC引脚 恢复默认
	OLED_RS(1);
}

main,c主函数

cpp 复制代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/OLED/oled.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
	sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    led_Init();                         /* LED初始化 */
	oled_init();
	oled_show_char(10,10);
	oled_refresh();
    while(1)
    { 
		LED0(1);
		LED1(0);
		delay_ms(500);
		
		LED0(0);
		LED1(1);
		delay_ms(500);
    }
}
相关推荐
崇山峻岭之间7 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious8 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng11 小时前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计13 小时前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu13 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu14 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32
wuyk5551 天前
24. C 语言模块化:不是拆几个.c 文件那么简单
c语言·开发语言·stm32·单片机
☆cwlulu1 天前
调试排查工具介绍(gdb、strace、Valgrind等)
开发语言·c++·嵌入式硬件·ubuntu
lzqrzpt1 天前
LED驱动电源选型标准与工程应用技术要点解析
python·单片机·嵌入式硬件·物联网