一句话梳理流程
stm32模拟硬件IIC时序,按照时序,EEPROM识别外部信号,完成对其的数据操作
目的:
使用32开发板软件模拟IIC实现对带有硬件IIC接口的eeprom完成写数据并将写入的数据读出来,内容显示到TFTLCD 硬件需求及连接:
STM32精英开发板
板载的eeprom(容量256B)
TFTLCD显示屏
为什么不使用硬件IIC
目前大部分 MCU 都带有 IIC 总线接口,但是这里我们不使用 STM32的硬件 IIC 来读写 24C02,而是通过软件模拟。STM32 的硬件 IIC 非常复杂,更重要的是不稳定,故不推荐使用。所以我们这里就通过模拟来实现了。有兴趣的读者可以研究一下 STM32的硬件 IIC。
IIC理解
它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
- 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
- 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
- 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU
接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
开始信号和结束信号都好理解,说明一下应答信号,主机每发完一帧数据,从机接收到了数据后应该回复主机一个应答信号,此时我们发送等待应答信号,去等待从机的回复,如果一定时间内不回复,则结束数据传输,只有回复了才能进行下面的传输
理解
我们stm32(mcu)定义为主机,eeprom定义为从机
很多读者梳理不清楚我们到底应该写哪些基本函数来完成从机的通信,我们想要写一个字节到从机里,首先有个开始信号,让外部通信的设备知道我们要开始传输数据了,这时候我们不用等待外部设备(从机)回复,因为这时候我们并未真正开始传输数据,也就不需要从机回复,接着把要写入的数据写入从机,不管从机接收到的是命令还是数据,统称为数据,都要对主机回复,反之,从机给主机发送数据,主机也要应答表示收到数据。
基础函数讲解
完成数据传输之前,我们需要模拟出stm32这一侧的时序,eeprom那一侧已经有硬件iic了,就不需要模拟。
理论不多说,直接上代码:
宏定义
前两行代码是寄存器的位操作,控制io口的输入输出,因为数据交换会时常改变数据的传输方向,如果经常使用GPIO_InitTypeDef*定义输入输出,很麻烦,所以使用位操作,方便快捷,这两个的效果是一样的,有兴趣的小伙伴可以去了解一下位操作。
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
#define IIC_SCL PBout(6)
#define IIC_SDA PBout(7)
#define READ_SDA PBin(7)
当然了,初始化IO口也是很有必要的:
EEPROM初始化IO:
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //ʹÄÜGPIOBʱÖÓ
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);
}
初始化函数写完了,我们就可以利用此IO口来写时序了。
起始信号
void iic_start(void)
{
SDA_OUT();//mcu发出来的,所以是输出模式
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);//延时
IIC_SDA=0;
delay_us(4);
IIC_SCL=0;
}
看步骤都很简单,但是有的新手不明白,如果弄清楚了方向就清楚了,这个方向是从mcu–>从机,所以这是mcu的起始信号,又有的要问了,为什么我们不需要配置从机的起始信号,因为我们的从机不会主动发送信号,使用的也是硬件IIC,不需要我们配置
停止信号
主机发给从机,停止(结束)数据传输
void iic_stop(void)
{
SDA_OUT();//mcu发出来的,所以是输出模式
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
}
mcu应答信号
mcu收到数据后应答给从机,也就是上面讲的
void iic_ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SCL=0;
}
mcu不应答信号
void iic_nack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SCL=0;
}
mcu等待从机给回复:
mcu发送数据到从机后,需要从机应答,在给定的时间内来允许从机应答,若未收到应答信号,则停止数据传输
uint8_t iic_wait_ack(void)
{
uint8_t time;
SDA_IN();
IIC_SDA=1;
delay_us(1);
IIC_SCL=1;
delay_us(4);
while(READ_SDA)
{
time++;
if(time>250)
{
iic_stop();
return 1;
}
}
delay_us(4);
IIC_SCL=0;
return 0;
}
接收到真实数据的数据接收方必须发送应答信号给发送方
这几个传输数据的逻辑函数写好了,就可以来传输真实数据的函数编写了
发送一个字节的时序函数:
void iic_send_byte( uint8_t data)
{
uint8_t i;
SDA_OUT();
IIC_SCL=0;
for(i=0;i<8;i++)
{
IIC_SDA=(data&0x80)>>7;
data<<=1;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SCL=0;
delay_us(4);
}
}
读一个字节的时序函数:
uint8_t iic_read_byte( uint8_t i)
{
uint8_t j;
uint8_t temp;
SDA_IN();
for(j=0;j<8;j++)
{
IIC_SCL=0;
delay_us(4);
IIC_SCL=1;
temp<<=1;
if(READ_SDA)
temp++;
delay_us(4);
}
if(i)
iic_ack();
else
iic_nack();
return temp;
}
基础函数写完了,我们并未写出传输到实际设备的函数,因为这里面包括器件的地址,数据,命令等等我们都没有涉及到现在,这只是为实际的数据传输做准备
想要操作EEPROM,我们就要利用上面的函数:
同样的,我们只是写了初始化函数,并未调用,所以我们要调用初始化函数
调用初始化函数:
void _24c02_init(void)
{
IIC_Init();
}
在指定的地址上读住此地址的值(一个字节大小):
传入一个地址,便可得到此地址的值
uint8_t read_byte( uint16_t addr)
{
uint8_t ttpm;
iic_start();
iic_send_byte(0xA0+((addr/256)<<1));
iic_wait_ack();
iic_send_byte(addr%256);
iic_wait_ack();
iic_start();
iic_send_byte(0xA1);
iic_wait_ack();
ttpm=iic_read_byte(0);
iic_stop();
return ttpm;
}
在指定的地址上写入指定的数据:参数是传入数据的地址和真实数据
void write_byte(uint16_t addr, uint8_t dat)
{
iic_start();
iic_send_byte(0xA0+((addr/256)<<1));
iic_wait_ack();
iic_send_byte(addr%256);
iic_wait_ack();
iic_send_byte(dat);
iic_wait_ack();
iic_stop();
delay_ms(10);
}
向指定地址,写入长度为len字节的数据
void write_len_byte( uint16_t addr,uint16_t data, uint8_t len)
{
uint8_t k;
for(k=0;k
write_byte(addr+k,(data>>(8*k))&0xff);
}
向指定地址开始,读出长度为len的值:
uint8_t read_len_byte( uint16_t addr, uint8_t len)
{
uint8_t i;
uint16_t temp;
for(i=0;i
{
temp<<=8;
temp+=read_byte((addr+len-i-1));
}
return temp;
}
从上两个函数可以看出,低位地址存放的是数据字节高位,高位地址存放的是数据字节的低位
校验EEPROM是否可用:
uint8_t _24c02_check(void)
{
uint8_t temp;
temp=read_byte(255);
if(temp==0X55)return 0;
else
{
write_byte(255,0X55);
temp=read_byte(255);
if(temp==0X55)return 0;
}
return 1;
}
初次读出的值应为0x55,如果不是,写入0x55,再读出,是否为0x55
指定地址,读出数据为len长度的数据存到指针里
void read_len_data_into_array( uint16_t device_add,uint8_t*a, uint8_t len)
{
uint8_t i;
for(i=0;i
{
*(a+i)=read_byte(device_add+i);
}
}
在指定地址,写入长度为len的数据:
void write_len_data_into_device( uint16_t device_add,uint8_t*a, uint8_t len)
{
uint8_t i;
for(i=0;i
write_byte((device_add+i),*(a+i));
}
主函数main,直接调用:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "iic.h"
#include "24c02.h"
const u8 TEXT_Buffer[]={"EEPROM TEST SUCCESS"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LCD_Init();
KEY_Init();
_24c02_init();
while(_24c02_check())
{
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);
}
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES)
{
LCD_Fill(0,170,239,319,WHITE);
write_len_data_into_device(0,(u8*)TEXT_Buffer,SIZE);
LCD_ShowString(30,170,200,16,16,"24C02 Write Finished!");
}
if(key==KEY0_PRES)
{
read_len_data_into_array(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);
}
}
主函数就不讲解了,梳理主函数流程也是很有必要的
实验现象
下载完成过后,屏幕无显示,全白,如果不是全白,按照字符串提示修改自己的代码,按键按下,写入数据,按另一个个按键,读出数据,显示在显示屏上。
其他
本实验讲解了对原理的理解讲解,按照时序理解,未给出的时序,希望自行理解,希望对读者有所帮助,如果有什么错误与建议,请及时沟通交流!谢谢
一句话梳理流程
stm32模拟硬件IIC时序,按照时序,EEPROM识别外部信号,完成对其的数据操作
目的:
使用32开发板软件模拟IIC实现对带有硬件IIC接口的eeprom完成写数据并将写入的数据读出来,内容显示到TFTLCD 硬件需求及连接:
STM32精英开发板
板载的eeprom(容量256B)
TFTLCD显示屏
为什么不使用硬件IIC
目前大部分 MCU 都带有 IIC 总线接口,但是这里我们不使用 STM32的硬件 IIC 来读写 24C02,而是通过软件模拟。STM32 的硬件 IIC 非常复杂,更重要的是不稳定,故不推荐使用。所以我们这里就通过模拟来实现了。有兴趣的读者可以研究一下 STM32的硬件 IIC。
IIC理解
它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
- 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
- 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
- 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU
接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
开始信号和结束信号都好理解,说明一下应答信号,主机每发完一帧数据,从机接收到了数据后应该回复主机一个应答信号,此时我们发送等待应答信号,去等待从机的回复,如果一定时间内不回复,则结束数据传输,只有回复了才能进行下面的传输
理解
我们stm32(mcu)定义为主机,eeprom定义为从机
很多读者梳理不清楚我们到底应该写哪些基本函数来完成从机的通信,我们想要写一个字节到从机里,首先有个开始信号,让外部通信的设备知道我们要开始传输数据了,这时候我们不用等待外部设备(从机)回复,因为这时候我们并未真正开始传输数据,也就不需要从机回复,接着把要写入的数据写入从机,不管从机接收到的是命令还是数据,统称为数据,都要对主机回复,反之,从机给主机发送数据,主机也要应答表示收到数据。
基础函数讲解
完成数据传输之前,我们需要模拟出stm32这一侧的时序,eeprom那一侧已经有硬件iic了,就不需要模拟。
理论不多说,直接上代码:
宏定义
前两行代码是寄存器的位操作,控制io口的输入输出,因为数据交换会时常改变数据的传输方向,如果经常使用GPIO_InitTypeDef*定义输入输出,很麻烦,所以使用位操作,方便快捷,这两个的效果是一样的,有兴趣的小伙伴可以去了解一下位操作。
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
#define IIC_SCL PBout(6)
#define IIC_SDA PBout(7)
#define READ_SDA PBin(7)
当然了,初始化IO口也是很有必要的:
EEPROM初始化IO:
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //ʹÄÜGPIOBʱÖÓ
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);
}
初始化函数写完了,我们就可以利用此IO口来写时序了。
起始信号
void iic_start(void)
{
SDA_OUT();//mcu发出来的,所以是输出模式
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);//延时
IIC_SDA=0;
delay_us(4);
IIC_SCL=0;
}
看步骤都很简单,但是有的新手不明白,如果弄清楚了方向就清楚了,这个方向是从mcu–>从机,所以这是mcu的起始信号,又有的要问了,为什么我们不需要配置从机的起始信号,因为我们的从机不会主动发送信号,使用的也是硬件IIC,不需要我们配置
停止信号
主机发给从机,停止(结束)数据传输
void iic_stop(void)
{
SDA_OUT();//mcu发出来的,所以是输出模式
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
}
mcu应答信号
mcu收到数据后应答给从机,也就是上面讲的
void iic_ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SCL=0;
}
mcu不应答信号
void iic_nack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SCL=0;
}
mcu等待从机给回复:
mcu发送数据到从机后,需要从机应答,在给定的时间内来允许从机应答,若未收到应答信号,则停止数据传输
uint8_t iic_wait_ack(void)
{
uint8_t time;
SDA_IN();
IIC_SDA=1;
delay_us(1);
IIC_SCL=1;
delay_us(4);
while(READ_SDA)
{
time++;
if(time>250)
{
iic_stop();
return 1;
}
}
delay_us(4);
IIC_SCL=0;
return 0;
}
接收到真实数据的数据接收方必须发送应答信号给发送方
这几个传输数据的逻辑函数写好了,就可以来传输真实数据的函数编写了
发送一个字节的时序函数:
void iic_send_byte( uint8_t data)
{
uint8_t i;
SDA_OUT();
IIC_SCL=0;
for(i=0;i<8;i++)
{
IIC_SDA=(data&0x80)>>7;
data<<=1;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SCL=0;
delay_us(4);
}
}
读一个字节的时序函数:
uint8_t iic_read_byte( uint8_t i)
{
uint8_t j;
uint8_t temp;
SDA_IN();
for(j=0;j<8;j++)
{
IIC_SCL=0;
delay_us(4);
IIC_SCL=1;
temp<<=1;
if(READ_SDA)
temp++;
delay_us(4);
}
if(i)
iic_ack();
else
iic_nack();
return temp;
}
基础函数写完了,我们并未写出传输到实际设备的函数,因为这里面包括器件的地址,数据,命令等等我们都没有涉及到现在,这只是为实际的数据传输做准备
想要操作EEPROM,我们就要利用上面的函数:
同样的,我们只是写了初始化函数,并未调用,所以我们要调用初始化函数
调用初始化函数:
void _24c02_init(void)
{
IIC_Init();
}
在指定的地址上读住此地址的值(一个字节大小):
传入一个地址,便可得到此地址的值
uint8_t read_byte( uint16_t addr)
{
uint8_t ttpm;
iic_start();
iic_send_byte(0xA0+((addr/256)<<1));
iic_wait_ack();
iic_send_byte(addr%256);
iic_wait_ack();
iic_start();
iic_send_byte(0xA1);
iic_wait_ack();
ttpm=iic_read_byte(0);
iic_stop();
return ttpm;
}
在指定的地址上写入指定的数据:参数是传入数据的地址和真实数据
void write_byte(uint16_t addr, uint8_t dat)
{
iic_start();
iic_send_byte(0xA0+((addr/256)<<1));
iic_wait_ack();
iic_send_byte(addr%256);
iic_wait_ack();
iic_send_byte(dat);
iic_wait_ack();
iic_stop();
delay_ms(10);
}
向指定地址,写入长度为len字节的数据
void write_len_byte( uint16_t addr,uint16_t data, uint8_t len)
{
uint8_t k;
for(k=0;k
write_byte(addr+k,(data>>(8*k))&0xff);
}
向指定地址开始,读出长度为len的值:
uint8_t read_len_byte( uint16_t addr, uint8_t len)
{
uint8_t i;
uint16_t temp;
for(i=0;i
{
temp<<=8;
temp+=read_byte((addr+len-i-1));
}
return temp;
}
从上两个函数可以看出,低位地址存放的是数据字节高位,高位地址存放的是数据字节的低位
校验EEPROM是否可用:
uint8_t _24c02_check(void)
{
uint8_t temp;
temp=read_byte(255);
if(temp==0X55)return 0;
else
{
write_byte(255,0X55);
temp=read_byte(255);
if(temp==0X55)return 0;
}
return 1;
}
初次读出的值应为0x55,如果不是,写入0x55,再读出,是否为0x55
指定地址,读出数据为len长度的数据存到指针里
void read_len_data_into_array( uint16_t device_add,uint8_t*a, uint8_t len)
{
uint8_t i;
for(i=0;i
{
*(a+i)=read_byte(device_add+i);
}
}
在指定地址,写入长度为len的数据:
void write_len_data_into_device( uint16_t device_add,uint8_t*a, uint8_t len)
{
uint8_t i;
for(i=0;i
write_byte((device_add+i),*(a+i));
}
主函数main,直接调用:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "iic.h"
#include "24c02.h"
const u8 TEXT_Buffer[]={"EEPROM TEST SUCCESS"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LCD_Init();
KEY_Init();
_24c02_init();
while(_24c02_check())
{
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);
}
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES)
{
LCD_Fill(0,170,239,319,WHITE);
write_len_data_into_device(0,(u8*)TEXT_Buffer,SIZE);
LCD_ShowString(30,170,200,16,16,"24C02 Write Finished!");
}
if(key==KEY0_PRES)
{
read_len_data_into_array(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);
}
}
主函数就不讲解了,梳理主函数流程也是很有必要的
实验现象
下载完成过后,屏幕无显示,全白,如果不是全白,按照字符串提示修改自己的代码,按键按下,写入数据,按另一个个按键,读出数据,显示在显示屏上。
其他
本实验讲解了对原理的理解讲解,按照时序理解,未给出的时序,希望自行理解,希望对读者有所帮助,如果有什么错误与建议,请及时沟通交流!谢谢
举报