ESP32 I2C外设的使用 读写AT24C04 FreeRTOS嵌入式实时操作系统思想
今天我们通过使用ESP32的I2C外设来进行对EEPROM的读写操作,本次我们使用FreeRTOS进行创建任务进程,而不是通过While死循环顺序执行。在乐鑫提供的SDK中已经包含了FreeRTOS相关代码文件,我们直接使用即可;
在此之间,在此提醒熟读乐鑫提供的ESP-IDF编程指南,以及AT24C04的相关芯片手册。
I2C简介
这里直接Copy百度百科了 IIC 简介 IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接 微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。 在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。 I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答 信号。 开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。 结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲, 表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接 收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为 受控单元出现故障。 I2C的时序图就不贴出来了;
提示:以下是本篇文章正文内容,下面案例可供参考
一、AT24C04
1.简介
AT24C04是Ateml公司的4Kb得电可擦除存储芯片,采用两线串行的总线和单片机通讯,电压最低可以到2.5V,额定电流为1mA,静态电流10uA(5.5V),芯片内的资料可以在断电的情况下保存100年,而且采用8 脚的 封装,使用方便。
我们来看AT24C04的电路图: 这里WP引脚接到了地,我们可以对整个512个字节进行读写操作。
2.程序设计
首先建立AT24C04.c和.h文件,引入将要使用到的头文件:
#include "AT24C04.h"
#include "esp_log.h"
#include "driver/i2c.h"
#include "freertos/FreeRTOS.h"
编辑AT24C04的头文件:
#define AT24C04_SCL_PIN GPIO_NUM_12
#define AT24C04_SDA_PIN GPIO_NUM_13
#define I2C_MASTER_TX_BUF_DISABLE 0
#define I2C_MASTER_RX_BUF_DISABLE 0
#define ACK_CHECK_EN 0x1
#define ACK_CHECK_DIS 0x0
#define ACK_VAL 0x0
#define NACK_VAL 0x1
#define CHIPADDR 0x50
对于I2C外设的初始化,可参考乐鑫提供的I2C示例,也可参考IDF编程指南:
这里我们把I2C配置为主机模式,与STM32不同的是ESP32配置好I2C后一定要记得注册外设;
void AT24C04_Init(void)
{
i2c_config_t i2c_configstructure;
i2c_configstructure.mode = I2C_MODE_MASTER;
i2c_configstructure.scl_io_num = AT24C04_SCL_PIN;
i2c_configstructure.scl_pullup_en = GPIO_PULLUP_ENABLE;
i2c_configstructure.sda_io_num = AT24C04_SDA_PIN;
i2c_configstructure.sda_pullup_en = GPIO_PULLUP_ENABLE;
i2c_configstructure.master.clk_speed = 100000;
i2c_configstructure.clk_flags = 0;
i2c_param_config(I2C_NUM_0, &i2c_configstructure);
i2c_driver_install(I2C_NUM_0, i2c_configstructure.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
至于这里的CLK频率如何确定,我们可以通过i2c_config_t这个结构体查看,规定时钟频率不能超过1MHz;不同芯片模组有不同,具体频率需要根据所使用的的模组进行确定。
typedef struct{
i2c_mode_t mode;
int sda_io_num;
int scl_io_num;
bool sda_pullup_en;
bool scl_pullup_en;
union {
struct {
uint32_t clk_speed;
} master;
struct {
uint8_t addr_10bit_en;
uint16_t slave_addr;
} slave;
};
uint32_t clk_flags;
} i2c_config_t;
然后我们编辑对AT24C04进行读写操作的函数:
uint8_t AT24C04_ReadByte(uint16_t ReadAddr)
{
esp_err_t ret = 0;
uint8_t data = 0;
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, 0xA0 + ((ReadAddr / 256) << 1), ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, ReadAddr % 256, ACK_CHECK_EN);
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, ((CHIPADDR << 1) | I2C_MASTER_READ), ACK_CHECK_EN);
i2c_master_read_byte(i2c_cmd, &data, NACK_VAL);
i2c_master_stop(i2c_cmd);
ret = i2c_master_cmd_begin(I2C_NUM_0, i2c_cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(i2c_cmd);
return data;
}
void AT24C04_WriteByte(uint16_t WriteAddr, uint8_t DatatoWrite)
{
esp_err_t ret = 0;
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, 0XA0 + ((WriteAddr / 256) << 1), ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, WriteAddr % 256, ACK_CHECK_EN);
i2c_master_write_byte(i2c_cmd, DatatoWrite, ACK_CHECK_EN);
i2c_master_stop(i2c_cmd);
ret = i2c_master_cmd_begin(I2C_NUM_0, i2c_cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(i2c_cmd);
vTaskDelay(10 / portTICK_RATE_MS);
}
uint8_t AT24C04_Check(void)
{
uint8_t temp;
temp = AT24C04_ReadByte(511);
ESP_LOGI(TAG, "AT24C04_Check : %02X \r\n", temp);
if (temp == 0x55)
{
return 0;
}
else
{
AT24C04_WriteByte(511, 0x55);
temp = AT24C04_ReadByte(511);
if (temp == 0X55)
return 0;
}
return 1;
}
void AT24C04_ReadData(uint16_t ReadAddr, uint8_t *pBuffer, uint16_t NumToRead)
{
while (NumToRead)
{
*pBuffer++ = AT24C04_ReadByte(ReadAddr++);
NumToRead--;
}
}
void AT24C04_WriteData(uint16_t WriteAddr, uint8_t *pBuffer, uint16_t NumToWrite)
{
while (NumToWrite--)
{
AT24C04_WriteByte(WriteAddr, *pBuffer);
WriteAddr++;
pBuffer++;
}
}
别忘了在头文件中进行声明; 到这里基本上对AT24C04的操作代码就基本编辑完毕,本次实验还使用到的ESP提供的日志库进行打印输出调试信息;理解较为容易,这里不再赘述。
二、FreeRTOS
1.简单理解嵌入式实时操作系统
我们在学习51单片机的时候,就已经接触过了中断,那么中断不同于程序顺序执行的思想,通过中断,我们可以跳出正在执行的程序,来处理发生中断的事件,通过触发中断事件,来执行中断回调函数,以解决一些原程序之外的由外部触发的突发事件,常见的中断有看门狗中断,外部中断,定时器中断等等;和计算机不同的是,简单的普通性能的单片机只有一个核心,程序执行的方式则是顺序执行,那么在学习中断的时候我们了解到了中断优先级的概念,在中断优先级当中又细分为了抢占优先级和子优先级,以此来处理不同条件触发中断时的先后顺序,通过中断是否可以达到近似任务同时执行的效果呢?因此有了嵌入式实时操作系统,如何来理解实时操作系统和原本的顺序执行程序的区别呢?通俗易懂的来说,就像一位母亲正在给小孩子喂饭,这个时候领导打电话过来,那么按照顺序执行的理念,要么先接完电话,再喂饭,要么喂完饭再接电话;这样是否会造成无论谁先执行,另一方都会处在一直等待的状态呢;必须等待上一个任务执行完成后,才能执行下一个任务;那么用嵌入式实时操作系统的理念来处理这件事呢。 我们通过一张图来理解: 套入示例来理解,这位母亲先讲两句电话,然后为几口饭,再讲几句电话,如此交替运行,即可达到类似两件事同时做的效果。在FreeRTOS内,我们直接使用提供的函数进行任务创建即可;注意事件的优先级; 这里对FreeRTOS描述的不是那么专业,可自行查询FreeRTOS的文档了解;
2.创建任务
我这里使用的是ESP32-S2,只有一个核心,不需要进行指定核心运行,如果是双核的ESP32可使用xTaskCreatePinnedToCore来指定核心; 我们先编写事件函数:
void AT24C04_ReadWriteTestTask(void *arg)
{
AT24C04_Init();
while (1)
{
while (AT24C04_Check())
{
ESP_LOGI(TAG, "24C04 Check Failed!\r\n");
vTaskDelay(1000 / portTICK_RATE_MS);
ESP_LOGI(TAG, "Please Check! \r\n");
vTaskDelay(1000 / portTICK_RATE_MS);
}
ESP_LOGI(TAG, "Start Write 24C04....\r\n");
AT24C04_WriteData(0, (uint8_t *)TEXT_Buffer, SIZE);
ESP_LOGI(TAG, "24C04 Write Finished!\r\n");
ESP_LOGI(TAG, "Start Read 24C04.... \r\n");
AT24C04_ReadData(0, datatemp, SIZE);
ESP_LOGI(TAG, "The Data Readed is: %s \r\n", datatemp);
ESP_LOGI(TAG, "Test ok\n");
vTaskDelay(5000 / portTICK_RATE_MS);
}
}
还使用到了之间的Led,那么我们为Led也创建一个事件,让它一直闪烁:
void Led_BlinkTask(void *arg)
{
Led_Init();
while (1)
{
ESP_LOGI(TAG, "Led On\n");
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "Led Off\n");
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
然后我们为两个事件创建进程,分配内存空间:
ESP_LOGI(TAG, "APP Start......");
ESP_ERROR_CHECK(nvs_flash_init());
xTaskCreate(&AT24C04_ReadWriteTestTask,
"sensorTask",
4096,
NULL,
2,
NULL
);
xTaskCreate(Led_BlinkTask, "Led_Blink", 2048, NULL, 5, NULL);
编译后烧录到开发板中,打开串口中断查看打印的信息。
可以看到读写成功
|