記錄一下,方便以後翻閱~
主要內容:
1) I2C通訊協議;
2) 24C02芯片介紹;
3) 相關實驗代碼解讀。
實驗功能:系統啓動後,通過KEY1按鍵來控制24C02的寫入,通過另外一個按鍵KEY0來控制24C02的讀取。並在串口調試助手上面顯示相關信息。
官方資料:《STM32中文參考手冊V10》第24章——I2C接口
1. I2C通訊協議概念
I2C(IIC,Inter-Integrated Circuit),兩線式串行總線,由PHILIPS公司開發用於連接微控制器及其外圍設備。
I2C是由數據線SDA和時鐘SCL構成的串行總線,可發送和接收數據。在CPU與被控IC之間、IC與IC之間進行雙向傳送,高速IIC總線一般可達400kbps以上。
I2C是半雙工通信方式(這個在《學習心得十四:串口通信相關知識及配置方法》有講)。
I2C總線系統結構如下圖所示(這裏先提一句,I2C協議裏,空閒狀態時SDA和SCL都是高電平!):
2. I2C通信協議及相關代碼解讀
2.1 空閒狀態
I2C總線總線的SDA和SCL兩條信號線同時處於高電平時,規定爲總線的空閒狀態。此時各個器件的輸出級場效應管均處在截止狀態,即釋放總線,由兩條信號線各自的上拉電阻把電平拉高。
2.2 開始信號和停止信號
起始信號:當SCL爲高時,SDA由高到低跳變。啓動信號是一種電平跳變時序信號,而不是一個電平信號。
起始信號相關代碼:
void IIC_Start(void)
{
SDA_OUT(); //先設SDA線爲輸出//
IIC_SDA=1; //SDA輸出高//
IIC_SCL=1; //SCL輸出高,此時爲空間狀態//
delay_us(4);
IIC_SDA=0; //SDA輸出由高變低,IIC開始//
delay_us(4);
IIC_SCL=0; //鉗住I2C總線,準備發送或接收數據//
}
停止信號:當SCL爲高時,SDA由低到高跳變。停止信號是一種電平跳變時序信號,而不是一個電平信號。
停止信號相關代碼:
void IIC_Stop(void)
{
SDA_OUT(); //先設SDA線爲輸出//
IIC_SCL=0; //SCL輸出低//
IIC_SDA=0; //SDA輸出低//
delay_us(4);
IIC_SCL=1; //SCL輸出高//
IIC_SDA=1; //SDA輸出由低變高,發送I2C總線結束信號//
delay_us(4);
}
2.3 應答信號
發送器每發送一個字節,就在時鐘脈衝9期間釋放數據線,由接收器反饋一個應答信號。應答信號爲低電平時,規定爲有效應答位(ACK簡稱應答位),表示接收器已經成功地接收了該字節;應答信號爲高電平時,規定爲非應答位(NACK),一般表示接收器接收該字節沒有成功。
等待應答信號的相關代碼:
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA設置爲輸入//
IIC_SDA=1;delay_us(1); //SDA設高電平//
IIC_SCL=1;delay_us(1); //SCL設高電平//
while(READ_SDA) //讀取PB7的值,0或1//
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1; //失敗//
}
}
IIC_SCL=0; //當PB7值爲0,則SCL時鐘輸出低電平,0//
return 0; //成功//
}
對於反饋有效應答位ACK的要求是,接收器在第9個時鐘脈衝之前的低電平期間將SDA線拉低,並且確保在該時鐘的高電平期間爲穩定的低電平(有效應答應這樣編寫)。
有效/無效應答相關代碼:
void IIC_Ack(void) //有效應答//
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
void IIC_NAck(void) //無效應答//
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
2.4 數據的有效性:
I2C總線進行數據傳送時,時鐘信號爲高電平期間,數據線上的數據必須保持穩定,只有在時鐘線上的信號爲低電平期間,數據線上的高電平或低電平狀態才允許變化。
即:數據在SCL的上升沿到來之前就需準備好。並在在下降沿到來之前必須穩定。
發送數據相關代碼(SCL爲高電平時,發送數據):
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //SCL爲低電平時,可以改變SDA,以實現數據的有效傳輸//
for(t=0;t<8;t++)
{
if((txd&0x80)>>7) //(txd&0x80)>>7值要麼是0要麼是1,IIC是從最高位開始傳輸數據的//
IIC_SDA=1; //(txd&0x80)>>7值爲1時,SDA給1//
Else
IIC_SDA=0; //(txd&0x80)>>7值爲0時,SDA給0//
txd<<=1;
delay_us(2); //對TEA5767這三個延時都是必須的//
IIC_SCL=1; //SCL爲高電平時,傳輸SDA//
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
讀數據相關代碼(SCL爲高電平時,接收數據):
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char
i,receive=0; //無符號字符型,對應整數範圍0~255//
SDA_IN(); //SDA設置爲輸入//
for(i=0;i<8;i++)
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1; //SCL由低設高後,開始讀信號//
receive<<=1; //先將之前接收的信號左移,最右位補0//
if(READ_SDA)receive++; //如果接收SDA爲1,則最右位改爲1//
delay_us(1); //如果接收SDA爲0,則不做操作//
}
if (!ack)
IIC_NAck(); //發送nACK//
else
IIC_Ack(); //發送ACK//
return receive;
}
2.5 數據傳輸:
在I2C總線上傳送的每一位數據都有一個時鐘脈衝相對應(或同步控制),即在SCL串行時鐘的配合下,在SDA上逐位地串行傳送每一位數據。數據位的傳輸是邊沿觸發。
3. 開發版的硬件連接
如下圖所示:
PB6對應IIC的時鐘線,PB7對應IIC的數據線。
PB6和PB7接在EEPROM(24C02)的SCL和SDA上。
4. EEPROM(24C02)介紹
EEPROM (Electrically Erasable Programmable read only memory)是指帶電可擦可編程只讀存儲器。是一種掉電後數據不丟失的存儲芯片。
24C02是一個2Kbit的串行EEPROM存儲芯片,可存儲256(2K/8)個字節數據。通過I2C總線通訊讀寫芯片數據,通訊時鐘頻率可達400KHz。(備註,1Byte=8bit)
4.1 24C02芯片引腳定義如下圖所示:
4.2 A0-A2的引腳用來設置24C02的地址線,由開發版給的硬件連接圖可知,默認A0、A1、A2跟GND連接。
24C02的設備地址由下圖的第一行所示:
上圖中,24C02地址的前4位爲0b1010,即0xA,後三位由A2、A1、A0決定,即0b000,最後一位根據讀或寫決定,讀至1,寫至0,因此24CO2的地址讀寫地址分別爲:0b10100001(0xA1)和0b10100000(0xA0)。
4.3 24C02字節寫時序/讀時序
24C02寫一個字節數據的驅動程序代碼:
//寫入一個字節,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);
}
24C02讀一個字節數據的驅動程序代碼:
//讀一個字節, ReadAddr:開始讀數的地址,返回值:讀到的數據//
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16) //實際用的是24C02,小於AT24C16,所以直接跳到else//
{
IIC_Send_Byte(0XA0); //發送寫命令//
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8); //發送高地址//
IIC_Wait_Ack();
}
else
IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //發送設備地址0XA0,寫數據,(ReadAddr/256)<<1)值爲0//
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;
}
4.4 其他24C02驅動程序
4.4.1 初始化AT24CXX函數
void AT24CXX_Init(void)
{
IIC_Init();
}
4.4.2 檢查AT24C02是否正常函數
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255); //避免每次開機都寫AT24CXX//
if(temp==0X55)return 0; //返回0說明正常//
else //排除第一次初始化的情況//
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}
4.4.3 發送一段數據函數
//WriteAddr :開始寫入的地址 對24c02爲0~255,pBuffer :數據數組首地址,NumToWrite:要寫入數據的個數//
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
4.4.4 讀取一段數據函數
// ReadAddr :開始讀出的地址對24c02爲0~255,pBuffer :數據數組首地址,NumToRead:要讀出數據的個數//
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
5. 實驗相關代碼解讀
5.1 myiic.h頭文件代碼解讀
#define __MYIIC_H
#include "sys.h"
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} //上拉輸入模式//
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //推輓輸出,50MHz//
#define IIC_SCL PBout(6) //SCL//
#define IIC_SDA PBout(7) //SDA//
#define READ_SDA PBin(7) //輸入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
5.2 myiic.c文件代碼解讀
#include "myiic.h"
#include "delay.h"
//編寫初始化IIC函數//
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); //PB6,PB7 輸出高//
}
//編寫IIC起始信號函數//
void IIC_Start(void)
{
SDA_OUT(); //SDA線設爲輸出//
IIC_SDA=1; //SDA輸出高//
IIC_SCL=1; //SCL輸出高,此時空間狀態//
delay_us(4);
IIC_SDA=0; //SDA輸出低,IIC開始//
delay_us(4);
IIC_SCL=0; //鉗住I2C總線,準備發送或接收數據//
}
//編寫IIC停止信號函數//
void IIC_Stop(void)
{
SDA_OUT(); //SDA線設爲輸出//
IIC_SCL=0; //SCL輸出低//
IIC_SDA=0; //SDA輸出低//
delay_us(4);
IIC_SCL=1; //SCL輸出高//
IIC_SDA=1; //SDA輸出高,發送I2C總線結束信號//
delay_us(4);
}
//編寫IIC等待應答信號函數,返回值:1,接收應答失敗;0,接收應答成功//
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA設置爲輸入//
IIC_SDA=1;delay_us(1); //SDA設高電平//
IIC_SCL=1;delay_us(1); //SCL設高電平//
while(READ_SDA) //讀取PB7的值,0或1//
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1; //失敗//
}
}
IIC_SCL=0; //當PB7值爲0,則SCL時鐘輸出低電平,0//
return 0; //成功//
}
//編寫IIC產生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;
}
//編寫IIC不產生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; //SCL爲低電平時,可以改變SDA,以實現數據的有效傳輸//
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7) //(txd&0x80)>>7值要麼是0要麼是1,IIC是從最高位開始傳輸數據的//
IIC_SDA=1; //(txd&0x80)>>7值爲1時,SDA給1//
else
IIC_SDA=0; //(txd&0x80)>>7值爲0時,SDA給0//
txd<<=1;
delay_us(2); //對TEA5767這三個延時都是必須的
IIC_SCL=1; //SCL爲高電平時,傳輸SDA//
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; //無符號字符型,對應整數範圍0~255//
SDA_IN();//SDA設置爲輸入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1; //SCL由低設高後,開始讀信號//
receive<<=1; //先將之前接收的信號左移,最右位補0//
if(READ_SDA)receive++; //如果接收SDA爲1,則最右位改爲1//
delay_us(1); //如果接收SDA爲0,則不做操作//
}
if (!ack)
IIC_NAck(); //發送nACK
else
IIC_Ack(); //發送ACK
return receive;
}
5.3 main.c文件代碼解讀
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "24cxx.h"
//要寫入到24c02的字符串數組
const u8 TEXT_Buffer[]={"這是從24C02讀取的數據"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
delay_init(); //延時函數初始化//
uart_init(115200); //串口初始化爲115200//
LED_Init(); //初始化與LED連接的硬件接口//
KEY_Init();
AT24CXX_Init(); //IIC初始化//
while(AT24CXX_Check()) //檢測24C02//
{
printf("檢測不到值\n");
LED0=!LED0; //DS0閃爍//
LED1=!LED1; //DS1閃爍//
delay_ms(100);
}
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES) //KEY_UP按下,寫入24C02數據//
{
AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
printf("向24C02寫入數據\n");
}
if(key==KEY0_PRES) //KEY0按下,讀取字符串並顯示
{
AT24CXX_Read(0,datatemp,SIZE);
printf("從24C02讀取數據,內容爲:%s\n",datatemp);
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0; //提示系統正在運行
i=0;
}
}
}
6. 實驗結果
實驗結果如下圖所示:
舊知識點
1)複習如何新建工程模板,可參考STM32學習心得二:新建工程模板;
2)複習基於庫函數的初始化函數的一般格式,可參考STM32學習心得三:GPIO實驗-基於庫函數;
3)複習寄存器地址,可參考STM32學習心得四:GPIO實驗-基於寄存器;
4)複習位操作,可參考STM32學習心得五:GPIO實驗-基於位操作;
5)複習寄存器地址名稱映射,可參考STM32學習心得六:相關C語言學習及寄存器地址名稱映射解讀;
6)複習時鐘系統框圖,可參考STM32學習心得七:STM32時鐘系統框圖解讀及相關函數;
7)複習延遲函數,可參考STM32學習心得九:Systick滴答定時器和延時函數解讀;
8)複習ST-LINK仿真器的參數配置,可參考STM32學習心得十:在Keil MDK軟件中配置ST-LINK仿真器;
9)複習ST-LINK調試方法,可參考STM32學習心得十一:ST-LINK調試原理+軟硬件仿真調試方法;
10)複習串口通信相關知識,可參考STM32學習心得十四:串口通信相關知識及配置方法。