对于学嵌入式的来说,i2c在熟悉不过了,两根线数据线(SDA)和时钟线(SCL)在加上一根地线就可以完成通信。
i2c 使用中涉及到的一些概念:起始位start、 从机地址slave_addr、 数据 data ,校验ACK NOACK、 停止位stop。具体硬件上怎么实现的我们暂且不管,我们只需记住这些概念就行了。
I2C通信规则
我现在简要说一下他的通信规则:i2c分成主机master 和从机slave 两种设备。每次都是主机发起通信请求的。
-
master 发送一个start 信号,在总线上所有的设备都可以接收到这个start信号。
-
紧接着master 发送slave的地址addr+w/r(w表示下一次发送的数据是主机向设备写数据,r表示下一次发送的是主机从从机中读取数据),这时只有对应地址adrr的设备才能回复信号,这个slave回复一个ack,表示通信成功。
-
这回主机就可以发送数据了。
-
当通信完成后,从机返回一个noack,表示通信结束,主机发送一个stop信号,释放总线。
以上就是i2c最简单的通信过程。
esp32中I2C的使用
“工欲善其事,必先利其器”
对于i2c接口来说乐鑫为开发者提供了i2c调试工具i2c-tools,它的功能和linux上的i2c-tools一样,对于基于esp32来调试i2c设备很方便。 这个工具是以例程方式提供给我们的,我们暂时不用管他是怎么实现,只需要知道它的是使用方法即可。
cd ~/esp/esp-idf/examples/peripherals/i2c
i2c文件夹中有两个工程:
i2c_self_test
i2c_tools
进入i2c_tools
$ make menuconfig //配置串口 $make -j4 // 编译 $make flash moitor// 调试
成功运行之后我们看到会
==============================================================
| Steps to Use i2c-tools on ESP32 |
| |
| 1. Try 'help', check all supported commands |
| 2. Try 'i2cconfig' to configure your I2C bus |
| 3. Try 'i2cdetect' to scan devices on the bus |
| 4. Try 'i2cget' to get the content of specific register |
| 5. Try 'i2cset' to set the value of specific register |
| 6. Try 'i2cdump' to dump all the register (Experiment) |
| |
==============================================================
用过linux的朋友都很熟悉这几个命令,这个linux的i2c-tools 一样,这个i2c-tools默认使用gpio18 和gpio19.可以使用i2cconfig 修改i2c配置。
我在gpio18 和gpio19 上接了一个oled ssd1603,ssd1603默认地址是0x3c,执行i2cdetcet命令
esp32> i2cdetect
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
在i2c总线成功读到设备,如果想知道其他命令的用法可以执行help 命令查看。
i2c 的配置过程
-
首先配置i2c_config_t 结构体,这个结构体包含了i2c的一些参数,以下内容可以在i2c.h 中找到。
/**
* @brief I2C initialization parameters
*/
typedef struct{
i2c_mode_t mode; /*!< I2C mode */
gpio_num_t sda_io_num; /*!< GPIO number for I2C sda signal */
gpio_pullup_t sda_pullup_en; /*!< Internal GPIO pull mode for I2C sda signal*/
gpio_num_t scl_io_num; /*!< GPIO number for I2C scl signal */
gpio_pullup_t scl_pullup_en; /*!< Internal GPIO pull mode for I2C scl signal*/
union {
struct {
uint32_t clk_speed; /*!< I2C clock frequency for master mode, (no higher than 1MHz for now) */
} master;
struct {
uint8_t addr_10bit_en; /*!< I2C 10bit address mode enable for slave mode */
uint16_t slave_addr; /*!< I2C address for slave mode */
} slave;
};
}i2c_config_t;
具体的配置如下:
#define SDA_PIN GPIO_NUM_18
#define SCL_PIN GPIO_NUM_19
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,//主机模式
.sda_io_num = SDA_PIN,//sda i引脚编号
.scl_io_num = SCL_PIN,//scl 引脚编号
.sda_pullup_en = GPIO_PULLUP_ENABLE,//上拉使能
.scl_pullup_en = GPIO_PULLUP_ENABLE,//上拉使能
.master.clk_speed = 1000000 // 100k
};
i2c_param_config(I2C_NUM_0, &i2c_config);//配置参数初始化,此函数内部就是将i2c_config 中的相关参数 填入到i2c[i2c_num_0] 结构体中。
i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);//初始化配置以外的所有相关参数,将配置写入寄存器
-
初始化完成之后,就开始使用esp-idf提供的函数进行i2c的读写操作了。
根据官方例程讲解esp-idf 读写流程 ,下面的函数是我从官方例成中提取出的发送函数。
/**
* @brief test code to operate on BH1750 sensor
*
* 1. set operation mode(e.g One time L-resolution mode)
* _________________________________________________________________
* | start | slave_addr + wr_bit + ack | write 1 byte + ack | stop |
* --------|---------------------------|---------------------|------|
* 2. wait more than 24 ms
* 3. read data
* ______________________________________________________________________________________
* | start | slave_addr + rd_bit + ack | read 1 byte + ack | read 1 byte + nack | stop |
* --------|---------------------------|--------------------|--------------------|------|
*/
static esp_err_t i2c_master_sensor_test(i2c_port_t i2c_num, uint8_t *data_h, uint8_t *data_l)
{
int ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 在执行i2c之前,必须执行此函数 创建一个i2c 命令 链接,为之后的i2c操作执行,在执行完成之后需要销毁
i2c_master_start(cmd);//i2c运行开始函数。注意这不是真的开始,只是是一个开始标记,初始化cmd
//以下是i2c真正的读写操作
i2c_master_write_byte(cmd, BH1750_SENSOR_ADDR << 1 | WRITE_BIT, 0x01);// 向BH1750_SENSOR_ADDR 写入操作,等待从机返回数据
i2c_master_write_byte(cmd, BH1750_CMD_START, 0x01); // 继续写入数据
i2c_master_stop(cmd);//i2c停止运行。并不是真正的停止,因为此时i2c还没有真正的运行,我认为这是一个标识,当时i2c运行的时候读取到此标志就停止运行。
ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); //按照cmd中的记录的操作顺序开始运行i2c (start-> write BH1750 地址 -> write BH1750_CMD -> stop)
i2c_cmd_link_delete(cmd); // 操作完成 删除cmd
if (ret != ESP_OK) {
return ret;
}
vTaskDelay(30 / portTICK_RATE_MS);//freeRTOS的系统延时函数
cmd = i2c_cmd_link_create(); //重新创建cmd 链接
i2c_master_start(cmd);
i2c_master_write_byte(cmd, BH1750_SENSOR_ADDR << 1 | READ_BIT, ACK_CHECK_EN);
i2c_master_read_byte(cmd, data_h, ACK_VAL);
i2c_master_read_byte(cmd, data_l, NACK_VAL);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
esp32 i2c 驱动 ssd1603
关于ssd1306的工作原理就不做介绍了,只需要记住ssd1306的默认地址是0x3c。
跟据上面介绍的esp-idf i2c 的操作方式实现对ssd1306初始化 1. ssd1306 初始化函数
void ssd1306_init() {
esp_err_t esp_err;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();//必须创建
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, 0x00, true);
i2c_master_write_byte(cmd, 0x8d, true);
i2c_master_write_byte(cmd, 0x14, true);
i2c_master_write_byte(cmd, 0xa1, true); // reverse left-right mapping
i2c_master_write_byte(cmd, 0xc8, true); // reverse up-bottom mapping
i2c_master_write_byte(cmd, 0xaf, true);
i2c_master_stop(cmd);
esp_err = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
if (esp_err == ESP_OK) {
ESP_LOGI(tag, "configured successfully");
} else {
ESP_LOGE(tag, " configuration failed. code: 0x%.2X", espRc);
}
i2c_cmd_link_delete(cmd);
}
-
ssd1306 清屏函数
void ssd1306_clear() {
i2c_cmd_handle_t cmd;
uint8_t zero[128];
for (uint8_t i = 0; i < 8; i++) {
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_SINGLE, true);
i2c_master_write_byte(cmd, 0xB0 | i, true);
i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
i2c_master_write(cmd, zero, 128, true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
}
}
其他函数就不多写了,按照这个写法很容易将其他平台的ssd1306代码移植到esp32上。
欢迎关注我的个人网站:zwww.zcxbb.com