OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 9614|回复: 1

STM32F103C8T6的I2C的正确用法(自以为)

[复制链接]

2

主题

3

帖子

0

精华

初级会员

Rank: 2

积分
64
金钱
64
注册时间
2018-2-8
在线时间
16 小时
发表于 2018-2-22 17:57:41 | 显示全部楼层 |阅读模式
本帖最后由 mn4888 于 2018-2-27 15:24 编辑

先上重要结论,f10x标准库3.5.0里I2C_CheckEvent函数参数I2C_EVENT_MASTER_BYTE_RECEIVED,即判断主机收到一个字节的事件,标准库中是判断把SR2左移16位与SR1相加后看是否与I2C_EVENT_MASTER_BYTE_RECEIVED相等,这个事件官方给的值默认是0x00030040,但我在调试中发现这个值应该是0x00030044,多出来的那个4是SR1的第二位置位,寄存器手册上说当接收到一个字节并且没有被读取的时候这一位会被置1,清除条件是关闭I2C器件或发起一个起始或终止条件或 先读SR1再读数据寄存器 ,这就很尴尬,你先判断事件再读数据卡判断,先读数据再判断事件一不小心就把I2C设备地址给当成数据读下来了。因此我把这个值直接改为0x00030044,先判断事件再读数据寄存器,下面上代码:
void OpenI2C()//启动I2C1
{
//先开启时钟什么的
        I2C_InitTypeDef I;
        L.GPIO_Pin = GPIO_Pin_6;
        L.GPIO_Mode = GPIO_Mode_Out_PP;
        L.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &L);
        GPIO_WriteBit(GPIOB, GPIO_Pin_6, Bit_SET);
        L.GPIO_Mode = GPIO_Mode_AF_OD;
        GPIO_Init(GPIOB, &L);
        L.GPIO_Pin = GPIO_Pin_7;
        L.GPIO_Mode = GPIO_Mode_Out_PP;
        L.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &L);
        GPIO_WriteBit(GPIOB, GPIO_Pin_7, Bit_SET);
        L.GPIO_Mode = GPIO_Mode_AF_OD;
        GPIO_Init(GPIOB, &L);//以上GPIO配置,先把GPIO设为推挽输出并拉倒高电平再转设为复用开漏输出,这样可以有效解决I2C设备死锁的问题
        
        I.I2C_Mode = I2C_Mode_I2C;
        I.I2C_DutyCycle = I2C_DutyCycle_16_9;
        I.I2C_Ack = I2C_Ack_Disable;
        I.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
        I.I2C_ClockSpeed = 400000;
        I.I2C_OwnAddress1 = 0x6A;//这个我随便填的
        I2C_StretchClockCmd(I2C1, DISABLE);
        I2C_SoftwareResetCmd(I2C1, ENABLE);
        I2C_SoftwareResetCmd(I2C1, DISABLE);//I2C软复位,配合GPIO强制拉高哪怕读取着数据把设备拔了再插上也能控制住总线,复位好像不会自己清零,要先ENABLE再DISABLE
        I2C_Init(I2C1, &I);
        I2C_Cmd(I2C1, DISABLE);
}

