芯片:esp32s3
开发环境:espidfv4.4
一、官网相关资料(哎嘿嘿,中文的
I2C 驱动程序 – ESP32-S3 – — ESP-IDF 编程指南 v4.4.2 文档
)
1)概述
ESP32-S3 有两个 I2C 控制器(也称为端口),负责处理在 I2C 两根总线上的通信。每个控制器都可以设置为主机或从机。例如,可以同时让一个控制器用作主机,另一个用作从机。
2)功能
I2C 驱动程序管理在 I2C 总线上设备的通信,该驱动程序具备以下功能:
-
在主机模式下读写字节
-
支持从机模式
-
读取并写入寄存器,然后由主机读取/写入
3)如何使用
-
配置驱动程序
– 设置初始化参数(如主机模式或从机模式,SDA 和 SCL 使用的 GPIO 管脚,时钟速度等) -
安装驱动程序
– 激活一个 I2C 控制器的驱动,该控制器可为主机也可为从机 -
根据是为主机还是从机配置驱动程序,选择合适的项目
-
中断处理
– 配置 I2C 中断服务 -
用户自定义配置
– 调整默认的 I2C 通信参数(如时序、位序等) -
错误处理
– 如何识别和处理驱动程序配置和通信错误 -
删除驱动程序
– 在通信结束时释放 I2C 驱动程序所使用的资源
4)配置流程
设置
i2c_config_t
结构中的几个参数
-
设置 I2C
工作模式
– 从
i2c_mode_t
中选择主机模式或从机模式 -
设置
通信管脚
-
指定 SDA 和 SCL 信号使用的 GPIO 管脚
-
是否启用 ESP32-S3 的内部上拉电阻
-
-
(仅限主机模式)设置 I2C
时钟速度
-
(仅限从机模式)设置以下内容:
-
是否应启用
10 位寻址模式
-
定义
从机地址
-
然后,初始化给定 I2C 端口的配置,请使用端口号和
i2c_config_t
作为函数调用参数来调用
i2c_param_config()
函数。在此阶段,
i2c_param_config()
还将其他 I2C 配置参数设置为 I2C 总线协议规范中定义的默认值。有关默认值及修改默认值的详细信息
5) 时钟选择
增加了
时钟源分配器
,用于支持不同的时钟源。时钟分配器将选择一个
满足所有频率和能力要求的时钟源
(如
i2c_config_t::clk_flags
中的要求)。
当
i2c_config_t::clk_flags
为
0
时,时钟分配器将仅根据所需频率进行选择。如果不需要诸如 APB 之类的特殊功能,则可以将时钟分配器配置为
仅根据所需频率选择源时钟
。为此,请将
i2c_config_t::clk_flags
设置为 0。有关时钟特性,请参见下表
时钟名称 | 时钟频率 | SCL 的最大频率 | 时钟功能 |
---|---|---|---|
XTAL 时钟 | 40 MHz | 2 MHz | / |
RTC 时钟 | 20 MHz | 1 MHz |
I2C_SCLK_SRC_FLAG_AWARE_DFS , I2C_SCLK_SRC_FLAG_LIGHT_SLEEP |
时钟功能是嘛
对
i2c_config_t::clk_flags
的解释如下:
-
I2C_SCLK_SRC_FLAG_AWARE_DFS
:当 APB 时钟改变时,时钟的波特率不会改变。 -
I2C_SCLK_SRC_FLAG_LIGHT_SLEEP
:支持轻度睡眠模式,APB 时钟则不支持。 -
ESP32-S3 可能不支持某些标志,请在使用前阅读技术参考手册
sugar,暂时用不到这些功能
6)安装驱动程序
配置好 I2C 驱动程序后,使用以下参数调用函数
i2c_driver_install()
安装驱动程序:
-
端口号,从
i2c_port_t
中二选一 -
主机或从机模式,从
i2c_mode_t
中选择 -
(仅限从机模式)分配用于在从机模式下发送和接收数据的缓存区大小。I2C 是一个以主机为中心的总线,数据只能根据主机的请求从从机传输到主机。因此,从机通常有一个发送缓存区,供从应用程序写入数据使用。数据保留在发送缓存区中,由主机自行读取。
-
用于分配中断的标志(请参考
esp_hw_support/include/esp_intr_alloc.h
中 ESP_INTR_FLAG_* 值)
7)主机模式通讯
为优化通信流程,驱动程序提供一个名为 “命令链接” 的容器,该容器应填充一系列命令,然后传递给 I2C 控制器执行。
8)向从机发送数据
使用命令链接的流程
-
使用
i2c_cmd_link_create()
创建一个命令链接。然后,将一系列待发送给从机的数据填充命令链接:
-
启动位
–
i2c_master_start()
-
从机地址
–
i2c_master_write_byte()
。提供单字节地址作为调用此函数的实参。 -
数据
– 一个或多个字节的数据作为
i2c_master_write()
的实参。 -
停止位
–
i2c_master_stop()
函数
i2c_master_write_byte()
和
i2c_master_write()
都有额外的实参,规定主机是否应确认其有无接受到 ACK 位。 -
-
通过调用
i2c_master_cmd_begin()
来触发 I2C 控制器执行命令链接。一旦开始执行,就不能再修改命令链接。 -
命令发送后,通过调用
i2c_cmd_link_delete()
释放命令链接使用的资源
9)主机读取数据
write改为
i2c_master_read_byte()
和/或
i2c_master_read()
填充命令链接,
在步骤 5 中配置最后一次的读取,以便主机不提供 ACK 位。
10)指示写入或读取数据
主机实际执行的操作信息存储在从机地址的最低有效位中。
因此,为了将数据写入从机,主机发送的命令链接应包含地址
(ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE
11)从机模式,还从来没用过
API 为从机提供以下功能:
-
当主机将数据写入从机时,从机将自动将其存储在接收缓存区中。从机应用程序可自行调用函数
i2c_slave_read_buffer()
。如果接收缓存区中没有数据,此函数还具有一个参数用于指定阻塞时间。这将允许从机应用程序在指定的超时设定内等待数据到达缓存区。 -
发送缓存区是用于存储从机要以 FIFO 顺序发送给主机的所有数据。在主机请求接收前,这些数据一直存储在发送缓存区。函数
i2c_slave_write_buffer()
有一个参数,用于指定发送缓存区已满时的块时间。这将允许从机应用程序在指定的超时设定内等待发送缓存区中足够的可用空间。
在
peripherals/i2c
中可找到介绍如何使用这些功能的代码示例。
12)中断处理
安装驱动程序时,默认情况下会安装中断处理程序。但是,您可以通过调用函数
i2c_isr_register()
来注册自己的而不是默认的中断处理程序。在运行自己的中断处理程序时,可以参考
ESP32-S3 技术参考手册
>
I2C 控制器 (I2C)
>
中断
[
PDF
],以获取有关 I2C 控制器触发的中断描述。
调用函数
i2c_isr_free()
删除中断处理程序
13)用户自定义配置
如本节末尾所述
配置驱动程序
,函数
i2c_param_config()
在初始化 I2C 端口的驱动程序配置时,也会将几个 I2C 通信参数设置为
I2C 总线协议规范
规定的默认值。 其他一些相关参数已在 I2C 控制器的寄存器中预先配置。
通过调用下表中提供的专用函数,可以将所有这些参数更改为用户自定义值。请注意,时序值是在 APB 时钟周期中定义。APB 的频率在
I2C_APB_CLK_FREQ
中指定
要更改的参数 | 函数 |
---|---|
SCL 脉冲周期的高电平和低电平 |
i2c_set_period() |
在产生 启动 信号期间使用的 SCL 和 SDA 信号时序 |
i2c_set_start_timing() |
在产生 停止 信号期间使用的 SCL 和 SDA 信号时序 |
i2c_set_stop_timing() |
从机采样以及主机切换时,SCL 和 SDA 信号之间的时序关系 |
i2c_set_data_timing() |
I2C 超时 |
i2c_set_timeout() |
优先发送/接收最高有效位 (LSB) 或最低有效位 (MSB),可在 i2c_trans_mode_t 定义的模式中选择 |
i2c_set_data_mode() |
上述每个函数都有一个
get
对应项来检查当前设置的值。例如,调用
i2c_get_timeout()
来检查 I2C 超时值。
要检查在驱动程序配置过程中设置的参数默认值,请参考文件
driver/i2c.c
并查找带有后缀
_DEFAULT
的定义。
通过函数
i2c_set_pin()
可以为 SDA 和 SCL 信号选择不同的管脚并改变上拉配置。如果要修改已经输入的值,请使用函数
i2c_param_config()
。
ESP32-S3 的内部上拉电阻范围为几万欧姆,因此在大多数情况下,它们本身不足以用作 I2C 上拉电阻。建议用户使用阻值在
I2C 总线协议规范
规定范围内的上拉电阻。
14)错误处理
大多数 I2C 驱动程序的函数在成功完成时会返回
ESP_OK
,或在失败时会返回特定的错误代码。实时检查返回的值并进行错误处理是一种好习惯。驱动程序也会打印日志消息,其中包含错误说明,例如检查输入配置的正确性。有关详细信息,请参考文件
driver/i2c.c
并用后缀
_ERR_STR
查找定义。
使用专用中断来捕获通信故障。例如,如果从机将数据发送回主机耗费太长时间,会触发
I2C_TIME_OUT_INT
中断。详细信息请参考
中断处理
。
如果出现通信失败,可以分别为发送和接收缓存区调用
i2c_reset_tx_fifo()
和
i2c_reset_rx_fifo()
来重置内部硬件缓存区。
15)删除驱动程序
当使用
i2c_driver_install()
建立 I2C 通信,一段时间后不再需要 I2C 通信时,可以通过调用
i2c_driver_delete()
来移除驱动程序以释放分配的资源。
由于函数
i2c_driver_delete()
无法保证线程安全性,请在调用该函数移除驱动程序前务必确保所有的线程都已停止使用驱动程序。
16)应用示例
I2C 主机和从机示例:
peripherals/i2c
。已经没有啦
我将修改示例程序来驱动oled,会添加详尽的注释
二、配置流程
1)配置i2c_config_t,调用i2c_param_config,再安装驱动程序i2c_driver_install
2)读寄存器:i2c_master_write_read_device
3)写寄存器:i2c_master_write_to_device
4)删去驱动:i2c_driver_delete
就这些,不考虑一些默认参数,还是很简单呐
三、oled屏驱动简要知识
SSD1306 的每页包含了 128 个字节(128
8个点,每页8行128列,一列8bit,自自上到下刷新),总共 8 页,这样刚好是 128
64 的点阵大小
1)初始化:写一堆寄存器,看厂家给的就ok,就能用
2)页地址:0xb0-0xb7[2:0],低三位
3)列地址(低四位):00-0f 低四位
4)列地址(高四位):10-1f
5)对比度:0x81写8位数据,值越大越亮
6)关闭:0xae,开启oxaf
8)电荷泵:0x8d写8位数据:a2=0/1
1、清屏
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
//起始列地址0x00,写128个数据,写满一页
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
} //更新显示
需要注意一点,设备地址!!!!!
我使用的商家的代码
将写函数直接替换后屏幕并不能点亮,是设备地址的问题,图片中将地址右移动才是正确的地址,原因是:写函数再一次将地址左移,类似这样 addr<<1|WRITE
明白了吧
三、代码加注释
main
/* i2c - Simple example
Simple I2C example that shows how to initialize I2C
as well as reading and writing from and to registers for a sensor connected over I2C.
The sensor used in this example is a MPU9250 inertial measurement unit.
For other examples please check:
https://github.com/espressif/esp-idf/tree/master/examples
See README.md file to get detailed usage of this example.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "oled.h"
#include "bmp.h"
static const char *TAG = "i2c-simple-example";
#define I2C_MASTER_SCL_IO GPIO_NUM_22 /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_SDA_IO GPIO_NUM_21 /*!< GPIO number used for I2C master data */
#define I2C_MASTER_NUM 0 /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
#define I2C_MASTER_FREQ_HZ 400000 /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_TIMEOUT_MS 1000
// return i2c_master_write_read_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, ®_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
// uint8_t write_buf[2] = {reg_addr, data};
// ret = i2c_master_write_to_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
/**
* @brief i2c master initialization
*/
static esp_err_t i2c_master_init(void)
{
int i2c_master_port = I2C_MASTER_NUM;
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_param_config(i2c_master_port, &conf);
return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
void app_main(void)
{
uint8_t data[2];
ESP_ERROR_CHECK(i2c_master_init());
ESP_LOGI(TAG, "I2C initialized successfully");
/* Read the MPU9250 WHO_AM_I register, on power up the register should have the value 0x71 */
OLED_Init(); //初始化OLED
OLED_Clear() ;
OLED_DrawBMP(0,0,128,8,BMP1); //图片显示(图片显示慎用,生成的字表较大,会占用较多空间,FLASH空间8K以下慎用)
//ESP_ERROR_CHECK(i2c_driver_delete(I2C_MASTER_NUM));
ESP_LOGI(TAG, "I2C unitialized successfully");
}
oled.c
//
// 本程序只供学习使用,未经作者许可,不得用于其它任何用途
// 中景园电子
// 店铺地址:http://shop73023976.taobao.com/?spm=2013.1.0.0.M4PqC2
//
// 文 件 名 : main.c
// 版 本 号 : v2.0
// 作 者 : HuangKai
// 生成日期 : 2014-0101
// 最近修改 :
// 功能描述 : OLED 4接口演示例程(51系列)
// 说明:
// ----------------------------------------------------------------
// GND 电源地
// VCC 接5V或3.3v电源
// D0 P1^0(SCL)
// D1 P1^1(SDA)
// RES 接P12
// DC 接P13
// CS 接P14
// ----------------------------------------------------------------
// 修改历史 :
// 日 期 :
// 作 者 : HuangKai
// 修改内容 : 创建文件
// 版权所有,盗版必究。
// Copyright(C) 中景园电子2014/3/16
// All rights reserved
//******************************************************************************/。
#include "oled.h"
#include "oledfont.h"
// OLED的显存
// 存放格式如下.
//[0]0 1 2 3 ... 127
//[1]0 1 2 3 ... 127
//[2]0 1 2 3 ... 127
//[3]0 1 2 3 ... 127
//[4]0 1 2 3 ... 127
//[5]0 1 2 3 ... 127
//[6]0 1 2 3 ... 127
//[7]0 1 2 3 ... 127
void OLED_WR_Byte(unsigned dat, unsigned cmd)
{
int ret;
uint8_t write_buf[2] = {cmd?0x40:0x00, dat};
ret = i2c_master_write_to_device(0, OLED_ADDR>>1, write_buf, sizeof(write_buf), 1000 / portTICK_RATE_MS);
if (ret != ESP_OK)
{
printf("err\r\n");
}
}
/********************************************
// fill_Picture
********************************************/
void fill_picture(unsigned char fill_Data)
{
unsigned char m, n;
for (m = 0; m < 8; m++)
{
OLED_WR_Byte(0xb0 + m, 0); // page0-page1
OLED_WR_Byte(0x00, 0); // low column start address
OLED_WR_Byte(0x10, 0); // high column start address
for (n = 0; n < 128; n++)
{
OLED_WR_Byte(fill_Data, 1);
}
}
}
// 坐标设置
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0 + y, OLED_CMD);
OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
OLED_WR_Byte((x & 0x0f), OLED_CMD);
}
// 开启OLED显示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D, OLED_CMD); // SET DCDC命令
OLED_WR_Byte(0X14, OLED_CMD); // DCDC ON
OLED_WR_Byte(0XAF, OLED_CMD); // DISPLAY ON
}
// 关闭OLED显示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D, OLED_CMD); // SET DCDC命令
OLED_WR_Byte(0X10, OLED_CMD); // DCDC OFF
OLED_WR_Byte(0XAE, OLED_CMD); // DISPLAY OFF
}
// 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
u8 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); // 设置显示位置—列高地址
for (n = 0; n < 128; n++)
OLED_WR_Byte(0, OLED_DATA);
} // 更新显示
}
void OLED_On(void)
{
u8 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); // 设置显示位置—列高地址
for (n = 0; n < 128; n++)
OLED_WR_Byte(1, OLED_DATA);
} // 更新显示
}
// 在指定位置显示一个字符,包括部分字符
// x:0~127
// y:0~63
// mode:0,反白显示;1,正常显示
// size:选择字体 16/12
void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 Char_Size)
{
unsigned char c = 0, i = 0;
c = chr - ' '; // 得到偏移后的值
if (x > Max_Column - 1)
{
x = 0;
y = y + 2;
}
if (Char_Size == 16)
{
OLED_Set_Pos(x, y);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
}
else
{
OLED_Set_Pos(x, y);
// for(i=0;i<6;i++)
// OLED_WR_Byte(F6x8[c][i],OLED_DATA);
}
}
// m^n函数
u32 oled_pow(u8 m, u8 n)
{
u32 result = 1;
while (n--)
result *= m;
return result;
}
// 显示2个数字
// x,y :起点坐标
// len :数字的位数
// size:字体大小
// mode:模式 0,填充模式;1,叠加模式
// num:数值(0~4294967295);
void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size2)
{
u8 t, temp;
u8 enshow = 0;
for (t = 0; t < len; t++)
{
temp = (num / oled_pow(10, len - t - 1)) % 10;
if (enshow == 0 && t < (len - 1))
{
if (temp == 0)
{
OLED_ShowChar(x + (size2 / 2) * t, y, ' ', size2);
continue;
}
else
enshow = 1;
}
OLED_ShowChar(x + (size2 / 2) * t, y, temp + '0', size2);
}
}
// 显示一个字符号串
void OLED_ShowString(u8 x, u8 y, u8 *chr, u8 Char_Size)
{
unsigned char j = 0;
while (chr[j] != '\0')
{
OLED_ShowChar(x, y, chr[j], Char_Size);
x += 8;
if (x > 120)
{
x = 0;
y += 2;
}
j++;
}
}
// 显示汉字
void OLED_ShowCHinese(u8 x, u8 y, u8 no)
{
u8 t, adder = 0;
OLED_Set_Pos(x, y);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no][t], OLED_DATA);
adder += 1;
}
OLED_Set_Pos(x, y + 1);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no + 1][t], OLED_DATA);
adder += 1;
}
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
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;
else
y = y1 / 8 + 1;
for (y = y0; y < y1; y++)
{
OLED_Set_Pos(x0, y);
for (x = x0; x < x1; x++)
{
OLED_WR_Byte(BMP[j++], OLED_DATA);
}
}
}
// 初始化SSD1306
void OLED_Init(void)
{
OLED_WR_Byte(0xAE, OLED_CMD); //--display off
OLED_WR_Byte(0x00, OLED_CMD); //---set low column address
OLED_WR_Byte(0x10, OLED_CMD); //---set high column address
OLED_WR_Byte(0x40, OLED_CMD); //--set start line address
OLED_WR_Byte(0xB0, OLED_CMD); //--set page address
OLED_WR_Byte(0x81, OLED_CMD); // contract control
OLED_WR_Byte(0xFF, OLED_CMD); //--128
OLED_WR_Byte(0xA1, OLED_CMD); // set segment remap
OLED_WR_Byte(0xA6, OLED_CMD); //--normal / reverse
OLED_WR_Byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3F, OLED_CMD); //--1/32 duty
OLED_WR_Byte(0xC8, OLED_CMD); // Com scan direction
OLED_WR_Byte(0xD3, OLED_CMD); //-set display offset
OLED_WR_Byte(0x00, OLED_CMD); //
OLED_WR_Byte(0xD5, OLED_CMD); // set osc division
OLED_WR_Byte(0x80, OLED_CMD); //
OLED_WR_Byte(0xD8, OLED_CMD); // set area color mode off
OLED_WR_Byte(0x05, OLED_CMD); //
OLED_WR_Byte(0xD9, OLED_CMD); // Set Pre-Charge Period
OLED_WR_Byte(0xF1, OLED_CMD); //
OLED_WR_Byte(0xDA, OLED_CMD); // set com pin configuartion
OLED_WR_Byte(0x12, OLED_CMD); //
OLED_WR_Byte(0xDB, OLED_CMD); // set Vcomh
OLED_WR_Byte(0x30, OLED_CMD); //
OLED_WR_Byte(0x8D, OLED_CMD); // set charge pump enable
OLED_WR_Byte(0x14, OLED_CMD); //
OLED_WR_Byte(0xAF, OLED_CMD); //--turn on oled panel
}