[应用相关]

【Alientek STM32 实验16】--IIC实验

[复制链接]
850|28
手机看帖
扫描二维码
随时随地手机跟帖
dsdfdcdx|  楼主 | 2019-3-25 16:43 | 显示全部楼层 |阅读模式
原贴链接:http://www.openedv.com/forum.php?mod=viewthread&tid=33


3.16 IIC实验
这一节我们将向大家介绍IIC。本节将利用IIC来实现24C02的读写,并将结果显示在TFTLCD模块上。本节分为如下几个部分:
3.16.1 IIC简介
3.16.2 硬件设计
3.16.3 软件设计
3.16.4 下载与测试


3.16.1 IIC简介

IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。
I2C总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。IIC总线时序图如下:
9034658a817c1825da7f8542c11d8b00_662.jpg

                          图3.16.1.1 IIC总线时序图
ALIENTEK MiniSTM32开发板板载了EEPROM芯片:。该芯片的总容量是256个字节,该芯片通过IIC总线与外部连接,我们本节就通过STM32来实现24C02的读写。
目前大部分MCU都带有IIC总线接口,STM32也不例外。但是这里我们不使用STM32的硬件IIC来读写24C02,而是通过软件模拟。因为STM32的IIC实在太难用了,一个很简单的东西,ST的人把它弄得很复杂,不得不说STM32的IIC很**肋。所以我们这里就通过模拟来实现了。有兴趣的大家可以研究一下STM32的硬件IIC。
本节实验功能简介:开机的时候先检测24C02是否存在,然后在主循环里面用1个按键用来执行写入24C02的操作,另外一个按键用来执行读出操作,在TFTLCD模块上显示相关信息。同时用DS0提示程序正在运行。
3.16.2 硬件设计

本节所要用到的硬件资源如下:
1)STM32F103RBT6。
2)DS0(外部LED0)。
3)KEY0和KEY2。
4)TFTLCD液晶模块。
5)24C02。
前面4部分的资源,我们前面已经介绍了,请大家参考相关章节。这里只介绍24C02与STM32的连接,板上的24C02是直接连在STM32F103RBT6上的,连接关系如下图:
9da60b6569b41cb9bbb9c7cb432f5f2e_626.jpg

                                                     图3.16.2.1  STM32F103RBT6与24C02连接图


附件:
ALIENTEK MINISTM32 实验16 IIC实验.rar (1.52 MB)

使用特权

评论回复
dsdfdcdx|  楼主 | 2019-3-25 16:44 | 显示全部楼层
3.16.3 软件设计


打开上一节的工程,首先在HARDWARE文件夹下新建一个24CXX的文件夹。然后新建一个24cxx.c、myiic.c的文件和24cxx.h、myiic.h的头文件,保存在24CXX文件夹下,并将24CXX文件夹加入头文件包含路径。
打开myiic.c文件,输入如下代码:
#include "myiic.h"
#include "delay.h"
//STM32软件模拟IIC,STM32的硬件IIC太难用了!
//Mini STM32开发板
//IIC 驱动函数
//正点原子@ALIENTEK
//2010/6/10
//初始化IIC
void IIC_Init(void)
{                                                      
     RCC->APB2ENR|=1<<4;//先使能外设IO PORTC时钟                                                                             
     GPIOC->CRH&=0XFFF00FFF;//PC11/12 推挽输出
     GPIOC->CRH|=0X00033000;      
     GPIOC->ODR|=3<<11;     //PC11,12 输出高
}
//产生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驱动代码,实现包括IIC的初始化(IO口)、IIC开始、IIC结束、ACK、IIC读写等功能,在其他函数里面,只需要调用相关的IIC函数就可以和外部IIC器件通信了,这里并不局限于24C02。
保存该部分代码,把myiic.c加入到HARDWARE组下面,然后在myiic.h里面输入如下代码:
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//Mini STM32开发板
//IIC 驱动函数
//正点原子@ALIENTEK
//2010/6/10
//IO方向设置
#define SDA_IN() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;}
#define SDA_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;}
//IO操作函数        
#define IIC_SCL    PCout(12) //SCL
#define IIC_SDA    PCout(11) //SDA         
#define READ_SDA   PCin(11)  //输入SDA
//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口                                    
void IIC_Start(void);                                               //发送IIC开始信号
void IIC_Stop(void);                                               //发送IIC停止信号
void IIC_Send_Byte(u8 txd);                                    //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void);                                          //IIC等待ACK信号
void IIC_Ack(void);                                                            //IIC发送ACK信号
void IIC_NAck(void);                                             //IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);     
#endif
     接下来我们在24cxx.c文件里面输入如下代码:
