【STM32】软件I2C(支持多字节)

I2C简介

I2C总线是一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信。I2C总线有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步。

  • 在一个i2c通讯总线中,可连接多个i2c通讯设备(分为主机和从机)。
  • 主机有权发起和结束一次通信,从机只能被动呼叫。当总线上有多个主机同时启动总线时,i2c也具备冲突检测和仲裁的功能来防止错误产生。
  • 每个连接到i2c总线上的器件都有一个唯一的地址(7bit或者10bit),且每一个器件都可以作为主机也可以作为从机(但同一时刻只能有一个主机)。
  • 串行的8位双向数据传输速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。

I2C协议


有几点需要重点说明,下面会详细介绍每一个环节

  • SLAVE_ADDRESS有7bit格式或者10bit格式。
  • 除开起始和终止信号,每次发送或者读取一字节数据后,都需要等待ack或者发送ack。
  • 寄存器地址或者值也肯能占多字节,先发送高字节的数据。

起始和终止信号

起始信号:当SCL在高电平期间SDA从高电平向低电平切换

终止信号:当SCL在高电平期间SDA从低电平向高电平切换

数据有效性

SDA数据线在SCL的每一个时钟周期传输一位数据。

  • SCL高电平期间:SDA表示的数据有效,此时SDA的电平要稳定,SDA为高电平时表示数据“1”,为低电平时表示数据“0”。

  • SCL低电平期间:SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。

    数据和地址按8位进行传输,先传输数据的高位,每次传输的字节数不受限制.

I2C地址及数据方向

I2C总线上的每一个设备都有自己的独立地址,主机发起通讯时,通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机.I2C协议规定设备地址可以是7位或者10位,实际中7位地址应用比较广泛.紧跟设备地址的一个数据位用来表示数据传输方向,第8位或第11位.

  • 数据方向位为"1":表示主机由从机读数据
  • 数据方向位为"0":表示主机向从机写数据

10bit slave_address要分两个字节传输,高字节需要补上标志位"11110".

I2C设备寄存器地址和数据

上文中,我们默认设备寄存器地址和数据都是一个字节,但是实际项目中寄存器地址可能是两字节,数据可能是两字节或四字节.这种情况下其实和单字节类似,只不过是将多字节拆分为单字节传输,中间还是需要ACK.

举例,16位寄存器地址和32位数据的读时序:

Burst模式

burst模式其实就是连续模式,连续写或者读.

还是只传输一个地址,但是值可以是多个,写入的地址则是在原地址上递增.

写寄存器时序,就不举例了.就是多接收数据,在不要数据的时候发送NACK,否则还是继续发送ACK.理论上,burst的长度是没有限制,但是实际I2C设备是有限制的,具体就需要看I2C设备的说明了.

应答和非应答信号

I2C的数据或者地址传输都带响应.响应包括"ACK"和"NACK"两种信号.作为数据接收端时,当收到I2C传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送"ACK"信号,发送方会继续发送下一个数据;若接收方希望结束数据传输,则向对方发送"NACK"信号.发送方接收到信号后会产生一个停止信号,结束信号传输.

注意一点,在等待应答信号前,需要释放SDA的控制权,将SDA总线置为高电平.

数据发送端会释放SDA的控制权,由数据接收端控制SDA,给发送端传输应答或非应答信号

  • SDA为高电平:表示非应答信号(NACK)

  • SDA为低电平:表示应答信号(ACK)

软件I2C GPIO引脚配置

这里先说一下,为什么要使用软件模拟i2c,硬件上不是已经实现了i2c吗?我们在stm32中直接将引脚配置成i2c,然后使用hal库不就可以实现i2c通信吗?

理论上是的,但是据说,STM32芯片I2C有bug,所以很少人愿意冒险。

所以,要使用软件模拟I2C的时候,GPIO需要配置为开漏输出,至于为什么这样配置,可以参考(2条消息) I2C 开漏输出与上拉电阻_jiangdf的博客-CSDN博客

软件I2C的实现

软件设计的几个目标

  • GPIO配置由外部实现(其实也想放在这里实现的,但是使能GPIO时钟有点绕)
  • 支持7位或10位SLAVE_ADDRESS
  • 支持多字节寄存器地址或数据
  • 支持多个I2C速率

先看头文件

