STM32教程----软件I2C方式驱动SSD1306 0.96寸OLED显示屏

一、I2C协议介绍

(一)概述

        I2C(Inter-Integrated Circuit)是一种由飞利浦公司(现为恩智浦半导体)开发的串行通信协议,广泛用于连接低速外围设备到处理器或微控制器。I2C协议使用两根线进行通信:SDA(串行数据线)和SCL(串行时钟线)。

  1. 多主多从:支持多个主设备和从设备在同一总线上通信。同一时刻只有一个主机。
  2. I双线制:2C总线只需要一根数据线和一根时钟线两根(SDA、SCL)。
  3. 地址寻址:每个从设备都有一个唯一的7位或10位地址,主设备通过地址选择通信对象。
  4. 半双工通讯:数据在同一根线上双向传输,但不能同时进行。
  5. 模式:标准模式(100 kbps)、快速模式(400 kbps)、高速模式(3.4 Mbps)等。

(二)基本电路

(三)通讯协议

     1、起始信号和终止信号

        SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;

        SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。

2、数据状态

    I2C的SDA线和SCL线在空闲时为高电平状态。在进行数据传输时,SCL为高电平期间SDA必须保持稳定(可改变电平状态);只有在SCL为低电平期间,SDA才允许改变电平状态。在SCL为高电平期间,SDA线的任何改变都会被视为总线发出的起始信号或终止信号。

3、应答/非应答信号

        每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。

  • 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
  • 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

4、I2C传输协议

主设备和从设备进行数据传输时遵循以下协议格式。数据通过一条SDA数据线在主设备和从设备之间传输0和1的串行数据。串行数据序列的结构可以分为:

二、STM32的软件I2C实现基本代码

oled.h文件:(只展示一些宏定义)

#ifndef __OLED_H
#define __OLED_H

#include "main.h"

#define OLED_I2C_SDA GPIO_PIN_6 // SDA<----->PB6
#define OLED_I2C_SCL GPIO_PIN_7 // SCL<----->PB7
#define OLED_I2C_PORT GPIOB
#define OLED_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE() // 使能I2C的GPIO时钟
#define SCL_PIN_SET HAL_GPIO_WritePin(OLED_I2C_PORT, OLED_I2C_SCL, GPIO_PIN_SET)
#define SCL_PIN_RESET                                                          \
  HAL_GPIO_WritePin(OLED_I2C_PORT, OLED_I2C_SCL, GPIO_PIN_RESET)
#define SDA_PIN_SET HAL_GPIO_WritePin(OLED_I2C_PORT, OLED_I2C_SDA, GPIO_PIN_SET)
#define SDA_PIN_RESET                                                          \
  HAL_GPIO_WritePin(OLED_I2C_PORT, OLED_I2C_SDA, GPIO_PIN_RESET)

#endif //__OLED_H

(一)初始化GPIO

void OLED_I2C_Init(void)    {
    // 1、使能GPIO时钟
    OLED_CLK_ENABLE;

    // 2、初始化I2C使用到的GPIO口
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = OLED_I2C_SCL | OLED_I2C_SDA; // SCL,SDA
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;        // 推完输出模式
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;       // 低速模式
    HAL_GPIO_Init(OLED_I2C_PORT, &GPIO_InitStruct);

    // 空闲时SCL和SDA都是高电平
    HAL_GPIO_WritePin(OLED_I2C_PORT, OLED_I2C_SCL, GPIO_PIN_SET);
    HAL_GPIO_WritePin(OLED_I2C_PORT, OLED_I2C_SDA, GPIO_PIN_SET);
}

(二)起始终止信号

// 发送I2C起始信号
void I2C_Start(void) {
    SDA_PIN_SET;
    delay_us(2); // 将SDA拉高
    SCL_PIN_SET;
    delay_us(2); // 将SCL拉高
    SDA_PIN_RESET;
    delay_us(2); // 将SDA拉低
    SCL_PIN_RESET;
    delay_us(2); // 将SCL拉低
}

// 发送I2C终止信号
void I2C_Stop(void) {
    SDA_PIN_RESET;
    delay_us(2); // 将SDA拉低
    SCL_PIN_SET;
    delay_us(2); // 将SCL拉高
    SDA_PIN_SET;
    delay_us(2); // 将SDA拉高
}

(三)发送一个byte数据


void I2C_SendByte(u8 data) {
    for (u8 i = 0; i < 8; i++) { // 将data的8位从最高位依次写入
        SCL_PIN_RESET;
        delay_us(2);
        if (data & 0x80) {
            SDA_PIN_SET;
        } else {
            SDA_PIN_RESET;
        }
        delay_us(2);
        SCL_PIN_SET;
        delay_us(2);
        SCL_PIN_RESET;
        delay_us(2);
        data <<= 1; // 左移一位
    }
}

