1、I2C介紹
2、I2C通訊程序介紹
3、函式配置
4、代碼
1、I2C介紹
i2c總線是nxp公司(philp公司)開發的兩線式串行總線,用于連接微控制器及外圍設備,它是由資料線SDA和時鐘線SCL構成,我們平常使用還會加上一個供電即vcc和gnd,外設可能還會有一些其他介面比如寫保護、還有地址線(用于選擇掛在i2c總線上的特定設備,通常i2c總線上會掛載很多外圍設,怎么選擇相應的設備就需要這個地址線來確定)i2c是同步的半雙工通信協議(區分是異步還是同步主要看有沒有時鐘信號線,區分是全雙工還是半雙工主要看是有專門的發送和接收資料線,這里i2c有時鐘信號線和一條資料線,所以是同步的半雙工的協議),i2c有好幾種傳輸速率,一般模式傳輸速率為100kb/s,高速的i2c的傳輸速率可以達到400kb/s.i2c的信號有三種型別的信號,分別是開始信號、結束信號、應答信號,
開始信號是是scl為高電平的期間,sda由高電平向低電平跳變,
結束信號是scl信號是高電平期間,sda由低電平向高電平跳變,
應答信號是cpu接收到一個位元組的信號時,外設會釋放總線的控制權,從而cpu會產生一個應答信號,外設接收到應答信號之后會繼續發送下一個位元組的資料,如果外設沒有接收到應答信號或者接收到的非應答信號,那么外設會停止發送資料,應答信號是在SCL變成高電平之前就將SDA置為低電平,然后我們將SCL變成高電平,在此期間保持SDA為低電平,最后再將SCL電平拉低,如果此程序中SDA信號始終是高電平,那么產生的就是非應答信號,
2、I2C通訊程序介紹

這邊以操作eeprom24c02來舉例分別說明寫時序和讀時序
寫時序:首先cpu會產生一個起始信號,然后cpu的下一個位元組就的前七位就是我們選擇操作的外設地址(一般為7位,也有10位)第八位就是我們需要對外設進行的操作(0表示寫操作,1表示讀操作),然后等待外設的應答信號,收到設備的應答信號之后,cpu開始發送資料給外設,每發送一個資料,外設都要產生一個應答信號,最后一個位元組傳輸結束后,產生一個結束信號,
讀時序:首先cpu會產生一個起始信號,然后cpu發送的下一個位元組的前七位就是我們選擇操作的外設的地址,第八位就是我們的讀操作位0,然后等待外設的應答信號,收到設備的應答信號后,會再次產生一個起始信號,發送的第一個位元組就是外設暫存器的偏移地址(意思是我要讀取你外設上的哪一個區域的資訊)同樣的,每收到一個位元組,cpu都會產生一個應答信號,直到最后讀取結束,外設會產生一個終止信號,
3、函式配置
這里我們的函式配置,實際是模擬i2c,意思是整個i2c的時序都是通過軟體拉高拉低電平來實作的(硬體i2c是與模擬i2c不同的,硬體i2c是我們需要產生一個起始信號,我們只需要發送這個命令給硬體,硬體自動產生一個起始信號,就不需要對我們的i2c時序這么清楚,但是模擬i2c就需要對i2c的時序很清楚才能夠進行配置)
1、初始化io口,這里選取PB6為SCL,PB7為SDA,將IO口的作業模式配置為推挽輸出,其他沒什么需要注意的,
2、起始信號(SCL信號為高電平期間,SDA從高電平變成低電平)
3、終止信號(SCL信號為高電平期間,SDA從低電平到高電平)
4、等待應答信號(因為應答信號是低電平,所以在等待應答信號的時候,我們先將SCL、SDA信號拉高,然后再讀取SDA上的信號,如果接收到SDA是低電平,那么認為接收到了應答信號,就將SCL信號拉低,讀取應答信號結束,如果讀取到的SDA的信號是高電平,那么我們認為收到的是非應答信號,定義一個變數,稍微計時一下,如果到最后都沒有收到應答信號,那么就認為沒有收到應答信號,I2C通信終止)

