十三.I2C使用2——主從機程序編寫


在前面一章我們已經鋪墊了I2C的使用流程,下面我們就按照I2C的通訊流程寫對應的代碼,這個流程應該嚴格按照參考手冊給出的定義

 

 

上面兩幅圖就是I2C通訊的流程

master代碼流程

I2C的代碼流程比較復雜,我們一個個函數來說

初始化

首先是初始化

void i2c_init(I2C_Type *base)
{
    base->I2CR &= ~(1<<7);      //disable I2C
    base->IFDR= 0x15;          //640分頻,clk=103.125KHz
    base->I2CR |= (1<<7);       //I2C Enable
}

初始化里只是設置了個分頻器,我們使用的時鍾源是66MHz,選擇640分頻,速率為103.125KHz,設置分頻器前要將I2C停止,設置完成后一定要使能I2C,其他寄存器操作時有些是要求I2C使能的。

產生Start流程

在完成初始化后,數據可以有主機先行發送。發送數據時,要先檢查I2C的BUSY標志位是否為0(I2C_I2SR[IBB]),標志位為0時才能發送數據。發送第一組數據時要將從機的地址寫入I2C_I2DR寄存器,從機地址是7位的,地址后面要跟一個讀寫標志。

/**
 * @brief 生成start信號
 * 
 * @param base I2C結構體
 * @param address 從機地址
 * @param direction 方向
 * @return unsigned char 
 */
unsigned char i2c_master_start(I2C_Type *base, 
                                unsigned char address,
                                enum i2c_direction direction)
{
    if((base->I2SR) & (1<<5)){      //IBB,I2C忙標志位
            return 1;               //IBB=1時,I2C忙,返回1
    } 
    
    //設置為主機模式
    base->I2CR |= ((1<<5) | (1<<4));  //[5]MSTA master mode,[4]MXT 發送模式
    //產生Start信號
    base->I2DR = ((unsigned int)address <<1) | ((direction== kI2C_Read)?1 :0);  //address位高7位為地址,低位為讀寫標志
    return 0;                       //無異常,返回0
}

函數中先判定是否為總線忙,如果總線忙跳出函數,拋出異常,如果正常就將設備設置成master、發送模式,將數據寄存器寫入從機地址。注意地址左移1位,最低位留給讀寫標志位,用了個3元運算符(A?1:2,當A為True時值為1,否則為2)。

 

I2C Stop

master結束通訊,要發送一個Stop標志。

//產生stop信號
unsigned char i2c_master_stop(I2C_Type *base)
{
    unsigned short timeout = 0xffff;                //超時等待值
    
    //清除I2CRbit5:3
    base->I2CR &= ~(7<<3);                          //MSTA=0 Slave模式、MTX=0 接收模式 TXAK=0 發送NoACK 

    //等待I2C空閑
    while(base->I2SR &(1<<5))                       //BUSY位
    {
        timeout--;
        if(timeout ==0)
        return I2C_STATUS_TIMEOUT;                  //返回超時異常
    }
    return I2C_STATUS_OK;
}

這個Stop標志的產生要求我不太明白為什么要把I2C設置成slave模式。按照手冊31.5.4 Generation of Stop章節說的,停止標志的產生要在數據倒數第二個Byte時master告訴從機最后一個byte發送完成后跟一個NoACK信號

重新發送Start

在數據傳輸中,如果master還需要占據總線,他需要重新發個Start信號,這個Start標志后還需要跟一個地址,這個地址可以是設備地址也可以是寄存器地址,restart信號和start信號之間是不能有Stop信號的。

//重復發送Start信號
unsigned char i2c_master_repeated_start(I2C_Type *base, 
                                unsigned char address,
                                enum i2c_direction direction)
{

    //檢查I2C是否空閑或在從機模式下
    if(((base->I2SR & (1<<5)) && ((base->I2CR) &(1<<5))) == 0 )     //I2SR[5]:IBB 總線忙標志 I2CR[5]:MSTA master模式
        {return 1;}  //總線空閑模式無法重新生成Restart slave模式也無法生產Restart信號,跳出  

    base->I2CR |=(1<<4) |(1<<2);    //I2CR[4]MTX=1發送模式  [2]產生Repeat Start
    base->I2DR = ((unsigned int)address <<1) | ((direction== kI2C_Read)?1 :0);  //address位高7位為地址,低位為讀寫標志

    return I2C_STATUS_OK;
}

ReStart的流程和Start差不多,都是先判定是否總線忙,當總線不在空閑狀態且I2C為master模式下,重新生成Start信號,信號是發送一個地址和讀寫標志。前面的if判斷比較繞,就是在IBB位不能為0或MSTA不能為0,就是I2C不能是空閑或設備不是Slave模式,否則跳出

