设置 | 登录 | 注册

作者共发了9篇帖子。

【程序】STM32軟體模擬I2C協議讀寫24C04存儲器

1楼 巨大八爪鱼 2017-1-13 14:15
#include <stm32f10x.h>

#define _BV(n) (1 << (n))

#define SCL_1 GPIOB->BSRR = _BV(6)
#define SCL_0 GPIOB->BRR = _BV(6)
#define SDA ((GPIOB->IDR & _BV(7)) != 0)
#define SDA_1 GPIOB->BSRR = _BV(7)
#define SDA_0 GPIOB->BRR = _BV(7)
#define SDA_R GPIOB->CRL = 0x83000000
#define SDA_W GPIOB->CRL = 0x33000000

uint8_t num = 0;
uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};

void delay_short(void)
{
    uint8_t i;
    for (i = 0; i < 6; i++);
}

void delay(void)
{
    uint32_t i;
    for (i = 0; i < 20000; i++);
}

void SerIn(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        GPIOC->BRR = _BV(15);
        if (data & 0x80)
            GPIOA->BSRR = _BV(0);
        else
            GPIOA->BRR = _BV(0);
        GPIOC->BSRR = _BV(15);
        data <<= 1;
    }
}

void ParOut(void)
{
    GPIOC->BRR = _BV(14);
    GPIOC->BSRR = _BV(14);
}

void seg_scan(void)
{
    SerIn(seg8[num / 100]);
    SerIn(_BV(2));
    ParOut();
    delay();
    
    SerIn(seg8[num % 100 / 10]);
    SerIn(_BV(1));
    ParOut();
    delay();
    
    SerIn(seg8[num % 10]);
    SerIn(_BV(0));
    ParOut();
    delay();
}

void I2CStart(void)
{
    SDA_1;
    SCL_1;
    delay_short();
    SDA_0;
    delay_short();
}

void I2CStop(void)
{
    SDA_0;
    SCL_1;
    delay_short();
    SDA_1;
    
    // 必須延長足夠的時間
    delay();
    delay();
}

uint8_t I2CAck(void)
{
    uint8_t ack;
    SDA_1;
    SCL_0;
    delay_short();
    SDA_R; // 這兩行不可交換!
    SCL_1; //
    delay_short();
    ack = SDA;
    SDA_W;
    SCL_0;
    return !ack;
}

void I2CSendAck(uint8_t ack)
{
    if (ack)
        SDA_0;
    else
        SDA_1;
    delay_short();
    SCL_1;
    delay_short();
    SCL_0;
}

uint8_t I2CWrite(uint8_t dat)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        SCL_0;
        if (dat & 0x80)
            SDA_1;
        else
            SDA_0;
        dat <<= 1;
        SCL_1;
        delay_short();
    }
    SCL_0;
    return I2CAck();
}

uint8_t I2CRead(void)
{
    uint8_t i;
    uint8_t dat = 0;
    SDA_1;
    SDA_R;
    for (i = 0; i < 8; i++)
    {
        SCL_1;
        dat <<= 1;
        if (SDA)
            dat |= 1;
        SCL_0;
        delay_short();
    }
    SDA_W;
    return dat;
}

void read(void)
{
    I2CStart();
    I2CWrite(0xa0);
    I2CWrite(0x00); // 從0x00處開始連續讀三個字節
    I2CStart();
    I2CWrite(0xa1);
    I2CRead();
    I2CSendAck(1);
    I2CRead();
    I2CSendAck(1);
    num = I2CRead();
    I2CSendAck(0);
    I2CStop();
}

void write(void)
{
    I2CStart();
    I2CWrite(0xa0);
    I2CWrite(0x01); // 從0x01處開始連續寫兩個字節
    I2CWrite(36);
    I2CWrite(51);
    I2CStop();
}

