|
之前有寫過I2C的基本讀寫程序,但拿來驅(qū)動MPU6050時,還是遇到一些麻煩,搞了兩天終于把問題解決了.
這兩天遇到的最大的問題是,I2C的SCL頻率達不到設(shè)置的100KHz,只有8KHz,這相差也太遠了吧.
還好我手上有邏輯分析儀可以看到波形,第一行是SCL的波形.

從上面的波形可以看到,低電平明顯比高電平要長,上圖的數(shù)據(jù)是高電平是10us,低電平時間是100us,而數(shù)據(jù)手冊上說,在標準模式下,低電平和高電平應(yīng)該是1:1的關(guān)系.這里的高電平時間和程序里設(shè)置的是一樣的,就是低電平不正常.
因為低電平時間離奇的長,這樣就導(dǎo)致I2C讀寫速度很慢,讀或?qū)懸粋字節(jié)數(shù)據(jù)要用4ms,這根本就不能用.如果我用mpu6050來做平衡小車,這樣的速度根本就沒法控制小車平衡.為此我冥思苦想想,不斷修改程序,給mpu6050模塊焊拆電阻,折騰了好久,最終還是沒有解決.搞的我崩潰的想放棄了.

在群里發(fā)了這個求助,我自己群,也沒報太大希望能解決這個問題.就是想問一下,要把握憋壞了,悶聲我憋不出大招了.

直到群里的一位大神出現(xiàn),我才重拾信心,突發(fā)靈感找到問題所在.因為大神說,STM8L的I2C是可以達到100KHz的頻率的,之前都懷疑STM8L的硬件I2C能不能達到100KHz.

不知道,怎么就想到打開電路圖,看了下,發(fā)現(xiàn)了問題所在了.PC1這個引腳上接了個按鍵,最重要的是還接了阻容濾波的電路.PC1就是SCL引腳.
還好,ST聰明的工程師,在這里設(shè)計了一個跳線,用烙鐵,拆掉SB17這個零歐電阻跳線,就可以了.
拆掉后,果然看到的久違的波形了,感覺這波形很清爽,心情一下好了很多,愁眉舒展.

雖然只有95KHz,但我已經(jīng)很滿意了,肯定會有偏差的,畢竟用的是默認的內(nèi)部時鐘源,可能還有其他原因,導(dǎo)致頻率偏差。
總體感覺,這個邏輯分析儀的功能很強大,查看I2C讀寫數(shù)據(jù)很方便,不用人工去根據(jù)波形翻譯讀寫數(shù)據(jù)。老外這個邏輯分析儀做的不錯,對不住了,我用的是山寨的。
下面就是我用的這個邏輯分析儀,其實是一塊CY7C68013A最小系統(tǒng)板,我燒寫了開源的saleae邏輯分析儀固件,配合saleae的上位機軟件,就可以用了。這個在淘寶上,三十幾塊錢就可以買到。

這個其實我也能做的,如果大家需要,我可以親自畫板,做幾個,給大家,不要我墊錢就行了。

