STM32——IIC詳細解析

STM32——IIC詳解

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 總線時序圖如圖 27.1.1 所示:
在這裏插入圖片描述
ALIENTEK 精英 STM32F103 板載的 EEPROM 芯片型號爲 24C02。該芯片的總容量是 256個字節,該芯片通過 IIC 總線與外部連接,我們本章就通過 STM32F1 來實現 24C02 的讀寫。
目前大部分 MCU 都帶有 IIC 總線接口, STM32F1 也不例外。但是這裏我們不使用 STM32F1的硬件 IIC 來讀寫 24C02,而是通過軟件模擬。 ST 爲了規避飛利浦 IIC 專利問題,將 STM32的硬件 IIC 設計的比較複雜, 而且穩定性不怎麼好,所以這裏我們不推薦使用。 有興趣的讀者可以研究一下 STM32F1 的硬件 IIC。
用軟件模擬 IIC, 最大的好處就是方便移植, 同一個代碼兼容所有 MCU, 任何一個單片機只要有 IO 口,就可以很快的移植過去,而且不需要特定的 IO 口。 而硬件 IIC,則換一款 MCU,基本上就得重新搞一次,移植是比較麻煩的,這也是我們推薦使用軟件模擬 IIC 的另外一個原因。
實驗功能簡介: 開機的時候先檢測 24C02 是否存在,然後在主循環裏面檢測兩個按鍵,其中 1 個按鍵(KEY1) 用來執行寫入 24C02 的操作,另外一個按鍵(KEY0) 用來執行讀出操作,在 TFTLCD 模塊上顯示相關信息。同時用 DS0 提示程序正在運行。

硬件連接

本次實驗用到的硬件資源:
1) 指示燈 DS0
2) KEY0 和 KEY1 按鍵
3) 串口( USMART 使用)
4) TFTLCD 模塊
5) 24C02
這裏只介紹 24C02 與STM32F1 的連接, 24C02 的 SCL 和 SDA 分別連在 STM32F1 的 PB6 和 PB7 上的,連接關係如圖 27.2.1 所示:
在這裏插入圖片描述

軟件設計

在 HARDWARE 文件夾下新建一個 24CXX 的文件夾。然後新建一個 24cxx.c、 myiic.c的文件和 24cxx.h、 myiic.h 的頭文件,保存在 24CXX 文件夾下,並將 24CXX 文件夾加入頭文件包含路徑。
打開 myiic.c 文件,輸入如下代碼:

//初始化 IIC
void IIC_Init(void)
{
RCC->APB2ENR|=1<<3; //先使能外設 IO PORTB 時鐘GPIOB->CRL&=0X00FFFFFF; //PB6/7 推輓輸出GPIOB->CRL|=0X33000000;
GPIOB->ODR|=3<<6; //PB6,7 輸出高}
//產生 IIC 起始信號
void IIC_Start(void)
{
SDA_OUT(); //sda 線輸出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0; 
delay_us(4);//START:when CLK is high,DATA change form high to low 
IIC_SCL=0; //鉗住 I2C 總線,準備發送或接收數據
}
//產生 IIC 停止信號
void IIC_Stop(void)
{
SDA_OUT(); 
IIC_SCL=0;
IIC_SDA=0; 
delay_us(4);
IIC_SCL=1;
IIC_SDA=1; //sda 線輸出//STOP:when CLK is high DATA change form low to high//發送 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,發送
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.c 加入到 HARDWARE 組下面,然後在 myiic.h 裏面輸入如下代碼:

#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//IO 方向設置
#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 
#define IIC_SDA //SCL
//SDA
//輸入 SDAPBout(6) 
PBout(7) #define READ_SDA PBin(7) //IIC 所有操作函數void IIC_Init(void); //初始化 IIC 的 IO 口void IIC_Start(void); 
void IIC_Stop(void); //發送 IIC 開始信號
//發送 IIC 停止信號
//IIC 發送一個字節void IIC_Send_Byte(u8 txd); u8 IIC_Read_Byte(unsigned char ack);//IIC 讀取一個字節u8 IIC_Wait_Ack(void); //IIC 等待 ACK 信號
//IIC 發送 ACK 信號
//IIC 不發送 ACK 信號void IIC_Ack(void); void IIC_NAck(void); void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif 

該部分代碼的 SDA_IN()和 SDA_OUT()分別用於設置 IIC_SDA 接口爲輸入和輸出,如果這兩句代碼看不懂,請好好溫習下 IO 口的使用。 接下來我們在 24cxx.c 文件裏面輸入如下代碼:

//初始化 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 是否正常
//這裏用了 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
//ALIENTEK STM32 開發板使用的是 24c02,所以定義 EE_TYPE 爲 AT24C02
#define EE_TYPE AT24C02
u8 AT24CXX_ReadOneByte(u16 ReadAddr);//指定地址讀取一個字節
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); //指定地址寫入一個字節
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[]={"ELITE STM32 IIC TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key; u16 i=0;
u8 datatemp[SIZE];
Stm32_Clock_Init(9); 
uart_init(72,115200); 
delay_init(72); 
usmart_dev.init(72); 
LED_Init(); //系統時鐘設置
//串口初始化爲 115200
//延時初始化
//初始化 USMART
//初始化與 LED 連接的硬件接口 
LCD_Init(); 
KEY_Init(); 
AT24CXX_Init(); //初始化 LCD
//按鍵初始化
//IIC 初始化POINT_COLOR=RED; //設置字體爲紅色
LCD_ShowString(30,50,200,16,16,"ELITE STM32F103 ^_^");
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,"2015/1/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==KEY1_PRES)//KEY_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)//KEY1 按下,讀取字符串並顯示
{
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;} //提示系統正在運行
}
} 

該段代碼,我們通過 KEY1 按鍵來控制 24C02 的寫入,通過另外一個按鍵 KEY0 來控制24C02 的讀取。並在 LCD 模塊上面顯示相關信息。
最後,我們將 AT24CXX_WriteOneByte 和 AT24CXX_ReadOneByte 函數加入 USMART 控制,這樣,我們就可以通過串口調試助手,讀寫任何一個 24C02 的地址,方便測試。

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