異常處理

這里的異常處理主要是通過I2SR寄存器的狀態判定兩種異常狀態:仲裁丟失和無應答

/**
 * @brief 檢查並處理異常
 * 
 * @param base I2C
 * @param state 狀態(I2SR寄存器)
 * @return unsigned char 
 */
unsigned char i2c_check_and_clear_error(I2C_Type *base,unsigned int state)
{   
    //先檢查是否為仲裁丟失錯誤
    if(state & (1<<4))              //state值為I2SR,bit[4]:IAL仲裁丟失
    {
        base->I2SR &= ~(1<<4);      //清除IAL異常
        base->I2CR &= ~(1<<7);      //禁止I2C
        base->I2CR |= (1<<7);       //使能I2C
        return I2C_STATUS_ARBITRATIONLOST;  //拋出異常:仲裁丟失
    }

    else if(state & (1<<0))         //I2SR[0]RXAK 1時收到NoACK信號
    {
        return I2C_STATUS_NAK;      //拋出異常:無應答
    }

    return I2C_STATUS_OK;           //返回正常
}

仲裁丟失時需要重新使能I2C,拋出異常狀態(宏在頭文件里定義了),無應答時不用重啟I2C,直接拋異常。

數據發送

數據發送要按照本章節最開始那個流程圖去進行,先看代碼

/**
 * @brief I2C,master發送數據
 * 
 * @param base I2C結構體
 * @param buf  待發送數據
 * @param size 發送數據大小
 */
void i2c_master_write(I2C_Type *base,const unsigned char *buf,unsigned int size)
{
    while(!(base->I2SR & (1<<7)));  //ICF,等待傳輸完成,1時數據傳輸中,0時跳出循環

    base->I2SR &= ~(1<<1);          //IIF=0,清除中斷

    base->I2CR |= (1<<4);           //MTX=1設置為發送

    while(size--){
        base->I2DR = *buf ++ ;          //將待寫入的值寫入寄存器
        while(!(base->I2SR & (1<<1)));  //等待傳輸完成,這里用到時IIT,沒有用傳輸完成標志位(bit7)
        base->I2SR &= ~(1<<1);          //清除標志

        /*檢查ACK*/
        if(i2c_check_and_clear_error(base,base->I2SR))
            break;                      //無異常時狀態字為I2C_STATUS_OK=0,否則跳出循環
    }

    base->I2SR &= ~(1<<1);              //清除標志
    i2c_master_stop(base);              //停止傳輸
}

這里有個BUG:在發送過程中等待過程,按照手冊上所說要檢查I2SR的bit7

 

當該位為1的時候說明傳輸完成。但是按照官方的驅動,是通過檢查bit[1],即IIF來實現的。這個不知道是為什么,如果檢查ICF標志I2C通訊無法進行。但開始有個等待傳輸的過程又是檢查的ICF。官方給出的硬件驅動就是這樣寫的整個流程就是

  1. 等待I2C上一發送過程結束
  2. 清除IIF中斷標志
  3. 設置MTX為傳輸
  4. 將要發送到數據依次寫入I2DR寄存器,寫入后等待發送完畢標志
  5. 所有數據發送完畢,清除IIF標志
  6. 發送Stop標志停止傳輸

數據讀取

數據讀取的流程和發送基本一致,但是要判定是否是倒數第二個Byte的數據,因為I2C協議要求讀數據為8bit數據加1bit的ACK標志,但最后一個Byte的數據后要跟的是NoACK,並且主機要在讀取倒數第二個Byte數據時告訴從機下一組數據准備發個NoACK。

/**
 * @brief 讀數據
 * 
 * @param base I2C結構體
 * @param buf  讀取數據寫入的緩存
 * @param size 讀取數據大小
 */
void i2c_master_read(I2C_Type *base,unsigned char *buf,unsigned size){
    volatile uint8_t dummy = 0;         //假讀數據
    dummy++;                            //防止編譯報錯,具體原因不詳

    while(!(base->I2SR & (1<<7)));      //ICF,1時數據傳輸中,0時跳出循環

    base->I2SR &= ~(1<<1);              //IIF=0,清除中斷標志

    base->I2CR &= ~((1<<4)|(1<<3));     //[4]MTX=0:接收數據 [3]TXAK=0 8位數據后加1位ACK響應

    if(size==1)                         //發送數據大小為1
        base->I2CR |= (1<<3);           //發送NoACK

    dummy = base->I2DR;                 //假讀(個人覺得通知從機NoACK必須在讀倒數第二個數據時發送,所以假讀一次)

    while(size--){
        while(!(base->I2SR & (1<<1)));      //IIF是否為0?等待傳輸完成
        base->I2SR &= ~(1<<1);              //IIF=0,清除標志位
        if(size==0){                        //最后一個數據讀取完畢
            i2c_master_stop(base);          //主機發送Stop標志
        }
        if(size==1){                        //倒數第二個數據
            base->I2CR |= (1<<3);           //NoACK通知 
        }

        *buf++ = base->I2DR;                //讀取信息
    }
}

