网站开发php jsp,静态购物网站模板,wordpress 主题 汉化教程,wap网站开发教程#x1f431;作者#xff1a;一只大喵咪1201 #x1f431;专栏#xff1a;《STM32学习》 #x1f525;格言#xff1a;你只管努力#xff0c;剩下的交给时间#xff01; 今天需要将代码烧录到开发板中#xff0c;本喵默认大家都会创建工程#xff0c;以及进行基本的… 作者一只大喵咪1201 专栏《STM32学习》 格言你只管努力剩下的交给时间 今天需要将代码烧录到开发板中本喵默认大家都会创建工程以及进行基本的外设配置。 I2C通信协议 | OLED屏 I2C协议数据格式I2C信号时序I2C驱动代码 OLED显示SSD1306 SSD1306的I2C总线数据格式OLED的显示OLED初始化显示字符 源码及资料总结 I2C协议
I2C协议是一种通信协议通常用来在主设备和从设备之间进行通信本喵使用的主设备是STM32F103ZET6芯片的开发板从设备使用的是SSD1306芯片驱动的OLED屏幕。 I2C在硬件上的接法如上图所示主控芯片引出两条线SCL和SDA线在一条I2C总线上可以接很多I2C设备我们还会放一个上拉电阻放一个上拉电阻的原因以后我们再说。
数据格式
写操作 如上图所示白色背景表示主→从灰色背景表示从→主具体流程如下
主芯片要发出一个start信号表示通信开始。然后发出一个字节的数据包括设备地址(用来确定是往哪一个从芯片写数据)该地址有7个比特位以及方向(读/写0表示写1表示读)这里该位是0。从设备回应(用来确定这个设备是否存在)如果存在就可以传输数据。主设备发送一个字节的数据给从设备并等待回应。每传输一个字节数据接收方要有一个回应信号确定数据是否接受完成)然后再传输下一个数据。数据发送完之后主芯片就会发送一个停止信号。
读操作 如上图所示白色背景表示主→从灰色背景表示从→主具体流程如下
主芯片要发出一个start信号表示通信开始。然后发出一个字节的数据包括设备地址(用来确定是往哪一个从芯片写数据)该地址有7个比特位以及方向(读/写0表示写1表示读)这里该位是1。从设备回应(用来确定这个设备是否存在)然后就可以接收数据。从设备发送一个字节数据给主设备并等待回应。主设备每接收一个字节数据就要有一个回应信号确定数据是否接受完成)然后再接收下一个数据。主设备认为数据接收完之后就会发送一个停止信号。 上面的写操作和读操作都是由主设备占据主动无论是开始发送数据还是接收数据从设备被动的根据方向位的值来配合主设备工作。
I2C信号时序 如上图所示便是I2C信号的时序图I2C协议中数据传输的单位是字节也就是8位。但是要用到9个时钟前面8个时钟用来传输8数据第9个时钟用来传输应答信号传输时先传输最高位(MSB)。 SDA线上传输的数据必须在SCL线为高电平期间保持稳定只能在SCL为低电平期间变化(由高到低或由低到高)。 开始信号SSCL为高电平时SDA由高电平向低电平跳变开始传送数据。结束信号PSCL为高电平时SDA由低电平向高电平跳变结束传送数据。应答信号(ACK)接收方在接收到8位数据后在第9个时钟周期拉低SDA上的电平状态。
在一个字节传输完成并且得到应答ACK信号以后需要将SCL线上的电平状态拉低一段时间为了给接收方充足的时间去处理数据避免数据覆盖。
细节
主、从设备都可以通过SDA发送数据肯定不能同时发送数据怎么错开时间
在9个时钟里前8个时钟由主设备发送数据的话第9个时钟就由从设备发送应答数据前8个时钟由从设备发送数据的话第9个时钟就由主设备发送应答数据。
双方设备中某个设备发送数据时另一方怎样才能不影响SDA上的数据
假设主设备正在给从设备发送数据但是在某个时刻从设备发生了故障或者误操作导致连接双方的SDA线有了电势差此时SDA线就导通了可能产生严重的影响甚至烧坏芯片。 如上图所示为了避免另一方对SDA线上的数据造成影响需要让双方设备的SDA中有一个三极管所以使用开极/开漏电路(三极管是开极CMOS管是开漏作用一样)并且使用上拉电阻将SDA线拉高。 开漏输出模式正好符合上面的要求所以使用I2C通信的时候需要将主设备的SDA线和SCL线所在IO口设置成开漏输出模式。从设备也必须具有开漏输出的特性。 如上图所示是GPIO的输出电路可以设置成推挽或者开漏输出模式其中TTL肖特基触发器是打开的所以IO口引脚的电平状态直接在输入数据寄存器中可以读到。
将输出设置为开漏输出模式时输出驱动器中的P-MOS管就不会在导通了只有N-MOS管在输出控制器输出低电平的时候会导通。 输出控制器输出高电平时IO引脚的电平状态由外部决定由外部上拉电阻或者通信对端决定。输出控制器输出低电平时IO口引脚接地输出低电平。 当开漏输出的IO控制器输出高电平时相当于释放了该IO口的电平状态控制权。
所以当主设备A和从设备B都使用开漏输出模式控制SDA线的时候SDA的真值表如下
ABSDA000010100111 通过真值表可以看到SDA线上是不会存在电势差的所以也不会导通。 所以接收方在接收数据之前需要给SDA口输出高电平释放控制权(写1)此时SDA上的电平状态就完全由发送方决定并且和IO口控制器输出的电平相一致。
而且双方都可以通过读取输入数据寄存器中的值来获取当前SDA线上的电平状态。 此时再看I2C通信中主设备向从设备写数据的过程
启动信号发出后前8个时钟clk
从设备不能影响SDA线所以不驱动N-MOS管从设备IO口始终输出高电平释放控制权。主设备决定数据IO口变化SDA线电平状态低电平时驱动N-MOS管SDA线电平为低高电平时不驱动N-MOS管SDA线电平被外部上拉电阻拉高。
第9个时钟clk
主设备不驱动N-MOS管IO口输出高电平释放SDA控制权。从设备决定数据因为是应答信号所以驱动N-MOS管SDA线为低电平。 在主设置经过8个clk后需要先将SCL线电平拉低同时给SDA写1保持一定时间后再将SCL线拉高读取SDA线的电平状态如果变成低说明应答到来。将SCL拉低的目的是好让从设备改变SDA线电平状态然后SCL保持高电此时读到的SDA线电平才是真实的电平状态。 为什么SCL也需要上拉呢
在第9个时钟之后如果有某一方需要更多的时间来处理数据它可以一直驱动三极管把SCL拉低也就是输出低电平。
当SCL为低电平时候大家都不应该使用I2C总线只有当SCL从低电平变为高电平的时候I2C总线才能被使用。
当它就绪后就可以不再驱动三极管这时上拉电阻把SCL变为高电平其他设备就可以继续使用I2C总线了。
I2C驱动代码
driver_i2c.h
#ifndef __DRIVER_I2C_H
#define __DRIVER_I2C_H#include stm32f1xx_hal.h/*********引脚定义**********/#define SCL_PIN GPIO_PIN_10
#define SDA_PIN GPIO_PIN_11#define SCL_PORT GPIOF
#define SDA_PORT GPIOF/*********宏定义**********/#define SCL_LOW HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET)
#define SCL_HIGH HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET)#define SDA_LOW HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_RESET)
#define SDA_HIGH HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET)
#define SDA_IN HAL_GPIO_ReadPin(SDA_PORT, SDA_PIN)/*********I2C引脚初始化**********/void extern I2C_GPIO_ReInit(void);/*********I2C驱动********/
extern void I2C_Start(void);
extern void I2C_Stop(void);
extern int I2C_GetAck(void);
extern void I2C_Ack(void);
extern void I2C_WriteByte(uint8_t data);
extern uint8_t I2C_ReadByte(uint8_t ack);#endif /*__DRIVER_I2C_H*/将用到的资源进行宏定义像SCL线电平拉高拉低SDA线电平拉高拉低等简单操作同样通过宏来实现比较复杂的操作就用函数实现这里放的是函数声明具体的定义再driver_i2c.c中下面本喵就讲解它们的实现。
I2C延时函数
/*********I2C延时函数*********/
void I2C_Delay(uint32_t cnt)
{volatile uint32_t tmp cnt;while(tmp--);
}SCL线和SDA线上的电平状态需要保持一定的时间HAL_Delay延时函数的单位是1ms所以最短延时1ms对于I2C通信来说这个时间太长了通信效率太低所以本喵自己实现了一个用来I2C延时的函数具体时间大家可以自己决定。
/*********I2C引脚初始化**********/
void I2C_GPIO_ReInit(void)
{GPIO_InitTypeDef GPIO_InitStruct {0};//实例化IO口HAL_GPIO_DeInit(SCL_PORT,SCL_PIN);HAL_GPIO_DeInit(SDA_PORT,SDA_PIN);//恢复默认__HAL_RCC_GPIOF_CLK_ENABLE();//开启IO口时钟GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD;//设置开漏输出模式GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Pin SCL_PIN;//指定SCL引脚HAL_GPIO_Init(SCL_PORT,GPIO_InitStruct);//初始化SCLIO口GPIO_InitStruct.Pin SDA_PIN;//指定SDA引脚HAL_GPIO_Init(SDA_PORT,GPIO_InitStruct);//初始化SDAIO口
}上面代码是对SDA线和SCL线IO口引脚的初始化必须设置成开漏输出模式其他部分不解释可以根据实际情况做修改。
开始信号
/**********开始***********/
void I2C_Start(void)
{SCL_HIGH;//SCL线拉高SDA_HIGH;//SDA线拉高I2C_Delay(100);//保持SDA_LOW;//SDA线拉低I2C_Delay(100);//保持
}先将SDA线和SCL线都拉高维持一段时间后将SDA线拉低再维持一段时间此时就实现了SCL高电平期间SDA由高电平变成了低电平I2C通信开始。
停止信号
/**********结束**********/
void I2C_Stop(void)
{SCL_HIGH;//SCL线拉高SDA_LOW;//SDA线拉低I2C_Delay(100);SDA_HIGH;//SDA线拉高I2C_Delay(100);
}先将SCL线拉高和SDA线拉低维持一段时间后再将SDA线拉高再维持一段时间此时就实现了SCL高电平期间SDA由低电平变成了高电平I2C通信结束。
获取应答信号
/**********获取应答**********/
int I2C_GetAck(void)
{uint16_t i 0;SCL_LOW;//SCL线拉低SDA_HIGH;//SDA线拉高I2C_Delay(100);SCL_HIGH;//SCL线拉高while(SDA_IN ! 0){//读取SDA状态一段时间i;if(i 1000) {SCL_LOW;//SCL线拉低return -1;//仍然是1返回-1表示无应答信号}}SCL_LOW;//SCL线拉低return 0;//读到低电平返回0表示这是应答信号
}主机获取应答信号时先将SCL线拉低才能将SDA线拉高然后维持一段时间再将SCL线拉高释放SDA线控制权再检测SDA线电平状态检测一定时间后如果SDA仍然是高电平说明从机没有返回应答返回-1如果SDA变成低电平说明从机返回了应答信号返回0。
发送应答信号
/***********发送应答**********/
void I2C_Ack(void)
{SCL_LOW;//SCL拉低SDA_LOW;//SDA拉低I2C_Delay(100);SCL_HIGH;//SCL拉高I2C_Delay(100);
}先将SCL先和SDA线都拉低维持一段时间后将SCL线拉高好让从机读取被主机拉低的SDA。
不发送应答信号
/**********不发送应答信号********/
void I2C_NoAck(void)
{SCL_LOW;SDA_HIGH;//SDA线不拉低I2C_Delay(100);SCL_HIGH;I2C_Delay(100);
}主机不发送应答信号时只需要维持SDA线是高电平即可。
使用I2C发送一个字节的数据
/***********发送一个字节数据**********/
void I2C_WriteByte(uint8_t data)
{uint8_t i 0;//8个比特位8个clkfor(i 0; i 8; i){SCL_LOW;//SCL拉低I2C_Delay(100);if(data 0x80){//发送数据的高位是1拉高SDASDA_HIGH;}else{//发送数据的高位是0拉低SDASDA_LOW;}data 1;//左移1位方便下次判断次高位SCL_HIGH;//SCL拉高I2C_Delay(100);}I2C_GetAck();//8个clk结束后获取应答信号
}一个字节有8个比特位所以需要8个clk来发送一个字节的数据每发送一个比特位时先将SCL拉低并维持然后判断要发送数据data的高位。
如果高位是1则将SDA拉高如果是高位是0则将SDA拉低然后将数据data左移移位方便下次判断次高位并且将SCL线拉高保持好让对方读取SDA状态。
当8个比特位全部发送完毕后去获取接收方的应答信号。 在发送一个字节数据的时候先判断的是data的高位并且通过SDA线发送所以发送一个字节是按照从高位到低位的顺序发送的。 读取一个字节数据
/**********读取一个字节数据***********/
uint8_t I2C_ReadByte(uint8_t ack)
{uint8_t i 0;uint8_t data 0;SDA_HIGH;//SDA拉高放弃控制权//8个比特位读取8次for(i 0; i 8; i){SCL_LOW;//SCL拉低让从机改变SDA状态I2C_Delay(100);SCL_HIGH;//SCL拉高I2C_Delay(100);data 1;//高位左移移位方便接收次高位if(SDA_IN 1){//SDA高电平data;}}//决定要不要给从机应答if(ack 0){I2C_Ack();//接收完毕给从机应答信号}else if(ack 1){I2C_NoAck();//接收完毕不给从机应答信号}return data;//返回接收到的数据
}从机向主机发送数据时先将SDA拉高放弃SDA线的控制权此时SDA线的状态由从机决定。一个字节8个比特位所以需要8个clk读取8次每次读取时先将SCL拉低此时从机才能改变SDA电平状态才能发送数据然后保持一段时间后再拉高SCL此时主机读到的SDA数据才是准确的。
将存放数据的data左移一位方便接收次高位当SDA线的电平是高时data加一如此反复八次。 这个过程中先接收到的比特位是高位所以会被不停左移八次读取后得到的8个比特位拼成一个字节的数据。和发送时先发送高位相对应。 一个字节数据读取完毕后根据ack形参的值决定要不要给从机应答信号最后再将接收到的数据data返回。
OLED显示
在OLED屏上还有一块驱动芯片它是用来让屏幕显示内容的我们让OLED显示内容其实就是在控制这块驱动芯片本喵使用的OLED是SSD1306驱动芯片。
SSD1306
特点
128×64点阵面板也就是一共有8192个点。有256阶对比度可调节。支持6800/8080并行总线。支持SPI、I2C串行总线。支持水平方向和垂直方向的滚动。支持行或列的重映射也就是反转方向。
设备地址 从芯片手册中可以看到该芯片的地址有7位从b1~b7其中b2~b7是固定的二进制序列是0111 10b1是由芯片的D/C引脚决定的。 从上面的芯片电路图中可以看到D/C引脚是接地的所以b1的值就是0所以该芯片的地址就是0111 100通过这7个比特位可以找到这个芯片。
b0是读写控制位1表示从该芯片中读取数据0表示向该芯片写入数据所以
0b0111 1000十六进制0x78是写数据时的设备地址。0b0111 1001十六进制0x79是读数据时的设备地址。
/***************定义设备SSD1306读写地址*************/
#define OLED_WRITE_ADDR 0x78 //写地址
#define OLED_READ_ADDR 0x79 //读地址/***************定义设备控制命令**************/
#define OLED_WRITE_CMD 0x00 //向OLED写命令
#define OLED_WRITE_DATA 0x40 //向OLED写数据在代码中使用宏来定义设备的读写地址以及告诉从设备是写命令还是写数据。 SSD1306的I2C总线数据格式 如上图所示便是和SSD1306控制芯片通信的I2C总数据格式主机STM32F103ZET6首先发送起始信号S然后发送设备地址(由7位Slave Address和1位R/W组成一个字节)再读取从机SSD1306的应答信号。
得到应答信号以后再发送一个控制字节告诉SSD1306芯片接下来的数据是控制命令还是向驱动芯片的GRAM中写入数据。 如上图所示便是控制字节其中Co位表示该字节中紧跟着的数据是仅有数据字节还是会包含控制字节默认为0D/C位为1表示紧跟着的字节数据为写入驱动芯片GRAM的数据为0则表示这是一个命令数据。
写命令
/***********写命令**********/
void OLED_WriteCmd(uint8_t cmd)
{I2C_Start();//开始信号I2C_WriteByte(OLED_WRITE_ADDR);//写从送设备地址I2C_WriteByte(OLED_WRITE_CMD);//告诉设备要写命令I2C_WriteByte(cmd);//写具体命令I2C_Stop();//停止信号
}先产生开始信号然后发送从设备SSD1306芯片地址(写函数中已经包含获取应答)再发送控制字节表明要向从设备中写命令然后再写入具体的命令cmd最后产生停止信号。
写一个字节数据
/***********写一个字节数据**********/
void OLED_WriteDate(uint8_t data)
{I2C_Start();//开始信号I2C_WriteByte(OLED_WRITE_ADDR);//写从送设备地址I2C_WriteByte(OLED_WRITE_DATA);//告诉设备要写数据I2C_WriteByte(data);//写具体数据I2C_Stop();//停止信号
}通信开始后先写从设备地址然后发送控制字节告诉从设备要写入数据再写入具体的数据最后产生停止信号。
写多个字节数据
/***********写多个字节数据*********/
void OLED_WriteNBytes(uint8_t* buffer, uint16_t length)
{uint16_t i 0;if(buffer NULL) return;//源缓冲区为空直接返回I2C_Start();//开始信号I2C_WriteByte(OLED_WRITE_ADDR);//写从设备地址I2C_WriteByte(OLED_WRITE_DATA);//告诉从设备要写数据//写入多个字节for(i 0; i length; i){I2C_WriteByte(buffer[i]);}I2C_Stop();//停止信号
}首先进行判断如果写数据的源缓冲区为空则直接返回不为空则继续执行。通信开始后同样需要写从设备地址并且告诉从设备要写数据之后多次调用写一个字节的函数发送多个字节数据最后停止通信。
OLED的显示 如上图所示便是OLED的内部示意图外部处理器STM32F103ZET6通过I2C协议将数据发送到OLED内部的MCU上然后内部MUC将数据给到GDDRAM上存储再将数据给到显示控制器然后进行行/列地址驱动最终在OLED屏幕上显示内容。 如上图所示是OLED屏幕示意图整个拼命有128×64个像素点分为128列64行由于一个字节有8个比特位所以一列中每8行对应一个字节。
64个行又划分为8页每一页有127列×8页个像素点。上图中第一页PAGE0的第一列COL0对应的8个比特位是01010101右边屏幕上对应比特位为1的像素点是白色其他为黑色。 OLED的显示其实就是在填充这128×64个像素点。 当I2C发送多个字节数据的时候显存GDDRAM又是如何保存这些数据的呢保存这些数据有三种地址模式页地址模式垂直地址模式水平地址模式。
本喵这里仅介绍最常用的页地址模式 如上图所示在页地址模式下当往显存里面写入数据后列地址指针会自动递增1所以设置好起始页和起始列之后就可以连续发送数据而不用每发送一个数据就去指定一个页和列的地址了。
如果列地址指针递增到了设置的结束列地址那么列地址指针就会复位回到设置的起始列地址而页地址指针是不会有变化的。 向下一页显存中存放数据时用户必须设置新的页和列的起始地址。 如上图所示这是从SSD1306芯片手册中截取的用来设置显存的地址模式主设备需要先向从设备发送0x20控制字节表示要设置页地址模式然后再发送一个字节范围为0x00~0x03的数据来指定地址模式。
设置地址模式
typedef enum
{H_ADDR_MODE 0, // 水平地址模式V_ADDR_MODE 1, // 垂直地址模式PAGE_ADDR_MODE 2, // 页地址模式
}MEM_MODE; // 内存地址模式static MEM_MODE mem_mode PAGE_ADDR_MODE;
void OLED_SetMemAddrMode(MEM_MODE mode)
{if((mode ! H_ADDR_MODE) (mode ! V_ADDR_MODE) (mode ! PAGE_ADDR_MODE)) return;OLED_WriteCmd(0x20);OLED_WriteCmd(mode);mem_mode mode;
}根据芯片手册所描述的给从设备发对应的数据就可以设置成页地址模式这也是一种最常用的地址模式。 如上图所示这是用来设置页起始地址的在写指令时发送一个字节范围是0xB0~0xB7的数据其中低3位的值是告诉显存要将数据存放在哪一页。
设置起始页地址
#define PAGE_ADDR_MODE_BASE 0xB0
void OLED_SetPageAddr_PAGE(uint8_t addr)
{if(mem_mode ! PAGE_ADDR_MODE) return;if(addr 7) return;OLED_WriteCmd(PAGE_ADDR_MODE_BASE addr);
}在调用该函数的时候可以指定起始页地址但是不能超过7因为一共有8页判断合法后写命令写入起始页地址的值。
芯片手册中D7~D3的值是固定的0b1011 0所以PAGE_ADDR_MODE_BASE为0xB0页地址在这个基础上作偏移即可。 还有屏幕的打开和关闭等等
#define DISP_ON() OLED_WriteCmd(0xAF) //开始显示
#define DISP_OFF() OLED_WriteCmd(0xAE) //关闭显示设置起始列地址等等功能的方法等等大家可以自己对着芯片手册去查找它的使用规则本喵后面会将源码及手册分享出来。
OLED初始化 如上图所示是OLED整个初始化过程这个过程图在芯片手册中也有我们只需要按照流程挨个调用自己实现的功能函数即可。
初始化
void OLED_Init(void)
{ OLED_SetMemAddrMode(PAGE_ADDR_MODE); // 0. 设置地址模式OLED_SetMuxRatio(0x3F); // 1. 设置多路复用率OLED_SetDispOffset(0x00); // 2. 设置显示的偏移值OLED_SetDispStartLine(0x00); // 3. 设置起始行OLED_SEG_REMAP(); // 4. 行翻转OLED_SCAN_REMAP(); // 5. 翻转扫描OLED_SetComConfig(COM_PIN_SEQ, COM_NOREMAP); // 6. COM 引脚设置OLED_SetContrastValue(0x7F); // 7. 设置对比度ENTIRE_DISPLAY_OFF(); // 8. 背景熄灭DISP_NORMAL(); // 9. 显示模式OLED_SetDCLK_Freq(0x00, 0x08); // 10. 设置分频系数和频率增值OLED_SetChargePump(PUMP_ENABLE); // 11. 使能电荷碰撞OLED_SetComConfig(COM_PIN_ALT, COM_NOREMAP); //改变显示字体大小DISP_ON(); //开始显示
}其中第4步就是让原本在右边显示变成在左边显示第5步是让原本在下面显示变成在上面显示根据屏幕摆放的位置做好调整即可本喵这里就是将原本从右下角开始显示变成从左上角开始显示。
显示字符
到目前在OLED上只能点亮指定位置的像素点如果要显示字符还需要我们将字符对应的所有像素点点亮通过字符生成工具可以直接获得要显示的字符所有对应的数据。 如上图设置成阴码显示选择列行式以及逆向取模然后输入字符A点击生成以后就会生成一个长为16字节的数组将这个数组中的数据发送给显存就会显示出来字符A。
设置显示起始位置
void OLED_SetPosition(uint8_t page, uint8_t col)
{OLED_SetPageAddr_PAGE(page); //设置页起始地址OLED_SetColAddr_PAGE(col); //设置列起始地址
}设置显示起始位置指定起始页地址和起始列地址。 uint8_t ch[16] {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20};/*A,0*/I2C_GPIO_ReInit(); //I2C的GPIO配置OLED_Init(); //初始化OLEDOLED_Clear(); //清屏OLED_SetPosition(0, 0); //设置起始位置OLED_WriteNBytes(ch,16); //发送16个字节在main.c中初始化完成后设置显示的起始地址是第0页的第0列发送A字符对应的16个字节数据显示字符A。 如上图所示但是此时显示的并不是一个完整的字符A这是因为我们用软件生成的子模是8×16的所以需要用两页来显示。 OLED_SetPosition(0, 0); //第0页OLED_WriteNBytes(ch,8); //发送8字节OLED_SetPosition(1, 0); //第1页OLED_WriteNBytes(ch 8,8);//发送8字节给第0页发送8字节数据再给第1页发送8字节数据此时字符A才能显示完整。
如上图此时一个完整的字符就显示完成了那么如果要显示字符串呢难道把所有需要的字符都生成一遍吗 同样使用该软件生成一个字库该字库中包含所有ASCII码中的所有值。 如上图所示该字库是一个二维数组其中行号就对应着ASCII码值所以根据行号就可以找到任何一个英文字母所对应的16字节数据然后发给显存即可。
显示一个字符
void OLED_PutChar(uint8_t page, uint8_t col, char c)
{OLED_SetPosition(page, col); OLED_WriteNBytes((uint8_t*)ascii_font[c][0],8);//根据ASCII码索引发送前8字节OLED_SetPosition(page 1, col);OLED_WriteNBytes((uint8_t*)ascii_font[c][8],8);//根据ASCII码索引发送后8字节
}在发送一个字符的时候同样需要两页来显示根据ASCII码值在二维数组中找到对应字符所对应的数据第一页发送前8个数据第二页发送后8个数据。
显示一个字符串
void OLED_PrintString(uint8_t page, uint8_t col, char* str)
{while(*str ! \0){OLED_PutChar(page,col,*str);col8;if(col 127){//127列显示满调整页数page 2;} if(page 7){//全部显示满后从头开始显示page 0;}str;}
}调用该函数显示字符串的时候传入一个字符串的形参通过该指针将字符串中的所有字符挨个显示出来直到遇到\0结束显示当127列显示满后就需要调整页数当页数满了以后从头开始重新显示。 EnableDebugIRQ();KEY_GPIO_ReInit();I2C_GPIO_ReInit();OLED_Init();OLED_Clear();OLED_PrintString(0,0,I Love Shanghai);在main.c中执行上面代码就会显示I Love Shanghai字符串在OLED屏幕上。
如上图成功显示字符串它的大小样式等都可以调节有兴趣的小伙伴可以自己研究一下。
源码及资料
本喵已经将源码包括I2C驱动源OLED驱动源码还有字模制作工具SSD1306驱动芯片等资源上传有需要的小伙伴可以去下载。传送门
总结
用OLED屏幕来显示字符是人机交互的一种重要方式也是本喵之后要做的小项目中的一部分I2C在这个过程中扮演了非常重要的角色通过应用OLED可以对I2C通信协议有一个清晰的认识。