int main(void)
{
    RCC->APB2ENR = 0x1c; // 開啟PA、PB、PC時鐘
    GPIOA->CRL = 0x00000003; // PA0設為輸出
    GPIOB->CRL = 0x33000000; // PB6~7設為推挽50MHz輸出
    GPIOC->CRH = 0x33000000; // PC14~15設為輸出
    
    // 初始化總線
    SCL_1;
    SDA_1;
    
    write();
    read();
    
    while (1)
    {
        seg_scan(); // 數碼管顯示051
    }
}
2楼 巨大八爪鱼 2017-1-13 14:32
【測試程序:開始信號由硬體產生,其餘時序全部由軟體模擬】
int main(void)
{
    RCC->APB1ENR = _BV(21); // 開啟I2C1時鐘
    RCC->APB2ENR = 0x1c; // 開啟PA、PB、PC時鐘
    GPIOA->CRL = 0x00000003; // PA0設為輸出
    GPIOB->CRL = 0xbb000000; // PB6~7設為復用功能推輓輸出
    GPIOC->CRH = 0x33000000; // PC14~15設為輸出
   
    I2C1->CR2 = 36;
    I2C1->CCR = 90;
    I2C1->TRISE = 37;
    I2C1->OAR1 = 0x40bc;
    I2C1->CR1 = 0x01; // PE=1(啟動總線)
   
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
   
    GPIOB->CRL = 0x33000000; // PB6~7設為推挽50MHz輸出
    num = I2CWrite(0xa0);
    I2CWrite(0x02); // 讀取0x02處的內容
    I2CStart();
    I2CWrite(0xa1);
    num = I2CRead();
    I2CSendAck(0);
    I2CStop();
   
    while (1)
    {
        seg_scan(); // 數碼管顯示051
    }
}
3楼 巨大八爪鱼 2017-1-13 14:38

【兩個Start信號都由硬體產生,其餘時序由軟體模擬】
int main(void)
{
    RCC->APB1ENR = _BV(21); // 開啟I2C1時鐘
    RCC->APB2ENR = 0x1c; // 開啟PA、PB、PC時鐘
    GPIOA->CRL = 0x00000003; // PA0設為輸出
    GPIOB->CRL = 0xbb000000; // PB6~7設為復用功能推輓輸出
    GPIOC->CRH = 0x33000000; // PC14~15設為輸出
   
    I2C1->CR2 = 36;
    I2C1->CCR = 90;
    I2C1->TRISE = 37;
    I2C1->OAR1 = 0x40bc;
    I2C1->CR1 = 0x01; // PE=1(啟動總線)
   
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
   
    GPIOB->CRL = 0x33000000; // PB6~7設為推挽50MHz輸出
    num = I2CWrite(0xa0);
    I2CWrite(0x02); // 讀取0x02處的內容
   
   
    GPIOB->CRL = 0xbb000000;
    I2C1->CR1 |= _BV(15); // 復位I2C硬體
    I2C1->CR1 &= ~_BV(15);
    I2C1->CR1 = 0x01; // PE=1(啟動總線)
    delay();
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    GPIOB->CRL = 0x33000000;
   
    I2CWrite(0xa1);
    num = I2CRead();
    I2CSendAck(0);
    I2CStop();
   
    while (1)
    {
        seg_scan(); // 數碼管顯示051
    }
}
4楼 巨大八爪鱼 2017-1-13 14:59
【由硬體產生開始信號及發送從器件地址,其餘仍由軟體完成】
void disp_status(void)
{
    uint8_t i;
    uint32_t d;
    while (1)
    {
        d = (I2C1->SR1 << 16) | I2C1->SR2;
        for (i = 0; i < 8; i++)
        {
            SerIn(seg8[d & 0x0f]);
            SerIn(_BV(i));
            ParOut();
            delay();
            d >>= 4;
        }
    }
}

