實現模擬IIC與EEPROM(24c02)通信(基於STM32F103ZET6)

一句話梳理流程

stm32模擬硬件IIC時序,按照時序,EEPROM識別外部信號,完成對其的數據操作

目的:

使用32開發板軟件模擬IIC實現對帶有硬件IIC接口的eeprom完成寫數據並將寫入的數據讀出來,內容顯示到TFTLCD

硬件需求及連接:

STM32精英開發板
板載的eeprom(容量256K)
TFTLCD顯示屏
在這裏插入圖片描述
在這裏插入圖片描述

爲什麼不使用硬件IIC

目前大部分 MCU 都帶有 IIC 總線接口,但是這裏我們不使用 STM32的硬件 IIC 來讀寫 24C02,而是通過軟件模擬。STM32 的硬件 IIC 非常複雜,更重要的是不穩定,故不推薦使用。所以我們這裏就通過模擬來實現了。有興趣的讀者可以研究一下 STM32的硬件 IIC。

IIC理解

它是由數據線 SDA 和時鐘 SCL 構成的串行總線,可發送和接收數據。在 CPU 與被控 IC 之間、IC 與 IC 之間進行雙向傳送,高速 IIC 總線一般可達 400kbps 以上。I2C 總線在傳送數據過程中共有三種類型信號, 它們分別是:開始信號、結束信號和應答信號。

  1. 開始信號:SCL 爲高電平時,SDA 由高電平向低電平跳變,開始傳送數據。
  2. 結束信號:SCL 爲高電平時,SDA 由低電平向高電平跳變,結束傳送數據。
  3. 應答信號:接收數據的 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<len;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<len;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<len;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<len;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);
		   
	} 
}

主函數就不講解了,梳理主函數流程也是很有必要的

實驗現象

下載完成過後,屏幕無顯示,全白,如果不是全白,按照字符串提示修改自己的代碼,按鍵按下,寫入數據,按另一個個按鍵,讀出數據,顯示在顯示屏上。

其他

本實驗講解了對原理的理解講解,按照時序理解,未給出的時序,希望自行理解,希望對讀者有所幫助,如果有什麼錯誤與建議,請及時溝通交流!謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章