(四)接收一个byte数据

            在OLED显示中用不到:

//读取一个字节的数据
u8 I2C_ReadByte(u8 ack){
	SCL_PIN_RESET;
	SDA_IN();	//设置SDA为输入模式
	u8 ret = 0x00;

	for(u8 index = 0; index < 8; index++){
		//接收顺序 : 高位 ---> 低位
		SCL_PIN_RESET;
		delay_us(2);
		SCL_PIN_SET;
		ret <<= 1;
		if(READ_I2C_SDA() == GPIO_PIN_SET)	//读取SDA电平值
			ret++;
	}
	if(ack){
	    I2C_PIN_ACK();
	} else {
		I2C_PIN_NACK();
	}
	SCL_PIN_RESET;
	return ret;
}

(五)应答/非应答信号

//等待从机发送应答码
u8 I2C_WaitAck(void) {
    SCL_PIN_SET;
    delay_us(2);
    SCL_PIN_RESET;
    delay_us(2);
    return 1;
}

//发送应答码ACK
void MY_I2C_ACK(void){
    /*
    
    */
	SCL_PIN_RESET;
	I2C_SDA_OUT();	//SDA输出模式

	SDA_PIN_RESET;
	delay_us(2);
	SCL_PIN_SET;
	delay_us(2);
	SCL_PIN_RESET;
}

//发送非应答码NACK
void MY_I2C_NACK(void){
	SCL_PIN_RESET;
	SDA_OUT();	//SDA输出模式

	SDA_PIN_SET;
	delay_us(2);
	SCL_PIN_SET;
	delay_us(2);
	SCL_PIN_RESET;
}

三、SSD1306 0.96寸OLED显示屏

(一)上电流程

(二)写数据格式及流程

1、写数据格式

2、写数据流程

来自:江协科技

3、代码实现写一个byte数据

void OLED_SendByte(u8 data, u8 cmd) {
    I2C_Start();             // 发送一个起始信号
    I2C_SendByte(OLED_ADDR); // 发送 OLED地址
    I2C_WaitAck();           // 等待ack
    if (cmd) {                //写命令 or 写数据
        I2C_SendByte(0x40);    //写命令
    } else {
        I2C_SendByte(0x00);    //协数据
    }
    I2C_WaitAck(); // 等待ack
    I2C_SendByte(data);    //写入的数据or命令
    I2C_WaitAck();
    I2C_Stop();        //发送停止信号
}

4、SSD1306 OLED初始化代码

OLED_SendByte(0xAE,OLED_CMD);   //关闭显示

OLED_SendByte(0xD5,OLED_CMD);   //设置显示时钟分频比/振荡器频率
OLED_SendByte(0x80,OLED_CMD);

OLED_SendByte(0xA8,OLED_CMD);   //设置多路复用率
OLED_SendByte(0x3F,OLED_CMD);

OLED_SendByte(0xD3,OLED_CMD);   //设置显示偏移
OLED_SendByte(0x00,OLED_CMD);

OLED_SendByte(0x40,OLED_CMD);   //设置显示开始行

OLED_SendByte(0xA1,OLED_CMD);   //设置左右方向,0xA1正常 0xA0左右反置

OLED_SendByte(0xC8,OLED_CMD);   //设置上下方向,0xC8正常 0xC0上下反置

OLED_SendByte(0xDA,OLED_CMD);   //设置COM引脚硬件配置
OLED_SendByte(0x12,OLED_CMD);

OLED_SendByte(0x81,OLED_CMD);   //设置对比度控制
OLED_SendByte(0xCF,OLED_CMD);

OLED_SendByte(0xD9,OLED_CMD);   //设置预充电周期
OLED_SendByte(0xF1,OLED_CMD);

OLED_SendByte(0xDB,OLED_CMD);   //设置VCOMH取消选择级别
OLED_SendByte(0x30,OLED_CMD);

OLED_SendByte(0xA4,OLED_CMD);   //设置整个显示打开/关闭

OLED_SendByte(0xA6,OLED_CMD);   //设置正常/倒转显示

OLED_SendByte(0x8D,OLED_CMD);   //设置充电泵
OLED_SendByte(0x14,OLED_CMD);

OLED_SendByte(0xAF,OLED_CMD);   //开启显示

(三)OLED操作

//清屏
void OLED_Clear(void) {
    for (u8 row = 0; row < 128; row++) {
        for (u8 column = 0; column < 8; column++)
            OLED_RAM[row][column] = 0x00;
    }
    OLED_Flush();
}

