STM32 基础知识(探索者开发板)--146讲 IIC

IIC特点:

        同步串行半双工通信总线

IIC有一个弱上拉电阻,在主机和从机都没有传输数据下拉时,总线会自动上拉

SCL在低电平期间,改变SDA的值来上传数据,方便SCL电平上升时进行数据读取

SCL在高电平期间,不能改变SDA的值,若改变,SDA高到低为起始信号,低到高为终止信号

IIC配置步骤

1.使能SCL和SDA对应时钟        _HAL_RCC_GPIOB_CLK_ENABLE()

2.设置GPIO工作模式                HAL_GPIO_Init()

3.编写基本信号                起始send ack 停止send nak 应答wait ack

4.编写读和写函数                  iic_read_byte   iic_send_byte

软件驱动外设步骤

1.初始化IIC接口

2.编写写入/读取一个字节数据的函数

3.编写连续读和连续写函数

iic代码

//myiic.c

#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"


//初始化IIC
void iic_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    IIC_SCL_GPIO_CLK_ENABLE();  /* SCL引脚时钟使能 */
    IIC_SDA_GPIO_CLK_ENABLE();  /* SDA引脚时钟使能 */

    gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;        /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 快速 */
    HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);/* SCL */

    gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;            
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;        /* 开漏输出 */
    HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct);/* SDA */
    /* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */

    iic_stop();     /* 停止总线上所有设备 */
}

//IIC延时函数,给芯片反应时间
static void iic_delay(void)
{
    delay_us(2);    /* 2us的延时, 读写速度在250Khz以内 */
}



//编写时序,产生IIC起始信号
void iic_start(void)
{
    IIC_SDA(1);
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(0);     /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
    iic_delay();
    IIC_SCL(0);     /* 下拉I2C总线,准备发送或接收数据 */
    iic_delay();
}


//编写时序,产生IIC结束信号
void iic_stop(void)
{
    IIC_SDA(0);     /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(1);     /* 发送I2C总线结束信号 */
    iic_delay();
}



//接收应答信号,1为接收失败,0为接收成功
uint8_t iic_wait_ack(void)
{
    uint8_t waittime = 0;
    uint8_t rack = 0;

    IIC_SDA(1);     /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
    iic_delay();
    IIC_SCL(1);     /* SCL=1, 此时从机可以返回ACK应答信号 */
    iic_delay();

    while (IIC_READ_SDA)    /* 等待应答 */
    {
        waittime++;

        if (waittime > 250)
        {
            iic_stop();
            rack = 1;
            break;
        }
    }

    IIC_SCL(0);     /* SCL=0, 结束ACK检查 */
    iic_delay();
    return rack;
}

/**
 * @brief       从机产生ACK应答
 */
void iic_ack(void)
{
    IIC_SDA(0);     /* SCL = 1 时 SDA = 0,表示应答 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SCL(0);     /* 产生下一个时钟 */
    iic_delay();
    IIC_SDA(1);     /* 释放SDA线 */
    iic_delay();
}

/**
 * @brief       不产生ACK应答
 */
void iic_nack(void)
{
    IIC_SDA(1);     /* SCL = 1  时 SDA = 1,表示不应答 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SCL(0);     /* 产生下一个时钟 */
    iic_delay();
}