程序里做了個假讀的流程,我估計原因就是如果讀取數據長度為1時要假裝讀一下,以便從機發送NoACK。讀取的流程和寫的流程基本一樣,也是通過IIF判定是否在讀寫操作。但是這里沒有做異常檢查和處理,我估計是因為要讀操作時前面必須有個寫操作,如果無響應就會在寫的流程中出現異常。

I2C通訊函數

教程里最后把發送和接受的功能集成到了一個函數中,這個函數是最后我們調用的接口。

/*
 * @description    : I2C數據傳輸,包括讀和寫
 * @param - base: 要使用的IIC
 * @param - xfer: 傳輸結構體
 * @return         : 傳輸結果,0 成功,其他值 失敗;
 */
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
    unsigned char ret = 0;
    enum i2c_direction direction = xfer->direction;    

    base->I2SR &= ~((1 << 1) | (1 << 4));            /* 清除標志位 */

    /* 等待傳輸完成 */
    while(!((base->I2SR >> 7) & 0X1)){}; 

    /* 如果是讀的話,要先發送寄存器地址,所以要先將方向改為寫 */
    if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
    {
        direction = kI2C_Write;
    }

    ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 發送開始信號 */
    if(ret)
    {    
        return ret;
    }

    while(!(base->I2SR & (1 << 1))){};            /* 等待傳輸完成 */

    ret = i2c_check_and_clear_error(base, base->I2SR);    /* 檢查是否出現傳輸錯誤 */
    if(ret)
    {
          i2c_master_stop(base);                         /* 發送出錯,發送停止信號 */
        return ret;
    }
    
    /* 發送寄存器地址 */
    if(xfer->subaddressSize)
    {
        do
        {
            base->I2SR &= ~(1 << 1);            /* 清除標志位 */
            xfer->subaddressSize--;                /* 地址長度減一 */
            
            base->I2DR =  ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器寫入子地址
  
            while(!(base->I2SR & (1 << 1)));      /* 等待傳輸完成 */

            /* 檢查是否有錯誤發生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
                 i2c_master_stop(base);                 /* 發送停止信號 */
                 return ret;
            }  
        } while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));

        if(xfer->direction == kI2C_Read)         /* 讀取數據 */
        {
            base->I2SR &= ~(1 << 1);            /* 清除中斷掛起位 */
            i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read); /* 發送重復開始信號和從機地址 */
            while(!(base->I2SR & (1 << 1))){};/* 等待傳輸完成 */

            /* 檢查是否有錯誤發生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
                 ret = I2C_STATUS_ADDRNAK;
                i2c_master_stop(base);         /* 發送停止信號 */
                return ret;  
            }                         
        }
    }    

    /* 發送數據 */
    if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
    {
        i2c_master_write(base, xfer->data, xfer->dataSize);
    }

    /* 讀取數據 */
    if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
    {
           i2c_master_read(base, xfer->data, xfer->dataSize);
    }
    return 0;    
}

這個函數就比較復雜了,有時間的話可以分析一下,這里就不再講了,最后把文件路徑、頭文件和最終代碼都放出來

文件夾和前面的硬件驅動路徑一樣,在bsp文件夾下放了兩個文件

 頭文件 

/**
 * @file bsp_i2c.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-01-21
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#ifndef __BSP_I2C_H
#define __BSP_I2C_H

#include "imx6ul.h"

#define I2C_STATUS_OK                 (0)
#define I2C_STATUS_BUSY               (1)
#define I2C_STATUS_IDLE               (2)
#define I2C_STATUS_NAK                (3)
#define I2C_STATUS_ARBITRATIONLOST    (4)
#define I2C_STATUS_TIMEOUT            (5)
#define I2C_STATUS_ADDRNAK            (6)


enum i2c_direction
{
    kI2C_Write = 0x0,
    kI2C_Read = 0x1,
};

/*
 * I2Cmaster傳輸結構體
 */
struct i2c_transfer
{
    unsigned char slaveAddress;          /* 7位從機地址             */
    enum i2c_direction direction;         /* 傳輸方向             */
    unsigned int subaddress;               /* 寄存器地址            */
    unsigned char subaddressSize;        /* 寄存器地址長度             */
    unsigned char *volatile data;        /* 數據緩沖區             */
    volatile unsigned int dataSize;      /* 數據緩沖區長度             */
};