//将OLED_RAM数据写入屏幕,即屏幕刷新
void OLED_Flush(void) {
    u8 i, n;
    for (i = 0; i < 8; i++) {
        OLED_SendByte(0xb0 + i, OLED_CMD); // 设置行起始地址
        OLED_SendByte(0x00, OLED_CMD);     // 设置低列起始地址
        OLED_SendByte(0x10, OLED_CMD);     // 设置高列起始地址
        for (n = 0; n < 128; n++)
            OLED_SendByte(OLED_RAM[n][i], OLED_DATA);
    }
}
void OLED_DisplayOn(void) {
    OLED_SendByte(0x8D, OLED_CMD); // 电荷泵使能
    OLED_SendByte(0x14, OLED_CMD); // 开启电荷泵
    OLED_SendByte(0xAF, OLED_CMD); // 点亮屏幕
}
void OLED_displayOff(void) {
    OLED_SendByte(0x8D, OLED_CMD); // 电荷泵使能
    OLED_SendByte(0x10, OLED_CMD); // 关闭电荷泵
    OLED_SendByte(0xAE, OLED_CMD); // 关闭屏幕
}

/*
 * 在屏幕的(x, y)位置画一个点
 * x:x坐标(0~127)
 * y:y坐标(0~63)
 * */
void OLED_DrawPoint(u8 x, u8 y) {
    u8 i, m, n;
    if (x < 0 || x > 127 || y < 0 || y > 63) {
        printf("Function OLED_DrawPoint parameter error\r\n");
        printf("0 <= x <= 127, 0 <= y <= 63\r\n");
        return;
    }
    i = y / 8;
    m = y % 8;
    n = 1 << m;
    OLED_RAM[x][i] |= n;
}
void OLED_ClearPoint(u8 x, u8 y) {
    u8 i, m, n;
    if (x < 0 || x > 127 || y < 0 || y > 63) {
        printf("Function OLED_DrawPoint parameter error\r\n");
        printf("0 <= x <= 127, 0 <= y <= 63\r\n");
        return;
    }
    i = y / 8;
    m = y % 8;
    n = 1 << m;
    OLED_RAM[x][i] = ~OLED_RAM[x][i];
    OLED_RAM[x][i] |= n;
    OLED_RAM[x][i] = ~OLED_RAM[x][i];
}
/*
 * 在屏幕的上的(x1, y1)、(x2,y2)连接画一条直线
 * x:x坐标(0~127)
 * y:y坐标(0~63)
 * */
void OLED_DrawLine(u8 x1, u8 y1, u8 x2, u8 y2) {
    if (x1 > 127 || x2 < 0 || y1 > 63 || y2 < 0 || x1 > x2 || y1 > y2) {
        printf("Function OLED_DrawLine parameter error:\r\n");
        printf("    0 <= x <= 127, 0 <= y <= 63;\r\n");
        printf("    x1 < x2, y1 < y2;\r\n");
        printf("x1=%d,y1=%d,x2=%d,y2=%d\r\n", x1, y1, x2, y2);
        return;
    }
    x1 = x1 > 0 ? x1 : 0;
    x2 = x2 > 127 ? 127 : x2;
    y1 = y1 < 0 ? 0 : y1;
    y2 = y2 > 63 ? 63 : y2;
    if (x1 == x2) { // 画竖线
        for (u8 i = 0; i < (y2 - y1); i++)
            OLED_DrawPoint(x1, y1 + i);
    } else if (y1 == y2) { // 画横线
        for (u8 i = 0; i < (x2 - x1); i++)
            OLED_DrawPoint(x1 + i, y1);
    } else {
        u8 k1 = y2 - y1;
        u8 k2 = x2 - x1;
        u8 k = k1 * 10 / k2;
        for (u8 i = 0; i < (x2 - x1); i++) {
            OLED_DrawPoint(x1 + i, y1 + i * k / 10);
        }
    }
}

//画一个矩形
void OLED_DrawRectangle(u8 x1, u8 y1, u8 x2, u8 y2, u8 fill) {
    if (x1 > 127 || x2 < 0 || y1 > 63 || y2 < 0 || x1 > x2 || y1 > y2) {
        printf("Function OLED_DrawRectangle parameter error\r\n");
        printf("0 <= x <= 127, 0 <= y <= 63\r\n");
        return;
    }
    x1 = x1 > 0 ? x1 : 0;
    x2 = x2 > 127 ? 127 : x2;
    y1 = y1 < 0 ? 0 : y1;
    y2 = y2 > 63 ? 63 : y2;
    if (fill) { // 填充
        for (u8 i = x1; i < x2; i++)
            OLED_DrawLine(i, y1, i, y2);
    } else { // 空心
        OLED_DrawLine(x1, y1, x1, y2);
        OLED_DrawLine(x1, y1, x2, y1);
        OLED_DrawLine(x1, y2, x2, y2);
        OLED_DrawLine(x2, y1, x2, y2);
    }
}