int main(void)
{
    RCC->APB1ENR = _BV(21); // 開啟I2C1時鐘
    RCC->APB2ENR = 0x1c; // 開啟PA、PB、PC時鐘
    GPIOA->CRL = 0x00000003; // PA0設為輸出
    GPIOB->CRL = 0xbb000000; // PB6~7設為復用功能推輓輸出
    GPIOC->CRH = 0x33000000; // PC14~15設為輸出
   
    I2C1->CR2 = 36;
    I2C1->CCR = 90;
    I2C1->TRISE = 37;
    I2C1->OAR1 = 0x40bc;
    I2C1->CR1 = 0x01; // PE=1(啟動總線)
   
    /* 發送從器件地址0xa0 */
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    num = I2C1->SR1;
    I2C1->DR = 0xa0;
    while (I2C1->SR1 == num); // 等待SR1寄存器內容發生變化
    // disp_status(); // 用數碼管顯示此時SR寄存器的狀態
    // 檢查結果為: SR1=0x0400=_BV(10), SR2=0x0003=_BV(1)|_BV(0)
    // 也就是AF=1(未接收到ACK信號), BUSY=MSL=1
    delay(); // 跳過無法由硬體識別的ACK信號
   
    /* 由軟體時序指定要讀取的存儲單元地址 */
    GPIOB->CRL = 0x33000000;
    I2CWrite(0x02);
   
    /* 由硬體再次產生起始信號*/
    GPIOB->CRL = 0xbb000000;
    I2C1->CR1 |= _BV(15); // 復位I2C硬體
    I2C1->CR1 &= ~_BV(15);
    I2C1->CR1 = 0x01; // PE=1(啟動總線)
    delay();
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    GPIOB->CRL = 0x33000000;
   
    /* 由軟體完成剩下的時序 */
    I2CWrite(0xa1);
    num = I2CRead();
    I2CSendAck(0);
    I2CStop();
   
    while (1)
    {
        seg_scan(); // 數碼管顯示051
    }
}
5楼 巨大八爪鱼 2017-1-13 15:04
這次還要好一點,BUSY=MSL=1,畢竟沒有從主模式變回從模式。雖然硬體出錯了,但是軟體還是完成了剩下的時序,成功的顯示了51這個數字。
今天上午的情況更糟糕,發完從器件地址0xa0後,SR1=0x300, SR2=0,也就是ARLO=1(仲裁失敗),BERR=1(總線錯誤:開始/結束信號放置的位置有誤),MSL=0(變回了Slave模式),BUSY=SB=0
6楼 巨大八爪鱼 2017-1-13 17:00
現在我找到原因了。因為開發板上的I2C埠沒有接上拉電阻。因此,只需要另找兩個沒有使用的I/O口,設為帶上拉輸入的模式,然後用杜邦線接到I2C埠上,最後在程序中把推輓輸出改為開漏輸出,問題就解決了。
【完全用硬體I2C操作24C04的程序】
#include <stm32f10x.h>

#define _BV(n) (1 << (n))

uint8_t num = 0;
uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};

void delay(void)
{
    uint32_t i;
    for (i = 0; i < 20000; i++);
}

void SerIn(uint8_t data)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        GPIOC->BRR = _BV(15);
        if (data & 0x80)
            GPIOA->BSRR = _BV(0);
        else
            GPIOA->BRR = _BV(0);
        GPIOC->BSRR = _BV(15);
        data <<= 1;
    }
}

void ParOut(void)
{
    GPIOC->BRR = _BV(14);
    GPIOC->BSRR = _BV(14);
}

void seg_scan(void)
{
    SerIn(seg8[num / 100]);
    SerIn(_BV(2));
    ParOut();
    delay();
   
    SerIn(seg8[num % 100 / 10]);
    SerIn(_BV(1));
    ParOut();
    delay();
   
    SerIn(seg8[num % 10]);
    SerIn(_BV(0));
    ParOut();
    delay();
}