/**
 * @brief       IIC发送一个字节 
*/
void iic_send_byte(uint8_t data)
{
    uint8_t t;
    
    for (t = 0; t < 8; t++)
    {
        IIC_SDA(data & (0x80 >> t));    /* 高位先发送,发完后右移 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
    }
    IIC_SDA(1);         /* 发送完成, 主机释放SDA线 */
}

/**
 * @brief       IIC读取一个字节
 * @param       ack:  ack=1时,发送ack; ack=0时,发送nack
 * @retval      接收到的数据
 */
uint8_t iic_read_byte(uint8_t ack)
{
    uint8_t i, receive = 0;
    IIC_SDA(1);
    for (i = 0; i < 8; i++ )    /* 接收1个字节数据,循环八次 */
    {
        IIC_SCL(1);
        if(IIC_READ_SDA == 1){receive |= (0x80 >> i);}  //SDA为1时,读取当前这位,为0时,不读取因为本身该位为0
        IIC_SCL(0);
    }

    if (!ack)
    {
        iic_nack();     /* 发送nACK */
    }
    else
    {
        iic_ack();      /* 发送ACK */
    }

    return receive;
}


//myiic.h

#ifndef __MYIIC_H
#define __MYIIC_H

#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define IIC_SCL_GPIO_PORT               GPIOB
#define IIC_SCL_GPIO_PIN                GPIO_PIN_8
#define IIC_SCL_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

#define IIC_SDA_GPIO_PORT               GPIOB
#define IIC_SDA_GPIO_PIN                GPIO_PIN_9
#define IIC_SDA_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

/******************************************************************************************/

/* IO操作 */
#define IIC_SCL(x)        do{ x ? \
                              HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* SCL */

#define IIC_SDA(x)        do{ x ? \
                              HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* SDA */

#define IIC_READ_SDA     HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 读取SDA */


/* IIC所有操作函数 */
void iic_init(void);            /* 初始化IIC的IO口 */
void iic_start(void);           /* 发送IIC开始信号 */
void iic_stop(void);            /* 发送IIC停止信号 */
void iic_ack(void);             /* IIC发送ACK信号 */
void iic_nack(void);            /* IIC不发送ACK信号 */
uint8_t iic_wait_ack(void);     /* IIC等待ACK信号 */
void iic_send_byte(uint8_t txd);/* IIC发送一个字节 */
uint8_t iic_read_byte(unsigned char ack);/* IIC读取一个字节 */

#endif

iic软件驱动MPU6050代码

//MPU6050.c

#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/MPU6050/MPU6050.h"

#define MPU6050_ADDRESS     0xD0

void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//向外设写数据
{
    iic_start();                //开始信号
    iic_send_byte(MPU6050_ADDRESS);//向该外设地址发送数据
    iic_wait_ack();                 //接收应答
    
    iic_send_byte(RegAddress);      //发送目标寄存器地址
    iic_wait_ack();
    
    iic_send_byte(Data);            //向该地址发送数据,这里是发送一个数据
    iic_wait_ack();
    
/*
    for(int i = 0;i<8; i++)          //发送多个字节数据
    {
        iic_send_byte(Data);
        iic_wait_ack();
    }
*/
    iic_stop();                   //终止信号
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)    //从外设读取数据
{
    uint*_t Data;
    
    iic_start();                //开始信号
    iic_send_byte(MPU6050_ADDRESS);//向该外设地址发送数据
    iic_wait_ack();                 //接收应答
    
    iic_send_byte(RegAddress);      //发送目标寄存器地址
    iic_wait_ack();
    
    iic_start();                //重新起始
    iic_send_byte(MPU6050_ADDRESS | 0x01)   //将最后一位变1,进行从机的读取数据
    iic_wait_ack();
    
    Data = iic_read_byte();         //读取数据
    iic_nack();                     //因为读取一个字节,所以不用产生应答
    iic_stop();
/*
    for(int i = 0;i < n;i++)        //读取n个字节,就要产生应答
    {
        Data = iic_read_byte();
        iic_ack();
    }
*/
    return Data;
}


void MPU6050_Init(void)
{
    iic_init();
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);      //解除睡眠,X轴陀螺仪时钟
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x01);      //6个轴都不需要待机
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);      //选择十分频的采样率
    MPU6050_WriteReg(MPU6050_CONFIG,0x06);          //滤波参数给最大
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);     //陀螺仪最大量程
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);    //加速度计最大量程
    
}



void MPU6050_GetData(struct MPU6050_Data* p)    //获得芯片记录的各类数据
{
    uint16_t DataH,DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);  //获取加速度x轴数据
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
    p->AccX = (DataH << 8) | DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    p->AccY = (DataH << 8) | DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    p->AccZ = (DataH << 8) | DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);   //获取陀螺仪x轴数据
    DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    p->GyroX = (DataH << 8) | DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    p->GyroY = (DataH << 8) | DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    p->GyroZ = (DataH << 8) | DataL;
}

uint8_t MPU6050_GetID(void)                 //获取芯片ID号
{
    return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

//MPU6050.h

#ifndef __MPU6050_H
#define __MPU6050_H

#include "./SYSTEM/sys/sys.h"

typedef struct MPU6050_Data{
    uint16_t AccX;      //加速度x轴数据
    uint16_t AccY;
    uint16_t AccZ;
    uint16_t GyroX;     //陀螺仪x轴数据
    uint16_t GyroY;
    uint16_t GyroZ;
}Data;

void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(struct MPU6050_Data* p);

#define MPU6050_SMPLRT_DIV      0x19    //陀螺仪采样率,典型值:0x07(125Hz)
#define MPU6050_CONFIG          0x1A    //低通滤波频率,典型值:0x06(5Hz)
#define MPU6050_GYRO_CONFIG     0x1B    //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define MPU6050_ACCEL_CONFIG    0x1C    //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define MPU6050_ACCEL_XOUT_H    0x3B
#define MPU6050_ACCEL_XOUT_L    0x3C
#define MPU6050_ACCEL_YOUT_H    0x3D
#define MPU6050_ACCEL_YOUT_L    0x3E
#define MPU6050_ACCEL_ZOUT_H    0x3F
#define MPU6050_ACCEL_ZOUT_L    0x40
#define MPU6050_TEMP_OUT_H      0x41
#define MPU6050_TEMP_OUT_L      0x42
#define MPU6050_GYRO_XOUT_H     0x43
#define MPU6050_GYRO_XOUT_L     0x44
#define MPU6050_GYRO_YOUT_H     0x45
#define MPU6050_GYRO_YOUT_L     0x46
#define MPU6050_GYRO_ZOUT_H     0x47
#define MPU6050_GYRO_ZOUT_L     0x48
#define MPU6050_PWR_MGMT_1      0x6B    //电源管理,典型值:0x00(正常启用)
#define MPU6050_PWR_MGMT_2      0x6C    //电源管理寄存器2
#define MPU6050_WHO_AM_I        0x75    //IIC地址寄存器(默认数值0x68,只读)

#endif

main.c

int main(void)
{
    Data Data1;
    int ID;

    OLED_Init();        //显示屏初始化
    MPU6050_Init();      //MPU6050初始化  
    
    OLED_ShowString(1,1,"ID:");
    ID = MPU6050_GetID();        //获取芯片ID
    
    while(1)
    {
        MPU6050_GetData(&Data1);
        OLED_ShowSignedNum(2,1,Data1.AccX,5);    //获取x的加速度值
    }
}

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值