码迷,mamicode.com
首页 > 其他好文 > 详细

STM32通信:IIC (二)

时间:2021-02-01 12:23:47      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:str   上拉   无响应   reads   ==   hat   stopped   end   enable   

有几个参考资料写得挺好的
一个是NXP出的I2C规范和用户手册,直接搜索UM10204 pdf即可
还有一个是ADI出的技术文章
https://www.analog.com/en/technical-articles/i2c-primer-what-is-i2c-part-1.html
https://www.analog.com/en/technical-articles/i2c-timing-definition-and-specification-guide-part-2.html

一、硬件部分

I2C使用两根信号线,一根SDA用于传输数据,一根SCL用于产生时钟
一条总线上可以有一个主设备和多个从设备,时钟由主设备控制,因此软件模拟I2C主设备较为容易
SCL和SDA在实际电路中需要配置成开漏/开集输出并使用上拉电阻,从而支持线与逻辑

技术图片

STM32作为主设备时可配置开漏输出并使用GPIO自带的上拉功能。如果使用推挽输出,则可能会在与一些设备通信时产生异常,同时这种操作也不符合I2C规范
下图可以看到推挽输出具有输出高低电平的能力,同时切换速度较快;开漏输出只能输出低电平,其高电平靠上拉电阻实现,而它的上升时间也更长

技术图片

二、基本时序

先是几个比较明显的特点:
1.正常传输数据时,当SCL线处于低电平期时,SDA线上的电平允许变动
2.正常传输数据时,当SCL线处于高电平期时,SDA线上的电平不变
3.如果在SCL线的高电平期,SDA线由高电平向低电平跳变,则表示开始传输数据(START信号)
4.如果在SCL线的高电平期,SDA线由低电平向高电平跳变,则表示停止传输数据(STOP信号)

技术图片

然后看一下具体时序要求

技术图片

这里以普通I2C模式举例(SCL时钟频率为100kHz),其它模式可以查阅上表

1.START信号

在单片机实现时不需要太关注下降时间,但需要关注在SCL保持高电平时SDA下降沿之前的setup时间和hold时间。最小值分别为4.7us和4.0us

技术图片

2.STOP信号

接着是STOP信号,可以看到STOP信号只有一个setup时间要求,最小值为4.0us
然而,在STOP信号和START信号之间会有一个buffer时间用于让其它设备检测总线状态,buffer时间最小值为4.7us,因此SDA上升沿之后至少要跟4.7us的延迟

技术图片技术图片

3.数据位

图中可以看到在SDA稳定到SCL变为高电平之间有setup时间要求,同时在SDA跳变到下一个SCL上升沿之间也有setup时间要求,最小值均为250ns
需要注意的是,在SDA稳定且SCL处于高电平时会有一个hold时间要求,表格中给出的是至少300ns,但同时还要考虑到SCL的低电平时间和高电平时间也有最小值要求,分别是4.7us和4.0us

技术图片

三、读写时序

1.发送一个字节

MSB在前,LSB在后,每次只能发送长度为8的字节,然后接收方会返回NACK/ACK作为回应
NACK(高电平)表示数据错误或者无响应(被外部上拉电阻拉高)
ACK(低电平)表示成功接收

技术图片

2.读单个字节

START信号 -> 发送7位数据地址+1位写标识(HIGH) -> 从设备返回ACK -> 发送寄存器地址/命令 -> 从设备返回ACK -> STOP信号(部分设备不需要在这里发送STOP信号) -> START信号 -> 发送7位数据地址+1位读标识(LOW) -> 从设备返回ACK -> 从设备返回1个字节 -> 主设备发送ACK -> STOP信号
如果按照7位设备地址来计算此图对应的设备地址是0x29,将地址左移1位,或0得到0x52(写),或1得到0x53(读)
图中Ac是从设备发送的ACK,Am是主设备发送的ACK

技术图片

3.写单个字节

START信号 -> 发送7位数据地址+1位写标识(HIGH) -> 从设备返回ACK -> 发送寄存器地址/命令 -> 从设备返回ACK -> 发送写入的字节 -> 从设备返回ACK -> STOP信号

技术图片

4.读多个字节

如图

技术图片

5.写多个字节

如图

技术图片

四、代码

使用HAL库编写,稍作修改也可移植到使用库函数开发的项目上

Delay_ticks()函数:

