Nuru_Banmian
Nuru_Banmian
Published on 2025-04-18 / 66 Visits
0
1

I2C-案例2-寄存器方式使用I2C

代码

I2C.h

 #ifndef __I2C_H
 #define __I2C_H
 ​
 #include "stm32f10x.h"
 #include "delay.h"
 ​
 // 宏定义
 #define OK 0
 #define FAIL 1
 ​
 // 初始化
 void I2C_Init(void);
 ​
 // 发出起始信号
 uint8_t I2C_Start(void);
 ​
 // 设置发出停止信号
 void I2C_Stop(void);
 ​
 // 主机设置使能应答信号
 void I2C_Ack(void);
 ​
 // 主机设置使能非应答信号
 void I2C_Nack(void);
 ​
 // 主机发送设备地址,并等待应答
 uint8_t I2C_SendAddr(uint8_t addr);
 ​
 // 主机发送一个字节的数据(写入),并等待应答
 uint8_t I2C_SendByte(uint8_t byte);
 ​
 // 主机从EEPROM接收一个字节的数据(读取)
 uint8_t I2C_ReadByte(void);
 ​
 #endif
 ​

I2C.c

1. I2C模式设置+I2C使能

 void I2C_Init(void)
 {
     // 1.配置时钟
     RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
     RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
 ​
     // 2.GPIO工作模式配置:服用开漏输出 CNF-11,MODE-11
     GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11 |
                    GPIO_CRH_CNF10 | GPIO_CRH_CNF11);
 ​
     // 3.I2C2配置
     // 3.1 硬件工作模式
     I2C2->CR1 &= ~I2C_CR1_SMBUS;
     I2C2->CCR &= ~I2C_CCR_FS;
 ​
     // 3.2 选择输入的时钟频率
     I2C2->CR2 |= 36;
 ​
     // 3.3 配置CCR,对应数据传输速率100kb/s,SCL高电平时间为5us
     I2C2->CCR |= 180;
 ​
     // 3.4 配置TRISE,SCL上升沿最大时钟周期数 + 1
     I2C2->TRISE |= 37;
 ​
     // 3.5 使能I2C2模块
     I2C2->CR1 |= I2C_CR1_PE;
 }

2. 产生起始和终止条件

 // 发出起始信号
 uint8_t I2C_Start(void)
 {
     // 产生一个起始信号
     I2C2->CR1 |= I2C_CR1_START;
 ​
     // 引入一个超时时间
     uint16_t timeout = 0xFFFF;
 ​
     // 等待信号发出
     while ((I2C1->SR1 & I2C_SR1_SB) == 0 && timeout)
     {
         timeout--;
     }
 ​
     return timeout ? OK : FAIL;
 }
 ​
 // 设置接收完成之后发出停止信号
 void I2C_Stop(void)
 {
     I2C2->CR1 |= I2C_CR1_STOP;
 }

3. 产生应答信号和非应答信号

 // 主机设置使能应答信号
 void I2C_Ack(void)
 {
     I2C2->CR1 |= I2C_CR1_ACK;
 }
 ​
 // 主机设置使能非应答信号
 void I2C_Nack(void)
 {
     I2C2->CR1 &= ~I2C_CR1_ACK;
 }

4. 收发数据

 // 主机发送设备地址,并等待应答
 uint8_t I2C_SendAddr(uint8_t addr)
 {
     // 直接讲要发送的地址发送给到DR
     I2C2->DR = addr;
 ​
     // 等待应答
     uint16_t timeout = 0xFFFF;
     while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout)
     {
         timeout--;
     }
     // 访问SR2,清除ADDR标志位
     if (timeout)
     {
         I2C2->SR2;
     }
 ​
     return timeout ? OK : FAIL;
 }
 ​
 // 主机从EEPROM接收一个字节的数据(读取)
 uint8_t I2C_SendByte(uint8_t byte)
 {
     // 1. 等待DR为空,上一个字节数据发送完毕
     uint16_t timeout = 0xFFFF;
     while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout)
     {
         timeout--;
     }
 ​
     // 2. 讲要发送的字节放入DR
     I2C2->DR = byte;
 ​
     // 3. 等待应答
     timeout = 0xFFFF;
     while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout)
     {
         timeout--;
     }
     /*
     BTF的清除条件为:对数据寄存器的读或写操作,
     或者在传输中发送一个其实或者停止条件后,
     或者PE=0时,硬件自动清零该位,
     所以根据各操作的流程来判断,这个清零操作不必要
     */
 ​
     return timeout ? OK : FAIL;
 }
 ​
 uint8_t I2C_ReadByte(void)
 {
     // 1. 等待DR为满
     uint16_t timeout = 0xFFFF;
     while ((I2C2->SR1 & I2C_SR1_RXNE) == 0 && timeout)
     {
         timeout--;
     }
 ​
     // 2. 将收到的字节返回
     return timeout ? I2C2->DR : FAIL;
 }

