STM32F1 的普通 IO 口模拟 IIC 时序(HAL库)

        本次向大家介绍如何利用 STM32F1 的普通 IO 口模拟 IIC 时序,并实现和 24C02 之间的双向通信。在本章中,我们将利用 STM32F1 的普通 IO 口模拟 IIC 时序,来实现 24C02 的读写,并将结果打印在串口上。
        IIC 它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数 据。
        I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
        开始信号:SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
        结束信号:SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
        应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉
冲,表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,
CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,
由判断为受控单元出现故障。
        这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。IIC 总线时序图如
一、硬件设计
二、软件设计
        IIC驱动代码如下:
//IIC 初始化
void IIC_Init(void)
{
     GPIO_InitTypeDef GPIO_Initure;
 
     __HAL_RCC_GPIOB_CLK_ENABLE(); //使能 GPIOB 时钟
     
     //PB6,7 初始化设置
     GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7;
     GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
     GPIO_Initure.Pull=GPIO_PULLUP; //上拉
     GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
     HAL_GPIO_Init(GPIOB,&GPIO_Initure);
     IIC_SDA=1;
     IIC_SCL=1;
}
    //产生 IIC 起始信号
void IIC_Start(void)
{
    SDA_OUT(); //sda 线输出
    IIC_SDA=1; 
    IIC_SCL=1;
    delay_us(4);
    IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
    delay_us(4);
    IIC_SCL=0;//钳住 I2C 总线,准备发送或接收数据
} 
//产生 IIC 停止信号
void IIC_Stop(void)
{
    SDA_OUT();//sda 线输出
    IIC_SCL=0;
    IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
    delay_us(4);
    IIC_SCL=1; 
    IIC_SDA=1;//发送 I2C 总线结束信号
    delay_us(4); 
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
    u8 ucErrTime=0;
    SDA_IN(); //SDA 设置为输入 
    IIC_SDA=1;delay_us(1); 
    IIC_SCL=1;delay_us(1);
    while(READ_SDA)
    {
        ucErrTime++;
        if(ucErrTime>250)
        {
            IIC_Stop();
            return 1;
        }
    }
    IIC_SCL=0;//时钟输出 0
    return 0; 
} 
//产生 ACK 应答
void IIC_Ack(void)
{
    IIC_SCL=0;
    SDA_OUT();
    IIC_SDA=0;
    delay_us(2);
    IIC_SCL=1;
    delay_us(2);
    IIC_SCL=0;
}
//不产生 ACK 应答 
void IIC_NAck(void)
{
    IIC_SCL=0;
    SDA_OUT();
    IIC_SDA=1;
    delay_us(2);
    IIC_SCL=1;
    delay_us(2);
    IIC_SCL=0;
} 
//IIC 发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答 
void IIC_Send_Byte(u8 txd)
{ 
     u8 t; 
     SDA_OUT(); 
     IIC_SCL=0;//拉低时钟开始数据传输
     for(t=0;t<8;t++)
     { 
         IIC_SDA=(txd&0x80)>>7;
         txd<<=1; 
         delay_us(2); //对 TEA5767 这三个延时都是必须的
         IIC_SCL=1;
         delay_us(2); 
         IIC_SCL=0;
         delay_us(2);
     }
} 
//读 1 个字节,ack=1 时,发送 ACK,ack=0,发送 nACK 
u8 IIC_Read_Byte(unsigned char ack)
{
    unsigned char i,receive=0;
    SDA_IN();//SDA 设置为输入
     for(i=0;i<8;i++ )
    {
         IIC_SCL=0; 
         delay_us(2);
         IIC_SCL=1;
         receive<<=1;
         if(READ_SDA)receive++; 
        delay_us(1); 
     }
     if (!ack)
     IIC_NAck();//发送 nACK
     else
     IIC_Ack(); //发送 ACK 
     return receive;
}

IIC的.h文件

#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO 操作
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入 SDA
        该部分为 IIC 驱动代码,实现包括 IIC 的初始化( IO 口)、 IIC 开始、 IIC 结束、 ACK IIC
读写等功能,在其他函数里面,只需要调用相关的 IIC 函数就可以和外部 IIC 器件通信了,这
里并不局限于 24C02 ,该段代码可以用在任何 IIC 设备上。
        接下来我们看看 24cxx.c 文件代码:
