赞
踩
目录
初始化步骤如下:
- /* i2c 初始化 */
- void i2c_init()
- {
- // 时钟源的初始化放到了时钟初始化(CCM_init)
-
- // 关闭I2C
- I2C1_I2CR &= ~(1 << 7);
- // 设置分频值(分频值为 640)
- I2C1_IFDR = 0x15;
- // 打开I2C
- I2C1_I2CR |= (1 << 7);
- }
当总线空闲时,总线上的设备可以发送开始信号来占用总线。如果存在多个设备同时占用总线,此时就会发生冲裁。
- /* 产生开始信号 */
- i2c_status_t i2c_master_start(unsigned char address, i2c_direction direction)
- {
- /* 总线是否被占用 */
- if (is_bus_busy())
- {
- return Status_I2C_Busy;
- }
-
- /*
- * 发送开始信号
- * bit 5: 1 主机
- * bit 4: 1 发送
- */
- I2C1_I2CR |= ((1 << 4) | (1 << 5));
-
- /* 发送通信地址和通信方向 */
- I2C1_I2DR = (((unsigned int)address << 1) | (direction == I2C_Read ? 1 : 0));
-
- return Status_I2C_Success;
- }
当某个设备在通信时,可能会需要临时改变通信方向,此时就可以发送一个重复开始信号来改变通信方向。发送重复开始信号要求总线空闲,且当前设备是主机,只要有一者不满足,就会发送失败
- /* 重复产生开始信号(可能是修改通信方向、通信设备的地址) */
- i2c_status_t i2c_master_repeated_start(unsigned char address, i2c_direction direction)
- {
- /*
- * 总线是否被占用
- * - 如果是其他设备占用总线,直接返回
- * - 如果是当前设备占用总线,发送一个重复的开始信号
- */
- if (is_bus_busy() && is_master() == 0)
- {
- return Status_I2C_Busy;
- }
-
- /* 产生一个重复开始信号 */
- I2C1_I2CR |= ((1 << 2) | (1 << 4));
-
- /* 发送通信地址和通信方向 */
- I2C1_I2DR = (((unsigned int)address << 1U) | (direction & 0x01));
-
- return Status_I2C_Success;
- }
当主机打算继续通信时,可以发送一个停止信号来结束通信;如果从机不想继续通信,可以选择不回应主机表示想结束通信。
- /* 产生停止信号 */
- i2c_status_t i2c_master_stop()
- {
- unsigned short timeout = UINT16_MAX;
-
- /*
- * 产生一个停止信号
- * bit 3: 0 产生一个ACK
- * bit 4: 0 接收(要产生一个ACK,当前设备必须为接收模式)
- * bit 5: 0 从机
- */
- I2C1_I2CR &= ~((1 << 3) | (1 << 4) | (1 << 5));
-
- /* 等待总线被释放 */
- while (is_bus_busy() && (timeout--));
-
- // 等待超时
- if (timeout == 0)
- {
- return Status_I2C_Timeout;
- }
-
- return Status_I2C_Success;
- }
只有当上一次的数据全部发送完,才能开始下一次通信。其本质是,每次传输 8 bit(需要 8 个时钟周期),当第 9 个时钟周期的边沿触发时,我们认为数据传输完毕。
- /*
- * @description: 阻塞式发送
- * @param - sendbuf: 保存要发送的数据(允许多字节)
- * @param - len: 发送数据的长度
- * @param - stopflag: 下一次是否还要继续发送
- */
- i2c_status_t i2c_master_write_blocking(unsigned char* sendbuf, unsigned int len, unsigned int stopflag)
- {
- i2c_status_t i2c_stat = Status_I2C_Success;
-
- /* 等待DR寄存器准备就绪 */
- while (((I2C1_I2SR >> 7) & 0x01) == 0);
-
- /* 清除中断标志位 */
- clear_intpending_flag();
-
- /* 状态设为发送 */
- I2C1_I2CR |= (1 << 4);
-
- /* 逐字节发送 */
- while (len--)
- {
- // 将要发送的数据保存到寄存器
- I2C1_I2DR = *(sendbuf++);
-
- // 等待传输完成(传输完成时会触发中断)
- while (((I2C1_I2SR >> 1) & 0x01) == 0);
-
- // 清除中断标志位
- clear_intpending_flag();
-
- // 仲裁是否失败
- if(((I2C1_I2SR >> 4) & 0x01))
- {
- i2c_stat = Status_I2C_Lost_Arbitration;
- }
-
- // 是否收到 NACK
- if (((I2C1_I2SR >> 0) & 0x01))
- {
- i2c_stat = Status_I2C_NAK;
- }
-
- if (i2c_stat != Status_I2C_Success)
- {
- break;
- }
- }
-
- // 从机不想收了 或者 主机不想发了
- if ((i2c_stat == Status_I2C_NAK) || stopflag)
- {
- clear_intpending_flag();
- i2c_stat = i2c_master_stop();
- }
-
- return i2c_stat;
- }
-
- /* 清除中断标志位 */
- void clear_intpending_flag()
- {
- I2C1_I2SR &= ~(1 << 1);
- }
在DR寄存器准备就绪以后,要将当前状态设为接收,逐字节接收(需要考虑接收缓冲区的大小),一旦接收缓冲区大小不足以接收后续的数据,需要提前发送停止信号告诉对方,不要再继续发了。
接收缓冲区大小 = 0,表示本次接收继续,下一次接收无法进行,直接发送停止信号
接收缓冲区大小 = 1,表示下一次接收继续,但是后续接收无法继续,发送 NACK
- /*
- * @description : 阻塞式读取
- * @param - recvbuf : 读取到数据
- * @param - len : 要读取的数据大小(单位:字节)
- */
- i2c_status_t i2c_master_read_blocking(unsigned char* recvbuf, unsigned int len)
- {
- i2c_status_t result = Status_I2C_Success;
- volatile unsigned char dummy = 0;
-
- /* 使用这个变量的目的是避免编译报错 */
- dummy++;
-
- /* 等待DR寄存器准备就绪 */
- while (((I2C1_I2SR >> 1) & 0x01) == 0);
-
- /* 清除中断标志位 */
- clear_intpending_flag();
-
- /* 状态设为接收 */
- I2C1_I2CR &= ~((1 << 3) | (1 << 4));
- /* 后续只想再接收一个字节的数据 */
- if (len == 1)
- {
- I2C1_I2CR |= (1 << 3);
- }
-
- /* 假读 */
- dummy = I2C1_I2DR;
-
- while (len--)
- {
- /* 有一个字节的数据被传输到总线上,中断触发 */
- while (((I2C1_I2SR >> 1) & 0x01) == 0);
-
- /* 清除中断标志位 */
- clear_intpending_flag();
-
- /* 下一次没有足够的空间去读取 */
- if (len == 0)
- {
- result = i2c_master_stop();
- }
-
- // 后续只能再接收一个字节的数据,告诉对方不要再发数据了
- if (len == 1)
- {
- I2C1_I2CR |= (1 << 3);
- }
-
- /* 从寄存器中读取 */
- *(recvbuf++) = I2C1_I2DR & 0xFF;
- }
-
- return result;
- }
-
- /* 清除中断标志位 */
- void clear_intpending_flag()
- {
- I2C1_I2SR &= ~(1 << 1);
- }
实际上我们真正要调用的接口,只需要这一个即可,上述内容其实都是在为这个接口做铺垫。当数据开始传输的时候,
这里需要分别参考读时序和写时序的通信过程:I2C 通信时序
写时序:
读时序:
- typedef struct
- {
- unsigned char slaveAddress; // 要通信的设备地址
- i2c_direction direction; // 传输方向
- unsigned char reg; // 要读写的寄存器地址
- unsigned int reg_size; // 寄存器地址大小(单位: 字节, 一般是1个字节,也有可能大于1个字节)
- unsigned char stopFlag; // 下一次是否要停止发送
- unsigned char* buf; // 缓冲区
- unsigned int buf_size; // 缓冲区大小
- } i2c_transfer_t;
-
- /* 数据传输(发送 / 接收) */
- i2c_status_t i2c_master_transfer(i2c_transfer_t* xfer)
- {
- i2c_direction direction = xfer->direction;
-
- /* 传输之前清除所有的标志位(仲裁、中断挂起) */
- I2C1_I2SR &= ~((1 << 1) | (1 << 4));
-
- /* DR 寄存器是否准备就绪 */
- while (!((I2C1_I2SR >> 7) & 0x01));
-
- /* 如果是读时序,需要先临时修改通信方向 */
- if ((xfer->reg_size > 0) && (xfer->direction == I2C_Read))
- {
- direction = I2C_Write;
- }
-
- /* 1、发送开始信号 */
- i2c_status_t ret = i2c_master_start(xfer->slaveAddress, direction);
- if (ret != Status_I2C_Success)
- {
- return ret;
- }
-
- /* 等待传输完成 */
- while (!((I2C1_I2SR >> 1) & 0x01));
- /* 检查传输是否发生错误 */
- if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
- {
- i2c_master_stop();
- return ret;
- }
-
- /* 2、发送寄存器地址 */
- if (xfer->reg_size > 0)
- {
- do
- {
- clear_intpending_flag();
- xfer->reg_size --;
-
- /* 写入地址 */
- I2C1_I2DR = ((xfer->reg) >> (xfer->reg_size * 8)); // 先传输高字节
-
- /* 等待传输完成 */
- while (((I2C1_I2SR >> 1) & 0x01) == 0);
- /* 检查传输是否发生错误 */
- if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
- {
- i2c_master_stop();
- return ret;
- }
- } while (xfer->reg_size > 0);
-
- /* 如果是读时序,需要先发送重复开始信号;如果是写时序,直接发送数据 */
- if (xfer->direction == I2C_Read)
- {
- clear_intpending_flag();
-
- /* 发送重复开始信号 */
- ret = i2c_master_repeated_start(xfer->slaveAddress, I2C_Read);
- if (ret != Status_I2C_Success)
- {
- i2c_master_stop();
- return ret;
- }
- /* 等待传输完成 */
- while (((I2C1_I2SR >> 1) & 0x01) == 0);
- /* 检查传输是否发生错误 */
- if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
- {
- i2c_master_stop();
- return ret;
- }
- }
- }
-
- /* 到此,准备工作完毕 */
- /* 发送数据 */
- if (xfer->direction == I2C_Write)
- {
- ret = i2c_master_write_blocking(xfer->buf, xfer->buf_size, xfer->stopFlag);
- }
-
- if (xfer->direction == I2C_Read)
- {
- ret = i2c_master_read_blocking(xfer->buf, xfer->buf_size);
- }
-
- return ret;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。