m24c02.h

 #ifndef __M24C02_H
 #define __M24C02_H
 ​
 #include "i2c.h"
 ​
 // 宏定义
 #define W_ADDR 0xA0
 #define R_ADDR 0xA1
 ​
 // 初始化
 void M24C02_Init(void);
 ​
 // 向EEPROM写入一个字节
 void M24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
 ​
 // 从EEPROM读取一个字节
 uint8_t M24C02_ReadByte(uint8_t inneraddr);
 ​
 // 连续写入多个字节(页写)
 void M24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t size);
 ​
 // 连续读取多个字节
 void M24C02_ReadBytes(uint8_t inneraddr, uint8_t *buffer, uint8_t size);
 ​
 #endif
 ​

m24c02.c

 #include "m24c02.h"
 ​
 // 初始化
 void M24C02_Init(void)
 {
     I2C_Init();
 }
 ​
 // 向EEPROM写入一个字节
 void M24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
 {
     // 1.发送起始信号
     I2C_Start();
 ​
     // 2.发送写地址
     I2C_SendAddr(W_ADDR);
 ​
     // 4.发送内部地址
     I2C_SendByte(inneraddr);
 ​
     // 5.发送具体数据
     I2C_SendByte(byte);
 ​
     // 6.发送停止信号
     I2C_Stop();
 ​
     // 延迟等待写入周期结束
     Delay_ms(5);
 }
 ​
 // 从EEPROM读取一个字节
 uint8_t M24C02_ReadByte(uint8_t inneraddr)
 {
     // 1. 发出开始信号
     I2C_Start();
 ​
     // 2. 发送写地址(假写)
     I2C_SendAddr(W_ADDR);
 ​
     // 3. 发送内部地址
     I2C_SendByte(inneraddr);
 ​
     // 4. 发出开始信号
     I2C_Start();
 ​
     // 5. 发送读地址(真读)
     I2C_SendAddr(R_ADDR);
 ​
     // 6. 设置发送一个非应答
     I2C_Nack();
 ​
     // 7. 设置发出一个停止信号
     I2C_Stop();
 ​
     // 8. 读取一个字节
     uint8_t byte = I2C_ReadByte();
 ​
     return byte;
 }
 ​
 // 连续写入多个字节(页写)
 void M24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t size)
 {
     // 1.发送起始信号
     I2C_Start();
 ​
     // 2.发送写地址
     I2C_SendAddr(W_ADDR);
 ​
     // 4.发送内部地址
     I2C_SendByte(inneraddr);
 ​
     // 利用循环不停发送数据
     for (uint8_t i = 0; i < size; i++)
     {
         // 4.发送具体数据
         I2C_SendByte(bytes[i]);
     }
     // 5.发送停止信号
     I2C_Stop();
 ​
     // 延迟等待写入周期结束
     Delay_ms(5);
 }
 ​
 // 连续读取多个字节
 void M24C02_ReadBytes(uint8_t inneraddr, uint8_t *buffer, uint8_t size)
 {
     // 1.发送起始信号
     I2C_Start();
 ​
     // 2.发送写地址
     I2C_SendAddr(W_ADDR);
 ​
     // 3.发送内部地址
     I2C_SendByte(inneraddr);
 ​
     // 4.发出开始信号
     I2C_Start();
 ​
     // 5.发送读地址
     I2C_SendAddr(R_ADDR);
 ​
     // 利用循环不停读取数据
     for (uint8_t i = 0; i < size; i++)
     {
         // 6.设置应答或非应答
         if (i < size - 1)
         {
             I2C_Ack();
         }
         else
         {
             I2C_Nack();
             // 7. 设置发出停止信号
             I2C_Stop();
         }
 ​
         // 8.读取一个字节
         buffer[i] = I2C_ReadByte();
     }
 }
 ​

