Nuru_Banmian
Nuru_Banmian
Published on 2025-04-14 / 80 Visits
0
1

I2C-案例1-寄存器方式USART模拟I2C

i2c.h

 #ifndef __I2C_H
 #define __I2C_H
 ​
 #include "stm32f10x.h"
 #include "delay.h"
 ​
 // 宏定义
 #define ACK 0
 #define NACK 1
 ​
 // 控制SCL,SDA的输出高低电平
 #define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
 #define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
 #define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
 #define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
 ​
 // 读取操作
 #define READ_SDA (GPIOB->IDR & GPIO_ODR_ODR11)
 ​
 // 定义操作的基本延迟,数据的传输速率为100Kb/s,可以算出时钟周期为1/100000 = 10us,所以设置10微秒延迟
 #define I2C_DELAY Delay_us(10)
 ​
 // 初始化
 void I2C_Init(void);
 ​
 // 发出起始信号
 void I2C_Start(void);
 ​
 // 发出停止信号
 void I2C_Stop(void);
 ​
 // 主机发出应答信号
 void I2C_Ack(void);
 ​
 // 主机发出非应答信号
 void I2C_Nack(void);
 ​
 // 主机等待从设备发来应答信号
 uint8_t I2C_Wait4Ack(void);
 ​
 // 主机发送一个字节的数据
 void I2C_SendByte(uint8_t byte);
 ​
 // 主机从EEPROM接收一个字节的数据(读取)
 uint8_t I2C_ReadByte(void);
 ​
 #endif
 ​

