关于stm32f4xx的片上外设I2C模块用作主模式下BUSY位总是置1的解决方法

原创 2016年12月18日 09:35:24

1. 假设

本文假设读者:

  1. 有使用stm32的经验
  2. 有使用stm32库函数编程的经验
  3. 了解IIC通讯协议

本文适合初学者参考

2. stm32f4xx系列的芯片的IIC接口

1. 模式选择

stm32f4xx的IIC模块有四种工作模式,默认的工作模式是从模式,在发送起始位会自动由从模式切换为主模式.
2
相关概念如下:

  1. 发送器:发送数据到总线的器件
  2. 接收器:从总线接收数据的器件
  3. 主机:发起/停止数据传输、提供时钟信号的器件
  4. 从机:被主机寻址的器件
  5. 多主机:可以有多个主机试图去控制总线,但是不会破坏数据
  6. 仲裁:当多个主机试图去控制总线时,通过仲裁可以使得只有一个主机获得总线控制权,并且它传输的信息不会被破坏
  7. 同步:多个器件同步时钟信号的过程
2. 通讯过程

1

尽管已经假设读者了解iic总线协议,但还是对iic通讯的一些概念做简单介绍.

  1. 开始信号(S):SCL为高电平时,SDA由高电平向低电平跳变,表示起始信号,开始传送数据
  2. 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,表示结束信号,结束传送数据
  3. 响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA电平。即接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。
  4. I2C总线进行数据传送时,
    4.1 SCL高电平期间,数据稳定(不允许变化),此时用于接收器读数据;
    4.2 SCL低电平期间,数据允许变化,此时用于发送器发(写)数据;

  5. stm32f4xx的IIC模块的通讯时序如下:
    5.1 模块默认工作在从模式, 发送起始位后自动由从模式切换为主模式. 在主模式下,I2C 接口会启动数据传输并生成时钟信号。串行数据传输始终是在出现起始位时开始,在出现停止位时结束。起始位和停止位均在主模式下由软件生成。
    5.2 IIC模块的应答位可以由软件使能或者禁止. 可以软件选择IIC模块的寻址方式( 7 位/10 位双寻址模式和/或广播呼叫地址)
    3
    5.3 IIC模块作为主发送器发送数据的过程如下(更加详细的说明请参考《stm32f4xx中文参考手册》654页,这里不再赘述)
    4
    5.4 IIC模块作为主接收器接收数据的过程如下(更加详细的说明请参考《stm32f4xx中文参考手册》655页,这里不再赘述)
    5

3.“BUSY位总是置1”的问题

1. 问题描述

在STM32F4xx的IIC模块的状态寄存器SR2有一个BUSY位用于指示IIC总线的占用状态。
6
然而在实际的应用中,使能了IIC模块的时钟之后,BUSY位就自动置位了,但是我们知道,事实上总线上并无数据在传输. 在主机发送停止位之后,也无法将BUSY位复位.

2. 问题解决

注意当IIC模块处于复位的状态的时候,BUSY位也是复位的. 有两种方法可以将IIC模块复位,一种是直接使用RCC模块的寄存器将IIC模块复位,另一种是方法是将IIC模块控制寄存器CR1中的SWRST置位,使IIC模块处于复位状态. 本文介绍的是第二种方法, 第一种方法也是类似第二种方法,只不过写的寄存器不一样而已.
7
由于STM32F4XX的IIC模块,在使能了模块时钟之后,BUSY位总是为1,不能够自动跳转状态,那么我们就只能够通过复位和取消复位IIC模块来手动控制BUSY位的状态跳转.
另外还需要注意的是,当我们复位IIC模块的时候,我们先前对IIC模块做的所有配置也复位了. 在取消IIC复位状态之后,还需要重新配置IIC模块,再开始数据传输.

3. 参考代码如下:
// ************ iic.h ************
#ifndef __IIC_H
#define __IIC_H
#include "stm32f4xx.h"
#include "stm32f4xx_i2c.h"
#include "stm32f4xx_gpio.h"

void IIC1_init(void);
u8 I2C1_Write(u8 address, u8 *pData, u8 bytes);
u8 I2C1_Read(u8 address, u8 *pData, u8 bytes);
void IIC1_Register_Detected(void);

#endif

// ************ iic.c ************
#include "iic.h"

static void IIC1_BUSY_RESET(void);
static void IIC1_BUSY_SET(void);

/*
IIC1_SCL : PB8
IIC1_SDA : PB9
*/
void IIC1_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    // ********** GPIOB PB8 PB9 配置成复用I2C模式 **********
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    GPIO_InitStruct.GPIO_Pin     = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB,&GPIO_InitStruct);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);

    // ********** 配置I2C1模块 **********
    /*I2C Peripheral clock enable */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

}

