I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,被广泛地使用在系统内多个集成电路(IC)间的通讯。
I2C总线由数据线SDA和时钟线SCL两条线构成通讯线路,即可发送数据,也可接收数据。在CPU与被控IC之间、IC与IC之间都可以进行双向传递,最高传送速率为400kbps,各种被控器件均并联在总线上,但每个器件都有唯一的地址。
上图为I2C总线系统的硬件结构图,他的物理层有以下几个主要特点:
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广
播等环节。
I2C 通讯过程的基本结构如下:
主机写数据到从机
图例说明:
SLAVE_ADDRESS: 从机地址;
空白表示数据由从机传输至主机 :传输方向选择,1为读,0为写;
: 应答(ACK)或非应答(NACK)信号;
P:停止传输信号。
写数据的基本过程:
主机由从机中读数据
主机由从机中读数据与上述写数据过程类似,广播完地址,接收到应答信号后,从机开始向主机放回数据(DATA),数据包大小为8位,从机每发完一个数据,都会等待主机的应答信号(ACK),重复这个过程,主机就由从机读到数据。但由于是主机读数据,从机传输数据,因此当主机不希望接收数据时,会返回非应答信号(NACK),这时从机就会停止发送数据。(主动权都在主机手中)
通讯复合格式
除了基本的读写数据,I2C协议更长用到的是复合格式。该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过SLAVE_ADDRESS寻找到从机设备后,发送一段“数据”,这段数据通常用于表示从机设备内部的寄存器或存储地址(SLAVE_ADDRESS表示的是该从机区别于其他从机的地址,此处注意区分),在第二次的传输中,对该地址的内容进行读或写。即,第一次通讯是告诉从机要读写的内存地址,第二次则是实际要读写的内容。
I2C 使用SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。SDA数据线在SCL 的每个时钟周期传输一位数据。数据传送时,SLC时钟信号为高电平时SDA表示的数据有效,即此时的SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低电平时,SDA 的数据无效,数据线上的高电平或低电平状态才允许变化,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。
上文中提到的起始(S)和停止(P)信号是两种特殊的状态, 如下图:
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,先发送启动信号,再发出寻址信号,来寻找从机。
I2C 协议规定设备地址可以是 7 位或 10 位,实际中7 位的地址应用比较广泛。
紧跟设备地址的一个数据位用来表示数据传输方向(R/W')。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。
设备地址(7位)及数据传输方向
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接
收信号,写数据方向时,SDA 由主机控制,从机接收信号。
I2C协议规定,每传输一个字节数据(包含地址和命令字)后,都要有一个应答信号,以确定数据传输是否被对方接受到。应答信号由接收设备产生,包括“应答(ACK)”和“非应答(NACK)”两种信号。
作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号(SCL信号为高电平期间,接收设备将SDA拉为低电平),发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号(SCL信号为高电平期间,接收设备将SDA拉为高电平),发送方接收到该信号后会产生一个停止信号,结束信号传输。如下图:
I2C通讯一般具有两种方式,分别为软件模拟的方式或硬件 I2C 的方式。
本文以OLED显示为例,介绍ESP32中,通讯协议的使用方法。
上文中,介绍了两种不同的实现I2C通讯的方式,一种是通过软件模拟的方式,控制GPIO电平实现,另外一种是硬件实现方式。在ESP32中,已经带了硬件的I2C,因此只需要调用相关的API接口,就可以实现I2C通讯。
I2C API接口函数如下:
函数原型 | esp_err_t i2c_param_config ( i2c_port_t i2c_num, const i2c_config_t* i2c_conf ) |
函数功能 | 硬件I2C配置 |
参数 | i2c_num:I2C编号,取值:
i2c_conf:I2C参数配置 typedef struct{ }; |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
函数原型 | esp_err_t i2c_driver_install ( i2c_port_t i2c_num, i2c_mode_t mode, size_t slv_rx_buf_len, size_t slv_tx_buf_len, int intr_alloc_flags ) |
函数功能 | I2C功能安装使能函数 |
参数 | [in] i2c_num:I2C 编号 [in] mode:I2C 模式 [in] slv_rx_buf_len:接收缓存大小 [in] slv_tx_buf_len:发送缓存大小 [in] intr_alloc_flags:分配中断标记 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
函数原型 | int i2c_cmd_link_create() |
函数功能 | 创建 I2C 连接函数 |
参数 | |
返回值 | i2c_cmd_handle_t:I2C 连接的句柄 |
函数原型 | esp_err_t i2c_master_start ( i2c_cmd_handle_t cmd_handle ) |
函数功能 | I2C写启动信号到缓存函数 |
参数 | [in] cmd_handle:I2C 连接的句柄,i2c_cmd_link_create()函数的返回值 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
函数原型 | esp_err_t i2c_master_write_byte ( i2c_cmd_handle_t cmd_handle, uint8_t data, bool ack_en ) |
函数功能 | I2C 写一个字节的命令放到缓存函数 |
参数 | [in] cmd_handle:I2C 连接的句柄,i2c_cmd_link_create()函数的返回值 [in] data:发送的数据 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
函数原型 | esp_err_t i2c_master_stop ( i2c_cmd_handle_t cmd_handle ) |
函数功能 | I2C 写停止信号到缓存函数 |
参数 | [in] cmd_handle:I2C 连接的句柄,i2c_cmd_link_create()函数的返回值 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
函数原型 | esp_err_t i2c_master_cmd_begin ( i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, TickType_t ticks_to_wait ) |
函数功能 | I2C 发送函数 |
参数 | [in] i2c_num:I2C 编号 [in] cmd_handle:I2C 连接的句柄,i2c_cmd_link_create()函数的返回值 [in] ticks_to_wait:等待时间 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 ESP_FAIL:发送错误 ESP_ERR_INVALID_STATE:I2C 设备未初始化 ESP_ERR_TIMEOUT:超时 |
函数原型 | void i2c_cmd_link_delete ( i2c_cmd_handle_t cmd_handle ) |
函数功能 | 删除I2C连接函数 |
参数 | [in] cmd_handle:I2C 连接的句柄,i2c_cmd_link_create()函数的返回值 |
返回值 |
函数原型 | esp_err_t i2c_master_read_byte ( i2c_cmd_handle_t cmd_handle, uint8_t* data, i2c_ack_type_t ack ) |
函数功能 | I2C 读一个字节的命令放到缓存函数 |
参数 | [in] cmd_handle:I2C 连接的句柄,i2c_cmd_link_create()函数的返回值 [in] data:数据 [in] ack:应答的值 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display)。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
#ifndef COMPONENTS_I2C_I2COLED_H_
#define COMPONENTS_I2C_I2COLED_H_
/*
=============
头文件包含
=============
*/
#include
#include "esp_system.h"
#include
#include
#include "freertos/task.h"
#include "driver/i2c.h"
#include "fonts.h"
/*
===========================
宏定义
===========================
*/
//I2C
#define I2C_OLED_MASTER_SCL_IO 33 /*!< gpio number for I2C master clock (SCL)*/
#define I2C_OLED_MASTER_SDA_IO 32 /*!< gpio number for I2C master data (SDA)*/
#define I2C_OLED_MASTER_NUM I2C_NUM_1 /*!< I2C port number for master dev */
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write 0*/
#define READ_BIT I2C_MASTER_READ /*!< I2C master read 1*/
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */
//OLED
#define OLED_WRITE_ADDR 0x78
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64
#define WRITE_CMD 0X00
#define WRITE_DATA 0X40
#define TURN_OFF_CMD 0xAE //--turn off oled panel
#define SET1_LOW_COL_ADDR_CMD 0x00 //---set low column address
#define SET2_HI_COL_ADDR_CMD 0x10 //---set high column address
#define SET3_START_LINE_CMD 0x40 //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
#define SET4_CONTR_REG 0x81 //--set contrast control register
#define SET5_OUT_CURR 0xff // Set SEG Output Current Brightness
#define SET6_SEG_MAPPING 0xA1 //--Set SEG/Column Mapping 0xa0���ҷ��� 0xa1����
#define SET7_SCAN_DIR 0xC8 //Set COM/Row Scan Direction 0xc0���·��� 0xc8����
#define SET8_NORMAL_DIS 0xA6 //--set normal display
#define SET9_RATIO 0xA8 //--set multiplex ratio(1 to 64)
#define SET10_DUTY 0x3f //--1/64 duty
#define SET11_DIS_OFFSET 0xD3 //-set display offset Shift Mapping RAM Counter (0x00~0x3F)
#define SET12_NO_OFFSET 0x00 //-not offset
#define SET13_CLOCK_DIV 0xd5 //--set display clock divide ratio/oscillator frequency
#define SET14_CLOCK_FC 0x80 //--set divide ratio, Set Clock as 100 Frames/Sec
#define SET15_PRE_CHARGE 0xD9 //--set pre-charge period
#define SET16_PER_CHARGE_15 0xF1 //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
#define SET17_COM_PIN_CONF 0xDA //--set com pins hardware configuration
#define SET18_STG1 0x12
#define SET19_vCOMH 0xDB //--set vcomh
#define SET20_vCOM_D_LEVVEL 0x40 //Set VCOM Deselect Level
#define SET21_PAGE_ADDR_MODE 0x20 //-Set Page Addressing Mode (0x00/0x01/0x02)
#define SET22_STG2 0x02 //
#define SET23_CHARGE_PUMP 0x8D //--set Charge Pump enable/disable
#define SET24_DIS_ 0x14 //--set(0x10) disable
#define SET25_ENTIRE_DIS 0xA4 // Disable Entire Display On (0xa4/0xa5)
#define SET26_INV_DIS 0xA6 // Disable Inverse Display On (0xa6/a7)
#define TURN_ON_CMD 0xAF //--turn on oled panel
//显示1,擦除0
typedef enum {
SSD1306_COLOR_BLACK = 0x00, /*!< Black color, no pixel */
SSD1306_COLOR_WHITE = 0x01 /*!< Pixel is set. Color depends on LCD */
} SSD1306_COLOR_t;
typedef struct {
uint16_t CurrentX;
uint16_t CurrentY;
uint8_t Inverted;
uint8_t Initialized;
} SSD1306_t;
void i2c_inti(void);
void oled_init(void);
void oled_claer(void);
void oled_all_on(void);
void oled_set_pos(uint8_t x,uint8_t y);
int oled_write_cmd(uint8_t command);
int oled_write_data(uint8_t data);
void clean_oled_buff(void);
void oled_update_screen(void);
int oled_write_lang_data(uint8_t *data,uint16_t len);
void oled_drawpixel(uint16_t x, uint16_t y, SSD1306_COLOR_t color);
void oled_gotoXY(uint16_t x, uint16_t y) ;
char oled_show_char(uint16_t x, uint16_t y,char ch, FontDef_t* Font, SSD1306_COLOR_t color) ;
char oled_show_str(uint16_t x, uint16_t y,char* str, FontDef_t* Font, SSD1306_COLOR_t color) ;
#endif /* COMPONENTS_I2C_I2COLED_H_ */
#include "I2COled.h"
//#include "oledfont.h"
#include "string.h"
#include "stdlib.h"
/*
===========================
全局变量定义
===========================
*/
//OLED缓存128*64bit
static uint8_t g_oled_buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8];
//OLED实时信息
static SSD1306_t oled;
//OLED是否正在显示,1显示,0等待
static bool is_show_str =0;
/*
===========================
函数定义
===========================
*/
/**
* oled_i2c 初始化
* @param[in] NULL
* @retval
* NULL
*/
void i2c_init(void)
{
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = I2C_OLED_MASTER_SDA_IO; //SDA IO映射
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = I2C_OLED_MASTER_SCL_IO; //SCL IO映射
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = 400000; //I2C CLK 频率
i2c_param_config(I2C_OLED_MASTER_NUM, &conf); //配置I2C
i2c_driver_install(I2C_OLED_MASTER_NUM, conf.mode,0, 0, 0); //I2C功能安装使能函数
}
/**
* 向oled写命令
* @param[in] command
* @retval
*/
int oled_write_cmd(uint8_t command)
{
int ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); //创建I2C连接函数
ret = i2c_master_start(cmd); //写启动信号到缓存函数
ret = i2c_master_write_byte(cmd, OLED_WRITE_ADDR |WRITE_BIT , ACK_CHECK_EN); //写数据
ret = i2c_master_write_byte(cmd, WRITE_CMD, ACK_CHECK_EN);
ret = i2c_master_write_byte(cmd,command, ACK_CHECK_EN);
ret = i2c_master_stop(cmd); //写停止信号
ret = i2c_master_cmd_begin(I2C_OLED_MASTER_NUM, cmd, 100 / portTICK_RATE_MS); //I2C发送函数
i2c_cmd_link_delete(cmd); //删除I2C链接
if (ret != ESP_OK)
{
return ret;
}
return ret;
}
/**
* 向oled写数据
* @param[in] data
* @retval
* - ESP_OK
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
int oled_write_data(uint8_t data)
{
int ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
ret = i2c_master_start(cmd);
ret = i2c_master_write_byte(cmd, OLED_WRITE_ADDR | WRITE_BIT, ACK_CHECK_EN);
ret = i2c_master_write_byte(cmd, WRITE_DATA, ACK_CHECK_EN);
ret = i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
ret = i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(I2C_OLED_MASTER_NUM, cmd, 100 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK)
{
return ret;
}
return ret;
}
/**
* 向oled写长数据
* @param[in] data 要写入的数据
* @param[in] len 数据长度
* @retval
* - ESP_OK
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
int oled_write_long_data(uint8_t *data,uint16_t len)
{
//注释参考sht30之i2c教程
int ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
ret = i2c_master_start(cmd);
ret = i2c_master_write_byte(cmd, OLED_WRITE_ADDR | WRITE_BIT, ACK_CHECK_EN);
ret = i2c_master_write_byte(cmd, WRITE_DATA, ACK_CHECK_EN);
ret = i2c_master_write(cmd, data, len,ACK_CHECK_EN);
ret = i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(I2C_OLED_MASTER_NUM, cmd, 10000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK)
{
return ret;
}
return ret;
}
/**
* 初始化 oled
* @param[in] NULL
* @retval
* NULL
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
void oled_init(void)
{
//i2c初始化
i2c_init();
//oled配置
oled_write_cmd(TURN_OFF_CMD);
oled_write_cmd(0xAE);//关显示
oled_write_cmd(0X20);//低列地址
oled_write_cmd(0X10);//高列地址
oled_write_cmd(0XB0);//
oled_write_cmd(0XC8);
oled_write_cmd(0X00);
oled_write_cmd(0X10);
//设置行显示的开始地址(0-63)
//40-47: (01xxxxx)
oled_write_cmd(0X40);
//设置对比度
oled_write_cmd(0X81);
oled_write_cmd(0XFF);//这个值越大,屏幕越亮(和上条指令一起使用)(0x00-0xff)
oled_write_cmd(0XA1);//0xA1: 左右反置, 0xA0: 正常显示(默认0xA0)
//0xA6: 表示正常显示(在面板上1表示点亮,0表示不亮)
//0xA7: 表示逆显示(在面板上0表示点亮,1表示不亮)
oled_write_cmd(0XA6);
oled_write_cmd(0XA8);//设置多路复用率(1-64)
oled_write_cmd(0X3F);//(0x01-0x3f)(默认为3f)
oled_write_cmd(0XA4);
//设置显示抵消移位映射内存计数器
oled_write_cmd(0XD3);
oled_write_cmd(0X00);
//设置显示时钟分频因子/振荡器频率
oled_write_cmd(0XD5);
//低4位定义显示时钟(屏幕的刷新时间)(默认:0000)分频因子= [3:0]+1
//高4位定义振荡器频率(默认:1000)
oled_write_cmd(0XF0);
//时钟预充电周期
oled_write_cmd(0XD9);
oled_write_cmd(0X22);
//设置COM硬件应脚配置
oled_write_cmd(0XDA);
oled_write_cmd(0X12);
oled_write_cmd(0XDB);
oled_write_cmd(0X20);
//电荷泵设置(初始化时必须打开,否则看不到显示)
oled_write_cmd(0X8D);
oled_write_cmd(0X14);
//开显示
oled_write_cmd(0XAF);
//清屏
oled_claer();
}
/**
* 将显存内容刷新到oled显示区
* @param[in] NULL
* @retval
* NULL
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
void oled_update_screen(void)
{
uint8_t line_index;
for(line_index=0 ; line_index<8 ; line_index++)
{
oled_write_cmd(0xb0+line_index);
oled_write_cmd(0x00);
oled_write_cmd(0x10);
oled_write_long_data(&g_oled_buffer[SSD1306_WIDTH * line_index],SSD1306_WIDTH);
}
}
/**
* 清屏
* @param[in] NULL
* @retval
* NULL
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
void oled_claer(void)
{
//清0缓存
memset(g_oled_buffer,SSD1306_COLOR_BLACK,sizeof(g_oled_buffer));
oled_update_screen();
}
/**
* 填屏
* @param[in] NULL
* @retval
* NULL
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
void oled_all_on(void)
{
//置ff缓存
memset(g_oled_buffer,0xff,sizeof(g_oled_buffer));
oled_update_screen();
}
/**
* 移动坐标
* @param[in] x 显示区坐标 x
* @param[in] y 显示去坐标 y
* @retval
* 其它
* @par 修改日志
* Ver0.0.1:
XinC_Guo, 2018/07/18, 初始化版本\n
*/
void oled_gotoXY(uint16_t x, uint16_t y)
{
oled.CurrentX = x;
oled.CurrentY = y;
}
/**
* 向显存写入
* @param[in] x 坐标
* @param[in] y 坐标
* @param[in] color 色值0/1
* @retval
*/
void oled_drawpixel(uint16_t x, uint16_t y, SSD1306_COLOR_t color)
{
if (
x >= SSD1306_WIDTH ||
y >= SSD1306_HEIGHT
)
{
return;
}
if (color == SSD1306_COLOR_WHITE)
{
g_oled_buffer[x + (y / 8) * SSD1306_WIDTH] |= 1 << (y % 8);
}
else
{
g_oled_buffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8));
}
}
/**
* 在x,y位置显示字符
* @param[in] x 显示坐标x
* @param[in] y 显示坐标y
* @param[in] ch 要显示的字符
* @param[in] font 显示的字形
* @param[in] color 颜色 1显示 0不显示
* @retval
*/
char oled_show_char(uint16_t x,uint16_t y,char ch, FontDef_t* Font, SSD1306_COLOR_t color)
{
uint32_t i, b, j;
if ( SSD1306_WIDTH <= (oled.CurrentX + Font->FontWidth) || SSD1306_HEIGHT <= (oled.CurrentY + Font->FontHeight) )
{
return 0;
}
if(0 == is_show_str)
{
oled_gotoXY(x,y);
}
for (i = 0; i < Font->FontHeight; i++)
{
b = Font->data[(ch - 32) * Font->FontHeight + i];
for (j = 0; j < Font->FontWidth; j++)
{
if ((b << j) & 0x8000)
{
oled_drawpixel(oled.CurrentX + j, (oled.CurrentY + i), (SSD1306_COLOR_t) color);
}
else
{
oled_drawpixel(oled.CurrentX + j, (oled.CurrentY + i), (SSD1306_COLOR_t)!color);
}
}
}
oled.CurrentX += Font->FontWidth;
if(0 == is_show_str)
{
oled_update_screen();
}
return ch;
}
/**
* 在x,y位置显示字符串
* @param[in] x 显示坐标x
* @param[in] y 显示坐标y
* @param[in] str 要显示的字符串
* @param[in] font 显示的字形
* @param[in] color 颜色 1显示 0不显示
* @retval
*/
char oled_show_str(uint16_t x,uint16_t y, char* str, FontDef_t* Font, SSD1306_COLOR_t color)
{
is_show_str=1;
oled_gotoXY(x,y);
while (*str)
{
if (oled_show_char(x,y,*str, Font, color) != *str)
{
is_show_str=0;
return *str;
}
str++;
}
is_show_str=0;
oled_update_screen();
return *str;
}
本文主要介绍了I2C通讯协议的实现原理,以及ESP32硬件I2C的API函数。