void I2CRead(u8 DeviceAddress, u8 MemoryAddress, u8 *Data)//读取数据
{
        int Step = 0;//把I2C读取分为7步
        int Timer = _IICDELAY_;这个值是1500,用作超时检测
        Timer = _IICDELAY_;
        //OpenI2C();//如果中断很多,I2C频率很高就把注释符号去掉,这样可以大大降低误码率
        while (--Timer&&(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) == ENABLE))//等待总线空闲
                ;
        if (!Timer&&Timer > _IICDELAY_)
        {
                USART_SendString(USART1, "A");
        }
        for (Step = 1; Step <= 7; Step++)//7步读取数据
        {
                I2C_Cmd(I2C1, ENABLE);//启动I2C
                switch ((Timer) ? Step :0)//判断是否发生了超时
                {
                case 0:
                        {//超时处理部分
                                //USART_SendString(USART1, "E");
                                OpenI2C();
                                Step = 7;
                                break;
                        }
                case 1:
                        {
                                if (!Timer)//超时判断
                                {
                                        break;
                                }
                                I2C_GenerateSTART(I2C1, ENABLE);//发送起始位
                                Timer = _IICDELAY_;
                                while (--Timer && !(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == SUCCESS))//等待事件发生
                                        ;
                                break;
                        }
                case 2:
                        {
                                if (!Timer)//超时判断
                                {
                                        break;
                                }
                                I2C_Send7bitAddress(I2C1, DeviceAddress, I2C_Direction_Transmitter);//发送设备硬件地址
                                Timer = _IICDELAY_;
                                while (--Timer && !(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == SUCCESS))//等待事件发生
                                        ;
                                break;
                        }
                case 3:
                        {
                                if (!Timer)//超时判断
                                {
                                        break;
                                }
                                I2C_SendData(I2C1, MemoryAddress);//发送设备地址
                                Timer = _IICDELAY_;
                                while (--Timer && !(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == SUCCESS))//等待事件发生
                                        ;
                                break;
                        }
                case 4:
                        {
                                if (!Timer)//超时判断
                                {
                                        break;
                                }
                                I2C_GenerateSTART(I2C1, ENABLE);//重发设备地址
                                Timer = _IICDELAY_;
                                while (--Timer && !(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) == SUCCESS))//等待事件发生
                                        ;
                                break;
                        }
                case 5:
                        {
                                if (!Timer)//超时判断
                                {
                                        break;
                                }
                                //I2C_AcknowledgeConfig(I2C1, ENABLE);//这个函数用于设置是否在接收到一个字节后返回一个ACK位,ENABLE返回ACK,不设置或DISABLE返回NAK,在I2C协议中收到一个字节后返回ACK,从设备就将继续把下一个数据送到总线上,用于页读     //取,没仔细用过
                                I2C_Send7bitAddress(I2C1, DeviceAddress, I2C_Direction_Receiver);//发送设备硬件地址
                                Timer = _IICDELAY_;
                                while (--Timer && !(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == SUCCESS))//等待事件发生
                                        ;
                                break;
                        }
                case 6:
                        {
                                if (!Timer)//超时判断
                                {
                                        break;
                                }
                                Timer = _IICDELAY_;
                                while (--Timer && !(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) == SUCCESS))//等待收到一个字节
                                        ;
                                I2C_GenerateSTOP(I2C1, ENABLE);//停止总线
                                break;
                        }
                case 7:
                        {
                                *Data = I2C_ReceiveData(I2C1);//接收数据
                                break;
                        }
                }
                I2C_Cmd(I2C1, DISABLE);//关闭I2C
        }
}
实验结果:
如图:
捕获.PNG
用ADC悬空引脚产生随机数然后用随机数填充SysTick产生中断,中断中用随机数Delay一段时间,图中逻辑分析仪channel0低电平代表MCU正在处理中断,高电平时MCU从I2C上读取AT24C02的数据,我开了仿真后去睡了一觉,醒了的时候图中r已经累加到0x01028604,即进行了16942596次I2C读取,没有出现错误。

捕获1.PNG
这是AT24C02预填充的内容。。嗯。。

现在还有个问题,那个I2C时钟不知道为什么总是自己多跑8位,导致逻辑分析仪上多抓到一个0xFF,实际上这个0xFF好像没被读入数据寄存器,在读取的代码中我先发送停止位再读数据,如果先读数据再发送停止位那个时钟信号就会多跑16位。。。但是现在看来似乎除了时间效率没别的影响,希望有大佬能给我解解惑
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

9

帖子

0

精华

初级会员

Rank: 2

积分
85
金钱
85
注册时间
2019-8-5
在线时间
21 小时
发表于 2019-12-14 14:35:05 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-20 17:45

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表