int main(void)
{
    uint16_t temp;
    RCC->APB1ENR = _BV(21); // 開啟I2C1時鐘
    RCC->APB2ENR = 0x1c; // 開啟PA、PB、PC時鐘
    GPIOA->CRL = 0x00000003; // PA0設為輸出
    GPIOB->CRL = 0xff000000; // PB6~7設為復用功能開漏輸出
    GPIOC->CRH = 0x33000000; // PC14~15設為輸出
   
    // 開發板上的I2C接口沒有接上拉電阻, 無法使用開漏輸出
    GPIOA->CRH = 0x00000088; // PA8~9設為輸入
    GPIOA->BSRR = _BV(8) | _BV(9); // PA8~9設為帶上拉電阻的輸入
    // 這樣PA8~9就輸出了一對上拉電阻, 把它們用杜邦線分別接到PB6~7上就可以了
   
    I2C1->CR2 = 36; // FREQ=100100(36MHz)
    I2C1->CCR = 90; // CCR=FREQ/2f, f=200kHz
    I2C1->CR1 = 0x01; // PE=1(啟動總線)
   
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    I2C1->DR = 0xa0; // 發送從器件地址
    while ((I2C1->SR1 & _BV(1)) == 0); // 等待ADDR=1
    temp = I2C1->SR2; // 讀SR2, 清ADDR
    I2C1->DR = 0x02; // 指定要讀取的存儲單元地址
    while ((I2C1->SR1 & _BV(7)) == 0); // 等待TxE=1
   
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    I2C1->DR = 0xa1; // 發送從器件地址
    while ((I2C1->SR1 & _BV(1)) == 0); // 等待ADDR=1
    temp = I2C1->SR2; // 讀SR2, 清ADDR
    while ((I2C1->SR1 & _BV(6)) == 0); // 等待RxNE=1
    num = temp; // **用於消除temp變量未使用的警告
    num = I2C1->DR; // 讀出收到的數據
    I2C1->CR1 |= _BV(9); // 結束信號
    while (I2C1->CR1 & _BV(9)); // 等待STOP=0
   
    while (1)
    {
        seg_scan(); // 數碼管顯示051
    }
}
7楼 巨大八爪鱼 2017-1-13 17:35
【硬體方式接收多個字節】
void read(void)
{
    uint16_t temp;
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    I2C1->DR = 0xa0; // 發送器件地址
    while ((I2C1->SR1 & _BV(1)) == 0); // 等待ADDR=1
    temp = I2C1->SR2;
    num = temp; // **用於消除temp變量未使用的警告
   
    I2C1->DR = 0x00; // 發送存儲單元地址
    while ((I2C1->SR1 & _BV(7)) == 0); // 等待TxE=1
    I2C1->CR1 |= _BV(8); // 起始信號
    while ((I2C1->SR1 & _BV(0)) == 0); // 等待SB=1
    I2C1->DR = 0xa1; // 發送器件地址
    while ((I2C1->SR1 & _BV(1)) == 0); // 等待ADDR=1
    temp = I2C1->SR2; // 讀SR2, 清ADDR
   
    I2C1->CR1 |= _BV(10); // 接收第一個字節前就打開ACK
    while ((I2C1->SR1 & _BV(6)) == 0); // 等待RxNE=1
    num = I2C1->DR;
    while ((I2C1->SR1 & _BV(6)) == 0);
    num = I2C1->DR;
    I2C1->CR1 &= ~_BV(10); // 在接收最後一個字節前關閉ACK, 不再接收更多的數據
    while ((I2C1->SR1 & _BV(6)) == 0);
    num = I2C1->DR;
   
    I2C1->CR1 |= _BV(9); // 終止信號
    while (I2C1->CR1 & _BV(9));
}
8楼 巨大八爪鱼 2017-1-13 17:37
更多關於24C0x存儲晶片的操作方法請參閱:
https://zh.arslanbar.net/post.php?t=24268
9楼 巨大八爪鱼 2017-1-13 17:39
如果I2C埠上沒有接上拉電阻,則只能用軟體模擬總線時序的方法讀寫存儲器,因為此時開漏輸出功能不可用。
而硬體I2C必須使用開漏輸出,否則會在發送從器件地址時卡住,導致總線出錯。

内容转换:

回复帖子
内容:
用户名: 您目前是匿名发表。
验证码:
看不清?换一张
©2010-2025 Purasbar Ver3.0 [手机版] [桌面版]
除非另有声明,本站采用知识共享署名-相同方式共享 3.0 Unported许可协议进行许可。