void i2c_init(I2C_Type *base);

unsigned char i2c_master_start(I2C_Type *base, 
                                unsigned char address,
                                enum i2c_direction direction);

unsigned char i2c_master_repeated_start(I2C_Type *base, 
                                unsigned char address,
                                enum i2c_direction direction);

unsigned char i2c_master_stop(I2C_Type *base);

unsigned char i2c_check_and_clear_error(I2C_Type *base,unsigned int state);

unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer);
#endif
bsp_i2c.h

c文件

#include "bsp_i2c.h"

void i2c_init(I2C_Type *base)
{
    
    base->I2CR &= ~(1<<7);  //disable I2C

    base->IFDR=0x15;   //640分頻,clk=103.125KHz

    base->I2CR |= (1<<7);   //I2C Enable

}

/**
 * @brief 生成start信號
 * 
 * @param base I2C結構體
 * @param address 從機地址
 * @param direction 方向
 * @return unsigned char 
 */
unsigned char i2c_master_start(I2C_Type *base, 
                                unsigned char address,
                                enum i2c_direction direction)
{
    if((base->I2SR) & (1<<5)){      //IBB,I2C忙標志位
            return 1;               //IBB=1時,I2C忙,返回1
    } 
    
    //設置為主機模式
    base->I2CR |= ((1<<5) | (1<<4));  //[5]MSTA master mode,[4]MXT 發送模式
    //產生Start信號
    base->I2DR = ((unsigned int)address <<1) | ((direction== kI2C_Read)?1 :0);  //address位高7位為地址,低位為讀寫標志
    return 0;                       //無異常,返回0
}


//產生stop信號
unsigned char i2c_master_stop(I2C_Type *base)
{
    unsigned short timeout = 0xffff;                //超時等待值
    
    //清除I2CRbit5:3
    base->I2CR &= ~(7<<3);                          //MSTA=0 Slave模式、MTX=0 接收模式 TXAK=0 發送NoACK 

    //等待I2C空閑
    while(base->I2SR &(1<<5))                       //BUSY位
    {
        timeout--;
        if(timeout ==0)
        return I2C_STATUS_TIMEOUT;                  //返回超時異常
    }
    return I2C_STATUS_OK;
}

//重復發送Start信號
unsigned char i2c_master_repeated_start(I2C_Type *base, 
                                unsigned char address,
                                enum i2c_direction direction)
{

    //檢查I2C是否忙或在從機模式下
    if(((base->I2SR & (1<<5)) && ((base->I2CR) &(1<<5))) == 0 )     //I2SR[5]:IBB 總線忙標志 I2CR[5]:MSTA master模式
        {return 1;}  //總線空閑模式無法重新發送start信號,slave模式也無法生成Restart信號,跳出  

    base->I2CR |=(1<<4) |(1<<2);    //I2CR[4]MTX=1發送模式  [2]產生Repeat Start
    base->I2DR = ((unsigned int)address <<1) | ((direction== kI2C_Read)?1 :0);  //address位高7位為地址,低位為讀寫標志

    return I2C_STATUS_OK;
}

/**
 * @brief 檢查並處理異常
 * 
 * @param base I2C
 * @param state 狀態(I2SR寄存器)
 * @return unsigned char 
 */
unsigned char i2c_check_and_clear_error(I2C_Type *base,unsigned int state)
{   
    //先檢查是否為仲裁丟失錯誤
    if(state & (1<<4))              //state值為I2SR,bit[4]:IAL仲裁丟失
    {
        base->I2SR &= ~(1<<4);      //清除IAL異常
        base->I2CR &= ~(1<<7);      //禁止I2C
        base->I2CR |= (1<<7);       //使能I2C
        return I2C_STATUS_ARBITRATIONLOST;  //拋出異常:仲裁丟失
    }

    else if(state & (1<<0))         //I2SR[0]RXAK 1時收到NoACK信號
    {
        return I2C_STATUS_NAK;      //拋出異常:無應答
    }

    return I2C_STATUS_OK;           //返回正常
}

//發送和接受按要求是要判定傳輸完畢標志位,但是根據官方文檔是判定的IIF,也就是中斷標志位
/*發送數據*/
/**
 * @brief I2C,master發送數據
 * 
 * @param base I2C結構體
 * @param buf  待發送數據
 * @param size 發送數據大小
 */