#include "24cxx.h"
#include "delay.h"
//Mini STM32开发板
//24CXX驱动函数(适合~24C16,24C32~256未经过测试!有待验证!)
//正点原子@ALIENTEK
//2010/6/10
//V1.2

//初始化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);//发送高地址
                 IIC_Wait_Ack();                       
     }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);//发送高地址
                 IIC_Wait_Ack();                       
     }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++;
     }
}
这部分代码理论上是可以支持24Cxx所有系列的芯片的(地址引脚必须都设置为0),但是我们测试只测试了24C02,其他器件有待测试。大家也可以验证一下,24CXX的型号定义在24cxx.h文件里面,通过EE_TYPE设置。
保存该部分代码,把24cxx.c加入到HARDWARE组下面,然后在24cxx.h里面输入如下代码:
#ifndef __24CXX_H
#define __24CXX_H
#include "myiic.h"  
//Mini STM32开发板
//24CXX驱动函数(适合~24C16,24C32~256未经过测试!有待验证!)
//正点原子@ALIENTEK
//2010/6/10
//V1.2
#define AT             127
#define AT24C02                255
#define AT24C04                511
#define AT24C08                1023
#define AT24C16                2047
#define AT24C32                4095
#define AT24C64        8191
#define AT24C128  16383
#define AT24C256  32767
//Mini STM32开发板使用的是24C02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02
                                                      
u8 AT24CXX_ReadOneByte(u16 ReadAddr);                                                                                    //指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);                    //指定地址写入一个字节
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);//指定地址开始写入指定长度的数据
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len);                                                            //指定地址开始读取指定长度数据
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);            //从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);               //从指定地址开始读出指定长度的数据

u8 AT24CXX_Check(void);  //检查器件
void AT24CXX_Init(void); //初始化IIC
#endif
最后,我们在main函数里面编写应用代码,在test.c里面,修改main函数如下:
//要写入到24C02的字符串数组
const u8 TEXT_Buffer[]={"MiniSTM32 IIC TEST"};
#define SIZE sizeof(TEXT_Buffer)  
int main(void)
{              
     u8 key;
     u16 i=0;
     u8 datatemp[SIZE];
                              
     Stm32_Clock_Init(9);//系统时钟设置
     delay_init(72);               //延时初始化
     uart_init(72,9600); //串口1初始化  
     LED_Init();                               //LED初始化
     KEY_Init();                               //按键初始化
     LCD_Init();                   //TFTLCD液晶初始化
     AT24CXX_Init();                       //IIC初始化


     POINT_COLOR=RED;//设置字体为蓝色              
     LCD_ShowString(60,50,"Mini STM32");
     LCD_ShowString(60,70,"IIC TEST");   
     LCD_ShowString(60,90,"ATOM@ALIENTEK");
     LCD_ShowString(60,110,"2010/6/10");                          
     while(AT24CXX_Check())//检测不到24C02
     {
                 LCD_ShowString(60,130,"24C02 Check Failed!");
                 delay_ms(500);
                 LCD_ShowString(60,130,"Please Check!      ");
                 delay_ms(500);
                 LED0=!LED0;//DS0闪烁
     }
     LCD_ShowString(60,130,"24C02 Ready!");
     //显示提示信息
     LCD_ShowString(60,150,"KEY0:Write KEY2:Read");

     POINT_COLOR=BLUE;//设置字体为蓝色           
     while(1)
     {
                 key=KEY_Scan();
                 if(key==1)//KEY0按下,写入24C02
                 {
                             LCD_Fill(0,170,239,319,WHITE);//清除半屏   
                             LCD_ShowString(60,170,"Start Write 24C02....");
                             AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
                             LCD_ShowString(60,170,"24C02 Write Finished!");//提示传送完成
                 }
                 if(key==3)//KEY1按下,读取字符串并显示
                 {
                             LCD_ShowString(60,170,"Start Read 24C02.... ");
                             AT24CXX_Read(0,datatemp,SIZE);
                             LCD_ShowString(60,170,"The Data Readed Is:  ");//提示传送完成
                             LCD_ShowString(60,190,datatemp);//显示读到的字符串
                 }
                 i++;
                 delay_ms(1);
                 if(i==200)
                 {
                             LED0=!LED0;//提示系统正在运行        
                             i=0;
                 }                        
     }
}
至此,我们的软件设计部分就结束了。