void Delay_ticks(uint32_t ticks)
{
  uint32_t told, tnow, tcnt = 0;
  uint32_t reload = SysTick->LOAD;
  told = SysTick->VAL;
  while (1)
  {
    tnow = SysTick->VAL;
    if (tnow != told)
    {
      if (tnow < told)
        tcnt += told - tnow;
      else
        tcnt += reload - tnow + told;
      told = tnow;
      if (tcnt >= ticks)
        break;
    }
  }
}

头文件:
softi2c1.h

#ifndef _SOFTI2C1_H
#define _SOFTI2C1_H

#include "main.h"
#include "DELAY/delay.h"
#include "softi2c_common.h"

// ****** configuration start ******
#define SOFTI2C1_SCL_CLKEN() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SOFTI2C1_SDA_CLKEN() __HAL_RCC_GPIOB_CLK_ENABLE()

#define SOFTI2C1_SCL_GPIO GPIOB
#define SOFTI2C1_SDA_GPIO GPIOB

#define SOFTI2C1_SCL_PINID 8
#define SOFTI2C1_SDA_PINID 9

#define SOFTI2C1_RETRYTIMES 3
// ****** configuration end ******

#define SOFTI2C1_SCL_PIN ((uint16_t)1u << SOFTI2C1_SCL_PINID)
#define SOFTI2C1_SDA_PIN ((uint16_t)1u << SOFTI2C1_SDA_PINID)

#define SOFTI2C1_SCL(__PINSTATE__) (SOFTI2C1_SCL_GPIO->BSRR = (uint32_t)SOFTI2C1_SCL_PIN << ((__PINSTATE__) ? (0u) : (16u)) )
#define SOFTI2C1_SDA(__PINSTATE__) (SOFTI2C1_SDA_GPIO->BSRR = (uint32_t)SOFTI2C1_SDA_PIN << ((__PINSTATE__) ? (0u) : (16u)) )
#define SOFTI2C1_READSDA() ((SOFTI2C1_SDA_GPIO->IDR >> SOFTI2C1_SDA_PINID) & 1u)

#define SOFTI2C1_SDA_IN() {SOFTI2C1_SDA_GPIO->MODER &= ~(3 << (SOFTI2C1_SDA_PINID * 2u)); SOFTI2C1_SDA_GPIO->MODER |= 0 <<  (SOFTI2C1_SDA_PINID * 2u);}
#define SOFTI2C1_SDA_OUT() {SOFTI2C1_SDA_GPIO->MODER &= ~(3 << (SOFTI2C1_SDA_PINID * 2u)); SOFTI2C1_SDA_GPIO->MODER |= 1    << (SOFTI2C1_SDA_PINID * 2u);}

// For most of the cases
void SoftI2C1_Init(uint32_t speed);
uint8_t SoftI2C1_SendAddr(uint16_t addr, uint8_t addrLen, uint8_t RorW);
uint8_t SoftI2C1_Read(uint16_t deviceAddr, uint8_t deviceAddrLen, uint8_t memAddr, uint8_t *dataBuf, uint32_t dataSize);
uint8_t SoftI2C1_Write(uint16_t deviceAddr, uint8_t deviceAddrLen, uint8_t memAddr, uint8_t *dataBuf, uint32_t dataSize);

// Low Layer
void SoftI2C1_Start(void);
void SoftI2C1_Stop(void);
void SoftI2C1_SendACK(uint8_t ACK);
uint8_t SoftI2C1_WaitACK(void);
void SoftI2C1_SendByte(uint8_t byte);
uint8_t SoftI2C1_ReadByte(void);
uint8_t SoftI2C1_SendByte_ACK(uint8_t byte, uint8_t handleACK);
uint8_t SoftI2C1_ReadByte_ACK(uint8_t ACK);

#endif

softi2c_common.h

#ifndef _SOFTI2C_COMMON_H
#define _SOFTI2C_COMMON_H

#define SI2C_ACK 0
#define SI2C_NACK 1

#define SI2C_WRITE 0
#define SI2C_READ 1

#define SI2C_ADDR_7b 0
#define SI2C_ADDR_10b 1

#endif

源码:
softi2c1.c

#include "softi2c1.h"

uint16_t SoftI2C1_delayTicks = 0;

void SoftI2C1_Init(uint32_t speed)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  SOFTI2C1_SCL_CLKEN();
  SOFTI2C1_SDA_CLKEN();

  SOFTI2C1_SCL(1);
  SOFTI2C1_SDA(1);
  GPIO_InitStruct.Pin = SOFTI2C1_SCL_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(SOFTI2C1_SCL_GPIO, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = SOFTI2C1_SDA_PIN;
  HAL_GPIO_Init(SOFTI2C1_SDA_GPIO, &GPIO_InitStruct);

  // 这里用系统主频来替换Delay_GetSYSFreq(),例如 72000000,168000000,480000000
  SoftI2C1_delayTicks = Delay_GetSYSFreq() / speed / 2;
}

uint8_t SoftI2C1_SendAddr(uint16_t addr, uint8_t addrLen, uint8_t RorW)
{
  uint8_t buf;
  if (addrLen == SI2C_ADDR_7b)
  {
    buf = ((addr & 0x007F) << 1u) | RorW;
    return SoftI2C1_SendByte_ACK(buf, SI2C_ACK);
  }
  else
  {
    // 111100XX with first 2 bits
    buf = ((addr & 0x0003) << 1u) | RorW;
    buf |= 0x78;
    if (!SoftI2C1_SendByte_ACK(buf, SI2C_ACK))
      return 0;

    // the last 8 bits
    buf = addr & 0x00FF;
    return SoftI2C1_SendByte_ACK(buf, SI2C_ACK);
  }
}

uint8_t SoftI2C1_Read(uint16_t deviceAddr, uint8_t deviceAddrLen, uint8_t memAddr, uint8_t *dataBuf, uint32_t dataSize)
{
  uint32_t i;

  SoftI2C1_Start();
  if (!SoftI2C1_SendAddr(deviceAddr, deviceAddrLen, SI2C_WRITE))
    return 0;
  if (!SoftI2C1_SendByte_ACK(memAddr, SI2C_ACK))
    return 0;
// SoftI2C1_Stop(); // A STOP signal is required on some devices.

  SoftI2C1_Start();
  if (!SoftI2C1_SendAddr(deviceAddr, deviceAddrLen, SI2C_READ))
    return 0;
  for (i = 0; i < dataSize; i++)
    *(dataBuf + i) = SoftI2C1_ReadByte_ACK(SI2C_ACK);
  SoftI2C1_Stop();

  return 1;
}
uint8_t SoftI2C1_Write(uint16_t deviceAddr, uint8_t deviceAddrLen, uint8_t memAddr, uint8_t *dataBuf, uint32_t dataSize)
{
  uint32_t i;

  SoftI2C1_Start();
  if (!SoftI2C1_SendAddr(deviceAddr, deviceAddrLen, SI2C_WRITE))
    return 0;
  if (!SoftI2C1_SendByte_ACK(memAddr, SI2C_ACK))
    return 0;
  for (i = 0; i < dataSize; i++)
    if (!SoftI2C1_SendByte_ACK(*(dataBuf + i), SI2C_ACK))
      return 0;
  SoftI2C1_Stop();

  return 1;
}

void SoftI2C1_Start(void)
{
  SOFTI2C1_SDA_OUT();
  SOFTI2C1_SCL(1);
  SOFTI2C1_SDA(1);
  Delay_ticks(SoftI2C1_delayTicks); // setup time
  SOFTI2C1_SDA(0);                  // START: when CLK is high,DATA change form HIGH to LOW
  Delay_ticks(SoftI2C1_delayTicks); // hold time

  SOFTI2C1_SCL(0); // cannot be read
  Delay_ticks(SoftI2C1_delayTicks);
}

void SoftI2C1_Stop(void)
{
  SOFTI2C1_SDA_OUT();
  SOFTI2C1_SCL(1);
  SOFTI2C1_SDA(0);
  Delay_ticks(SoftI2C1_delayTicks);     // setup time
  SOFTI2C1_SDA(1);                      // STOP: when CLK is high,DATA change form LOW to HIGH
  Delay_ticks(SoftI2C1_delayTicks * 2); // hold time(not necessary in most of the situations) and buff time(necessary)

  // when the transmition is stopped, the SCL should be high
}

void SoftI2C1_SendACK(uint8_t ACK) // 0:ACK 1:NACK
{
  SOFTI2C1_SCL(0); // change start
  SOFTI2C1_SDA_OUT();
  SOFTI2C1_SDA(ACK);
  Delay_ticks(SoftI2C1_delayTicks / 8); // data setup time
  SOFTI2C1_SCL(1);                      // can be read
  Delay_ticks(SoftI2C1_delayTicks);     // hold
  SOFTI2C1_SCL(0);                      // cannot be read
  Delay_ticks(SoftI2C1_delayTicks);     // data setup time & SCL_LOW & SMBus requirement
}

uint8_t SoftI2C1_WaitACK(void) // 0:ACK 1:NACK/No response
{
  uint16_t waitTime = 0;
  uint8_t result = 0;
  SOFTI2C1_SDA_IN();
  SOFTI2C1_SCL(1);
  Delay_ticks(SoftI2C1_delayTicks / 8); // data setup time
  result = SOFTI2C1_READSDA();
  Delay_ticks(SoftI2C1_delayTicks);
  SOFTI2C1_SCL(0);
  Delay_ticks(SoftI2C1_delayTicks); // data setup time & SCL_LOW & SMBus requirement
  if (result == SI2C_NACK)
    SoftI2C1_Stop();
  return result;
}

// 8 SCL clock, just send a byte
void SoftI2C1_SendByte(uint8_t byte) // barely send a byte
{
  int8_t i;
  SOFTI2C1_SDA_OUT();
  SOFTI2C1_SCL(0);
  for (i = 7; i >= 0; i--)
  {
    SOFTI2C1_SDA((byte >> i) & 1u);
    Delay_ticks(SoftI2C1_delayTicks / 8); // data setup time
    SOFTI2C1_SCL(1);
    Delay_ticks(SoftI2C1_delayTicks); // data hold time
    SOFTI2C1_SCL(0);
    Delay_ticks(SoftI2C1_delayTicks); // data setup time & SCL_LOW & SMBus requirement
  }
}

// 8 SCL clock, just read a byte
uint8_t SoftI2C1_ReadByte(void) // barely read a byte
{
  uint8_t i, result = 0;
  SOFTI2C1_SDA_IN();
  for (i = 0; i < 8; i++)
  {
    SOFTI2C1_SCL(0);
    Delay_ticks(SoftI2C1_delayTicks); // data setup time & SCL_LOW & SMBus requirement
    SOFTI2C1_SCL(1);
    result <<= 1;
    Delay_ticks(SoftI2C1_delayTicks / 8); // data setup time
    result |= SOFTI2C1_READSDA();
    Delay_ticks(SoftI2C1_delayTicks); // data hold time
  }
  return result;
}

// 9 SCL clock, treat the 9th bit as ACK/NACK
// If SI2C_NACK is given, it will always return true(ignore ACK/NACK)
// If SI2C_ACK is given, it will retry SOFTI2C1_RETRYTIMES if no ACK is detected,
// and it will return whether the ACK is detected finally.
uint8_t SoftI2C1_SendByte_ACK(uint8_t byte, uint8_t handleACK) // handle ACK and retry
{
  uint8_t i;
  if (handleACK == SI2C_NACK)
  {
    SoftI2C1_SendByte(byte);
    SOFTI2C1_SDA_IN();
    SOFTI2C1_SCL(1);                      
    Delay_ticks(SoftI2C1_delayTicks / 8); // data setup time
    SOFTI2C1_SCL(1);                      // can be read
    Delay_ticks(SoftI2C1_delayTicks);     // hold
    SOFTI2C1_SCL(0);                      // cannot be read
    Delay_ticks(SoftI2C1_delayTicks);     // data setup time & SCL_LOW & SMBus requirement
    return 1;
  }
  else
  {
    for (i = 0; i < SOFTI2C1_RETRYTIMES; i++)
    {
      SoftI2C1_SendByte(byte);
      if (SoftI2C1_WaitACK() == SI2C_ACK)
        break;
    }
    return (i < SOFTI2C1_RETRYTIMES);
  }
}

// 9 SCL clock, send ACK/NACK after data received
uint8_t SoftI2C1_ReadByte_ACK(uint8_t ACK)
{
  uint8_t result;
  result = SoftI2C1_ReadByte();
  SoftI2C1_SendACK(ACK);
  return result;
}

2021.01.30

STM32通信:IIC (二)

标签:str   上拉   无响应   reads   ==   hat   stopped   end   enable   

原文地址:https://www.cnblogs.com/wh201906/p/14350929.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!