void i2c_master_write(I2C_Type *base,const unsigned char *buf,unsigned int size)
{
    while(!(base->I2SR & (1<<7)));  //ICF,1時數據傳輸中,0時跳出循環

    base->I2SR &= ~(1<<1);          //IIF=0,清除中斷

    base->I2CR |= (1<<4);           //MTX=1設置為發送

    while(size--){
        base->I2DR = *buf ++ ;          //將待寫入的值寫入寄存器
        while(!(base->I2SR & (1<<1)));  //等待傳輸完成,這里用到時IIT,沒有用傳輸完成標志位(bit7)
        base->I2SR &= ~(1<<1);          //清除標志

        /*檢查ACK*/
        if(i2c_check_and_clear_error(base,base->I2SR))
            break;                      //無異常時狀態字為I2C_STATUS_OK=0,否則跳出循環
    }

    base->I2SR &= ~(1<<1);              //清除標志
    i2c_master_stop(base);              //停止傳輸
}


/**
 * @brief 讀數據
 * 
 * @param base I2C結構體
 * @param buf  讀取數據寫入的緩存
 * @param size 讀取數據大小
 */
void i2c_master_read(I2C_Type *base,unsigned char *buf,unsigned size){
    volatile uint8_t dummy = 0;         //假讀數據
    dummy++;                            //防止編譯報錯,具體原因不詳

    while(!(base->I2SR & (1<<7)));      //ICF,1時數據傳輸中,0時跳出循環

    base->I2SR &= ~(1<<1);              //IIF=0,清除中斷標志

    base->I2CR &= ~((1<<4)|(1<<3));     //[4]MTX=0:接收數據 [3]TXAK=0 8位數據后加1位ACK響應

    if(size==1)                         //發送數據大小為1
        base->I2CR |= (1<<3);           //發送NoACK

    dummy = base->I2DR;                 //假讀(個人覺得通知從機NoACK必須在讀倒數第二個數據時發送,所以假讀一次)

    while(size--){
        while(!(base->I2SR & (1<<1)));      //IIF是否為0?等待傳輸完成
        base->I2SR &= ~(1<<1);              //IIF=0,清除標志位
        if(size==0){                        //最后一個數據讀取完畢
            i2c_master_stop(base);          //主機發送Stop標志
        }
        if(size==1){                        //倒數第二個數據
            base->I2CR |= (1<<3);           //NoACK 
        }

        *buf++ = base->I2DR;                //讀取信息
    }
}



/*
 * @description    : I2C數據傳輸,包括讀和寫
 * @param - base: 要使用的IIC
 * @param - xfer: 傳輸結構體
 * @return         : 傳輸結果,0 成功,其他值 失敗;
 */
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
    unsigned char ret = 0;
    enum i2c_direction direction = xfer->direction;    

    base->I2SR &= ~((1 << 1) | (1 << 4));            /* 清除標志位 */

    /* 等待傳輸完成 */
    while(!((base->I2SR >> 7) & 0X1)){}; 

    /* 如果是讀的話,要先發送寄存器地址,所以要先將方向改為寫 */
    if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
    {
        direction = kI2C_Write;
    }

    ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 發送開始信號 */
    if(ret)
    {    
        return ret;
    }

    while(!(base->I2SR & (1 << 1))){};            /* 等待傳輸完成 */

    ret = i2c_check_and_clear_error(base, base->I2SR);    /* 檢查是否出現傳輸錯誤 */
    if(ret)
    {
          i2c_master_stop(base);                         /* 發送出錯,發送停止信號 */
        return ret;
    }
    
    /* 發送寄存器地址 */
    if(xfer->subaddressSize)
    {
        do
        {
            base->I2SR &= ~(1 << 1);            /* 清除標志位 */
            xfer->subaddressSize--;                /* 地址長度減一 */
            
            base->I2DR =  ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器寫入子地址
  
            while(!(base->I2SR & (1 << 1)));      /* 等待傳輸完成 */

            /* 檢查是否有錯誤發生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
                 i2c_master_stop(base);                 /* 發送停止信號 */
                 return ret;
            }  
        } while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));

        if(xfer->direction == kI2C_Read)         /* 讀取數據 */
        {
            base->I2SR &= ~(1 << 1);            /* 清除中斷掛起位 */
            i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read); /* 發送重復開始信號和從機地址 */
            while(!(base->I2SR & (1 << 1))){};/* 等待傳輸完成 */

            /* 檢查是否有錯誤發生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
                 ret = I2C_STATUS_ADDRNAK;
                i2c_master_stop(base);         /* 發送停止信號 */
                return ret;  
            }                         
        }
    }    

    /* 發送數據 */
    if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
    {
        i2c_master_write(base, xfer->data, xfer->dataSize);
    }

    /* 讀取數據 */
    if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
    {
           i2c_master_read(base, xfer->data, xfer->dataSize);
    }
    return 0;    
}
bsp_i2c.c

頭文件里定義的結構體要注意一下