i2c.c

 #include "i2c.h"
 ​
 // 初始化
 void I2C_Init(void)
 {
     // 1.配置时钟
     RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
 ​
     // 2.GPIO工作模式配置:通用开漏输出 CNF-01,MODE-11
     GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
     GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
     GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
 }
 ​
 // 发出起始信号
 void I2C_Start(void)
 {
     // 1.SCL拉高,SDA拉高
     SCL_HIGH;
     SDA_HIGH;
     I2C_DELAY;
 ​
     // 2.SCL保持不变,SDA拉低
     SDA_LOW;
     I2C_DELAY;
 }
 ​
 // 发出停止信号
 void I2C_Stop(void)
 {
     // 1.SCL拉高,SDA拉低
     SCL_HIGH;
     SDA_LOW;
     I2C_DELAY;
 ​
     // 2.SCL保持不变,SDA拉高
     SDA_HIGH;
     I2C_DELAY;
 }
 ​
 // 主机发出应答信号
 void I2C_Ack(void)
 {
     // 1.SCL拉低,SDA拉低,准备发出应答信号
     SCL_LOW;
     SDA_LOW;
     I2C_DELAY;
 ​
     // 2.SCL拉高,SDA保持不变,发出信号
     SCL_HIGH;
     I2C_DELAY;
 ​
     // 3.SCL拉低,SDA保持不变,结束数据线信号采样
     SCL_LOW;
     I2C_DELAY;
 ​
     // 4.SCL保持不变,SDA拉高,释放数据总线
     SDA_HIGH;
     I2C_DELAY;
 }
 ​
 // 主机发出非应答信号
 void I2C_Nack(void)
 {
     // 1.SCL拉低,SDA拉低,准备发出应答信号
     SCL_LOW;
     SDA_HIGH;
     I2C_DELAY;
 ​
     // 2.SCL拉高,SDA保持不变,发出信号
     SCL_HIGH;
     I2C_DELAY;
 ​
     // 3.SCL拉低,SDA保持不变,结束数据线信号采样
     SCL_LOW;
     I2C_DELAY;
 }
 ​
 // 主机等待从设备发来应答信号
 uint8_t I2C_Wait4Ack(void)
 {
     // 1.SCL拉低,SDA拉高,释放数据总线
     SCL_LOW;
     SDA_HIGH;
     I2C_DELAY;
 ​
     // 2.SCL拉高,开始数据采样
     SCL_HIGH;
     I2C_DELAY;
 ​
     // 3.读取SDA数据线上的电平
     uint16_t ack = READ_SDA;
 ​
     // 4.SCL拉低,结束数据采样
     SCL_LOW;
     I2C_DELAY;
 ​
     return ack ? NACK : ACK; // ack = 0,ACK; ack != 0,NACK;   这么判断是因为ack的数据是用GPIOB->IDR & GPIO_ODR_ODR11与出来的,所以得到的数据不是1,所以要判断是否为0
 }
 ​
 // 主机发送一个字节的数据
 void I2C_SendByte(uint8_t byte)
 {
     for (uint8_t i = 0; i < 8; i++)
     {
         // 1.SCL,SDA都拉低,等待数据翻转
         SCL_LOW;
         SDA_LOW;
         I2C_DELAY;
 ​
         // 2.取字节的最高位,向SDA写入数据
         if (byte & 0x80)
         {
             SDA_HIGH;
         }
         else
         {
             SDA_LOW;
         }
         I2C_DELAY;
 ​
         // 3.SCL拉高,数据写入完成
         SCL_HIGH;
         I2C_DELAY;
 ​
         // 4.SCL拉低,采样结束
         SCL_LOW;
         I2C_DELAY;
 ​
         // 5.左移一位
         byte <<= 1;
     }
 }
 ​
 // 主机从EEPROM接收一个字节的数据(读取)
 uint8_t I2C_ReadByte(void)
 {
     // 定义一个变量,用来保存接收的数据
     uint8_t data = 0;
 ​
     // 循环处理每一位
     for (uint8_t i = 0; i < 8; i++)
     {
         // 1. SCL拉低,等待数据翻转
         SCL_LOW;
         I2C_DELAY;
 ​
         // 2. SCL拉高,开始采样
         SCL_HIGH;
         I2C_DELAY;
 ​
         // 3. 数据采样,读取SDA
         data <<= 1; // 先做左移,新存入的位永远在最低位
         if (READ_SDA)
         {
             data |= 0x01; // 先存入最低位,然后每次都左移1位
         }
 ​
         // 4. SCL拉低,结束采样
         SCL_LOW;
         I2C_DELAY;
     }
 ​
     return data;
 }
 ​

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_SendByte(W_ADDR);
 ​
     // 3.等待EEPROM应答
     uint8_t ack = I2C_Wait4Ack();
 ​
     if (ack == ACK)
     {
         // 4.发送内部地址
         I2C_SendByte(inneraddr);
 ​
         // 5.等待EEPROM应答
         I2C_Wait4Ack();
 ​
         // 6.发送具体数据
         I2C_SendByte(byte);
 ​
         // 7.等待EEPROM应答
         I2C_Wait4Ack();
 ​
         // 8.发送停止信号
         I2C_Stop();
     }
 ​
     // 延迟等待写入周期结束
     Delay_ms(5);
 }
 ​
 // 从EEPROM读取一个字节
 uint8_t M24C02_ReadByte(uint8_t inneraddr)
 {
     // 1. 发出开始信号
     I2C_Start();
 ​
     // 2. 发送写地址(假写)
     I2C_SendByte(W_ADDR);
 ​
     // 3. 等待EEPROM应答
     I2C_Wait4Ack();
 ​
     // 4. 发送内部地址
     I2C_SendByte(inneraddr);
 ​
     // 5. 等待应答
     I2C_Wait4Ack();
 ​
     // 6. 发出开始信号
     I2C_Start();
 ​
     // 7. 发送读地址(真读)
     I2C_SendByte(R_ADDR);
 ​
     // 8. 等待EEPROM应答
     I2C_Wait4Ack();
 ​
     // 9. 读取一个字节
     uint8_t byte = I2C_ReadByte();
 ​
     // 10. 发送一个非应答
     I2C_Nack();
 ​
     // 11. 发出一个停止信号
     I2C_Stop();
 ​
     return byte;
 }
 ​
 // 连续写入多个字节(页写)
 void M24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t size)
 {
     // 1.发送起始信号
     I2C_Start();
 ​
     // 2.发送写地址
     I2C_SendByte(W_ADDR);
 ​
     // 3.等待EEPROM应答
     uint8_t ack = I2C_Wait4Ack();
     if (ack == ACK)
     {
         // 4.发送内部地址
         I2C_SendByte(inneraddr);
 ​
         // 5.等待EEPROM应答
         I2C_Wait4Ack();
 ​
         // 利用循环不停发送数据
         for (uint8_t i = 0; i < size; i++)
         {
             // 6.发送具体数据
             I2C_SendByte(bytes[i]);
 ​
             // 7.等待EEPROM应答
             I2C_Wait4Ack();
         }
 ​
         // 8.发送停止信号
         I2C_Stop();
     }
 ​
     // 延迟等待写入周期结束
     Delay_ms(5);
 }
 ​
 // 连续读取多个字节
 void M24C02_ReadBytes(uint8_t inneraddr, uint8_t *buffer, uint8_t size)
 {
     // 1.发送起始信号
     I2C_Start();
 ​
     // 2.发送写地址
     I2C_SendByte(W_ADDR);
 ​
     // 3.等待EEPROM应答
     I2C_Wait4Ack();
 ​
     // 4.发送内部地址
     I2C_SendByte(inneraddr);
 ​
     // 5.等待EEPROM应答
     I2C_Wait4Ack();
 ​
     // 6.发出开始信号
     I2C_Start();
 ​
     // 7.发送读地址
     I2C_SendByte(R_ADDR);
 ​
     // 8.等待EEPROM应答
     I2C_Wait4Ack();
 ​
     // 利用循环不停读取数据
     for (uint8_t i = 0; i < size; i++)
     {
         // 9.读取一个字节
         buffer[i] = I2C_ReadByte();
 ​
         // 10.发送一个应答或非应答
         if (i < size - 1)
         {
             I2C_Ack();
         }
         else
         {
             I2C_Nack();
         }
     }
 ​
     // 11.发送停止信号
     I2C_Stop();
 }
 ​

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.向EEPROM写入多个字符
     M24C02_WriteBytes(0x00, "123456", 6);
 ​
     // 6.读取多个字符
     uint8_t buffer[100] = {0};
     M24C02_ReadBytes(0x00, buffer, 6);
 ​
     // 7.串口输出打印
     printf("buffer = %s\n", buffer);
 ​
     // 8.测试超出16个字节的写入
     // 清零缓冲区
     memset(buffer, 0, sizeof(buffer));
 ​
     M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
     M24C02_ReadBytes(0x00, buffer, 21);
     printf("buffer = %s\n", buffer);
 ​
     while (1)
     {
     }
 }
 ​



Comment