软件模拟I2C通信(STM32笔记)
简介
两根通信线:SDA和SCL。同步时序。有应答机制。可以跟多个设备之间进行通信(一主多从/多主多从)
SCL:同步时序,来中断的时候可以暂停
SDA:数据线,仅此一根,半双工
一主多从:从机只有被主机点名后才能控制IIC总线
多主多从:任何一个模块都有可能成为主机控制IIC
多主多从模式下在某时刻可能会有多个设备同时控制IIC总线,这时候IIC会进行总线仲裁
硬件电路

这个为一主多从的设备,空闲时刻下,主设备会控制SCL和SDA的控制权,只有当从机发数据和响应时,从机才会得到SDA的控制权,从机不能获取SCL的控制权,并且从机绝对不能主动地去获取SDA的控制权,只有主机在准备读取从机的数据或从机返回响应信号的时候从机才能得到SDA的控制。因为主机对SCL有绝对的控制权,所以主机的SCL输出模式设置为推挽输出,从机的SCL输入设置为上拉输入或浮空输入
SDA:禁止一切设备输出高电平,采用上拉电阻+开漏输出的模式(避免推挽输出模式下一高一低造成短路或者毁坏)
开漏输出模式内部没有上拉电阻,需要外接一个弱上拉电阻。
采用开漏+上拉电阻避免了输入输出模式的频繁切换
输出模式,输出低电平就拉低,输出高电平就不用拉低操作
输入模式:观察高低电平直接将对应的0/1输入
时序基本单元
起始和终止

起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
发送一个字节

SCL为低电平期间,主机将数据位依次放在SDA上,随后释放SCL。从机在SCL上升沿时读取数据位,SCL高电平期间SDA不能有任何数据变化
接收一个字节

SCL低电平期间,从机将数据位依次放在SDA上,随后主机释放SCL后,主机在SCL上升沿时读取数据
发送和接收应答

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
(主机接收到一个字节后,给从机发送一个应答位。告诉从机,我还想不想要。如果从机发送完一个字节后,接收到主机的应答,那么从机就继续发)
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
(主机发送字节,问有没有收到)
主机发送应答 = 我已收货 从机发送接收应答 = 买家已收货
时序
指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

指定设备7位,最后一位是读写位(0写,1读)
主机每次发送完一个字节后,都会得到一个接收从机的应答位,此时主机会释放SDA,使SDA变为高电平,从机此时拉低电平就是代表从机的接收应答位为0,不拉低就是代表从机的接收应答为1。
当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

设备内部寄存器是一个线性表。每次写入或者读出时,内部线性表的指针所指向的区域不会改变
起始信号,设备号+读写信号,随后进入接收应答。应答返回0后,数据传输方向改变,从从机到主机
指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

前面两个时序是指定设备的指定地址随后接收应答返回0,随后后面两个时序,另起一个时序进行指定的地址读
代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
/*******************************************************/
//封装对SCL的写操作
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
//封装对SDA的写操作
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
//封装对SDA的读操作
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue= GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
/*****************************************************/
void MyI2C_Init(void)
{
//1.把SCL和SDA都配置为开漏输出模式
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//2.把SCL和SDA都配置为高电平
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
//起始信号:先拉低SDA再拉低SCL
void MyI2C_Start(void)
{
//一定要先拉高SDA再拉高SCL,避免两者都是低电平的情况下先拉高SCL再拉高SDA会造成终止条件
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
//终止条件:先拉高SCL再拉高SDA
void MyI2C_Stop(void)
{
//一定要先拉低SCL再拉低SDA,避免两者都是高电平的情况下先拉低SDA再拉低SCL会造成起始条件
MyI2C_W_SCL(0);
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
//主机发送一个字节
void MyI2C_SengByte(uint8_t Byte)
{
uint8_t i;
for(i = 0; i < 8; i ++)
{
//此时SCL默认为低电平,放Byte的最高位
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1); //高电平,从机 会把数据读走
MyI2C_W_SCL(0);
}
//0x80 二进制为 1000 0000 ,第二次&0100 0000 就是0x80右移一位 以此类推
}
//主机接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00; //初始值,接收数据
//此时SCL是低电平,主机释放SDA,从机开始放数据
MyI2C_W_SDA(1); //主机释放对SDA的控制权
for(i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1); //SCL拉高,主机开始读数据
if(MyI2C_R_SDA() == 1) Byte |= (0x80 >> i);
MyI2C_W_SCL(0);
}
return Byte;
}
//发送应答:主机接收数据后主机发送一位应答位
void MyI2C_SendAck(uint8_t AckBit)
{
//默认SCL为低电平,主机把AcBit放在SDA上。SCL高时,从机取数据,再拉低SCL
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
//接收应答:主机发送一个数据后,从机返回一位数据(1应答,0未应答)
uint8_t MyI2C_ReceiveAck(void)
{
//默认低电平
uint8_t AckBit;
MyI2C_W_SDA(1); //主机释放SDA
MyI2C_W_SCL(1); //拉高SCL让从主机接收数据
AckBit = MyI2C_R_SDA(); //主机接收数据
MyI2C_W_SCL(0); //拉低SCL
return AckBit;
}
更多推荐



所有评论(0)