struct i2c_transfer
{
    unsigned char slaveAddress;          /* 7位從機地址*/
    enum i2c_direction direction;        /* 傳輸方向*/
    unsigned int subaddress;             /* 寄存器地址*/
    unsigned char subaddressSize;        /* 寄存器地址長度*/
    unsigned char *volatile data;        /* 數據緩沖區*/
    volatile unsigned int dataSize;      /* 數據緩沖區長度*/
};

這個結構體是在后面需要進行通訊的時候聲明的,注意數據緩沖是一個指針,所以在讀取數據的時候是沒有返回值的。

上面的代碼完成后,可以通過make檢驗一下。

 

I2C通訊

上面講的是i2c的驅動,下面講下該怎么用這個驅動進行數據交互。和前面一樣,我們一個一個函數講

讀取一個字節數據

首先是讀取一個Byte多數據

 * @brief ap3216c讀取一個byte數據
 * 
 * @param addr 3216的地址
 * @param reg  讀取寄存器地址
 * @return unsigned char 讀取數據
 */
unsigned char ap3216c_readonebyte(unsigned char addr,unsigned char reg)
{   
    unsigned char val = 0;
    struct i2c_transfer masterXfer;
    masterXfer.slaveAddress = addr;
    masterXfer.direction = kI2C_Read;
    masterXfer.subaddress = reg;
    masterXfer.subaddressSize = 1;
    masterXfer.data = &val;
    masterXfer.dataSize = 1;
    i2c_master_transfer(I2C1, &masterXfer);
    return val;
}

這里偷了個懶,注意一下定義的變量val,原來是打算把最后的狀態字作為函數的返回值得,但是函數定義的時候沒有傳入通訊的結構體,就借用val把I2C讀取的數據給了val作為函數的返回值。函數體內部調用了I2C的接口,在初始化以后可以直接調用函數進行I2C數據交互

寫入一個字節數據

寫數據和讀基本上一樣,也是調用了I2C接口函數

/**
 * @brief ap3216寫1個寄存器
 * 
 * @param addr 3216從機地址
 * @param reg  寫寄存器地址
 * @param data 數據
 * @return unsigned char 狀態字:1異常,0正常
 */
unsigned char ap3216c_writeonebyte(unsigned char addr,
                                    unsigned char reg,
                                    unsigned char data)
{   
    unsigned char writedata = data;
    unsigned char status = 0;
    struct i2c_transfer masterXfer;
    masterXfer.slaveAddress = addr;
    masterXfer.direction = kI2C_Write;
    masterXfer.subaddress = reg;
    masterXfer.subaddressSize = 1;
    masterXfer.data = &writedata;
    masterXfer.dataSize=1;

    if (i2c_master_transfer(I2C1, &masterXfer)){
        status = 1;  //如果錯誤,返回1
    }
    return status;
}

寫寄存器返回值是狀態,通訊正常返回0,異常返回1

初始化

在定義好寄存器的讀寫函數以后,要進行初始化

/**
 * @brief AP3216C初始化
 * 
 */
void ap3216c_init(void)
{
    /*IO初始化*/
    IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL,1);
    IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL,0x70B0);

    IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA,1);
    IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA,0x70B0);

    /*I2C初始化*/
    i2c_init(I2C1);

    /*芯片初始化*/
    int value=0;
    ap3216c_writeonebyte(AP3216C_ADDR,AP3216C_SYSTEMCONG,0x4);      //軟復位
    delay_ms(50);
    ap3216c_writeonebyte(AP3216C_ADDR,AP3216C_SYSTEMCONG,0x3);      //使能ir、ps、als
    value = ap3216c_readonebyte(AP3216C_ADDR,AP3216C_SYSTEMCONG);
    printf("ap3216c systemconfig reg = %#x\r\n",value);
}

 

初始化過程是先對IO復用初始化、IO電氣初始化、I2C初始化、芯片初始化。

電氣初始化給的值是0x70B0,對應下面的性能

  • bit 16:0 HYS關閉
  • bit [15:14]: 1 默認47K上拉
  • bit [13]: 1 pull功能
  • bit [12]: 1 pull/keeper使能
  • bit [11]: 0 關閉開路輸出
  • bit [7:6]: 10 速度100Mhz
  • bit [5:3]: 110 驅動能力為R0/6
  • bit [0]: 1 高轉換率

芯片初始化是給0x00地址寫個0x4進行復位,再寫入0x3使能ir、ps和als功能

 芯片的I2C地址是廠商已經固定的0x1E,這個IC沒有whoami功能,我們在初始化完成后通過讀了一個寫的寄存器0x00,打印出來的值就是我們寫的值

 

 這樣就能說明我們的讀寫函數(特別是I2C的驅動)功能都是沒問題的。

數據讀取