main.c

 #include "usart.h"
 #include "m24c02.h"
 #include <string.h>
 ​
 int main(void)
 {
     // 1.初始化
     USART_Init();
     M24C02_Init();
 ​
     USART_SendString("你好\r\n", 6);
 ​
     // 2.向EEPROM依次写入单个字符
     M24C02_WriteByte(0x00, 'a');
     M24C02_WriteByte(0x00, 'b');
     M24C02_WriteByte(0x00, 'c');
 ​
     // 3.读取字符
     uint8_t byte1 = M24C02_ReadByte(0x00);
     uint8_t byte2 = M24C02_ReadByte(0x01);
     uint8_t byte3 = M24C02_ReadByte(0x02);
 ​
     // 4.串口输出打印
     printf("byte1 = %c\t byte2 = %c\t byte3 = %c\n", byte1, byte2, byte3);
 ​
     // 5. 写入多个字符
     M24C02_WriteBytes(0x00, "abcdef", 6);
 ​
     // 6. 读取多个字符
     uint8_t buffer[] = {0};
     M24C02_WriteBytes(0x00, buffer, 6);
 ​
     // 7. 串口打印
     print("buffer = %s\n", buffer);
 ​
     // 8. 测试超出16个字节的写入
     // 清零缓冲区
     memset(buffer, 0, sizeof(buffer));
 ​
     M24C02_WriteBytes(0x00, "12345678490abcdefghijk", 21);
     M24C02_ReadBytes(0x00, buffer, 21);
     print("buffer = %s\n", buffer);
 ​
 ​
     while (1)
     {
     }
 }
 ​

寄存器

控制寄存器

I2C_案例2_控制寄存器1_1.png

SMBUS

I2C_案例2_控制寄存器1_2.png

PE

置1让I2C模块使能

I2C_案例2_控制寄存器1_3.png

START

置1时,如果在主模式下

CR1会重复发出起始信号,由于可能有其他设备占用总线,它会一直重复发出信号直到总线空闲时接管总线后即可清零

从模式则只有在总线空闲时才会发出起始信号

是否成功发出起始信号,可以在状态寄存器的SB位中得知

I2C_案例2_控制寄存器1_4.png

STOP

在字节传输结束后或当前起始条件发出后产生停止条件

I2C_案例2_控制寄存器1_5.png

ACK

在接受完一个字节之后产生应答

I2C_案例2_控制寄存器1_6.png


I2C_案例2_控制寄存器2_1.png

I2C_案例2_控制寄存器2_2.png

时钟控制寄存器

I2C_案例2_时钟控制寄存器_1.png

F/S

F:fast

S:standard

I2C_案例2_时钟控制寄存器_2.png

CCR

TPCLK1指当前的时钟周期,为1/36μs

数据的传输速率为100kb/s,所以I2C的Thigh+Tlow = 1/100k(10μs),所以Thigh,Tlow应为5μs

Thigh/low = 5μs,TPCLK1 = 1/36,得出CCR = 5*36=180

如果提供了8Mhz频率的FREQR时钟,则CRR需要写入40

Thigh和Tlow时长是稳定时长和上升下降过程时长之和

所以Thigh+Tlow就是一个周期

I2C_案例2_时钟控制寄存器_3.png

TRISE寄存器

I2C_案例2_TRISE寄存器_1.png

TRISE

标准模式中最大允许的SCL上升时间为1μs

时钟周期为1/36μs

TRISE的值表示我们的上升沿部分可以占时钟周期的多少个周期

1/36 * 36 = 1

因此TRISE最大可以设置为36,最后预留1个周期

及设置为36 + 1 = 37

I2C_案例2_TRISE寄存器_2.png

状态寄存器

I2C_案例2_状态寄存器1_1.png

BTF:beat transform finishi 一个表示传输是否完成的标志位

ADDR:判断读写地址是否被成功发送

STOPF:从设备模式检测是否接收到停止信号的标志位

SB

I2C_案例2_状态寄存器1_2.png

TxE

I2C_案例2_状态寄存器1_3.png

RxNE

I2C_案例2_状态寄存器1_4.png

数据寄存器

I2C_案例2_数据寄存器_1.jpg

DR

I2C_案例2_数据寄存器_2.jpg



Comment