介紹一下本例程,之前的I2C例程,里面讀一個字節(jié)的函數(shù)有個問題,就是讀完一個字節(jié)后不會發(fā)送無應(yīng)答NACK時序,最終的時序是錯誤的。(注:之前的文章會被刪掉,不用擔(dān)心找到的是錯誤的程序版本)本次進行了修正,在 I2C1_CR2_STOP=1語句前面加了I2C1_CR2_ACK=0。同時針對MPU6050增加了一個讀多個連續(xù)字節(jié)的函數(shù),下面貼出的例程,是正在測試這個讀多個字節(jié)的函數(shù)能否正常工作,例程已經(jīng)通過測試。讀出到數(shù)組中的數(shù)據(jù)和寫入的數(shù)據(jù)完全一致。
/*硬件連接*/
// PC0<--->SDA PC1---->SCL
/****************************************************************************************
*開發(fā)環(huán)境:IAR for stm8 v6.5.3
*硬件平臺:STM8L-DISCOVERY
*功能說明:通過硬件I2C等待的方法,測試連續(xù)讀MPU6050寄存器函數(shù)是否正常
*作 者:茗風(fēng)
****************************************************************************************/
#include"iostm8l152c6.h"
#include"stdbool.h"
#include"stdint.h"
uint8_t ui8Read_mpu6050_buffer[14];
/******************************************************************************************************
* 名 稱:void delay_10ms(uint8_t x_ms)
* 功 能:延時10ms
* 入口參數(shù):無
* 出口參數(shù):無
* 說 明:
* 范 例:無
******************************************************************************************************/
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
// 此延時函數(shù),延時時間為130ms
// 16M/8/2=1M 一個指令周期為1us
}
/******************************************************************************************************
* 名 稱: uint8_t I2C_ReadOneByteDataFromSlave(uint8_t address)
* 功 能:從I2C從設(shè)備中讀取一字節(jié)的數(shù)據(jù)
* 入口參數(shù):address:讀取數(shù)據(jù)的寄存器地址
* 出口參數(shù):返回一個從I2C從設(shè)備指定地址讀到的數(shù)據(jù)
* 說 明:
* 范 例:無
******************************************************************************************************/
uint8_t I2C_ReadOneByteDataFromSlave(uint8_t address)
{
volatile uint8_t t;
//----------I2C起始信號--------------
I2C1_CR2_START=1;//產(chǎn)生一個起始條件
while(!(I2C1_SR1_SB==1));//讀SR1寄存器,清除SB標志位
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
//-------發(fā)送寫I2C從器件地址---------
I2C1_DR=0xD0;//發(fā)送從設(shè)備地址
while(!(I2C1_SR1_ADDR==1));//讀SR1寄存器,清除ADDR標志位
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入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));//等待地址發(fā)送完成
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
//--------I2C重復(fù)起始信號-----------
I2C1_CR2_START=1;//重復(fù)產(chǎn)生一個起始條件
while(!(I2C1_SR1_SB==1));//讀SR1寄存器,清除SB標志位
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
//-------發(fā)送讀I2C從器件地址---------
I2C1_DR=0xD1;//發(fā)送從設(shè)備地址
while(!(I2C1_SR1_ADDR==1));//讀SR1寄存器,清除ADDR標志位
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
if(I2C1_SR3_TRA==1)return 1;//讀SR3寄存器,清除ADDR標志位
//-------------停止信號-------------
I2C1_CR2_ACK=0;//ACK位控制著ACK信號,此位為0可以產(chǎn)生一個NOACK信號
I2C1_CR2_STOP=1;
//-------------等待接收到數(shù)據(jù)-------------
while(!(I2C1_SR1_RXNE==1));//等待地址發(fā)送完成
//-------------讀取數(shù)據(jù)-------------
t=I2C1_DR;
return t;
}
/******************************************************************************************************
* 名 稱:void I2C_WriteOneByteDataToSlave(uint8_t address,uint8_t dat)
* 功 能:寫入一字節(jié)的數(shù)據(jù)到I2C設(shè)備中
* 入口參數(shù):address:寫入的數(shù)據(jù)存儲地址 dat:待寫入的數(shù)據(jù)
* 出口參數(shù):無
* 說 明: 通過MSTM8L硬件寫入I2C設(shè)備一個字節(jié)的數(shù)據(jù)
* 范 例:無
******************************************************************************************************/
uint8_t I2C_WriteOneByteDataToSlave(uint8_t address,uint8_t dat)
{
volatile uint8_t t;
I2C1_CR2_ACK=1;
//----------I2C起始信號--------------
I2C1_CR2_START=1;//產(chǎn)生一個起始條件
while(!(I2C1_SR1_SB==1));
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
I2C1_DR=0xD0;
//--------寫I2C從器件地址-----------
while(!(I2C1_SR1_ADDR==1));
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
if(I2C1_SR3_TRA==0)return 1;//讀SR3寄存器,清除ADDR標志位
//-----寫I2C從器件寄存器地址--------
while(!(I2C1_SR1_TXE==1));
I2C1_DR=address;
//-------寫I2C數(shù)據(jù)到寄存器中--------
while(!(I2C1_SR1_TXE==1));
I2C1_DR=dat;
while(!(I2C1_SR1_TXE==1));
while(!(I2C1_SR1_BTF==1));
// _5NOPS;//根據(jù)數(shù)據(jù)手冊,檢測到標志位后,需插入5個NOP進行延時
//-------------停止信號-------------
I2C1_CR2_STOP=1;
return 0;
}
/******************************************************************************************************
* 功 能:從I2C從設(shè)備讀取多個字節(jié)數(shù)據(jù)
* 入口函數(shù):
* 出口函數(shù):
* 說 明:
* 范 例:
* 日 期:
******************************************************************************************************/
uint8_t I2C_ReadMultiBytesFromSlave(uint8_t address,uint8_t *rxbuf,uint8_t len)
{
volatile uint8_t i=0;
if(len==0)return 1;//如果寫入字節(jié)長度為0退出
I2C1_CR2_ACK=1;
//----------I2C起始信號--------------
I2C1_CR2_START=1;//產(chǎn)生一個起始條件
while(!(I2C1_SR1_SB==1));//讀SR1寄存器,清除SB標志位
//-------發(fā)送寫I2C從器件地址---------
I2C1_DR=0xD0;//發(fā)送從設(shè)備地址
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));//等待地址發(fā)送完成
//--------I2C重復(fù)起始信號-----------
I2C1_CR2_START=1;//重復(fù)產(chǎn)生一個起始條件
while(!(I2C1_SR1_SB==1));//讀SR1寄存器,清除SB標志位
//-------發(fā)送讀I2C從器件地址---------
I2C1_DR=0xD1;//發(fā)送從設(shè)備地址
while(!(I2C1_SR1_ADDR==1));//讀SR1寄存器,清除ADDR標志位
if(I2C1_SR3_TRA==1)return 1;//讀SR3寄存器,清除ADDR標志位
//-------------讀取數(shù)據(jù)-------------
if(len>1)
{
for( i=len;i>1;i-- )
{
while(!(I2C1_SR1_RXNE==1));//等待I2C1_DR接收到數(shù)
*rxbuf++ = I2C1_DR;
}
}
//-------------停止信號-------------
I2C1_CR2_ACK=0;//ACK位控制著ACK信號,此位為0可以產(chǎn)生一個NOACK信號
I2C1_CR2_STOP=1;
while(!(I2C1_SR1_RXNE==1));//等待I2C1_DR接收到數(shù)
*rxbuf++ = I2C1_DR;
return 0;
}
/******************************************************************************************************
* 名 稱: IIC_init()
* 功 能:初始化IIC
* 入口參數(shù):無
* 出口參數(shù):無
* 說 明:PC0--SDA PC1--SCL
* 范 例:無
******************************************************************************************************/
void I2C_Init(void)
{
//----打開IIC外設(shè)時鐘----
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時鐘設(shè)置為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功能配置
* 入口函數(shù):
* 出口函數(shù):
* 說 明:
* 范 例:
* 日 期:
******************************************************************************************************/
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");
}
}
|
|