//画圆
void OLED_DrawCircle(u8 x, u8 y, u8 r, u8 fill) {
    if (x < 0 || x > 127 || y < 0 || y > 63 || r < 0) {
        printf("Function OLED_DrawPoint parameter error\r\n");
        printf("    0 <= x <= 127, 0 <= y <= 63, r > 0\r\n");
        return;
    }
    int a = 0;
    int b = r;
    int d = 3 - 2 * r;
    if (fill) {
        u8 x_Start = x - r < 0 ? 0 : x - r;
        u8 x_End = x + r > 127 ? 127 : x + r;
        u8 y_Start = y - r < 0 ? 0 : y - r;
        u8 y_End = y + r > 127 ? 127 : y + r;
        for (u8 i = x_Start; i <= x_End; i++)
            for (u8 j = y_Start; j <= y_End; j++)
                if ((x - i) * (x - i) + (y - j) * (y - j) <= r * r)
                    OLED_DrawPoint(i, j);
    } else {
        // 绘制圆的八分之一部分,然后利用对称性绘制整个圆
        while (a <= b) {

            // 绘制八个对称的点
            OLED_DrawPoint(x + a, y + b); // 第一象限
            OLED_DrawPoint(x - a, y + b); // 第二象限
            OLED_DrawPoint(x + a, y - b); // 第四象限
            OLED_DrawPoint(x - a, y - b); // 第三象限
            OLED_DrawPoint(x + b, y + a); // 第一象限(45度对称)
            OLED_DrawPoint(x - b, y + a); // 第二象限(45度对称)
            OLED_DrawPoint(x + b, y - a); // 第四象限(45度对称)
            OLED_DrawPoint(x - b, y - a); // 第三象限(45度对称)
            // 更新x和y的值,以及决策变量d
            if (d < 0) {
                d += 4 * a + 6;
                a++;
            } else {
                d += 4 * (a - b) + 10;
                a++;
                b--;
            }
        }
    }
}

//显示单个字符
void OLED_ShowChar(u8 x, u8 y, char ch, u8 size) {
    u8 i, m, temp, size2, chr1;
    u8 y0 = y;
    size2 = (size / 8 + ((size % 8) ? 1 : 0)) *
            (size / 2); // 得到字体一个字符对应点阵集所占的字节数
    chr1 = ch - ' ';    // 计算偏移后的值
    for (i = 0; i < size2; i++) {
        if (size == 12)
            temp = asc2_1206[chr1][i]; // 调用1206字体
        else if (size == 16)
            temp = asc2_1608[chr1][i]; // 调用1608字体
        else if (size == 24)
            temp = asc2_2412[chr1][i]; // 调用2412字体
        else
            return;
        for (m = 0; m < 8; m++) { // 写入数据
            if (temp & 0x80)
                OLED_DrawPoint(x, y);
            else
                OLED_ClearPoint(x, y);
            temp <<= 1;
            y++;
            if ((y - y0) == size) {
                y = y0;
                x++;
                break;
            }
        }
    }
}
// 显示字符串
// x,y:起点坐标
// size1:字体大小
//*chr:字符串起始地址
void OLED_ShowString(u8 x, u8 y, u8 *chr, u8 size1) {
    while ((*chr >= ' ') && (*chr <= '~')) { // 判断是不是非法字符!
        OLED_ShowChar(x, y, *chr, size1);
        x += size1 / 2;
        if (x > 128 - size1) { // 换行
            x = 0;
            y += 2;
        }
        chr++;
    }
}

//滚动显示
void OLED_ScrollDisplay() {
    OLED_SendByte(0x2E, OLED_CMD); // 先禁用滚动
    OLED_SendByte(0x00, OLED_CMD); // 发送一个虚拟字节

    OLED_SendByte(0x27, OLED_CMD); // 连续水平向右滚动显示
    OLED_SendByte(0x00, OLED_CMD); // 发送一个虚拟字节
    OLED_SendByte(0x00, OLED_CMD); // 开始页地址
    OLED_SendByte(0x00, OLED_CMD); // 每个滚动步骤之间的时间间隔为5帧
    OLED_SendByte(0x07, OLED_CMD); // 结束页地址

    OLED_SendByte(0x00, OLED_CMD); // 发送一个虚拟字节
    OLED_SendByte(0xff, OLED_CMD); // 发送一个虚拟字节

    OLED_SendByte(0x2F, OLED_CMD); // 开启滚动显示
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值