數據讀取要根據芯片手冊給定的寄存器結構

 

 每個傳感器值對應2個寄存器,一共6個寄存器, 把這連續6個寄存器的值讀出來再進行處理

 1 // AP3216C數據讀取
 2 void ap3216c_readdata(unsigned short *ir,unsigned short*ps,unsigned short *als)
 3 {
 4     unsigned char buf[6];
 5 
 6     for(int i=0;i<6;i++){
 7         buf[i] = ap3216c_readonebyte(AP3216C_ADDR,AP3216C_IRDATALOW +i);
 8     }
 9     if(buf[0] &= 0x80){     //buf[0][bit7]==1時,IR、PS數據無效
10         *ir=0;
11         *ps=0;
12     }
13     else{
14         /*ir數據10bit buf[1]高8位,buf[0][bit1:0]為低2位*/
15         *ir = ((unsigned short)buf[1]<<2) | (buf[0] & 0x03);    //與0x03,只保留低bit[1:0]
16         /*ps數據10bit buf[5][bit5:0]高6位,buf[4][bit3:0]為低4位*/        
17         *ps = (((unsigned short)buf[5] & 0x3F) <<4) | (buf[4] & 0x0F); //與0x3F保留bit[5:0],與0x0F保留bit[4:0]
18     }
19     /*als數據12位,buf[3]高8位,buf[2]低8位*/
20     *als = (unsigned short)buf[3] << 8 |buf[2];
21 }

在上面的程序中國中,先聲明了一個數組,數組元素為6。

第6行開始,用了1個for循環,循環體執行力6次,因為IR的低位數據0x0A是6個寄存器的起始寄存器,我們就從這個寄存器開始讀取,每次循環地址+1,讀取下一個寄存器。for循環結束后,6個寄存器的值就拿到了。

從第9行開始是對數據進行換算。

buf[0]是0x0A的值,和0x80進行與運算是判定其bit[7]是否為1,1時IR、ALS數據無效,直接賦值為0,bit[7]不為1時,通過14、15行計算ALS、IR的值

15行計算IR,IR數據一共10位,buf[0]的bit[1:0]為IR數據低2位,buf[1]的bit[7:0]對應IR高8位,所以我們把buf[1]的值左移2位構成高位,buf[0]的值取bit[1:0],即與上0x03。兩個結果或一下就是IR的值

17行計算PS,PS數據值為10位(只有值,因為PS對應寄存器還有數據無效、物體接近/遠離判定。我們只留數據位)。buf[4]的bit[3:0]為低4位,buf[5]的bit[5:0]為高6位,計算方法就不說了,和上面一樣

20行計算ALS數據,ALS數據為16位,buf[2]為低8位,buf[3]為高8位,所以就是將buf[3]左移8位后和buf[2]進行或運算。

最終代碼

先是目錄結構

 

 頭文件

/**
 * @file bsp_ap3216c.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-01-22
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#ifndef __BSP_AP3216C_H
#define __BSP_AP3216C_H

#include "imx6ul.h"
#include "bsp_gpio.h"
#include "bsp_i2c.h"
#include "bsp_delay.h"

#define AP3216C_ADDR            0x1E
//寄存器地址
#define AP3216C_SYSTEMCONG      0x00
#define AP3216C_INTSTATUS       0x01
#define AP3216C_INTCLEAR        0x02
#define AP3216C_IRDATALOW       0x0A
#define AP3216C_IRDATAHIGH      0x0B
#define AP3216C_ALSDATALOW      0x0C
#define AP3216C_ALSDATAHIGH     0x0D
#define AP3216C_PSDATALOW       0x0E
#define AP3216C_PSDATAHIGH      0x0F

void ap3216c_init(void);
unsigned char ap3216c_readonebyte(unsigned char addr,unsigned char reg);
unsigned char ap3216c_writeonebyte(unsigned char addr,unsigned char reg,unsigned char data);
void ap3216c_readdata(unsigned short *ir,unsigned short*ps,unsigned short *als);
#endif
bsp_ap3216c.h

c文件

/**
 * @file bsp_ap3216c.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-01-22
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include "bsp_ap3216c.h"
#include "stdio.h"

/**
 * @brief AP3216C初始化
 * 
 */
void ap3216c_init(void)
{
    /*IO初始化*/
    IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL,1);
    IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL,0x70B0);

    IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA,1);
    IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA,0x70B0);

    /*I2C初始化*/
    i2c_init(I2C1);

    /*芯片初始化*/
    int value=0;
    ap3216c_writeonebyte(AP3216C_ADDR,AP3216C_SYSTEMCONG,0x4);      //軟復位
    delay_ms(50);
    ap3216c_writeonebyte(AP3216C_ADDR,AP3216C_SYSTEMCONG,0x3);      //使能ir、ps、als
    value = ap3216c_readonebyte(AP3216C_ADDR,AP3216C_SYSTEMCONG);
    printf("ap3216c systemconfig reg = %#x\r\n",value);
}