5、產生應答信號(在SCL信號拉高之前,將SDA信號拉低,然后在SCL信號高電平期間,SDA信號始終為低電平,最后將SCL信號拉低,應答信號結束)
6、發送一個位元組(在SCL拉高之前就必須將資訊準備好,然后拉高SCL,開始發送,在發送的時候需要注意只有在SCL信號高電平的期間,我們才認為發送的資訊是有效的,比如我們發送的一個位是1,下一個位是0,我們需要在SDA信號切換之前,就將SCL信號拉低,然后對SDA信號進行變換,變換結束之后,我們再將SCL信號拉高進行傳輸)

這里的流程是先將SCL拉低(資料還沒準備好),然后將最高位與1與,將所得資料右移7位,資料準備好后,拉高SCL,發送資料,然后將資料左移一位繼續操作,重復八次,就將所有的8位資料全部讀取,
7、接收一個位元組,這里的流程是我接收到一位之后,第一位是最高位,我將這一位左移一次,然后重復八次,就完成了一個位元組的接收,完成一次接收之后,我需要發送一個應答信號給外設,告訴外設我已經接收到了一個位元組,外設收到應答信號之后才會開始下一位元組的發送,
8、這里我么操作的2401的地址根據所給電路可以得出,這里我們的A0、A1、A2 連接的是低電平,就是我們所謂的地址1011 000 1,這個地址有七位,前面四位是固定的,后面三位就是這三位決定的,第八位就是我們之前說的操作,是寫操作還是讀操作這位決定,所以我們的操作地址就是固定的為0XA0(寫)和0XA1(讀),

這里在我看在正點原子的代碼里看到兩句代碼,這邊解釋下,也方面日后自己理解
在myiic.h中它有兩句宏定義的代碼
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
這兩句話我找資料理解了一下,這邊它操作的是CRL暫存器,CRL暫存器是對A-G的IO口的低八位的模式進行操作,CRH暫存器是對高八位進行操作,第一行代碼就是將SDA配置為上拉輸入\下拉輸入,第二句代碼就是將SDA配置為推挽輸出,這邊鑒于自己學習不夠深,具體的暫存器如何配置的不做贅述,具體可以去搜索下,
4、代碼
i2c
#include "myiic.h"
#include "delay.h"
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;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//鉗住I2C總線,準備發送或接收資料
}
//產生IIC停止信號
void IIC_Stop(void)
{
SDA_OUT();//sda線輸出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//發送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,發送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;
}
24c01
#include "24cxx.h"
#include "delay.h"
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);//發送高地址
IIC_Wait_Ack();
}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);
}
//在AT24CXX里面的指定地址開始寫入長度為Len的資料
//該函式用于寫入16bit或者32bit的資料.
//WriteAddr :開始寫入的地址
//DataToWrite:資料陣列首地址
//Len :要寫入資料的長度2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{
u8 t;
for(t=0;t<Len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}
//在AT24CXX里面的指定地址開始讀出長度為Len的資料
//該函式用于讀出16bit或者32bit的資料.
//ReadAddr :開始讀出的地址
//回傳值 :資料
//Len :要讀出資料的長度2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{
u8 t;
u32 temp=0;
for(t=0;t<Len;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++;
}
}
main
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "usmart.h"
#include "24cxx.h"
const u8 TEXT_Buffer[]={"Elite STM32 IIC TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
delay_init(); //延時函式初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設定中斷優先級分組為組2:2位搶占優先級,2位回應優先級
uart_init(115200); //串口初始化為115200
LED_Init(); //初始化與LED連接的硬體介面
LCD_Init(); //初始化LCD
KEY_Init(); //按鍵初始化
AT24CXX_Init(); //IIC初始化
POINT_COLOR=RED;//設定字體為紅色
LCD_ShowString(30,50,200,16,16,"Elite 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,"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;
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/377201.html
標籤:其他
上一篇:[RISC-V]移植rtthread nano到risc-v芯片
下一篇:Arduino和樹莓派 對比