/** @Date: 2023-06-12 11:03:57* @LastEditors: zdk* @LastEditTime: 2023-06-16 14:30:05* @FilePath: \haptics_evb_h7_bootloaderd:\01Project\haptic\code_new\haptics_evb_h7\haptics_evb_h7\Core\Inc\i2c_sw.h*/
#ifndef I2C_SW_H
#define I2C_SW_H#ifdef __cplusplus
#define
extern "c"
{
#endif#include "gpio.h"
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>#define I2C_WRITE (0x00)
#define I2C_READ  (0x01)
#define I2C_10BIT_SLAVE_ADDRESS_INDICATOR (0b11110)typedef enum{Standard_Mode = 100 * 1000,Fast_Mode = 400 * 1000,Fast_Mode_Plus = 1000 * 1000,} I2C_SW_Speed_Mode_e; //i2c速率 unit Hztypedef enum{One_Byte = 1,Two_Byte = 2,Four_Byte = 4} Data_Width_e;//数据长度(寄存器地址或者值的数据长度)typedef enum{Slave_Address_7Bit,Slave_Address_10Bit,} Slave_Address_Width_e;//i2c slave address的位宽typedef struct{uint16_t scl_pin;uint16_t sda_pin;GPIO_TypeDef* scl_port;GPIO_TypeDef* sda_port;I2C_SW_Speed_Mode_e speed_mode;uint16_t slave_addr;Slave_Address_Width_e slave_address_width;Data_Width_e slave_reg_addr_width;Data_Width_e slave_reg_value_width;} I2C_SW_Handle_t;/*** @description: 检测slave address的i2c设备是否存在* @param {I2C_SW_Handle_t*} handle* @return {*}*/     bool i2c_device_exist(I2C_SW_Handle_t* handle);/*** @description: 读寄存器* @param {I2C_SW_Handle_t*} handle* @param {uint32_t} reg_addr* @param {uint32_t} len* @param {uint32_t*} buf* @return {*}*/    int i2c_reg_read(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, uint32_t* buf);/*** @description: 写寄存器* @param {I2C_SW_Handle_t*} handle* @param {uint32_t} reg_addr* @param {uint32_t} len* @param {uint32_t*} buf* @return {*}*/    int i2c_reg_write(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, const uint32_t* buf);#ifdef __cplusplus
#define
}
#endif#endif

再看源文件,源文件中有多个本地函数可以介绍一下