// 通过复位来手动复位BUSY位,表示IIC总线不忙
static void IIC1_BUSY_RESET(void)
{
    I2C1->CR1 |= 0x1<<15;  // SWRST 位置1
}

// 取消复位,BUSY位自动置1,然后初始化并且使能IIC外设
static void IIC1_BUSY_SET(void)
{
    I2C_InitTypeDef  I2C_InitStruct;

    I2C1->CR1 &= ~(0x1<<15);   // SWRST 位清0;

    // 初始化IIC1
    /* I2C Struct Initialize */
    I2C_InitStruct.I2C_ClockSpeed = 100000;             // 100 kHz
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_16_9;
    I2C_InitStruct.I2C_OwnAddress1 = 0xA0;
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    /* I2C Initialize */
    I2C_Init(I2C1, &I2C_InitStruct);
    /* 使能 IIC1 模块 */
    I2C_Cmd(I2C1,ENABLE);   
}


/* ***************************** I2C1读写函数 ****************************** */
#define FLAG_TIMEOUT 25*1000*10
u32 Timeout = FLAG_TIMEOUT; // 10毫秒超时
/* IIC1 写数据
功能: 向地址为address的从机写入bytes个字节,pData指向要写入的数据内容
输入参数:
    u8 address      : 7位的地址(在传入从机的地址之前,要先将从机的地址右移1位到[7:1])
    u8 *pData       : 指向要写的数据的指针
    u8 bytes        : 要写的字节数量
返回值:
    成功返回0,失败返回1
*/
u8 I2C1_Write(u8 address, u8 *pData, u8 bytes)
{
    // *************** S ***************
    /*!< While the bus is busy */
    Timeout = FLAG_TIMEOUT;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
    {
        if((Timeout--) == 0)
            return 1;
    }
    IIC1_BUSY_SET();

    /*!< Send START condition */
    // 发送起始条件
    I2C_GenerateSTART(I2C1, ENABLE);        // I2C1->CR1 的START位置1,接着I2C1->SR1 SB位置1,I2C1->SR2 MSL 位置1

    // *************** EV5: SB = 1, 通过先读取SR1寄存器再将地址写入DR寄存器来清零 ***************
    /*!< Test on EV5 and clear it */
    Timeout = FLAG_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if((Timeout--) == 0) return 1;
    }
    /*!< Send slave address for write */
    // 发送address
    I2C_Send7bitAddress(I2C1, address, I2C_Direction_Transmitter);

    // *************** EV6: ADDR=1,通过先读取SR1寄存器再读取SR2寄存器来清零 ***************
    /*!< Test on EV6 and clear it */
    Timeout = FLAG_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))  /* BUSY, MSL, ADDR, TXE and TRA flags */
    {
        if((Timeout--) == 0) return 1;
    }

    // *************** EV8_1: TXE=1 数据寄存器空,移位寄存器空,向DR写入Data1    
    bytes--;
    I2C_SendData(I2C1, *pData++);

    // *************** EV8: TXE=1 移位寄存器非空,数据寄存器空,向DR写入数据清零
    while(bytes--)
    {
        Timeout = FLAG_TIMEOUT;
        // 接收到ASK TXE才可以置1
        while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING))   /* TRA=1, BUSY=1, MSL=1, TXE=1 */
        {
            if((Timeout--) == 0) return 1;
        }       
        I2C_SendData(I2C1, *pData++);
        /*!< Test on EV8 and clear it */        
    }

    // *************** EV8_2: TXE=1 BTF=1 通过停止条件清零 ***************
    //BTF:字节传输完成 (Byte transfer finished)
    // 在最后一个字节发送完成后,不会再写一个字节到DR,BTF会置1,指示"发送过程中将发送一个新字节但尚未向 DR 寄存器写入数据 (TxE=1)。"
    while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
        if((Timeout--) == 0) return 1;
    }
    I2C_GenerateSTOP(I2C1,ENABLE);
    IIC1_BUSY_RESET();

    return 0;
}

