一.文章开头说明
此软件i2c源码是在stm32f103芯片hal库的基础上实现的,其它芯片及库需要修改相应的底层驱动,如gpio口初始化,头文件相应的宏定义修改。
二.iic协议主要内容介绍
1.基础知识
I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要USART、 CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯。
一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。主机及多个通讯从机。
特点:总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多I2C设备尚不支持高速模式。
2.工作时序介绍
1. 数据有效性
IIC 的数据读取动作都在 SCL为高 时产生,SCL为低时是数据改变的时期。所以,传输数据的过程中,当SCL为高时,数据应当保持稳定,避免数据的采集出错。
2. 开始和结束信号
开始信号(START/S): SCL为高时,SDA从高到低的跳变产生开始信号
结束信号(STOP/P) : SCL为高时,SDA从低到高的跳变产生结束信号
3. 重复开始信号
重复开始信号(ReSTART/Sr): 在结束时不给出STOP信号,而以一个时钟周期内再次给出开始信号作为替代。
4. 主机写-从机收
主机对从机发送数据时,主机对从机发送一个开始字节,然后即可一直发送数据。
5. 主机读-从机发
主机对向从机读取数据时,方式同发送数据有所不同,要多一次通信过程。
主机需要先向从机发送一次信号,告诉从机”我要读取数据“,然后重开一次通信,等待从机主动返回数据。
三.模拟iic的代码
源文件:
#include "myi2c.h"
/*说明:这个是基于stm32f103芯片的软件模拟iic源码,使用时需要注意先更改相应的引脚;
* 头文件也需要把gpio输入和输出相应的代码更改;
* 我的设置为:PB10->SCL PB11->SDA
*/
/**********************************************************************************/
/* 初始化部分 */
/* 需要更改为对应的引脚 */
/**********************************************************************************/
/*
*funtion:初始化i2c的IO口
*/
void I2C_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10|GPIO_PIN_11, GPIO_PIN_SET);
/*Configure GPIO pins : PB10 PB11 */
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/*设置相应数据的引脚为输出*/
void SDA_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure;
// PB11-SDA
GPIO_InitStructure.Pin = GPIO_PIN_11;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/*设置相应数据的引脚为输入*/
void SDA_IN()
{
GPIO_InitTypeDef GPIO_InitStructure;
// PB11-SDA
GPIO_InitStructure.Pin = GPIO_PIN_11;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/*us级别延迟函数*/
void delay_us(uint32_t us)//主频72M
{
uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
while (delay--)
{
;
}
}
/**********************************************************************************/
/* END */
/**********************************************************************************/
/**********************************************************************************/
/* iic协议部分 */
/**********************************************************************************/
/*iic起始信号*/
void IIC_Star(void)
{
SDA_OUT(); //sda线输出
IIC_SDA_HIGH;
IIC_SCL_HIGH;
delay_us(2);
IIC_SDA_LOW; //START:when CLK is high,DATA change form high to low
delay_us(2);
IIC_SCL_LOW; //钳住I2C总线,准备发送或接收数据
}
/*iic停止信号*/
void IIC_Stop(void)
{
SDA_OUT();
IIC_SDA_LOW; //STOP:when CLK is high DATA change form low to high
IIC_SCL_HIGH;
delay_us(2);
IIC_SDA_HIGH;
delay_us(2);
}
/*iic产生应答信号*/
//1:nack 0:ack
void IIC_ACK(uint8_t ack)
{
SDA_OUT();
IIC_SCL_LOW; //时钟线拉低,以便写数据
if(ack) IIC_SDA_HIGH; //写应答信号
else IIC_SDA_LOW;
IIC_SCL_HIGH; //拉高时钟线
delay_us(2); //延时
IIC_SCL_LOW; //拉低时钟线
delay_us(2); //延时
}
/*iic等待应答信号*/
uint8_t IIC_Wait_Ack(void)
{
uint16_t wait_time=0;
SDA_IN();
IIC_SCL_HIGH;delay_us(2);
while(IIC_SDA_READ)
{
wait_time++;
if(wait_time>=250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL_LOW;
return 0;
}
/*iic写一个字节数据*/
void IIC_Write_Byte(uint8_t Data)
{
uint8_t t=0,bit;
SDA_OUT();
IIC_SCL_LOW;
for(t=0;t<8;t++)
{
bit = Data&0x80;
if(bit) IIC_SDA_HIGH;
else IIC_SDA_LOW;
Data<<=1;
IIC_SCL_HIGH; //拉高时钟线
delay_us(2); //延时
IIC_SCL_LOW; //拉低时钟线
delay_us(2); //延时
}
IIC_Wait_Ack();
}
/*iic读一个字节数据*/
uint8_t IIC_Read_Byte(void)
{
uint8_t i;
uint8_t dat = 0;
SDA_IN(); //SDA设置为输入
IIC_SDA_HIGH; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
IIC_SCL_HIGH; //拉高时钟线
delay_us(2); //延时
if(IIC_SDA_READ) dat+=1;
IIC_SCL_LOW; //拉低时钟线
delay_us(2); //延时
}
return dat;
}
/**********************************************************************************/
/* END */
/**********************************************************************************/
头文件:
#ifndef _MYI2C_H_
#define _MYI2C_H_
/*下面为stmf103hal库相关的,其它库或者别的芯片根据需要更改
* 主要更改输入输出和读取数据的配置
*/
#include "gpio.h"
/*GPIO初始化*/
void I2C_GPIO_Init(void);
/*GPIO输出*/
#define IIC_SDA_HIGH GPIOB->ODR |= (1 << 11)
#define IIC_SDA_LOW GPIOB->ODR &= ~(1 << 11)
#define IIC_SCL_HIGH GPIOB->ODR |= (1 << 10)
#define IIC_SCL_LOW GPIOB->ODR &= ~(1 << 10)
/*GPIO读取数据*/
#define IIC_SDA_READ GPIOB->IDR &= (1<<11)
void IIC_Star(void);
void IIC_Stop(void);
void IIC_ACK(uint8_t ack);
uint8_t IIC_Wait_Ack(void);
void IIC_Write_Byte(uint8_t Data);
uint8_t IIC_Read_Byte(void);
#endif