之前有写过I2C的基本读写程序,但拿来驱动MPU6050时,还是遇到一些麻烦,搞了两天终于把问题解决了.
这两天遇到的最大的问题是,I2C的SCL频率达不到设置的100KHz,只有8KHz,这相差也太远了吧.
还好我手上有逻辑分析仪可以看到波形,第一行是SCL的波形.

从上面的波形可以看到,低电平明显比高电平要长,上图的数据是高电平是10us,低电平时间是100us,而数据手册上说,在标准模式下,低电平和高电平应该是1:1的关系.这里的高电平时间和程序里设置的是一样的,就是低电平不正常.
因为低电平时间离奇的长,这样就导致I2C读写速度很慢,读或写一个字节数据要用4ms,这根本就不能用.如果我用mpu6050来做平衡小车,这样的速度根本就没法控制小车平衡.为此我冥思苦想想,不断修改程序,给mpu6050模块焊拆电阻,折腾了好久,最终还是没有解决.搞的我崩溃的想放弃了.

在群里发了这个求助,我自己群,也没报太大希望能解决这个问题.就是想问一下,要把握憋坏了,闷声我憋不出大招了.

直到群里的一位大神出现,我才重拾信心,突发灵感找到问题所在.因为大神说,STM8L的I2C是可以达到100KHz的频率的,之前都怀疑STM8L的硬件I2C能不能达到100KHz.

不知道,怎么就想到打开电路图,看了下,发现了问题所在了.PC1这个引脚上接了个按键,最重要的是还接了阻容滤波的电路.PC1就是SCL引脚.
还好,ST聪明的工程师,在这里设计了一个跳线,用烙铁,拆掉SB17这个零欧电阻跳线,就可以了.
拆掉后,果然看到的久违的波形了,感觉这波形很清爽,心情一下好了很多,愁眉舒展.

虽然只有95KHz,但我已经很满意了,肯定会有偏差的,毕竟用的是默认的内部时钟源,可能还有其他原因,导致频率偏差。
总体感觉,这个逻辑分析仪的功能很强大,查看I2C读写数据很方便,不用人工去根据波形翻译读写数据。老外这个逻辑分析仪做的不错,对不住了,我用的是山寨的。
下面就是我用的这个逻辑分析仪,其实是一块CY7C68013A最小系统板,我烧写了开源的saleae逻辑分析仪固件,配合saleae的上位机软件,就可以用了。这个在淘宝上,三十几块钱就可以买到。

这个其实我也能做的,如果大家需要,我可以亲自画板,做几个,给大家,不要我垫钱就行了。