/*** @description: 延时函数,用于控制i2c频率* @param {I2C_SW_Speed_Mode_e} speed_mode* @return {*}*/
static void i2c_delay(I2C_SW_Speed_Mode_e speed_mode);/*** @description: 将时钟信号拉低* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void scl_l(I2C_SW_Handle_t* handle);/*** @description: 将时钟信号拉高* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void scl_h(I2C_SW_Handle_t* handle);/*** @description: 将数据信号拉低* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void sda_l(I2C_SW_Handle_t* handle);/*** @description: 将数据信号拉高* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void sda_h(I2C_SW_Handle_t* handle);/*** @description: 读取数据信号的电平* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static char sda_read(I2C_SW_Handle_t* handle);/*** @description: 产生i2c起始信号* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void i2c_start(I2C_SW_Handle_t* handle);/*** @description: 产生i2c终止信号* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void i2c_stop(I2C_SW_Handle_t* handle);/*** @description: 产生i2c应答信号* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void i2c_ack(I2C_SW_Handle_t* handle);/*** @description: 产生i2c非应答信号* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static void i2c_no_ack(I2C_SW_Handle_t* handle);/*** @description: 等待i2c应答信号* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static bool i2c_wait_ack(I2C_SW_Handle_t* handle);/*** @description: i2c发送一字节数据* @param {I2C_SW_Handle_t*} handle* @param {char} data* @return {*}*/
static void i2c_send_byte(I2C_SW_Handle_t* handle, char data);/*** @description: i2c读取一字节数据* @param {I2C_SW_Handle_t*} handle* @return {*}*/
static char i2c_recv_byte(I2C_SW_Handle_t* handle);/*** @description: 发送slave address信息* @param {I2C_SW_Handle_t*} handle* @param {char} rw 读或者写方向* @return {*}*/
static bool i2c_send_slave_address_with_wait_ack(I2C_SW_Handle_t* handle, char rw);/*** @description: 发送要读或写的地址信息* @param {I2C_SW_Handle_t*} handle* @param {uint32_t} address* @return {*}*/
static bool i2c_send_one_reg_address_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t address);/*** @description: 接收寄存器地址值信息* @param {I2C_SW_Handle_t*} handle* @param {uint32_t*} value* @param {bool} no_ack* @return {*}*/
static bool i2c_recv_one_reg_value_with_ack(I2C_SW_Handle_t* handle, uint32_t* value, bool no_ack);/*** @description: 发送寄存器值信息* @param {I2C_SW_Handle_t*} handle* @param {uint32_t} value* @return {*}*/
static bool i2c_send_one_reg_value_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t value);static void i2c_delay(I2C_SW_Speed_Mode_e speed_mode)
{uint32_t temp;SysTick->LOAD = SystemCoreClock / 8 / (speed_mode * 2) - 1;SysTick->VAL = 0X00; //SysTick->CTRL = 0X01; //do{temp = SysTick->CTRL; //}while((temp & 0x01) && (!(temp & (1 << 16)))); //SysTick->CTRL = 0x00; //SysTick->VAL = 0X00; //
}static void scl_l(I2C_SW_Handle_t* handle)
{HAL_GPIO_WritePin(handle->scl_port, handle->scl_pin, GPIO_PIN_RESET);
}static void scl_h(I2C_SW_Handle_t* handle)
{HAL_GPIO_WritePin(handle->scl_port, handle->scl_pin, GPIO_PIN_SET);
}static void sda_l(I2C_SW_Handle_t* handle)
{HAL_GPIO_WritePin(handle->sda_port, handle->sda_pin, GPIO_PIN_RESET);
}static void sda_h(I2C_SW_Handle_t* handle)
{HAL_GPIO_WritePin(handle->sda_port, handle->sda_pin, GPIO_PIN_SET);
}
static char sda_read(I2C_SW_Handle_t* handle)
{return HAL_GPIO_ReadPin(handle->sda_port, handle->sda_pin);
}static void i2c_start(I2C_SW_Handle_t* handle)
{scl_h(handle);sda_h(handle);i2c_delay(handle->speed_mode);sda_l(handle);i2c_delay(handle->speed_mode);scl_l(handle);i2c_delay(handle->speed_mode);
}static void i2c_stop(I2C_SW_Handle_t* handle)
{scl_h(handle);i2c_delay(handle->speed_mode);sda_l(handle);i2c_delay(handle->speed_mode);sda_h(handle);i2c_delay(handle->speed_mode);
}static void i2c_ack(I2C_SW_Handle_t* handle)
{scl_l(handle);i2c_delay(handle->speed_mode);sda_l(handle);i2c_delay(handle->speed_mode);scl_h(handle);i2c_delay(handle->speed_mode);scl_l(handle);i2c_delay(handle->speed_mode);sda_h(handle);i2c_delay(handle->speed_mode);
}static void i2c_no_ack(I2C_SW_Handle_t* handle)
{sda_h(handle);i2c_delay(handle->speed_mode);scl_h(handle);i2c_delay(handle->speed_mode);scl_l(handle);i2c_delay(handle->speed_mode);
}static bool i2c_wait_ack(I2C_SW_Handle_t* handle)
{sda_h(handle);i2c_delay(handle->speed_mode);scl_h(handle);i2c_delay(handle->speed_mode);int retry_cnt = 10;while(retry_cnt--){if(!sda_read(handle)){scl_l(handle);i2c_delay(handle->speed_mode);return true;}}scl_l(handle);i2c_delay(handle->speed_mode);return false;
}static void i2c_send_byte(I2C_SW_Handle_t* handle, char data)
{for(int offset = 0; offset < 8; offset++){scl_l(handle);i2c_delay(handle->speed_mode);if(data & 0x80)sda_h(handle);elsesda_l(handle);i2c_delay(handle->speed_mode);scl_h(handle);i2c_delay(handle->speed_mode);data <<= 1;}scl_l(handle);i2c_delay(handle->speed_mode);
}static char i2c_recv_byte(I2C_SW_Handle_t* handle)
{sda_h(handle);i2c_delay(handle->speed_mode);char value = 0;for(int offset = 0; offset < 8; offset++){scl_h(handle);i2c_delay(handle->speed_mode);value <<= 1;if(sda_read(handle))value++;scl_l(handle);i2c_delay(handle->speed_mode);}return value;
}static bool i2c_send_slave_address_with_wait_ack(I2C_SW_Handle_t* handle, char rw)
{if(handle->slave_address_width == Slave_Address_7Bit){i2c_send_byte(handle, (handle->slave_addr << 1) | rw);if(!i2c_wait_ack(handle))return false;}else if(handle->slave_address_width == Slave_Address_10Bit){uint8_t h_addr = (I2C_10BIT_SLAVE_ADDRESS_INDICATOR << 7)| ((handle->slave_addr >> 8 & 0x03) << 2) | rw;i2c_send_byte(handle, h_addr);if(!i2c_wait_ack(handle))return false;uint8_t l_addr = handle->slave_addr & 0xff;i2c_send_byte(handle, l_addr);if(!i2c_wait_ack(handle))return false;}return true;
}static bool i2c_send_one_reg_address_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t address)
{for(int j = 0; j < handle->slave_reg_addr_width; j++){uint8_t offset = (handle->slave_reg_addr_width - 1 - j) * 8;uint8_t temp_reg_addr = (address >> offset) & 0xff;i2c_send_byte(handle, temp_reg_addr);if(!i2c_wait_ack(handle))return false;}return true;
}static bool i2c_recv_one_reg_value_with_ack(I2C_SW_Handle_t* handle, uint32_t* value, bool no_ack)
{for(int k = 0; k < handle->slave_reg_value_width; k++){uint8_t offset = (handle->slave_reg_value_width - 1 - k) * 8;*value |= (i2c_recv_byte(handle) << offset);if(no_ack)i2c_no_ack(handle);elsei2c_ack(handle);}return true;
}static bool i2c_send_one_reg_value_with_wait_ack(I2C_SW_Handle_t* handle, uint32_t value)
{for(int k = 0; k < handle->slave_reg_value_width; k++){uint8_t offset = (handle->slave_reg_value_width - 1 - k) * 8;i2c_send_byte(handle, (value >> offset) & 0xff);if(!i2c_wait_ack(handle))return false;}return true;
}