//AP3216C讀1byte數據 這里偷了個懶,理論上master_transfer返回值是錯誤類型,但是通過指針指向的val是真實讀取的值
/**
 * @brief ap3216c讀取一個byte數據
 * 
 * @param addr 3216的地址
 * @param reg  讀取寄存器地址
 * @return unsigned char 讀取數據
 */
unsigned char ap3216c_readonebyte(unsigned char addr,unsigned char reg)
{   
    unsigned char val = 0;
    struct i2c_transfer masterXfer;
    masterXfer.slaveAddress = addr;
    masterXfer.direction = kI2C_Read;
    masterXfer.subaddress = reg;
    masterXfer.subaddressSize = 1;
    masterXfer.data = &val;
    masterXfer.dataSize = 1;
    i2c_master_transfer(I2C1, &masterXfer);
    return val;
}

/**
 * @brief ap3216寫1個寄存器
 * 
 * @param addr 3216從機地址
 * @param reg  寫寄存器地址
 * @param data 數據
 * @return unsigned char 狀態字:1異常,0正常
 */
unsigned char ap3216c_writeonebyte(unsigned char addr,
                                    unsigned char reg,
                                    unsigned char data)
{   
    unsigned char writedata = data;
    unsigned char status = 0;
    struct i2c_transfer masterXfer;
    masterXfer.slaveAddress = addr;
    masterXfer.direction = kI2C_Write;
    masterXfer.subaddress = reg;
    masterXfer.subaddressSize = 1;
    masterXfer.data = &writedata;
    masterXfer.dataSize=1;

    if (i2c_master_transfer(I2C1, &masterXfer)){
        status = 1;  //如果錯誤,返回1
    }
    return status;
}


// AP3216C數據讀取
void ap3216c_readdata(unsigned short *ir,unsigned short*ps,unsigned short *als)
{
    unsigned char buf[6];

    for(int i=0;i<6;i++){
        buf[i] = ap3216c_readonebyte(AP3216C_ADDR,AP3216C_IRDATALOW +i);
    }
    if(buf[0] &= 0x80){     //buf[0][bit7]==1時,IR、PS數據無效
        *ir=0;
        *ps=0;
    }
    else{
        /*ir數據10bit buf[1]高8位,buf[0][bit1:0]為低2位*/
        *ir = ((unsigned short)buf[1]<<2) | (buf[0] & 0x03);    //與0x03,只保留低bit[1:0]
        /*ps數據10bit buf[5][bit5:0]高6位,buf[4][bit3:0]為低4位*/        
        *ps = (((unsigned short)buf[5] & 0x3F) <<4) | (buf[4] & 0x0F); //與0x3F保留bit[5:0],與0x0F保留bit[4:0]
    }
    /*als數據12位,buf[3]高8位,buf[2]低8位*/
    *als = (unsigned short)buf[3] << 8 |buf[2];
}
bsp_ap3216c.c

這里有個疑問:觀察一下可以發現其實我們寫的讀一個數據和寫一個數據那兩個函數是和AP3216C這個芯片沒什么關系的,任何I2C設備的讀寫都可以通過這兩個函數進行,我不明白為什么要把這兩個函數放在這個路徑下而不放在I2C的驅動里。不知道和后期驅動開發有沒有關系。

驅動調用

上面兩部分完成后就可以在main函數調用這個驅動了

int main(void)
{   
    static unsigned int led_state = ON;
    int_init();
    imx6u_clkinit();
    clk_enable();
    delay_init();
        
    uart_init();
    led_init();
    beep_init();
   
    ap3216c_init();
    unsigned short ir,ps,als;
    while(1){
    led_state = !led_state;
    led_switch(LED0,led_state);
    ap3216c_readdata(&ir,&ps,&als);
    printf("ir=%d,ps=%d,als=%d\r\n",ir,ps,als);
    delay_ms(500);
    }
    return 0;
}

注意在循環體內部我們延時了500ms,因為在前面一章講到過,在IR、PS和ALS都使能的情況下,每次讀數時間間隔是由要求的

 

 我們用的模式就是0x3,按要求應該不小於232ms。所以延時了500ms。

最終的效果就是上面的圖,ir是紅外,不知道怎么測試,ps為接近傳感器,越靠近芯片值越大,到1023就對應了2^10,數據就溢出了。als為環境光傳感器,我用手機閃關燈照了芯片,能看到最高值為30000多,數據位數為16位最高能到65535。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM