stm32之IIC

        IIC(芯片间)总线接口连接微控制器和串行IIC总线。它提供多主机功能,控制所有IIC总线特定的时序、协议、仲裁和定时。支持标准和快速两种模式,同时与SMBus 2.0兼容。

IIC协议时序具体参考 51操控IIC点亮OLED

一、IIC功能

  • 并行总线/IIC总线协议转换器
  • 多主机功能:该模块既可做主设备也可做从设备
  • 产生和检测7/10位地址和广播呼叫
  • 支持不同的通讯速度
  • 状态标志
  • 错误标志
  • 2个中断向量
  • 具单字节缓冲器的DMA 
  • 可配置的PEC(信息包错误检测)的产生或校验
  • 兼容SMBus 2.0
  • 兼容SMBus

具体支持哪些功能, 要看具体的产品。

1.1、IIC 主模式

        在主模式时,II C 接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。当通过START 位在总线上产生了起始条件,设备就进入了主模式。
stm32之IIC_第1张图片
1.1.1、起始条件
         当BUSY=0时,设置START=1(SR1寄存器中的SB位),IIC接口将产生一个开始条件并切换至主模式(M/SL位置位)。SB位被硬件置位,如果设置了ITEVFEN位,则会产生一个中断。 然后主设备等待读SR1寄存器,紧跟着将从地址写入DR寄存器
1.1.2、从地址的发送
从地址通过内部移位寄存器被送到SDA 线上
10 位地址模式时,发送一个头段序列产生以下事件:
  •  ADD10位被硬件置位,如果设置了ITEVFEN位,则产生一个中断然后主设备等待读SR1寄存器,再将第二个地址字节写入DR寄存器
  • ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。随后主设备等待一次读SR1寄存器,跟着读SR2寄存器
  • 要进入发送器模式,主设备先送头字节 (11110xx0) ,然后送最低位为 ’0’ 的从地址。 ( 这里
    xx 代表 10 位地址中的最高 2 位。 )
  • 要进入接收器模式,主设备先送头字节(11110xx0) ,然后送最低位为 ’1’ 的从地址。然后再
    重新发送一个开始条件,后面跟着头字节 (11110xx1)( 这里 xx 代表 10 位地址中的最高 2 位。 )

在7位地址模式时,只需送出一个地址字节。

  • 一旦该地址字节被送出 ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。 随后主设备等待一次读SR1寄存器,跟着读SR2寄存器
  • 要进入发送器模式,主设备发送从地址时置最低位为’0’。
  • 要进入接收器模式,主设备发送从地址时置最低位为’1’。
1.1.3、主发送器

        在发送了地址和清除了ADDR位后, 主设备通过内部移位寄存器将字节从DR寄存器发送到SDA线上。主设备等待,直到TxE被清除。

        当收到应答脉冲时:TxE位被硬件置位,如果设置了INEVFENITBUFEN位,则产生一个中断。

        如果TxE被置位并且在上一次数据发送结束之前没有写新的数据字节到DR寄存器,则BTF(byte transfer finshied)被硬件置位,在清除BTF之前IIC接口将保持SCL为低电平;读出IIC_SR1之后再写入IIC_DR寄存器将清除BTF位。

1.1.4、关闭通信
        在DR 寄存器中写入最后一个字节后,通过设置 STOP(STOPF) 位产生一个停止条件 , 然后II C 接口将自动回到从模式 (M/S 位清除 )

二、IIC操作OLED(CPU主模式)

完成代码

CubeMX主要配置图:

stm32之IIC_第2张图片

核心代码如下:

除了HAL相关的代码,其它代码都是从51IIC中摘出来的。显示的是小猪佩奇的一图片。

void iic_write_cmd(uint8_t cmd) {
	HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
										&cmd, 1, 0xff);
}

void iic_write_data(uint8_t data) {
	HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
										&data, 1, 0xff);
}

void oled_init() {
	iic_write_cmd(0xAE);//--display off
	iic_write_cmd(0x00);//---set low column address
	iic_write_cmd(0x10);//---set high column address
	iic_write_cmd(0x40);//--set start line address  
	iic_write_cmd(0xB0);//--set page address
	iic_write_cmd(0x81); // contract control
	iic_write_cmd(0xFF);//--128   
	iic_write_cmd(0xA1);//set segment remap 
	iic_write_cmd(0xA6);//--normal / reverse
	iic_write_cmd(0xA8);//--set multiplex ratio(1 to 64)
	iic_write_cmd(0x3F);//--1/32 duty
	iic_write_cmd(0xC8);//Com scan direction
	iic_write_cmd(0xD3);//-set display offset
	iic_write_cmd(0x00);//
	
	iic_write_cmd(0xD5);//set osc division
	iic_write_cmd(0x80);//
	
	iic_write_cmd(0xD8);//set area color mode off
	iic_write_cmd(0x05);//
	
	iic_write_cmd(0xD9);//Set Pre-Charge Period
	iic_write_cmd(0xF1);//
	
	iic_write_cmd(0xDA);//set com pin configuartion
	iic_write_cmd(0x12);//
	
	iic_write_cmd(0xDB);//set Vcomh
	iic_write_cmd(0x30);//
	
	iic_write_cmd(0x8D);//set charge pump enable
	iic_write_cmd(0x14);//
	
	iic_write_cmd(0xAF);//--turn on oled panel		
}


void oled_clear() {
	int i, j;
	for(i = 0; i < 8; i ++) {
		iic_write_cmd(0xB0+i);
		iic_write_cmd(0x00);
		iic_write_cmd(0x10);
		for(j = 0; j < 128; j ++) {
			iic_write_data(0);
		}
	}
}


void oled_image(unsigned char *image)
{
	unsigned char i; 
	unsigned int j;
	
	for(i=0;i<8;i++){
		iic_write_cmd(0xB0 + i);
		iic_write_cmd(0x00);
		iic_write_cmd(0x10);
	
		for(j = 128 * i; j<(128 * (i+1));j++){
			iic_write_data(image[j]);
		}
	}
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_I2C1_Init();

  oled_init();
	
  // 3、addressing setting command table 第三行
  iic_write_cmd(0x20);
  iic_write_cmd(0x02);
  oled_clear();
  oled_image(peiqi);

  while (1)
  {
  }
}

2.1、HAL_I2C_Mem_Write

这个方法是HAL提供的,里面封装了具体的操作步骤,不像51,没有IIC外设需要模拟。HAL_I2C_Mem_Write的主要是按照1.1、IIC主模式进行处理的。

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{

  if (hi2c->State == HAL_I2C_STATE_READY)
  {
    /* Wait until BUSY flag is reset */
    if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)
    {
      return HAL_BUSY;
    }

    __HAL_LOCK(hi2c);

    if ((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)
    {
      __HAL_I2C_ENABLE(hi2c);
    }

    CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_POS);

    hi2c->State     = HAL_I2C_STATE_BUSY_TX;
    hi2c->Mode      = HAL_I2C_MODE_MEM;
    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;

    hi2c->pBuffPtr    = pData;
    hi2c->XferCount   = Size;
    hi2c->XferSize    = hi2c->XferCount;
    hi2c->XferOptions = I2C_NO_OPTION_FRAME;

    // 发送从机地址,内存地址
    if (I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart) != HAL_OK)
    {
      return HAL_ERROR;
    }

    while (hi2c->XferSize > 0U)
    {
      // 判断Txe 是否被置1,代表数据寄存器为空
      if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
      {
        if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
        {
          /* Generate Stop */
          SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
        }
        return HAL_ERROR;
      }

      // 写数据
      hi2c->Instance->DR = *hi2c->pBuffPtr;

      // 指向下个数据地址
      hi2c->pBuffPtr++;

      // 更新大小和数量
      hi2c->XferSize--;
      hi2c->XferCount--;

			// 两次判断断BTF 是否被置1且是否还有数据要发送
      if ((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))
      {
        /* Write data to DR */
        hi2c->Instance->DR = *hi2c->pBuffPtr;

        /* Increment Buffer pointer */
        hi2c->pBuffPtr++;

        /* Update counter */
        hi2c->XferSize--;
        hi2c->XferCount--;
      }
    }

    // 判断数据是否发送完成
    if (I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
    {
      if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
      {
        SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
      }
      return HAL_ERROR;
    }

    // 产生停止信号
    SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);

    hi2c->State = HAL_I2C_STATE_READY;
    hi2c->Mode = HAL_I2C_MODE_NONE;

    __HAL_UNLOCK(hi2c);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}
2.2.1、检测busy位 

 检测是SR2的busy位是否为1,为1时说明总线正在使用。这里有一个技巧,

  • 如果是SR1中的标识,所有的宏都是0x00010000,开头
  • 如果是SR2中的标识,所有的宏都是0x00100000,开头

这里使用的是I2C_FLAG_BUSY = 0x00100002, 右移动16位就是0x0002,就是busy位。

// 1 -----------------------------------------

if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) {
      return HAL_BUSY;
 }

#define __HAL_I2C_GET_FLAG(__HANDLE__, __FLAG__) ((((uint8_t)((__FLAG__) >> 16U)) == 0x01U) ? \
                                                  (((((__HANDLE__)->Instance->SR1) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK)) ? SET : RESET) : \
                                                  (((((__HANDLE__)->Instance->SR2) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK)) ? SET : RESET))
 2.2.2、检测PE位是否开启(使能IIC外设)
if ((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)
    {
      /* Enable I2C peripheral */
      __HAL_I2C_ENABLE(hi2c);
    }
 2.2.3、清除POS标识位

CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_POS);

操作CR1寄存器中的POS位,

stm32之IIC_第3张图片  2.2.4、请示内存写(产生起始信号等)I2C_RequestMemoryWrite

下面代码的意思是开启IIC起始信号,写从机地址,写内存地址

static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart)
{
  // 产生启动信号start
  SET_BIT(hi2c->Instance->CR1, I2C_CR1_START);
  // 启动信号已发送
  if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK)
  {
    if (READ_BIT(hi2c->Instance->CR1, I2C_CR1_START) == I2C_CR1_START)
    {
      hi2c->ErrorCode = HAL_I2C_WRONG_START;
    }
    return HAL_TIMEOUT;
  }
  // 设置从机地址
  hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress);
  // 地址发送结束
  if (I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK)
  {
    return HAL_ERROR;
  }
  // 依次读取SR1,SR2清除ADDR 位
  __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
  // 等待数据寄存器为空,代表发送完成
  if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK)
  {
    if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
    {
      SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
    }
    return HAL_ERROR;
  }

  // 只有8位,用LSB
  if (MemAddSize == I2C_MEMADD_SIZE_8BIT)
  {
    //  发送数据
    hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress);
  }
  else
  {
   // 如果是16位,先发送MSB,再发送LSB
    hi2c->Instance->DR = I2C_MEM_ADD_MSB(MemAddress);

    // 等待MSB发送完成
    if (I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK)
    {
      if (hi2c->ErrorCode == HAL_I2C_ERROR_AF)
      {
        SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
      }
      return HAL_ERROR;
    }
    hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress);
  }

  return HAL_OK;
}
2.2.5、发送数据

在while循环内判断数据是否发送完全,一个字节的发,

 while (hi2c->XferSize > 0U)

2.2.6、判断数据是否发送完

I2C_WaitOnBTFFlagUntilTimeout ---->

__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_TXE)---->

I2C_FLAG_TXE

操作的是SR1寄存器中的TxE

stm32之IIC_第4张图片 2.2.7、停止信号

// 8.产生停止信号
    SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);

你可能感兴趣的:(stm32,嵌入式硬件,单片机)