我们可以看到,比其他博客多了好几个函数,那些都是因为需要兼容多字节寄存器地址或者数据使用的.其中i2c_delay打算另外写一篇文章详细介绍(但是目前好像只在100MHz的时候比较准)

对外接口就是这几个:

bool i2c_device_exist(I2C_SW_Handle_t* handle)
{bool ret = 0;i2c_start(handle);ret = i2c_send_slave_address_with_wait_ack(handle, I2C_WRITE);i2c_stop(handle);return ret;
}int i2c_reg_read(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, uint32_t* buf)
{i2c_start(handle);if(!i2c_send_slave_address_with_wait_ack(handle, I2C_WRITE))goto fail;if(!i2c_send_one_reg_address_with_wait_ack(handle, reg_addr))goto fail;i2c_start(handle);if(!i2c_send_slave_address_with_wait_ack(handle, I2C_READ))goto fail;for(int i = 0; i < len; i++){i2c_recv_one_reg_value_with_ack(handle, &buf[i], len - 1 == i);}i2c_stop(handle);return 0;fail:i2c_stop(handle);return -1;
}int i2c_reg_write(I2C_SW_Handle_t* handle, uint32_t reg_addr, uint32_t len, const uint32_t* buf)
{i2c_start(handle);if(!i2c_send_slave_address_with_wait_ack(handle, I2C_WRITE))goto fail;if(!i2c_send_one_reg_address_with_wait_ack(handle, reg_addr))goto fail;for(int i = 0; i < len; i++){if(! i2c_send_one_reg_value_with_wait_ack(handle, buf[i]))goto fail;}i2c_stop(handle);return 0;fail:i2c_stop(handle);return -1;
}

那么以上一个软件I2C功能就完成了.

软件I2C的使用

首先,在STM32CubeMX中配置GPIO.

然后,实例化软件I2C句柄对象.

    I2C_SW_Handle_t* handle;handle = malloc(sizeof(I2C_SW_Handle_t));handle->scl_pin = TEMP_I2C_SCL_Pin;handle->scl_port = TEMP_I2C_SCL_GPIO_Port;handle->sda_pin = TEMP_I2C_SDA_Pin;handle->sda_port = TEMP_I2C_SDA_GPIO_Port;handle->slave_addr = TMP117_I2C_ADDRESS;handle->slave_address_width=Slave_Address_7Bit;handle->speed_mode = Standard_Mode;handle->slave_reg_addr_width = One_Byte;handle->slave_reg_value_width = Two_Byte;

最后,调用接口

    if(i2c_device_exist(handle))printf("hello i2c_address=0x%02x is exist\r\n", handle->slave_addr);uint32_t device_id = 0;if(0 == i2c_reg_read(handle, 0x0f, 1, &device_id))printf("device_id=0x%04x \r\n", device_id);uint32_t temp = 0;while(1){if(0 == i2c_reg_read(handle, 0x00, 1, &temp)){printf("read_value=0x%04x  read_temp=%0.5f\r\n", temp, (int32_t)temp * 0.0078125);}}

本文链接:https://my.lmcjl.com/post/6294.html

展开阅读全文

4 评论

留下您的评论.