完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子STM32mini开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第二十四章 IIC 实验 本章我们将向大家介绍如何使用 STM32 的普通 IO 口模拟 IIC 时序,并实现和 24C02 之间 的双向通信。在本章中,我们将使用 STM32 的普通 IO 口模拟 IIC 时序,来实现 24C02 的读写, 并将结果显示在 TFTLCD 模块上。本章分为如下几个部分: 24.1 IIC 简介 24.2 硬件设计 24.3 软件设计 24.4 下载验证 24.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 总线时序图如 图 24.1..1 所示: 图 24.1.1 IIC 总线时序图 ALIENTEK MiniSTM32 开发板板载的 EEPROM 芯片型号为 24C02。该芯片的总容量是 256 个字节,该芯片通过 IIC 总线与外部连接,我们本章就通过 STM32 来实现 24C02 的读写。 目前大部分 MCU 都带有 IIC 总线接口,STM32 也不例外。但是这里我们不使用 STM32 的硬件 IIC 来读写 24C02,而是通过软件模拟。STM32 的硬件 IIC 非常复杂,更重要的是不稳 定,故不推荐使用。所以我们这里就通过模拟来实现了。有兴趣的读者可以研究一下 STM32 的硬件 IIC。 本章实验功能简介:开机的时候先检测 24C02 是否存在,然后在主循环里面检测两个按键, 其中 1 个按键(WK_UP)用来执行写入 24C02 的操作,另外一个按键(KEY0)用来执行读出 操作,在 TFTLCD 模块上显示相关信息。同时用 DS0 提示程序正在运行。 24.2 硬件设计 本章需要用到的硬件资源有: 1) 指示灯 DS0 2) WK_UP 和 KEY0 按键 3) 串口(USMART 使用) 4) TFTLCD 模块 5) 24C02 前面 4 部分的资源,我们前面已经介绍了,请大家参考相关章节。这里只介绍 24C02 与 STM32 的连接,24C02 的 SCL 和 SDA 分别连在 STM32 的 PC12 和 PC11 上的,连接关系如图 24.2.1 所示: 图 24.2.1 STM32 与 24C02 连接图 24.3 软件设计 打开本章的实验工程可以看到,我们并没有在 FWLIB 分组之下添加新的 HAL 库文件支持, 因为我们是通过GPIO来模拟IIC。我们新增了myiic.c文件用来存放iic底层驱动。新增了24cxx.c 文件用来存放 24C02 的底层驱动。 打开 myiic.c 文件,代码如下: #include "myiic.h" #include "delay.h" //IIC 初始化 void IIC_Init(void) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOC_CLK_ENABLE(); //使能 GPIOC 时钟 //PC11,12 初始化设置 GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12; GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速 HAL_GPIO_Init(GPIOC,&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; delay_us(4);//IIC START: when CLK is high,DATA change form high to low IIC_SCL=0;//钳住 I2C 总线,准备发送或接收数据 } //产生 IIC 停止信号 void IIC_Stop(void) { SDA_OUT();//sda 线输出 IIC_SCL=0; IIC_SDA=0; delay_us(4); IIC_SCL=1;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SDA=1;//发送 I2C 总线结束信号 } //等待应答信号到来 //返回值: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); 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,该段代码可以用在任何 IIC 设备上。 打开 myiic.h 头文件可以看到,我们除了函数申明之外,还定义了几个宏定义标识符: //IO 方向设置 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} //PB7 输入模式 #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //PB7 输出模式 //IO 操作 #define IIC_SCL PBout(6) //SCL #define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //输入 SDA 该部分代码的 SDA_IN()和 SDA_OUT()分别用于设置 IIC_SDA 接口为输入和输出,如果这 两句代码看不懂,请好好温习下 IO 口的使用。其他几个宏定义就是我们通过位带实现 IO 口操 作。 接下来我们看看 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); //对于 EEPROM 器件,每写一次要等待一段时间,否则写失败! } //在 AT24CXX 里面的指定地址开始写入长度为 Len 的数据 //该函数用于写入 16bit 或者 32bit 的数据. //WriteAddr :开始写入的地址 //DataToWrite:数据数组首地址 //Len :要写入数据的长度 2,4 void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) { u8 t; for(t=0;t } //在 AT24CXX 里面的指定地址开始读出长度为 Len 的数据 //该函数用于读出 16bit 或者 32bit 的数据. //ReadAddr :开始读出的地址 //返回值 :数据 //Len :要读出数据的长度 2,4 u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) { u8 t; u32 temp=0; for(t=0;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" #define AT24C01 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 #define AT24C512 65535 //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]; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED KEY_Init(); //初始化按键 LCD_Init(); //初始化 LCD AT24CXX_Init(); //初始化 IIC POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"Mini STM32"); LCD_ShowString(30,70,200,16,16,"IIC TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2019/11/15"); LCD_ShowString(30,130,200,16,16,"KEY1:Write KEY0:Read"); //显示提示信息 while(AT24CXX_Check())//检测不到 24c02 { LCD_ShowString(30,150,200,16,16,"24C02 Check Failed!"); delay_ms(500); LCD_ShowString(30,150,200,16,16,"Please Check! "); delay_ms(500); LED0=!LED0;//DS0 闪烁 } LCD_ShowString(30,150,200,16,16,"24C02 Ready!"); POINT_COLOR=BLUE;//设置字体为蓝色 while(1) { key=KEY_Scan(0); if(key==WKUP_PRES)//WK_UP 按下,写入 24C02 { LCD_Fill(0,170,239,319,WHITE);//清除半屏 LCD_ShowString(30,170,200,16,16,"Start Write 24C02...."); AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE); LCD_ShowString(30,170,200,16,16,"24C02 Write Finished!");//提示传送完成 } if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示 { LCD_ShowString(30,170,200,16,16,"Start Read 24C02.... "); AT24CXX_Read(0,datatemp,SIZE); LCD_ShowString(30,170,200,16,16,"The Data Readed Is: ");//提示传送完成 LCD_ShowString(30,190,200,16,16,datatemp);//显示读到的字符串} i++; delay_ms(10); if(i==20) { LED0=!LED0; i=0; }//提示系统正在运行 } } 该段代码,我们通过 KEY_UP 按键来控制 24C02 的写入,通过另外一个按键 KEY0 来控 制 24C02 的读取。并在 LCD 模块上面显示相关信息。 最后,我们将 AT24CXX_WriteOneByte 和 AT24CXX_ReadOneByte 函数加入 USMART 控 制,这样,我们就可以通过串口调试助手,读写任何一个 24C02 的地址,方便测试。 至此,我们的软件设计部分就结束了。 24.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,通过先按 WK_UP 按键写入数据,然后按 KEY0 读取数据,得到如图 24.4.1 所示: 图 24.4.1 IIC 实验程序运行效果图 同时 DS0 会不停的闪烁,提示程序正在运行。程序在开机的时候会检测 24C02 是否存在, 如果不存在则会在 TFTLCD 模块上显示错误信息,同时 DS0 慢闪。大家可以通过跳线帽把 PC11 和 PC12 短接就可以看到报错了。 USMART 测试 24C02 的任意地址(地址范围:0~255)读写如图 24.4.2 所示: 图 24.4.2 USMART 控制 24C02 读写 图中,我们先通过:AT24CXX_ReadOneByte(123),读取地址:123 的值,为 0。然后,通 过 AT24CXX_WriteOneByte(123,0X32),往地址 123 写入数值 0X32,也就是 50。之后,再次调 用 AT24CXX_ReadOneByte(123),得到新写入的值:50。表明我们的例程操作 24C02 是正常的。 |
|
相关推荐
|
|
969 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
954 浏览 2 评论
2066 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1162 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1587 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 13:40 , Processed in 1.071700 second(s), Total 65, Slave 47 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号