使用STM32 模拟IIC进行通信,相较于自带硬件接口稳定。
寻址:想要知道总线上挂载的从机数量及其地址,需要在总线写地址,如果相应位有应答,器件地址
(地址位为7位)左移一位低位补0进行显示,无应答位使用“–”替代。
实际运行效果: (显示效果类似于树莓派,树莓派显示原7位地址,高位补0)
按页写完读(主函数示例):从0x00地址开始,依次写入17个字节(数组已定义赋值,非赋值数组最好放在函数外做为全局变量,这样默认初始值就为0,而在函数内则为随机值),16个字节为1页,写完1页数据保存进非易失区后,继续写入剩余字节。从0x00地址开始,依次读出17个字节。
iic.c
IIC为半双工通信,数据线SDA既用作于发又用作于收但是不能同时进行,而STM32不同于51类单片机的是,IO脚需要配置输入输出模式,所以在用IO模拟IIC时STM32比51多两个函数:
IIC_SDA_IN();IIC_SDA_OUT();
因为有些例程中通过寄存器配置的方式转换IO输入输出,不是太理解,所以改用库函数进行配置。
#include "firmware.h"//里面包含iic.h
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE );//时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;//SDA SCL WP
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ; //
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure); //
GPIO_SetBits(GPIOF,GPIO_Pin_0 | GPIO_Pin_1); //
}
void IIC_SDA_IN(void)//SDA用作输入:从机返回数据,从机应答等
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN ; //
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure);
}
void IIC_SDA_OUT(void)//SDA用作输出:写地址,写数据等
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ; //
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure);
}
void IIC_Start(void)//开始信号
{
IIC_SDA_OUT(); //SDA定义为输出
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 0;
delay_us(4);
IIC_SCL = 0;
}
void IIC_Stop(void)//停止信号
{
IIC_SDA_OUT(); //SDA定义为输出
IIC_SCL = 0;
IIC_SDA = 0;
delay_us(4);
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 1; //发送 I2C 总线结束信号
delay_us(4);
}
u8 IIC_Wait_Ack(void)//等待应答信号:0-应答;1-非应答
{
u8 ack = 0;
IIC_SDA = 1;
IIC_SDA_IN(); //SDA定义为输入
delay_us(4);
IIC_SCL = 1;
delay_us(1);
ack = READ_SDA;
delay_us(4);
IIC_SCL = 0;
return (ack);
}
void IIC_Ack(void)//产生 ACK 应答
{
IIC_SCL = 0;
IIC_SDA_OUT();
IIC_SDA = 0;
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
}
void IIC_NAck(void)//产生 NACK 非应答
{
IIC_SCL = 0;
IIC_SDA_OUT();
IIC_SDA = 1;
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
}
void IIC_Write_Byte(u8 txd)//IIC 发送一个字节
{
u8 t;
IIC_SDA_OUT();
IIC_SCL = 0;//拉低时钟开始数据传输
for(t = 0;t < 8;t++)
{
IIC_SDA = (txd & 0x80)>>7; //数据高位在前,低位在后,所以先发送高位
txd <<= 1;
delay_us(4);
IIC_SCL = 1;
delay_us(4);
IIC_SCL = 0;
//delay_us(2);
}
}
u8 IIC_Read_Byte(void)//读一个字节
{
u8 i,receive = 0;
IIC_SDA_IN(); //SDA定义为输入
for(i = 0;i < 8;i++ )
{
delay_us(4);
IIC_SCL = 1;
receive <<= 1;
if(READ_SDA == 1)
{
receive ++;
}
delay_us(4);
IIC_SCL = 0;
}
return receive;
}
u8 Write_Device_Addr(u8 addr)//只写地址
{
u8 Read_ACK = 0;
IIC_Start();
IIC_Write_Byte(addr);
Read_ACK = IIC_Wait_Ack();
IIC_Stop();
return(Read_ACK);
}
void Search_Device_Addr(void)//在总线上搜寻挂载的器件地址
{
u8 result = 0;
u8 j = 0;
for(j = 0;j < 128; j++)
{
if((j % 16) == 0)
{
printf("\r\n");
}
result = Write_Device_Addr(j << 1);
if(result == 0)
{
printf(" %X ",j << 1);//%X 十六进制输出,大写;%x 小写
}
else
{
printf(" -- ");
}
}
}
u8 Device_Read_One_Byte(u8 IC_Addr,u8 Addr)//读指定器件的指定位置中的一个字节
{
u8 dat;
IIC_Start();
IIC_Write_Byte(IC_Addr);//写地址,7位地址左移,低位补0
IIC_Wait_Ack();//等待应答
IIC_Write_Byte(Addr);//写位置
IIC_Wait_Ack();
IIC_Start();
IIC_Write_Byte((IC_Addr) | 0x01);//读写位改为读
IIC_Wait_Ack();
dat = IIC_Read_Byte();
IIC_NAck();//读一个字节结束
IIC_Stop();
return dat;
}
void Device_Write_One_Byte(u8 IC_Addr,u8 Addr,u8 data)//写指定器件的指定位置中的一个字节
{
IIC_Start();
IIC_Write_Byte(IC_Addr);//写地址,7位地址左移,低位补0
IIC_Wait_Ack();//等待应答
IIC_Write_Byte(Addr);//写位置
IIC_Wait_Ack();
IIC_Write_Byte(data);//写数据
IIC_Wait_Ack();
IIC_Stop();//发送完结束信号后,器件才会把数据进行擦写操作,搬运到非易失区,这段时间器件不再响应主机操作
}
void Device_Read_Bytes(u8 IC_Addr,u8 Addr,u8 *buf,u8 len)//连续读指定器件的指定位置中的多个字节
{
IIC_Start();
IIC_Write_Byte(IC_Addr);//写地址,7位地址左移,低位补0
IIC_Wait_Ack();//等待应答
IIC_Write_Byte(Addr);//写位置
IIC_Wait_Ack();
IIC_Start();
IIC_Write_Byte((IC_Addr) | 0x01);//读写位改为读
IIC_Wait_Ack();
while(len > 1)
{
*buf++ = IIC_Read_Byte();
IIC_Ack();
len--;
}
*buf = IIC_Read_Byte();//循环体结束指针已经指向最后一个字节存放位置
IIC_NAck();//读一个字节结束
IIC_Stop();
}
void Device_Write_Bytes(u8 IC_Addr,u8 Addr,u8 *buf,u8 len)//连续写指定器件的指定位置中的多个字节
{
while(len > 0)
{
IIC_Start();
IIC_Write_Byte(IC_Addr);//写地址,7位地址左移,低位补0
IIC_Wait_Ack();//等待应答
IIC_Write_Byte(Addr++);//写位置
IIC_Wait_Ack();
IIC_Write_Byte(*buf++);//写数据
IIC_Wait_Ack();
IIC_Stop();//发送完结束信号后,器件才会把数据进行擦写操作,搬运到非易失区,这段时间器件不再响应主机操作
delay_ms(10);//eeprom连续写时必须加,否则下个字节写入失败
len--;
}
}
void Device_Page_Write(u8 IC_Addr,u8 Addr,u8 *buf,u8 len)//按页写指定器件的指定位置
{
u8 i = 0;
while(len > 0)
{
IIC_Start();
IIC_Write_Byte(IC_Addr);//写地址,7位地址左移,低位补0
IIC_Wait_Ack();//等待应答
IIC_Write_Byte(Addr);//写位置
IIC_Wait_Ack();
while(len > 0)
{
IIC_Write_Byte(*buf++);//写数据
IIC_Wait_Ack();
len--;
Addr++;//防止写入字节数比一页多,自动累加至下一页起始地址
i++;//判断写入字节数
if(i == Page_Size)
{
break;
}
}
IIC_Stop();//发送完结束信号后,器件才会把数据进行擦写操作,搬运到非易失区,这段时间器件不再响应主机操作
delay_ms(10);//eeprom连续写时必须加,否则下个字节写入失败
i = 0;
}
}
iic.h
#ifndef __IIC_H
#define __IIC_H
#include "firmware.h"
#define IIC_SCL PFout(1) //模拟输出时钟脉冲
#define IIC_SDA PFout(0) //数据线定义输出
#define IIC_WP PFout(2) //数据写保护引脚
#define READ_SDA PFin(0) //数据线定义输入
#define Page_Size 16 //一页存储的字节数,具体看相应器件的datasheet
void IIC_SDA_IN(void); //IO定义输入输出配置
void IIC_SDA_OUT(void);
void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
u8 IIC_Wait_Ack(void);
void IIC_Ack(void);
void IIC_NAck(void);
void IIC_Write_Byte(u8 txd);
u8 IIC_Read_Byte(void);
u8 Write_Device_Addr(u8 addr);
void Search_Device_Addr(void);
u8 Device_Read_One_Byte(u8 IC_Addr,u8 Addr);
void Device_Write_One_Byte(u8 IC_Addr,u8 Addr,u8 data);
void Device_Read_Bytes(u8 IC_Addr,u8 Addr,u8 *buf,u8 len);
void Device_Write_Bytes(u8 IC_Addr,u8 Addr,u8 *buf,u8 len);
void Device_Page_Write(u8 IC_Addr,u8 Addr,u8 *buf,u8 len);
#endif
main.c
#include "firmware.h"
#include "string.h"
//u8 buf[17];
u8 buf[17] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
0x10,0x11,0x12,0x13,0x14,0x15,0x16};
int main(void)
{
u8 i = 0;
ALL_Init();//包含IIC初始化
printf("123456789\r\n");
IIC_WP = 0;//写保护引脚拉低
Search_Device_Addr();
printf("\r\n");
delay_ms(10);
for(i = 0;i < 17;i++)//处理定义的数组
{
buf[i] = buf[i] + 1;
}
Device_Page_Write(0xA4,0x00,buf,17);//页写入处理过后的数组
delay_ms(10);
Device_Read_Bytes(0xA4,0x00,buf,17);//读出写过后地址的数据
for(i = 0;i < 17;i++)
{
printf(" %2X ",buf[i]);//十六进制显示
}
while(1)
{
}
}