关闭

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

标签: stm32IICBUSY位置1
729人阅读 评论(0) 收藏 举报
分类:

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;
}
0
0
查看评论

STM32之I2C_FLAG_BUSY置位解决办法

stm32f429-disco上的触摸屏IC是STMPE811,使用I2C
  • jatamatadada
  • jatamatadada
  • 2014-11-06 14:26
  • 4970

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

解决STM32 I2C接口死锁在BUSY状态的方法讨论   关于STM32的I2C接口死锁在BUSY状态无法恢复的现象,网上已有很多讨论,看早几年比较老的贴子,有人提到复位MCU也无法恢复、只有断电才行的状况,那可是相当严重的问题。类似复位也无法恢复的情况是存在的,技术支持矢口否认问题...
  • dldw8816
  • dldw8816
  • 2016-06-03 16:19
  • 6115

(学习笔记3)STM32F429库函数之I2C读取EEPROM

I2C外设通信发送过程中的标志清零一开始真的很困扰人。。
  • wanglantian1
  • wanglantian1
  • 2016-07-22 21:09
  • 2011

STM32F407的硬件I2C

STM32F407的硬件IIC配置及读写一个字节的函数
  • Jianfeng_Zhang1990
  • Jianfeng_Zhang1990
  • 2015-04-30 16:21
  • 5702

STM32F407之I2C总线(一)

SCL:上升沿将数据输入到每个EEPROM器件中;下降沿驱动EEPROM器件输出数据。(边沿触发)
  • chen244798611
  • chen244798611
  • 2015-12-06 23:25
  • 2638

【stm32f407】I2C实验

一.I2C介绍 IIC(Inter-IntegratedCircuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400k...
  • XiaoXiaoPengBo
  • XiaoXiaoPengBo
  • 2017-06-21 10:48
  • 1133

STM32F4的I2C读取BMP085模块的温度和气压

这个i2c搞了好几天,网上很多人都讲这是ST封装库的问题,而且基本上讲的都是STM32F1系列的片子,甚至给出了一些他们自己研究的成果,至于F4,这方面的说法不多。 没办法,从头来吧。 研究了下BMP085的datasheet,就是要用I2C读写寄存器,地址为0xee(写),从而计算温度和气压。...
  • Stephen_yu
  • Stephen_yu
  • 2013-03-06 14:19
  • 11301

STM32F4XX I2C驱动

http://www.cnblogs.com/hiker-blogs/p/3694576.html 说到I2C很多用过STMF10X硬件I2C方式的工程师,都感觉有点头痛。大部分还是使用软件模拟的方式,I2C由于一般的工作频率是400,100KHz。所以在平凡读取,或所读数据量大时,使用这模拟的...
  • haiyangwuxian
  • haiyangwuxian
  • 2017-04-05 16:37
  • 402

STM32文档:I2C 接口进入 Busy 状态不能退出

  • 2014-12-24 08:53
  • 119KB
  • 下载

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

1. 假设本文假设读者: 有使用stm32的经验 有使用stm32库函数编程的经验 了解IIC通讯协议 本文适合初学者参考2. stm32f4xx系列的芯片的IIC接口1. 模式选择stm32f4xx的IIC模块有四种工作模式,默认的工作模式是从模式,在发送起始位会自动由从模式切换为主模式. ...
  • yongchengphy
  • yongchengphy
  • 2016-12-18 09:35
  • 729
    个人资料
    • 访问:4041次
    • 积分:147
    • 等级:
    • 排名:千里之外
    • 原创:7篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条