关于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的基本读写

用stm32f4使用I2C的基本读写操作

stm 32 gpio 模拟 i2c 备忘

最近调试i2c遇到一些问题,在此备忘,总结一句话,还是要特别注意时序,哪怕一个高低点平的错误都可能导致程序工作不正常 另外对于stm32 提供的i2c固件例程一直没有调好,特别烦人 #incl...

搞了一天的stm32f207芯片库函数的I2C问题终于被解决了

1.若程序停留在while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));      则可能的情况是:I2C的初始化没有配置好,可能缺少引脚复用功能的配置 ...

STM32F10x_硬件I2C读写EEPROM(标准外设库版本)

Ⅰ、写在前面 上一篇文章是“STM32F10x_模拟I2C读写EEPROM”,讲述使用IO口模拟I2C总线通信,对EEPROM(AT24Xxx)进行读写操作的过程。 上一篇文章主要内容:I2C协议...

【程序】STM32F1单片机I2C中Packet Error Checking(PEC)校验功能和DMA的使用方法

在STM32F1系列的单片机中,当I2C_CR1_ENPEC=1时启用CRC自动校验功能。注意这是一个自动校验的功能。发送方和接收方可以不同时开启自动校验,但发送方必须要发送CRC校验码,接收方也必须...

stm32f4xx标准外设固件库(By King先生)

原文网址:http://www.cnblogs.com/King-Gentleman/p/4369381.html STM32F4的相关资料:http://www.stmcu.org/documen...

STM32F0 I2C 驱动光感模块 GY30(BH1750FVI)

最近因为工程需要,需要使用光感模块BH1750FVI,使用STM32F030F4。本来打算移植店主给的51代码,但移植后发现无法调通,正好STM32F0系列的I2C还未调通过,于是打算用这个模块来练手...

(一)基于STM32f103的I2C通信接口的EPPROM模块(24C256)读写程序详解

想做到在24C256上读写数据,必须要掌握单片机的I2C通信知识,掌握这个对于其他外设也基于I2C通信的就可以引用了!      I2C 有四条连接线,SCL、SDA、VCC、GND。   I2C的通...

stm32f2xx标准外设库文档

  • 2015-05-20 13:50
  • 3.28MB
  • 下载

自制STM32F4最小系统烧写出现cannot reset error的解决方法

最近做了一个项目用到STM32F405RGT6,板子开始能够正常烧写,后来出现问题,总是提示:internal command error或者cannot reset target 等等错误。 1....
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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