/* IIC1 读数据
功能: 向地址为address的从机读取bytes个字节到pData指向的内存中
输入参数:
    u8 address      : 7位的地址(在传入从机的地址之前,要先将从机的地址右移1位到[7:1])
    u8 *pData       : 指向要保存数据的内存中
    u8 bytes        : 要读的字节数量
返回值:
    成功返回0,失败返回1
*/
u8 I2C1_Read(u8 address, u8 *pData, u8 bytes)
{
    // *************** S ***************
    /*!< While the bus is busy */
    Timeout = FLAG_TIMEOUT;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
    {
        if((Timeout--) == 0)
            return 1;
    }
    IIC1_BUSY_SET();
    /*!< Send START condition */
    // 发送起始条件
    I2C_GenerateSTART(I2C1, ENABLE);

    // *************** EV5: SB = 1, 通过先读取SR1寄存器再将地址写入DR寄存器来清零 ***************   
    /*!< Test on EV5 and clear it */
    // EV5: SB = 1, 通过先读取SR1寄存器再将地址写入DR寄存器来清零
    Timeout = FLAG_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
    {
        if((Timeout--) == 0) return 1;
    }
    /*!< Send slave address for write */
    // 发送 address
    I2C_Send7bitAddress(I2C1, address, I2C_Direction_Receiver);

    // *************** EV6: ADDR=1,通过先读取SR1寄存器再读取SR2寄存器来清零 ***************
    /*!< Test on EV6 and clear it */
    // EV6: ADDR=1,通过先读取SR1寄存器再读取SR2寄存器来清零
    Timeout = FLAG_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))     /* BUSY, MSL and ADDR flags */
    {
        if((Timeout--) == 0) return 1;
    }

    // *************** EV7: ***************
    while(--bytes>=0)           /* while(){} 语句是入口循环,所以使用减量前缀确保while()中byte的值和循环体中byte的值是一致的 */
    {
        /*!< Test on EV7 and clear it */
        Timeout = FLAG_TIMEOUT;
        while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))          /* BUSY, MSL and RXNE flags */
        {
            if((Timeout--) == 0) return 1;
        }       
        *pData++ = I2C_ReceiveData(I2C1);
        if(bytes == 1)
        {   // *************** EV7_1: ***************
            I2C_AcknowledgeConfig(I2C1,DISABLE);
            I2C_GenerateSTOP(I2C1,ENABLE);
        }       
    }

    IIC1_BUSY_RESET();

    return 0;
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

解决STM32 I2C接口死锁在BUSY状态的方法讨论

解决STM32 I2C接口死锁在BUSY状态的方法讨论   关于STM32的I2C接口死锁在BUSY状态无法恢复的现象,网上已有很多讨论,看早几年比较老的贴子,有人提到复位MCU也无法恢复、只有断...

STM32之I2C_FLAG_BUSY置位解决办法

stm32f429-disco上的触摸屏IC是STMPE811,使用I2C

STM32 I2C 难点

I2C 总线在所有嵌入式系统中用得极广, 是一个工业级别的总线, 但由于STM32 是一个32位的MCU, 注定了他的I2C硬件接口将会功能强大, 但同时也会较难于控制,不象8位机,如AVR8位机的T...
  • mcu_hong
  • mcu_hong
  • 2012年11月05日 15:59
  • 12567

STM32’s I2C 硬件BUG引发的血案(qzm)

2010-4-8 1: 45 下面的函数中有一个BUG, 也就是SR2不能用WHILE来轮询,而应直接读出.如下面代码段, 因此,在这里说的这是STM32的BUG其实是我的代码的错误:     I...
  • mcu_hong
  • mcu_hong
  • 2012年11月05日 15:52
  • 23090

IIC工作原理

转载:http://www.eefocus.com/article/08-07/48416s.html 图11-1给出一个由MCU作为主机,通过IIC总线带3个从机的单主机IIC总线系统。这是最常用...

STM32 IIC难点易错点

先来点题外话~网上说STM32F103的IIC有瑕疵!就当是有些短板吧,个人觉得,用起来肯定没问题,只是不好用。因为ST公司考虑到专利问题,所以没按飞利浦的标准来。导致STM32的IIC使用起来非常繁...
  • HouQi02
  • HouQi02
  • 2016年05月23日 19:42
  • 3432

IIC总线的总结与问题(IIC输出是开漏输出 硬件IIC要求从设备有应答能力 IC在多字节的读取的时候必须要正确的给出应答)

IIC输出是开漏输出,所以在SCL和SDA两个引脚上必须接上上拉电阻 IIC总线需要在发送了地址之后必须发送 模拟发送数据函数如下: void iic_sendid(uint8_t id,IIC_DI...

通信接口

串行通信的通信方式: 同步通信:带时钟同步信号传输。—SPI,IIC通信接口 异步通信:不带时钟同步信号。–UART,单总线。...

stm32f3的i2c使用小结

这几天拿到了stm32f3discovery,拿到手的第一件事就是测试了硬件i2c,使用stm32cube生成库,测试对象为AD5934与ADG715。经过两天的调试,完美调通。中间也碰到了些问题。 ...

stm32f1xx HAL库下载介绍

stm32f1xx HAL库下载介绍
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:关于stm32f4xx的片上外设I2C模块用作主模式下BUSY位总是置1的解决方法
举报原因:
原因补充:

(最多只允许输入30个字)