10. I2C
10.1 什么是I2C
IIC(Inter-Integrated Circuit)协议也称为I2C总线,是一种串行通信协议,通常用于连接低速外设。它由Philips(现在的NXP Semiconductors)公司于1980年代初开发,现在已经成为一个标准。IIC总线只需要两条数据线,分别是串行数据线(SDA)和串行时钟线(SCL),这使得它成为一种非常简单的接口。它适用基于芯片的通信,例如连接传感器、存储器或数字信号处理器等。
在IIC协议中,总线上有一个主设备和多个从设备。主设备掌控着总线上的通信过程,负责发起、控制、停止通信。而从设备则需要等待主设备的请求,接收或发送数据。主设备和从设备之间的数据交换采用帧格式,每个帧通常包含地址、数据和控制信息。主设备根据从设备的地址来选中要通信的设备,从设备则根据控制信息进行相应的操作。IIC协议可以支持多个从设备连接到同一个主设备,为系统设计提供了更大的灵活性。
10.2 I2C的硬件实现
I2C总线通常使用两种电压电平,即高电平(VH)和低电平(VL)。高电平为2.5V至5.5V,低电平为0V至0.3V;这些电压电平范围是根据I2C规范确定的。
I2C总线有不同的传输速率可选,包括标准模式(100 kbps)、快速模式(400 kbps)以及高速模式。传输速率的选择取决于应用的需求和设备的支持能力。
为避免信号冲突,微处理器(MCU)必须只能驱动SDA和SCL在低电平,即开漏输出。设置为开漏模式主要是为了保护器件和防止干扰。
- 防止干扰:多个器件共享同一条数据线(SDA)和同一条时钟线(SCL),如果采用推挽输出模式,多个器件的输出将会叠加在数据线上,造成信号干扰,严重时会损坏器件或导致通信错误。而采用开漏输出模式,则各个器件的输出只有拉低数据线的部分,不会干扰彼此,从而提高了总线的可靠性和抗干扰能力。
- 防止短路:在开漏输出模式下,由于器件的输出只有拉低数据线的部分,如果两个或多个器件同时输出,也不会造成短路。而如果采用推挽输出模式,两个或多个器件同时输出时,可能会形成短路。比如主设备输出高电平,从设备输出低电平。
因设置为开漏模式,需要连接一个外部的上拉电阻(例如:10k)将信号提拉至高电平。故I2C总线中的SDA(数据线)和SCL(时钟线)通常都连接了上拉电阻,以确保逻辑高电平的稳定性。上拉电阻的阻值通常在2.2kΩ至10kΩ之间,具体取决于总线的电容负载和通信距离。
I2C总线的最大线缆长度和传输容量受到一定限制。在标准模式下,最大线缆长度大约在1米左右,而在快速模式下,最大线缆长度约为0.3米。此外,线缆上的总线容量也会对传输速率产生影响。
10.3 I2C数据传输
I2C协议使用总线抢占制进行数据传输。它只有两根通信线,因此它数据传输是基于时钟信号的。时钟由主设备产生,并控制数据的传输速率。数据由主设备发送并接收,但其交换是通过从设备的应答来实现的。
下面是IIC总线的几个重要的时序:
起始信号:SCL在高电平的状态下,SDA的电平由高转低,表示开始一次通信。
void IIC_Start(void)
{
SDA_OUT();//设置SDA为输出模式
SDA(1);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
2
3
4
5
6
7
8
9
10
11
停止信号:SCL在高电平的状态下,SDA的电平由低转高,表示结束这次通信。主设备在发送停止信号后不能再向从设备发送任何数据,除非再次发送起始信号。
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
2
3
4
5
6
7
8
9
10
11
数据传输:主设备和从设备进行数据的传输,可以是一个或多个字节的数据,发送和接收都是基于地址选择的。
//发送一个字节
void IIC_Send_Byte(unsigned char dat)
{
int i = 0;
SDA_OUT();
SCL(0);
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(1);
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//接收一个字节
unsigned char IIC_Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() == 1 )
{
receive |= 1;
}
}
SCL(0);
return receive;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
I2C还提供了一种称为“ACK/NACK”(应答/非应答)的确认机制。如果一个设备接收到数据,它将通过在SDA线上拉低电平来发送一个应答信号以通知发送方数据已被接收。相反,如果数据被损坏或未接收,接收设备将发送非应答信号。(在SDA上保持高电平)。
void IIC_Send_Ack(void)
{
SDA_OUT();
SCL(0);
SDA(1);
SDA(0);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
2
3
4
5
6
7
8
9
10
11
void IIC_Send_Nack(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(0);
}
2
3
4
5
6
7
8
9
10
11
在IIC总线中,时钟线由主设备控制,每个数据位在时钟边沿更新,传输的最高速率取决于总线上最慢的设备。一般来讲,IIC总线的通信速率比较慢,通常在几百kbps的范围内。如果需要更高的传输速率,可以采用其他通信协议,如SPI协议、CAN协议等。
10.4 I2C通信流程
I2C通信流程按照以下步骤进行:
- 主控向总线发送开始信号。
- 主控将要通信的设备地址和读写位(R/W)发送到总线上。
- 设备接收到地址后发送应答信号,主控接收到应答信号后发送数据或继续发送地址。
- 设备接收到数据后发送应答信号,主控接收到应答信号后可以继续发送数据或者停止通信。
- 主控向总线发送停止信号。
10.5 IIC基本参数
- 速率:I2C总线有标准模式(100 kbit/s)和快速模式(400 kbit/s)两种传输模式,还有更快的扩展模式和高速模式可供选择。
- 器件地址:每个设备都有唯一的7位或10位地址,可以通过地址选择来确定与谁进行通信。
- 总线状态:I2C总线有五种状态,分别是空闲状态、起始信号、结束信号、响应信号、数据传输。
- 数据格式:I2C总线有两种数据格式,标准格式和快速格式。标准格式是8位数据字节加上1位ack/nack(应答/非应答)位,快速格式允许两个字节同时传输。
由于SCL和SDA线是双向的,它们也可能会由于外部原因(比如线路中的电容等)出现电平误差,而从而导致通信出错。因此,在IIC总线中,通常使用上拉电阻来保证信号线在空闲状态下的电平为高电平。
10.6 软件I2C与硬件I2C
I2C协议可以通过软件实现或者硬件实现。这两种方式的区别在于实现的方法和所需的硬件资源。
10.6.1 软件I2C
软件I2C是指通过在程序中编写代码来实现I2C通信协议。它利用通用输入输出(GPIO)引脚来模拟I2C的数据线(SDA)和时钟线(SCL),通过软件控制引脚的电平变化来传输数据和生成时序信号。与硬件I2C相比,软件I2C的优势在于不需要特定的硬件支持,可以在任何支持GPIO功能的微控制器上实现。它利用了微控制器的通用IO引脚来实现I2C通信协议。
软件I2C的实现通过编程方式来模拟I2C的主机和从机设备。通过逐位地读取和写入GPIO引脚的状态,并根据I2C协议的时序要求进行相应的操作,实现数据的传输和通信。软件I2C的灵活性较高,可以根据应用需求进行定制和扩展。它可以处理多个从机设备,并支持多主机环境。因此,软件I2C广泛应用于资源受限的MCU系统,特别是那些需要与多个外部设备进行通信的应用。
尽管软件I2C的性能相对于硬件I2C较低,但在一些低速通信和简单通信需求的场景下,软件I2C是一种经济实用的解决方案。
10.6.2 硬件I2C
硬件I2C是指通过专门的硬件模块来处理I2C通信协议。大多数现代微控制器和一些外部设备已经集成了硬件I2C模块,这些硬件模块负责处理I2C通信的细节,包括生成正确的时序信号、自动处理信号冲突、数据传输和错误检测等。可以直接使用硬件引脚连接,无需编写时序的代码。
使用硬件I2C通常相对简单,开发者无需编写复杂的代码来处理通信协议的细节。硬件模块可以直接与外部设备连接,通过专用的引脚进行数据和时钟传输,从而实现高效且可靠的通信。 在选择软件I2C还是硬件I2C时,需要考虑应用需求和硬件资源。软件I2C适用于资源受限的系统,可以在任何支持GPIO的微控制器上实现,但相对性能较低。硬件I2C通常性能更好,但需要硬件支持,并且可能占据一些特定的引脚资源。
10.6.3 IIC优缺点
10.6.3.1 优点
双向传输:I2C总线支持双向传输,可以通过SDA线同时传输主设备和从设备之间的数据,节约了总线的资源。
系统集成:I2C总线可以快速集成到芯片中,减少系统实现的逻辑复杂性,提高了设计效率。
多设备共享:I2C总线可以通过地址传输实现多个设备与主控器的通信,使得多个设备可以共享总线,并直接交互。
高可靠性:I2C总线使用逻辑层次的代替电气信号来表示数据传输,具有更高的传输可靠性。
10.6.3.2 缺点
带宽不高:I2C总线的传输速度限制在400 kbps,相比较于SPI总线和CAN总线,带宽相对较低。
时序要求严格:I2C总线传输数据需要严格遵循时序要求,特别是在高速传输过程,时序容易受到干扰,造成通信失败。
最长电缆长度有限:虽然I2C总线可以通过中继器扩展总线长度,但是由于信号线受到干扰,信号衰减和时序要求等问题,电缆最长长度一般限制在1~2米之间。
总之,I2C总线具有双向传输、系统集成、多设备共享等优点,但传输速度相对较低,时序要求严格且最长电缆长度有限等缺点。
10.7 ESP32S3的I2C介绍
ESP32S3有两个硬件I2C控制器(也称为端口),负责处理两条I2C总线上的通信。每个I2C控制器都可以作为主机或从机运行。ESP32的I2C接口可以配置为主模式或从模式,可以通过简单的API来控制I2C总线上的设备。
10.8 I2C的应用
我们可以查看下IIC相关的函数,对IIC应用有个基本的了解。
/*
使用IIC通信顺序分为 3 个部分【起始部分】【时序部分】【结束部分】
【起始部分】相关函数:
void IIC_GPIO_Init(void);
【时序部分】相关函数:
i2c_cmd_handle_t i2c_cmd_link_create(void);
esp_err_t i2c_master_start(i2c_cmd_handle_t cmd_handle);
esp_err_t i2c_master_stop(i2c_cmd_handle_t cmd_handle);
esp_err_t i2c_master_write_byte(i2c_cmd_handle_t cmd_handle, uint8_t data, bool ack_en);
esp_err_t i2c_master_read_byte(i2c_cmd_handle_t cmd_handle, uint8_t *data, i2c_ack_type_t ack);
【结束部分】相关函数:
esp_err_t i2c_master_cmd_begin(i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, TickType_t ticks_to_wait);
void i2c_cmd_link_delete(i2c_cmd_handle_t cmd_handle);
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
10.8.1 初始化 I2C 接口
#define GPIO_SCL 7 // IIC 时钟引脚
#define GPIO_SDA 8 // IIC 数据引脚
#define IIC_NUM I2C_NUM_1 // IIC 硬件1
#define ACK_CHECK_EN 0x1 /*!< I2C 主机将【理会】 应答*/
#define ACK_CHECK_DIS 0x0 /*!< I2C 主机将【不理会】应答 */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C 主机不需要发送缓存区 */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C 主机不需要接收缓存区 */
/******************************************************************
* 函 数 名 称:IIC_GPIO_Init
* 函 数 说 明:对IIC初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_GPIO_Init()
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER, // 设定为主机
.sda_io_num = GPIO_SDA, // SDA引脚设定
.sda_pullup_en = GPIO_PULLUP_ENABLE, // 启动SDA引脚上拉电阻
.scl_io_num = GPIO_SCL, // SCL引脚设定
.scl_pullup_en = GPIO_PULLUP_ENABLE, // 启动SCL引脚上拉电阻
.master.clk_speed = 100000, // 使用 100KHz 时钟频率
// .clk_flags = 0, /* 可选项,这个选项是用来选择时钟源 */
};
esp_err_t err = i2c_param_config(IIC_NUM, &conf); // 将conf结构体的设定配置到 IIC_NUM
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这部分代码定义了一个 i2c_config_t 类型的结构体 conf,并对其成员赋值。结构体成员解析如下:
conf.mode
:I2C 工作模式。这里设置为 I2C_MODE_MASTER,表示主设备模式。还有其他选项 I2C_MODE_SLAVE 表示从设备模式。conf.sda_io_num
:SDA 引脚编号。conf.sda_pullup_en
:SDA 引脚是否启用内部上拉电阻。这里设置为 GPIO_PULLUP_ENABLE,启用内部上拉,避免悬空。conf.scl_io_num
:SCL 引脚编号。conf.scl_pullup_en
:SCL 引脚是否启用内部上拉电阻。这里设置为 GPIO_PULLUP_ENABLE,启用内部上拉,避免悬空。conf.master.clk_speed
:以主设备模式工作时,I2C 的时钟速度。这里设置为 100000,即 100kHz,是 I2C 的常用速度。
10.8.2 安装 I2C 驱动
esp_err_t i2c_driver_install(i2c_port_t i2c_num, i2c_mode_t mode, size_t rx_buf_len, size_t tx_buf_len, int intr_flags)
调用 i2c_driver_install() 函数,为指定的 I2C 端口号安装 I2C 驱动程序。参数解析如下:
- i2c_num: I2C 总线端口号,类型为 i2c_port_t。可选参数有I2C_NUM_0、I2C_NUM_1;
- mode: I2C 工作模式,包括 I2C_MODE_MASTER(主模式)、I2C_MODE_SLAVE(从模式)和 I2C_MODE_MASTER_SLAVE(主从模式),类型为 i2c_mode_t。
- rx_buf_len: I2C 接收缓冲区长度,单位为字节,只有在从模式才用到,缓存大小必须大于 0,此处使用一个常量 I2C_MASTER_RX_BUF_DISABLE 表示禁用接收缓冲区。
- tx_buf_len: I2C 发送缓冲区长度,单位为字节,只有在从模式才用到,缓存大小必须大于 0,此处使用一个常量 I2C_MASTER_TX_BUF_DISABLE 表示禁用发送缓冲区。
- intr_flags: I2C 驱动中断标志,用于控制是否支持 I2C 中断处理程序。此处使用一个常量 0 表示不使用中断。 在调用该函数后,I2C 驱动程序会启动,从而可以读取或写入数据到 I2C 总线上的设备。示例:
//注册I2C服务即使能
i2c_driver_install(IIC_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
2
10.8.3 I2C 的读写操作
在进行 I2C 读写操作之前,必须创建一个 i2c_cmd_handle_t
参数,然后根据你的需要添加一系列的命令。例如,以下代码首先创建了一个 cmd
参数,然后添加了开始命令,写命令,停止命令。最后通过 i2c_master_cmd_begin
函数来执行这一系列的命令,读操作与之类似。
unsigned char dat;
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 创建并初始化一个IIC命令列表,并返回一个命令列表句柄。
i2c_master_start(cmd); // 起始信号
i2c_master_write_byte(cmd, 0xD0, ACK_CHECK_EN); // 写入一个字节
i2c_master_write_byte(cmd, 0x00, ACK_CHECK_EN); // 写入一个字节
i2c_master_start(cmd); // 起始信号
i2c_master_write_byte(cmd, 0xD1, ACK_CHECK_EN); // 写入一个字节
i2c_master_read_byte(cmd, &dat, ACK_CHECK_EN); // 读取一个字节并返回读取的字节。
i2c_master_stop(cmd); // 停止信号
esp_err_t ret = i2c_master_cmd_begin(IIC_NUM, cmd, 1000 / portTICK_PERIOD_MS); //在主机模式下,发送在I2C命令列表上所有排队命令。
i2c_cmd_link_delete(cmd); // 释放IIC命令列表
2
3
4
5
6
7
8
9
10
11
12
13
14
15
下面将逐个介绍这些函数的作用和参数。
i2c_cmd_handle_t cmd = i2c_cmd_link_create()
; 此函数用于创建一个新的 I2C 命令链,并返回其句柄。在发送或接收 I2C 数据时,需要先建立一个命令链,然后添加相应的命令。i2c_master_start(cmd)
; 向 I2C 命令链中添加开始信号。函数参数如下:
- cmd:要添加的I2C 命令链句柄。
i2c_master_write_byte(cmd, (DEVICE_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN)
; 向 I²C 命令链中添加一个字节的写操作。函数参数如下:
cmd
:要添加的I2C 命令链句柄。- (DEVICE_ADDR << 1) | WRITE_BIT:需要写入的字节,这里写入设备地址(左移 1 位)并添加写标识位。
- ACK_CHECK_EN:表示是否检查从设备的应答(ACK)。这里使用 ACK_CHECK_EN,表示需要应答。
i2c_master_stop(cmd)
; 向 I²C 命令链中添加停止信号。函数参数如下:
- cmd:要添加的I2C 命令链句柄。
ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
执行之前添加到 I2C 命令链中的操作。函数参数如下:
- I2C_MASTER_NUM:需要操作的 I2C 端口号。
- cmd:要添加的I2C 命令链句柄。
- 1000 / portTICK_RATE_MS:超时时间,单位为操作系统 tick。这里用 1000 / portTICK_RATE_MS 表示超时时间为 1000 ms,即 1 秒。
i2c_cmd_link_delete(cmd);
删除 I2C 命令链。在执行完命令链后,需要调用此函数释放资源。函数参数如下:
- cmd:要删除的I2C 命令链句柄。
实际上 I2C 通信过程中还有其他函数和参数可以使用。例如在读取数据时,可以使用i2c_master_read_byte()
和 i2c_master_read()
函数。这些函数允许你分别读取一个字节或一定长度的数据。此外,你还可以使用i2c_master_write()
函数一次性写入多个字节的数据。 此外,请注意在上面的例子中,DEVICE_ADDR
、WRITE_BIT
和 ACK_CHECK_EN
是未定义的常量。
10.9 I2C验证
使用 测试
在文件bsp_ds1307.c中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-01-12 LCKFB-lp first version
*/
#include "bsp_ds1307.h"
#include "stdio.h"
#include "sys/unistd.h"
//时间数据结构体
_time_struct_ RTC_Time = {0};
static void delay_ms(unsigned int ms)
{
vTaskDelay(ms / portTICK_PERIOD_MS);
}
static void delay_us(unsigned int us)
{
ets_delay_us(us);
}
static void delay_1ms(unsigned int ms)
{
vTaskDelay(ms / portTICK_PERIOD_MS);
}
static void delay_1us(unsigned int us)
{
ets_delay_us(us);
}
#define ACK_CHECK_EN 0x1 /*!< I2C 主机将【理会】 应答*/
#define ACK_CHECK_DIS 0x0 /*!< I2C 主机将【不理会】应答 */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C 主机不需要发送缓存区 */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C 主机不需要接收缓存区 */
/******************************************************************
* 函 数 名 称:DS1307_GPIO_Init
* 函 数 说 明:对IIC引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void DS1307_GPIO_Init(void)
{
int i2c_master_port = I2C_NUM_1;//I2C_NUMBER(CONFIG_I2C_SLAVE_PORT_NUM);
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = GPIO_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = GPIO_SCL,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
// .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */
};
esp_err_t err = i2c_param_config(i2c_master_port, &conf);
//注册I2C服务即使能
i2c_driver_install(I2C_NUM_1, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
/******************************************************************
* 函 数 名 称:Write1307
* 函 数 说 明:向DS1307的add地址写入dat数据
* 函 数 形 参:add写入寄存器地址 dat写入数据
* 函 数 返 回:0写入成功 1写入器件地址无应答 2写入寄存器地址无应答
* 作 者:LC
* 备 注:器件地址=0xD0
******************************************************************/
unsigned char Write1307(unsigned char add,unsigned char dat)
{
unsigned char temp;
/* 10进制转BCD码 */
temp=dat/10;
temp<<=4;
temp=dat%10+temp;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
int err=0;
i2c_master_start(cmd);
err = i2c_master_write_byte(cmd, 0xD0, ACK_CHECK_EN);
i2c_master_write_byte(cmd, add, ACK_CHECK_EN);
i2c_master_write_byte(cmd, temp, ACK_CHECK_EN);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return (0);
}
/******************************************************************
* 函 数 名 称:Read1307
* 函 数 说 明:读取DS1307的时间数据
* 函 数 形 参:add读取的寄存器地址
* 函 数 返 回:255-读取失败 其他-读取成功
* 作 者:LC
* 备 注:无
******************************************************************/
unsigned char Read1307(unsigned char add)
{
int i =0;
unsigned char temp;
unsigned char dat;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, 0xD0, ACK_CHECK_EN);
i2c_master_write_byte(cmd, add, ACK_CHECK_EN);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, 0xD1, ACK_CHECK_EN);
i2c_master_read_byte(cmd, &dat, ACK_CHECK_EN);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
/* BCD码转19进制 */
temp=dat/16;
dat=dat%16;
dat=dat+temp*10;
return(dat);
}
/******************************************************************
* 函 数 名 称:get_RTC_time
* 函 数 说 明:获取DS1307的时间
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Get_RTC_Time(void)
{
RTC_Time.sec = Read1307(0x00);
RTC_Time.min = Read1307(0x01);
RTC_Time.hour = Read1307(0x02);
RTC_Time.week = Read1307(0x03);
RTC_Time.date = Read1307(0x04);
RTC_Time.month = Read1307(0x05);
RTC_Time.year = Read1307(0x06);
}
/******************************************************************
* 函 数 名 称:set_RTC_time
* 函 数 说 明:设置RTC时间
* 函 数 形 参:year=年份 范围00-99(年数后两位)
* month=月份 范围01-12
* date=日期 范围01-31
* week=星期 范围01-07
* hour=小时 范围01-12 or 00-23
* min =分钟 范围00-59
* sec =秒数 范围00-59
* 函 数 返 回:
* 作 者:LC
* 备 注:例如设置时间 2023-4-7 - 星期5 -13:57:00
* set_RTC_time(23, 4, 7, 5, 13,57, 0)
******************************************************************/
void Set_RTC_Time(uint8_t year,uint8_t month,uint8_t date,uint8_t week,uint8_t hour,uint8_t min,uint8_t sec)
{
Write1307(0x00,sec); //设置秒
Write1307(0x01,min); //设置分
Write1307(0x02,hour); //设置时
Write1307(0x03,week); //设置周
Write1307(0x04,date); //设置日
Write1307(0x05,month); //设置月
Write1307(0x06,year); //设置年
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
在文件bsp_ds1307.h中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-01-12 LCKFB-lp first version
*/
#ifndef _BSP_DS1307_H_
#define _BSP_DS1307_H_
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include <inttypes.h>
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "rom/ets_sys.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "sdkconfig.h"
#define GPIO_SCL 7
#define GPIO_SDA 8
typedef struct _RTC_TIME_STRUCT_ {
unsigned char sec;
unsigned char min;
unsigned char hour;
unsigned char week;
unsigned char date;
unsigned char month;
unsigned char year;
}_time_struct_;
extern _time_struct_ RTC_Time;
void DS1307_GPIO_Init(void);//引脚初始化
unsigned char Write1307(unsigned char add,unsigned char dat);//写入一个字节
unsigned char Read1307(unsigned char add);//读取一个字节
void Set_RTC_Time(uint8_t year,uint8_t month,uint8_t date,uint8_t week,uint8_t hour,uint8_t min,uint8_t sec);//设置初始时间
void Get_RTC_Time(void);//获取RTC时间
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
在main.c中,编写如下。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-01-12 LCKFB-lp first version
*/
#include <stdio.h>
#include "bsp_ds1307.h"
#include "string.h"
#include "esp_private/esp_task_wdt.h"
#include "esp_private/esp_task_wdt_impl.h"
int app_main(void)
{
DS1307_GPIO_Init();
Set_RTC_Time(24, 1, 12, 5, 15, 36, 0); //第一次上电设置时间才使用
vTaskDelay(1000 / portTICK_PERIOD_MS); //延时1秒
printf("RTC Demo Start....\r\n");
while(1)
{
Get_RTC_Time();//获取时间
printf("%d-%d-%d-星期%d\r\n", RTC_Time.year,RTC_Time.month,RTC_Time.date,RTC_Time.week);
printf("%d:%d:%d\r\n",RTC_Time.hour,RTC_Time.min,RTC_Time.sec);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
上电现象:
开发板断开连接,再次连接之后发现时间不连续,说明在开发板断开连接的时候RTC还在计数。