使用特权

评论回复
dsdfdcdx|  楼主 | 2019-3-25 16:45 | 显示全部楼层
3.16.4 下载与测试


在代码编译成功之后,我们通过下载代码到ALIENTEK MiniSTM32开发板上,可以看到LCD显示如下内容:
500085c9894fe75c65.png
    图3.16.4.1 程序运行效果图
伴随DS0的不停闪烁,提示程序在运行。我们先按下KEY0,可以看到如图13.16.4.2所示的内容,证明数据已经被写入到24C02了。
48415c989513d3308.png
图3.16.4.2 数据成功写入24C02
接着我们按KEY2,可以看我们刚刚写入的数据被显示出来了,如下图所示:

357645c989525548af.png
图3.16.4.3 显示写入的内容
程序在开机的时候会检测24C02是否存在,如果不存在则会在TFTLCD模块上显示错误信息,同时DS0慢闪。大家可以通过跳线帽把PC11和PC12短接就可以看到报错了。

使用特权

评论回复
评论
饭冰冰 2020-11-1 20:51 回复TA
学习了, 
labasi| | 2019-4-10 10:17 | 显示全部楼层
iic不太稳定觉得

使用特权

评论回复
paotangsan| | 2019-4-10 10:26 | 显示全部楼层
非常感谢楼主分享

使用特权

评论回复
renzheshengui| | 2019-4-10 10:38 | 显示全部楼层
使用spi的时候多  感觉iic很娇气

使用特权

评论回复
wakayi| | 2019-4-10 10:43 | 显示全部楼层
感谢楼主分享

使用特权

评论回复
木木guainv| | 2019-4-10 11:07 | 显示全部楼层
iic主要考验的就是时序

使用特权

评论回复
xiaoqizi| | 2019-4-10 11:11 | 显示全部楼层
我都是用的模拟的

使用特权

评论回复
kkzz| | 2020-11-2 15:49 | 显示全部楼层
stm32内部的iic怎么感觉不稳定

使用特权

评论回复
hudi008| | 2020-11-2 15:49 | 显示全部楼层
stm32是所有型号都有iic吗  

使用特权

评论回复
lzmm| | 2020-11-2 15:49 | 显示全部楼层
stm32进行一次iic通信需要多长时间

使用特权

评论回复
minzisc| | 2020-11-2 15:49 | 显示全部楼层
硬件iic速度比模拟快   

使用特权

评论回复
selongli| | 2020-11-2 15:50 | 显示全部楼层
硬件I2C稳定吗   

使用特权

评论回复
fentianyou| | 2020-11-2 15:50 | 显示全部楼层
iic电路有什么要求     

使用特权

评论回复
febgxu| | 2020-11-2 15:50 | 显示全部楼层
stm32的iic的时钟频率怎么设置  

使用特权

评论回复
sdlls| | 2020-11-2 15:51 | 显示全部楼层
硬件II2C和软件I2C有什么区别

使用特权

评论回复
pixhw| | 2020-11-2 15:52 | 显示全部楼层
STM32有硬件IIC,为什么很多应用,还要模拟IIC

使用特权

评论回复
minzisc| | 2020-11-2 15:52 | 显示全部楼层
硬件iic用法比较复杂,模拟iic的流程更清楚一些  

使用特权

评论回复
lzmm| | 2020-11-2 15:52 | 显示全部楼层
stm32之间能不能iic通信  

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

80

帖子

0

粉丝