STM32基于IIC协议的OLED模块的使用
前言
一、项目涉及的内容
项目简介
二、模块实操
1. IIC模块
1.1 IIC协议格式
1.2 开始信号与停止信号
1.3 写数据
1.3.1 硬件IIC代码编写
1.3.2 软件模拟IIC代码编写
2. OLED板块
前言
本篇文章对使用IIC协议操作OLED屏幕进行了总结。
一、项目涉及的内容
本项实验的硬件组成有STM32F103C8T6开发板、OLED模块(0.96寸4针IIC接口OLED显示屏),设计到的软件模块有GPIO、IIC、系统定时器SysTick,接下来对这些主要模块进行讲解,回顾项目的重点,希望大家也能有所收获。
项目简介
通过IIC进行数据传输,操控OLED屏幕显示文字、图像。
二、模块实操
1. IIC模块
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。一个 I2C 总线只需要使用两条总线线路就可以进行通讯,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线SDA即用来表示数据,时钟线SCL用于数据收发同步。SCL由主机产生,SDA由主机或者从机产生。
I2C是同步串行通信,同时它属于半双工,也就是说同一时间SDA只能由一个设备发送信号。IIC是以字节的方式进行传输的,每次传输8位(一个字节)。
接口是通过SCL与SDA连接到IIC总线上的,接口可以下述4种模式中的一种运行:
● 从发送器模式
● 从接收器模式
● 主发送器模式
● 主接收器模式
模块默认地工作于从模式。接口在生成起始条件后自动地从从模式切换到主模式;当仲裁丢
失(两个及以上的主设备要同时通信时的优先级判定)或产生停止信号时,则从主模式切换到从模式。主模式时, I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。
1.1 IIC协议格式
IIC的通讯方式简单概括一下就是:
- 主机产生开始信号,跟在起始条件后的1或2个字节是地址(7位模式为1个字节, 10位模式为2个字节),字节的最后一位确定读/写。从机I2C接口能识别它自己的地址(7位或10位)和广播呼叫地址,I2C中与从机地址相同的从机开始与主机进行通讯。
- 第二个字节传送从机寄存器地址,也就是我们要往哪个地址传输内容,分为数据寄存器和命令寄存器两种,跟名字一样传输到对应寄存器表示要传输数据/命令。
- 第三个字节开始才是传送给从机数据看命令是多少或者数据是多少,主机每发送完一
个字节数据,都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输 N 个数据,
这个 N 没有大小限制。
图片来源:零死角玩转 STM32F103—霸道>>>第24章 I2C—读写EEPROM>>>图 24-4 I2C 通讯复合格式
1.2 开始信号与停止信号
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答
信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,
表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接
收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为
受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号可以不要。
图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3 I2C功能描述>>>24.3.1 模式选择
IIC通讯中,数据和地址都是按8位/字节进行传输,高位在前。跟在起始条件后的1或2个字节是地址(7位模式为1个字节, 10位模式为2个字节)。地址只在主模式发送。我们选择的是7位地址模式,可以看到我们发送起始信号之后,在一个字节传输的8个时钟后的第9个时钟期间,接收器必须回送一个应答位(ACK)给发送器。
1.3 写数据
IIC通讯有软件的方式和硬件的方式两种,软件方式引脚定义会更灵活,硬件方式引脚是固定的,但通讯的效率会更高一些。
1.3.1 硬件IIC代码编写
STM32的IIC片上外设专门负责实现IIC通讯协议,只要配置好这个外设,它就会自动根据协议的要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器就能完成数据收发。
stm32F1系列的硬件I2C外设默认情况下使用PB6和PB7两个引脚。
图片来源:STM32F1xx中文参考手册 >>> 8.3.9 I2C1 复用功能重映射
我们分别配置GPIO口与I2C外设的结构体成员,然后调用库函数GPIO_Init()与 I2C_Init() 把结构体的配置写入到寄存器中。
//硬件IIC初始化配置
void I2C_Configuration(void)
{GPIO_InitTypeDef GPIO_InitStructure;I2C_InitTypeDef I2C_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE); //PB6 --SCL; PB7 --SDAGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOB, &GPIO_InitStructure); //初始化GPIO配置I2C_DeInit( I2C1); //复位初始化IIC的配置I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //使能响应I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //指定地址长度,可为7或10I2C_InitStructure.I2C_ClockSpeed = 400000; //设置SCL时钟频率,400kHzI2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,可选low/high = 2:0或16:9I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //指定工作模式为IIC,可选IIC模式及SMBUS模式I2C_InitStructure.I2C_OwnAddress1 = 0X10; //自身的IIC设备地址I2C_Init( I2C1, &I2C_InitStructure); //初始化IIC外设配置I2C_Cmd( I2C1, ENABLE); //使能IIC外设
}
IIC主机发送符合协议内容的数据出去之后,IIC外设会产生相应的时间EVx,也就是IIC外设的相关寄存器会自动置0或置1,我们只要去读取对应的寄存器就可以确定事件是否发生。
图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3.1 模式选择>>>图245 主发送器传送序列图
图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3.3 I2C主模式>>>图242 I2C的功能框图
我们要发送数据,要做的有以下几件事情:
- 先检查IIC总线是否有被其他的主机使用
- 如果IIC总线处于空闲状态,我们就使用库函数I2C_GenerateSTART()产生IIC起始信号
- 发送从机地址
- 发送寄存器地址,确定是要输入命令还是数据内容
- 往命令/数据寄存器发送数据内容
- 关闭IIC总线,让IIC总线重新进入空闲状态等待被主机激活
其中除了一开始检查IIC总线是否处于繁忙状态外,每一件事情我们都要去检测对应的EVx事件是否发生,等待每一个事情触发事件后我们继续进行下一步,检测的函数以及对应的事件都已经在文件stm32f10x_i2c.h与stm32f10x_i2c.c中定义好了。
//硬件IIC写字节
void I2C_WriteByte(uint8_t addr, uint8_t data)
{//先检查I2C总线是否繁忙while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY)); //开启I2C1I2C_GenerateSTART( I2C1, ENABLE); while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //检查是否应答,等待触发EV5//发送器件地址I2C_Send7bitAddress( I2C1, Slave_Address, I2C_Direction_Transmitter); while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待触发EV6//发送寄存器地址I2C_SendData( I2C1, addr); while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); //等待触发EV8//发送数据I2C_SendData( I2C1, data); while( !I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ); //等待触发EV8_2//关闭I2C1总线I2C_GenerateSTOP( I2C1, ENABLE);
}
1.3.2 软件模拟IIC代码编写
我们也可以直接控制STM32的两个GPIO引脚分别用作SCL及SDA,然后按照上述信号的时序要求,遵循通讯协议,控制引脚的输出(如果是接收数据则是读取SDA电平),就可以实现IIC通讯。直接控制 GPIO 引脚电平产生通讯时序,由 CPU 控制每个时刻的引脚状态,这种方式称之为“软件模拟协议”方式。
我们进行初始化配置的时候,就不需要配置IIC外设了,只需要对GPIO口进行配置就可以了。
//软件模拟IIC协议初始化
void OLED_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);//PB0 --SCL; PB1 --SDA//I2C支持多个主设备与多个从设备连接在同一根总线上,如果不开漏输出,会出现短路现象GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOB, &GPIO_InitStructure);OLED_SCLK_Set(); //初始化时钟总线高电平OLED_SDIN_Set(); 初始化总线高电平
}
软件模拟IIC时序的话,就要按照IIC协议规定的方式严格执行控制引脚的输出。
图片来源:STM32F1xx中文参考手册>>>24 I2C接口>>>24.3 I2C功能描述>>>24.3.1 模式选择
//模拟I2C起始信号
static void OLED_IIC_Start(void)
{OLED_SCLK_Set(); //时钟总线高电平OLED_SDIN_Set(); //数据总线高电平us_delay(1);OLED_SDIN_Clr();us_delay(1);OLED_SCLK_Clr();us_delay(1);
}//模拟I2C结束信号
static void OLED_IIC_Stop(void)
{OLED_SDIN_Clr(); //数据总线低电平us_delay(1);OLED_SCLK_Set(); //时钟总线高电平us_delay(1);OLED_SDIN_Set();us_delay(1);
}//模拟I2C读取从机应答信号
static unsigned char IIC_Wait_Ack(void)
{unsigned char ack;OLED_SCLK_Clr(); //时钟总线低电平us_delay(1);OLED_SDIN_Set(); //数据总线高电平us_delay(1); OLED_SCLK_Set(); //时钟总线高电平us_delay(1);if(OLED_READ_SDIN()) //无应答{ack = IIC_NO_ACK;}else{ack = IIC_ACK;} OLED_SCLK_Clr(); //时钟线置低us_delay(1); return ack;
}
传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“ 1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。传输数据的时候,将SCL置低,然后设置SDA总线对应的引脚电平为高/低,SDA电平确定后再讲SCL置高,将8个位由高到低依次发送出去。
//IIC传输一个字节,传输时先从高位开始
static void Write_IIC_Byte(unsigned char IIC_Byte)
{unsigned char i; //定义变量for(i=0;i<8;i++){OLED_SCLK_Clr(); //时钟线置低us_delay(1);if(IIC_Byte & 0x80) //读取最高位OLED_SDIN_Set(); //最高位为1elseOLED_SDIN_Clr(); //最高位为0us_delay(1);OLED_SCLK_Set(); //时钟线置高,产生上升沿把数据送出去us_delay(1);IIC_Byte <<= 1; //数据左移一位}OLED_SCLK_Clr(); //时钟线置低us_delay(1);IIC_Wait_Ack(); //从机等待(这边应该是要监测返回值是否正确,当不应答时重新进行连接或者暂停传输数据)
}
2. OLED板块
我们用的0.96寸OLED显示屏芯片是SSD1306,这里我们将OLED屏幕作为从机,数据和应答都是通过SDA发送的,SSD1306的IIC通讯接口的数据总线是由SDAout和SDAin输入组成的,SDAin和SDAout绑定到了一起作为SDA,SDAout引脚可以不连接,当不连接时,应答信号将会被IIC总线忽略。
当主机通过开始条件初始化IIC通讯,并且广播的从机地址与我们要接收数据的从机地址相同时,发起开始信号的主机与从机双方将建立通讯,控制字节或数据字节开始通过SDA传输。
数据位的解读是基于D/C#位引脚的输入,如果 D/C#引脚是高,D[7:0]就被解读为写到图像显示数据 RAM( GDDRAM)中的显示数据,GDDRAM 列地址指针将会在每次数据写之后自动加 1。
如果是低, D[7:0]的输入就被解读为一个命令。然后数据输入就会被解码并写到相关的命令
寄存器中。
GDDRAM 是一个为映射静态 RAM 保存位模式来显示。该 RAM 的大小为 128 * 64 为, RAM
分为 8 页,从 PAFE0 到 PAGE7,用于单色 128 * 64 点阵显示,如下图所示
图片来源:SSD1306-OLED驱动芯片中文手册
SSD1306 中有三种不同的内存地址模式:页地址模式,水平地址模式,垂直地址模式。我们使用页地址模式,在页地址模式下,在显示 RAM 读写之后,列地址指针自动加一。如果列地址指针达到了列的结束地址,列地址指针重置为列开始地址并且页地址指针不会改变。用户需要设置新的页
和列地址来访问下一页 RAM 内容。页地址模式下 PAGE 和列地址指针的移动模式参考下图
使用下面的步骤来定义开始 RAM 访问的位置:
1. 通过命令 B0h 到 B7h 来设置目标显示位置的页开始地址
2. 通过 00h~0Fh 来设置低开始列地址的指针
3. 通过命令 10h~1Fh 来设置高开始列地址
比如说,如果页地址设置为 B2h,低列地址是 03h 高列地址为 00h,那么就意味着开始列是
PAGE2 的 SEG3.RAM 访问指针的位置如下图所示。输出数据字节将写到 RAM 列 3 的位置。
OLED屏幕的初始化代码用到的指令都是芯片手册上有固定的格式,厂家一般会提供初始化代码,格式略有不同,这部分就不贴出来了。
下边代码分别是设置屏幕显示的位置、显示字符、显示字符串、显示中文、显示图片
- 设置图像显示的起点行、列
//设置数据写入的起始行,列
//x:列的起始低地址与起始高地址
//y:起始页地址 0-7
void OLED_Set_Pos(unsigned char x, unsigned char y)
{OLED_WR_Byte( 0xb0+y, OLED_CMD); //写入页地址OLED_WR_Byte( x&0x0f, OLED_CMD); //写入列的地址,低半个字节OLED_WR_Byte( (x&0xf0)>>4 | 0x10, OLED_CMD); //写入列地址,高半个字节
}
- 设置OLED屏幕显示ASCII码字符,有8*16与6*8两种字符大小
//显示字符
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr)
{unsigned char j = 0;chr = chr - ' '; //获取字符的偏移量if( x >= Max_Column)//让x限制在128列内x=0; if( SIZE == 16) //字符8*16(高度是16,所以要分为两个上下两部分来发送){ OLED_Set_Pos(x,y); //上半段for(j=0; j<8; j++){OLED_WR_Byte( F8X16[chr*16+j],OLED_DAT);} OLED_Set_Pos(x,y+1); //下半段for(j=0; j<8; j++){OLED_WR_Byte( F8X16[chr*16+j+8],OLED_DAT);} }else if( SIZE == 6) //字符6*8{OLED_Set_Pos(x,y);for(j=0; j<6; j++)OLED_WR_Byte( F6x8[chr*8][j],OLED_DAT);} }
- 设置OLED屏幕显示字符串
//显示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr)
{unsigned char j=0;while(chr[j] != '\0') //判断是不是最后一个字符{if(SIZE == 16){if( x >= Max_Column){x=0;y=y+2;} OLED_ShowChar(x,y,chr[j]); //显示字符x=x+8; //一个字符占8列}else if(SIZE == 6){if( x >= Max_Column){x=0;y=y+1;}OLED_ShowChar(x,y,chr[j]); //显示字符 x=x+6; //一个字符占6列 }j++; //下一个字符}}
- 设置OLED屏幕显示中文字体
//显示文字
void OLED_ShowChinese(unsigned char x, unsigned char y, unsigned char list)
{unsigned char i=0;OLED_Set_Pos(x, y); //中文字体上半部分for(i=0;i<16;i++){OLED_WR_Byte( F16x16[list*32+i], OLED_DAT); //一个字体占用了16+16个字节,所以是list乘以32,下半部分就再加上16}OLED_Set_Pos(x,y+1); //中文字体下半部分for(i=0;i<16;i++){OLED_WR_Byte( F16x16[list*32+16+i], OLED_DAT);}
}
- 设置OLED屏幕显示bmp格式图像
//显示图片
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[])
{unsigned int j =0;unsigned char x,y;if(y1%8 == 0)y = y1/8;elsey = y1/8 +1;for(y=y0;y<y1;y++){OLED_SetPos(x0,y); for(x=x0;x<x1;x++){WriteDat(BMP[j++]);}}
}
STM32基于IIC协议的OLED模块的使用相关推荐
- STM32基于IIC通信协议的OLED模块使用(详解)
目录 前言 一.项目内容 实验简介 二.IIC模块 1.IIC协议简介 2.物理层 3.协议层 4.硬件IIC代码配置 5.软件模拟IIC配置 1.起始信号与停止信号 2.从机应答信号 3.数据的有效 ...
- [STC89C52RC]基于IIC协议的OLED显示字符
目录 起始信号 终止信号 应答信号 选择某一点点亮 页模式: 水平模式: 垂直模式: 选择行数 编辑 选择列数 清屏 Oled显示,我们会用到字模软件 显示图片 IIC协议 1.1 IIC协议概述 ...
- iic裸机与linux通信,基于IIC协议的Linux操作系统与裸机通信的方法
主权项: 1.一种基于IIC协议的Linux操作系统与裸机通信的方法,其特征在于,包括如下步骤:步骤S1,对采用Linux系统的多个硬件和裸机同时挂载在总线上,包括:配置一个主机设备和多个从机设备,每 ...
- STM32模拟IIC协议驱动AD7991/AD7995/AD7999芯片
STM32模拟IIC协议驱动AD7991/AD7995/AD7999芯片 AD7991/AD7995/AD7999是12位/10位/8位 4通道模拟输入的AD转换芯片 通信方式采用的是:I2C 芯片供 ...
- IIC协议与OLED
1.认识OLED 1.OLED概述: OLED(Organic Light-Emitting Diode,有机发光二极管)是一种显示技术,利用有机材料的发光特性来产生光. OLED显示器由一系列有机材 ...
- 基于IIC协议的4脚OLED模块的单片机驱动控制(含驱动程序)
文章目录 前言 OLED模块工作原理 iiciiciic总线协议 核心代码 51单片机 stm32单片机 总结 前言 嵌入式控制系统中,常常会遇到需要显示控制状态的控制场合,这种场景下往往不需要显示花 ...
- STM32驱动MPU6050基于IIC协议
一. 简介 MPU6050是一款六轴陀螺仪,可以通过IIC协议输出三个方向上的加速度和角速度,在平衡车和飞控中较为常见.一般情况下,模块有用的引脚只有四个(其它的不常用),即VCC,GND,SDA和S ...
- STM32通过SPI协议驱动OLED屏
坚持就是胜利 一.SPI协议介绍 01 简介 02 SPI物理层 03 SPI基本通讯过程 二.OLED显示器介绍 01 简介 02 接口定义 03 与STM32接线图 三.汉字取模软件介绍 01 下 ...
- STM32基于SPI接口的OLED数据显示
文章目录 一.SPI简介 1.1 什么是SPI 1.2 SPI原理 1.3 SPI的连接方式 1.4 协议层 二.OLED 2.1 OLED原理 2.2 点阵编码原理与显示 三.OLED显示实验 3. ...
最新文章
- SAP MM初阶之ERS功能展示
- cppcheck编译安装命令
- 同步中断和异步中断区别
- 【AI视野·今日Robot 机器人论文速览 第十二期】Tue, 22 Jun 2021
- 去除dataframe中的空行_Python数据分析中的处理与数值加速技巧简介
- python读取pdf文件 pdfplumber_Python pdfprumber用于PDF表提取,pythonpdfplumber,表格
- [转载] python笔记
- PTA题---求两个有序序列中位数所体现的思想。
- 记一次PLC和脉冲型伺服电机的接线方法_20210915
- 用计算机计算2的31次方,2的31次方,用什么方法可以最快算出来呢
- xp下格式化linux分区的硬盘,Windows XP 下格式化和清理磁盘(转)
- 习惯养成app_如何培养优秀的开发人员沟通技巧,养成不良习惯
- 美团财报电话会:将专注于创造外卖和到店业务的协同效应
- Kali Linux使用MSF木马入侵安卓手机
- ISO8583报文(一)
- Python 实验1-8
- 记一次面试准备(续上)
- Android高级工程师面试题-字节跳动,含BATJM大厂
- MySQL安装版本Navicat连接报错2509解决方案
- c++基础知识点(6)类的继承,构造,析构顺序,虚继承等
热门文章
- 常用类-String类
- 比大厂更香的Offer:有钱,有氛围,也有生活
- python中集合运算_Python中的集合操作与集合运算
- 02-前端-javaScript
- 30岁985大学计算机硕士,985院校硕士的心酸,今年已经30岁了,却还拿着不到5000块的工资...
- Henry前端笔记之 vertical-align与line-hight与基线详解
- php502bad gateway,经验之谈:nginx php 502 bad gateway 解决方法
- ObjectARX学习
- Python爬虫系列(二):爬取中国大学排名丁香园-用户名和回复内容淘宝品比价
- 昇腾宠粉嘉年华︱爆款豪礼送不停!