#include "24cxx.h" 
#include "delay.h" 
//初始化 IIC 接口
void AT24CXX_Init(void)
{ 
    IIC_Init();
}
//在 AT24CXX 指定地址读出一个数据
//ReadAddr:开始读数的地址 
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{ 
     u8 temp=0; 
     IIC_Start(); 
    if(EE_TYPE>AT24C16)
    { 
        IIC_Send_Byte(0XA0); //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(ReadAddr>>8); //发送高地址 
    }else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址 0XA0,写数据 
     IIC_Wait_Ack(); 
     IIC_Send_Byte(ReadAddr%256); //发送低地址
     IIC_Wait_Ack(); 
     IIC_Start(); 
     IIC_Send_Byte(0XA1); //进入接收模式 
     IIC_Wait_Ack();
     temp=IIC_Read_Byte(0); 
     IIC_Stop(); //产生一个停止条件 
    return temp;
}
//在 AT24CXX 指定地址写入一个数据
//WriteAddr :写入数据的目的地址 
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{ 
     IIC_Start(); 
     if(EE_TYPE>AT24C16)
    { 
        IIC_Send_Byte(0XA0); //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(WriteAddr>>8);//发送高地址 
    }else IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址 0XA0,写数据
     IIC_Wait_Ack(); 
     IIC_Send_Byte(WriteAddr%256); //发送低地址
     IIC_Wait_Ack(); 
     IIC_Send_Byte(DataToWrite); //发送字节
 
     IIC_Wait_Ack(); 
     IIC_Stop(); //产生一个停止条件
     delay_ms(10);
}
//在 AT24CXX 里面的指定地址开始写入长度为 Len 的数据
//该函数用于写入 16bit 或者 32bit 的数据.
//WriteAddr :开始写入的地址 
//DataToWrite:数据数组首地址
//Len :要写入数据的长度 2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{ 
    u8 t;
    for(t=0;t<Len;t++)
    { 
        AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
    } 
}
//在 AT24CXX 里面的指定地址开始读出长度为 Len 的数据
//该函数用于读出 16bit 或者 32bit 的数据.
//ReadAddr :开始读出的地址
//返回值 :数据
//Len :要读出数据的长度 2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{ 
    u8 t;
    u32 temp=0;
    for(t=0;t<Len;t++)
    { 
        temp<<=8;
        temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 
    }
    return temp; 
}
//检查 AT24CXX 是否正常
//这里用了 24XX 的最后一个地址(255)来存储标志字.
//如果用其他 24C 系列,这个地址要修改
//返回 1:检测失败
//返回 0:检测成功
u8 AT24CXX_Check(void)
{ 
    u8 temp;
    temp=AT24CXX_ReadOneByte(255); //避免每次开机都写 AT24CXX 
    if(temp==0X55)return 0; 
    else //排除第一次初始化的情况
    { 
        AT24CXX_WriteOneByte(255,0X55);
        temp=AT24CXX_ReadOneByte(255); 
        if(temp==0X55)return 0;
    }
    return 1; 
}
//在 AT24CXX 里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对 24c02 为 0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
     while(NumToRead)
    {
      *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
      NumToRead--;
    }
} 
//在 AT24CXX 里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对 24c02 为 0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{ 
    while(NumToWrite--)
    { 
        AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
        WriteAddr++;
        pBuffer++;
    }
}
        这部分代码实际就是通过 IIC 接口来操作 24Cxx 芯片,理论上是可以支持 24Cxx 所有系列
的芯片的(地址引脚必须都设置为 0 ),但是我们测试只测试了 24C02 ,其他器件有待测试。
大家也可以验证一下, 24CXX 的型号定义在 24cxx.h 文件里面,通过 EE_TYPE 设置。
最后,我们在 main 函数里面编写应用代码, main 函数如下:
         
        
//要写入到 24c02 的字符串数组
const u8 TEXT_Buffer[]={"NANO STM32 IIC TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
    u8 key;
    u16 i=0;
    u8 datatemp[SIZE];
    HAL_Init(); //初始化 HAL 库 
    Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
    delay_init(72); //初始化延时函数
    uart_init(115200); //串口初始化为 115200
    LED_Init(); //初始化与 LED 连接的硬件接口
    KEY_Init(); //按键初始化
    AT24CXX_Init(); //IIC 初始化
    usmart_dev.init(72); //初始化 USMART
    printf("NANO STM32\r\n");
    printf("IIC TEST\r\n"); 
     while(AT24CXX_Check())//检测不到 24c02
    {
        printf("24C02 Check Failed!\r\n");
        delay_ms(500);
        printf("Please Check!\r\n");
        delay_ms(500);
        LED0=!LED0;//DS0 闪烁
    }
    printf("24C02 Ready!\r\n");
    printf("WK_UP:Write KEY1:Read\r\n");//显示提示信息
    while(1)
    {
        key=KEY_Scan(0);
        if(key==WKUP_PRES)//WK_UP 按下,写入 24C02
        {
            LED2=0;
            printf("\r\nStart Write 24C02....\r\n");
            AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
            printf("24C02 Write Finished!\r\n");//提示传送完成
            LED2=1;
        }
        if(key==KEY1_PRES)//KEY1 按下,读取字符串并显示
        {
            LED2=0;
            printf("\r\nStart Read 24C02....\r\n");
            AT24CXX_Read(0,datatemp,SIZE);
            printf("The Data Readed Is:\r\n");//提示传送完成
            printf("%s\r\n",datatemp);//显示读到的字符串
            LED2=1;
         }
        i++;
        delay_ms(10);
        if(i==20)
        {
            LED0=!LED0;//提示系统正在运行
            i=0;
        }
    } 
}
        该段代码,我们通过 KEY_UP 按键来控制 24C02 的写入,通过另外一个按键 KEY1 来控
24C02 的读取。并在串口调试助手上面打印显示相关信息。
        最后,我们将 AT24CXX_WriteOneByte AT24CXX_ReadOneByte 函数加入 USMART
制,这样,我们就可以通过串口调试助手,读写任何一个 24C02 的地址,方便测试。
        下载到开发板上,打开串口助手,通过先按 KEY_UP 按键写入数据,然后按 KEY1 读取数据
得到如图
        在 IIC 读和写过程中 DS2 会闪烁,同时 DS0 也会不停的闪烁,提示程序正在运行。程序在
开机的时候会检测 24C02 是否存在,如果不存在则会在串口调试助手上显示错误信息,同时
DS0 慢闪。 USMART 测试 24C02 的任意地址(地址范围: 0~255 )读写如图
 
  • 36
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

No Bugs ToDay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值