目前共有9篇帖子。 內容轉換:不轉換▼
 
點擊 回復
399 8
【程序】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必须使用开漏输出,否则会在发送从器件地址时卡住,导致总线出错。

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:399 回複數:8
評論數: ?
作者: 巨大八爪鱼
最後回復:巨大八爪鱼
最後回復時間:2017-1-13 17:39
 
©2010-2024 Arslanbar Ver2.0
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。