介绍一下本例程,之前的I2C例程,里面读一个字节的函数有个问题,就是读完一个字节后不会发送无应答NACK时序,最终的时序是错误的。(注:之前的文章会被删掉,不用担心找到的是错误的程序版本)本次进行了修正,在 I2C1_CR2_STOP=1语句前面加了I2C1_CR2_ACK=0。同时针对MPU6050增加了一个读多个连续字节的函数,下面贴出的例程,是正在测试这个读多个字节的函数能否正常工作,例程已经通过测试。读出到数组中的数据和写入的数据完全一致。
/*硬件连接*/
// PC0<--->SDA PC1---->SCL
/****************************************************************************************
*开发环境:IAR for stm8 v6.5.3
*硬件平台:STM8L-DISCOVERY
*功能说明:通过硬件I2C等待的方法,测试连续读MPU6050寄存器函数是否正常
*作 者:茗风
****************************************************************************************/
#include"iostm8l152c6.h"
#include"stdbool.h"
#include"stdint.h"
uint8_t ui8Read_mpu6050_buffer[14];
/******************************************************************************************************
* 名 称:void delay_10ms(uint8_t x_ms)
* 功 能:延时10ms
* 入口参数:无
* 出口参数:无
* 说 明:
* 范 例:无
******************************************************************************************************/
void delay_100ms(void)
{
uint8_t i,j;
for(i=0;i<255;i++)//2*255个指令周期
for(j=0;j<255;j++);//2*255个指令周期
// delay_10ms共消耗 x_ms*2*255+2*x_ms个指令周期
// 255*2*255+2*255=130610us=130ms
// 此延时函数,延时时间为130ms
// 16M/8/2=1M 一个指令周期为1us
}
/******************************************************************************************************
* 名 称: uint8_t I2C_ReadOneByteDataFromSlave(uint8_t address)
* 功 能:从I2C从设备中读取一字节的数据
* 入口参数:address:读取数据的寄存器地址
* 出口参数:返回一个从I2C从设备指定地址读到的数据
* 说 明:
* 范 例:无
******************************************************************************************************/
uint8_t I2C_ReadOneByteDataFromSlave(uint8_t address)
{
volatile uint8_t t;
//----------I2C起始信号--------------
I2C1_CR2_START=1;//产生一个起始条件
while(!(I2C1_SR1_SB==1));//读SR1寄存器,清除SB标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//-------发送写I2C从器件地址---------
I2C1_DR=0xD0;//发送从设备地址
while(!(I2C1_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
if(I2C1_SR3_TRA==0)return 1;//读SR3寄存器,清除ADDR标志位
// 0: Data bytes received
// 1: Data bytes transmitted
//-----写I2C从器件寄存器地址--------
I2C1_DR=address;
while(!(I2C1_SR1_BTF==1));//等待地址发送完成
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//--------I2C重复起始信号-----------
I2C1_CR2_START=1;//重复产生一个起始条件
while(!(I2C1_SR1_SB==1));//读SR1寄存器,清除SB标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//-------发送读I2C从器件地址---------
I2C1_DR=0xD1;//发送从设备地址
while(!(I2C1_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
if(I2C1_SR3_TRA==1)return 1;//读SR3寄存器,清除ADDR标志位
//-------------停止信号-------------
I2C1_CR2_ACK=0;//ACK位控制着ACK信号,此位为0可以产生一个NOACK信号
I2C1_CR2_STOP=1;
//-------------等待接收到数据-------------
while(!(I2C1_SR1_RXNE==1));//等待地址发送完成
//-------------读取数据-------------
t=I2C1_DR;
return t;
}
/******************************************************************************************************
* 名 称:void I2C_WriteOneByteDataToSlave(uint8_t address,uint8_t dat)
* 功 能:写入一字节的数据到I2C设备中
* 入口参数:address:写入的数据存储地址 dat:待写入的数据
* 出口参数:无
* 说 明: 通过MSTM8L硬件写入I2C设备一个字节的数据
* 范 例:无
******************************************************************************************************/
uint8_t I2C_WriteOneByteDataToSlave(uint8_t address,uint8_t dat)
{
volatile uint8_t t;
I2C1_CR2_ACK=1;
//----------I2C起始信号--------------
I2C1_CR2_START=1;//产生一个起始条件
while(!(I2C1_SR1_SB==1));
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
I2C1_DR=0xD0;
//--------写I2C从器件地址-----------
while(!(I2C1_SR1_ADDR==1));
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
if(I2C1_SR3_TRA==0)return 1;//读SR3寄存器,清除ADDR标志位
//-----写I2C从器件寄存器地址--------
while(!(I2C1_SR1_TXE==1));
I2C1_DR=address;
//-------写I2C数据到寄存器中--------
while(!(I2C1_SR1_TXE==1));
I2C1_DR=dat;
while(!(I2C1_SR1_TXE==1));
while(!(I2C1_SR1_BTF==1));
// _5NOPS;//根据数据手册,检测到标志位后,需插入5个NOP进行延时
//-------------停止信号-------------
I2C1_CR2_STOP=1;
return 0;
}
/******************************************************************************************************
* 功 能:从I2C从设备读取多个字节数据
* 入口函数:
* 出口函数:
* 说 明:
* 范 例:
* 日 期:
******************************************************************************************************/
uint8_t I2C_ReadMultiBytesFromSlave(uint8_t address,uint8_t *rxbuf,uint8_t len)
{
volatile uint8_t i=0;
if(len==0)return 1;//如果写入字节长度为0退出
I2C1_CR2_ACK=1;
//----------I2C起始信号--------------
I2C1_CR2_START=1;//产生一个起始条件
while(!(I2C1_SR1_SB==1));//读SR1寄存器,清除SB标志位
//-------发送写I2C从器件地址---------
I2C1_DR=0xD0;//发送从设备地址
while(!(I2C1_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
if(I2C1_SR3_TRA==0)return 1;//读SR3寄存器,清除ADDR标志位
// 0: Data bytes received
// 1: Data bytes transmitted
//-----写I2C从器件寄存器地址--------
I2C1_DR=address;
while(!(I2C1_SR1_BTF==1));//等待地址发送完成
//--------I2C重复起始信号-----------
I2C1_CR2_START=1;//重复产生一个起始条件
while(!(I2C1_SR1_SB==1));//读SR1寄存器,清除SB标志位
//-------发送读I2C从器件地址---------
I2C1_DR=0xD1;//发送从设备地址
while(!(I2C1_SR1_ADDR==1));//读SR1寄存器,清除ADDR标志位
if(I2C1_SR3_TRA==1)return 1;//读SR3寄存器,清除ADDR标志位
//-------------读取数据-------------
if(len>1)
{
for( i=len;i>1;i-- )
{
while(!(I2C1_SR1_RXNE==1));//等待I2C1_DR接收到数
*rxbuf++ = I2C1_DR;
}
}
//-------------停止信号-------------
I2C1_CR2_ACK=0;//ACK位控制着ACK信号,此位为0可以产生一个NOACK信号
I2C1_CR2_STOP=1;
while(!(I2C1_SR1_RXNE==1));//等待I2C1_DR接收到数
*rxbuf++ = I2C1_DR;
return 0;
}
/******************************************************************************************************
* 名 称: IIC_init()
* 功 能:初始化IIC
* 入口参数:无
* 出口参数:无
* 说 明:PC0--SDA PC1--SCL
* 范 例:无
******************************************************************************************************/
void I2C_Init(void)
{
//----打开IIC外设时钟----
CLK_PCKENR1_PCKEN13=1;//
I2C1_CR1_PE=0;
// I2C1_CR2_SWRST=1;
I2C1_CR2_ACK=1;
//----I2C输入时钟频率选择----
I2C1_FREQR_FREQ=0x02;//2MHz
/* The allowed range is between 1 MHz and 16 MHz
000000: not allowed
000001: 1 MHz
000010: 2 MHz
...
010000: 16 MHz */
//----配置时钟控制寄存器----
I2C1_CCRH=0;
// I2C1_CCRH_F_S=0; //Standard mode I2C
// I2C1_CCRH_DUTY=0;
I2C1_CCRL=10; //SCL高电平时间配置
//I2C的SCK时钟设置为100KHz,则SCK周期为10us
//因为I2C1_FREQR_FREQ=0x02,即I2C输入时钟频率为2M,周期为0.5us
//CCR=10时,SCK的低电平时间为tlow=10*0.5us=5us,SCk高电平时间为thigh=10*0.5us=5us
//所以CCR=10时,SCK输出频率为100KHz
//----配置上升时间寄存器----
I2C1_TRISER_TRISE=1;//in standard mode, the maximum allowed SCL rise time is 1000 ns.
//1 us / 0.5 us = 2 + 1
I2C1_CR1_PE=1;//
}
/******************************************************************************************************
* 功 能:MPU6050功能配置
* 入口函数:
* 出口函数:
* 说 明:
* 范 例:
* 日 期:
******************************************************************************************************/
void MPU6050_Config(void)
{
// static uint8_t tmp=0;
// I2C_WriteOneByteDataToSlave(0x6B,0x00);//电源管理
// I2C_WriteOneByteDataToSlave(0x19,0x00);//陀螺仪采样率,不分频,8khz
// I2C_WriteOneByteDataToSlave(0x1A,0x00);//不启用低通滤波器
// I2C_WriteOneByteDataToSlave(0x1B,0x18);//陀螺仪不自检,2000deg/s
// I2C_WriteOneByteDataToSlave(0x1C,0x1F);//加速度计不自检,16g
I2C_WriteOneByteDataToSlave(0x6B,0x00);//电源管理
I2C_WriteOneByteDataToSlave(0x27,0x01);
I2C_WriteOneByteDataToSlave(0x28,0x02);
I2C_WriteOneByteDataToSlave(0x29,0x03);
I2C_WriteOneByteDataToSlave(0x2A,0x04);
I2C_WriteOneByteDataToSlave(0x2B,0x05);
I2C_WriteOneByteDataToSlave(0x2C,0x06);
I2C_WriteOneByteDataToSlave(0x2D,0x07);
I2C_WriteOneByteDataToSlave(0x2E,0x08);
I2C_WriteOneByteDataToSlave(0x2F,0x09);
}
void main(void)
{
static uint8_t tmp=0;
delay_100ms();
I2C_Init();
MPU6050_Config();
// tmp=I2C_ReadOneByteDataFromSlave(0x1C);
I2C_ReadMultiBytesFromSlave(0x27,ui8Read_mpu6050_buffer,9);
while(1)
{
// asm("wfi");
}
}
|