個人筆記
文章目錄
- 1. 我寫的 C51 I2C 時序
- 2. 普中51提供的 I2C 時序
- 3. 正點原子 STM32 的 I2C 時序
- 4. ARM Linux 內核 I2C 時序
- 5. 涉及到的 I2C 代碼(軟體模擬 I2C)
- 我的
- 普中科技的
- 正點原子的
前幾天寫了一篇關于 I2C 的文章,發現 I2C 其實還挺簡單的,(前提是不考慮多主機,10位尋址我也沒去研究)
那篇文章的 I2C 代碼是我按照 AT24C02 芯片手冊里的時序圖撰寫的(同時還參考了許多代碼模板),雖然功能實作了,但總覺得自己寫的代碼可能不夠規范,
所以今天我想使用邏輯分析儀(淘寶買的雜牌便宜貨😁),對 I2C 波形一探究竟,我將拿我的代碼和普中51單片機教程給出的 I2C 范例程式及正點原子STM32教程提供的 I2C 范例程式做一個簡單的對比,(對比時序波形),
【本人水平有限,本文的內容僅供參考,不能保證100%靠譜,】
1. 我寫的 C51 I2C 時序
這里只分析時序圖,對應 I2C 代碼見文章最后一章
總覽:
主機先向從機寫入資料0x00,然后讀取從機回傳的資料0x08,從機地址(設備地址+讀寫位)為0xA0,

起始信號:
起始信號:時鐘SCL高電平時,資料SDA由高變低,由于51單片機時鐘頻率低,空陳述句延時達到了30us,不過 I2C 周期長短只會影響它的傳輸速率,并不會導致傳輸失敗,

時鐘低電平修改SDA:
在時鐘SCL低電平期間,改變SDA狀態,下圖中,SDA電平變化至時鐘上升沿的時間間距為3.25us,這是由于我代碼中發送資料(改變SDA)和拉高時鐘電平寫在一起,中間沒有任何延時代碼,(總感覺這中間應該加一個延時代碼,其他人的代碼這里都加了延時)

傳輸8位地址資料:
主設備向從設備發送了1個位元組的資料,這里的資料是地址資料,時鐘SCL高電平時對應的資料SDA即為每位傳輸的資料,SDA高電平表示1,SDA為低表示0,一共8個周期,傳輸8位二機制資料,

等待從機應答:
在第9個周期前,需要將SDA拉高(下圖SDA并沒有出現高電平,說明在我們手動拉高瞬間,從機就把SDA拉低了,?),如果在第9個周期的高電平期間,從機已經將SDA拉低,說明從機發出了應答信號ACK,雙方繼續通信,

發送1個位元組的資料:
和上面的發送地址資料相同,這里繼續發送一個位元組的資料,可以看到SDA一直是低,說明傳輸的資料是0x00,

重新起始信號:
由于接下來需要切換成讀操作,所以主機需要再發送一個起始信號(又稱重新起始信號),

發送設備地址(讀):
和之前的第一個發送地址資料(設備地址+讀寫位)的時序相同,唯一的區別是上次發的是寫地址,這次發送的位讀地址,為0xA1(最后1位為1,表示主機讀資料),

讀取1個位元組的資料:
和寫操作時序類似,只是傳輸方向由主機->從機變成主機<-從機,

從機發送非應答信號+主機發送停止信號:
資料讀取完成后,從機發出非應答信號(不把SDA拉低),第9個周期過后,主機發送停止信號,結束本次通信,
停止信號:SCL為高電平時,資料SDA由低變高,

2. 普中51提供的 I2C 時序
這里只分析時序圖,對應 I2C 代碼見文章最后一章
總覽
和上文一樣的操作,寫三個位元組(2個地址資料+1個普通資料),讀取一個位元組,

這份時序中,在修改SDA電平的前后都加了延時,也就是低電平的持續時間是我的兩倍,也是高電平時間的兩倍,

應答信號的處理方式也和我不同,這份時序的第9個周期的高電平時間不是固定的,而是只要檢測到SDA被從機拉低,就算應答信號完成,所以周期信號重要的不是維持時間(但也有最小時間限制),而是上升沿信號,

其他地方和我的時序沒太大區別,
3. 正點原子 STM32 的 I2C 時序
這里只分析時序圖,對應 I2C 代碼見文章最后一章
由于我沒接 I2C 從機,所以沒有應答信號,這份時序和普中51的很類似,比如低電平時長為高低平的兩倍(在中間更改SDA電平),第9個周期采用回圈讀取應答信號,如果檢測到應答信號,就將時鐘拉低,否則一直檢測,直到超時(回圈內有計數值限制回圈時間),
另外,由于這款32位芯片的時鐘頻率較高,所以一個周期時長只有9.542us,而普中51的時序中周期時長為57.5us,

4. ARM Linux 內核 I2C 時序
內核的 I2C 原始碼我不太了解,平時只用過內核自帶的 I2C 介面函式
總覽
LInux 內核的 I2C 時序有個特點,起始信號、結束信號和中間資料傳輸直接隔得很開,讀和寫也分開了,但時序規則沒變,

發送地址資料
一個周期的時長為4.99us,高電平2us,低電平3us,9個周期長度相同,

讀取資料

5. 涉及到的 I2C 代碼(軟體模擬 I2C)
下面僅給出 I2C 的功能代碼和簡單的外設讀寫代碼(組成完整 I2C 時序),不涉及主函式和其他無關功能代碼
我的
代碼僅用來參考 I2C 具體實作程序,不能直接運行
/******************************************************************************
* @ 函式名 : Delay_10us
* @ 功 能 : 10us粗略延時
* @ 參 數 : 延時時間--單位10us
* @ 回傳值 : 無
******************************************************************************/
void Delay_10us(unsigned int time)
{
while(time--);
}
/******************************************************************************
* @ 函式名 : I2c_Delay
* @ 功 能 : I2C延時函式
* @ 參 數 : 無
* @ 回傳值 : 無
******************************************************************************/
void I2c_Delay()
{
Delay_10us(1);
}
/******************************************************************************
* @ 函式名 : I2c_Start
* @ 功 能 : I2C起始信號
* @ 參 數 : 無
* @ 回傳值 : 無
******************************************************************************/
void I2c_Start()
{
sda = 1;
scl = 1;
I2c_Delay(); //起始信號建立時間
sda = 0; //SDA拉低,下降沿
I2c_Delay(); //起始信號保持時間
scl = 0;
}
/******************************************************************************
* @ 函式名 : I2c_Stop
* @ 功 能 : I2C停止信號
* @ 參 數 : 無
* @ 回傳值 : 無
******************************************************************************/
void I2c_Stop()
{
scl = 0;
I2c_Delay(); //上一個時鐘周期的低電平
sda = 0;
scl = 1;
I2c_Delay(); //停止信號建立時間
sda = 1; //SDA拉高,上升沿
I2c_Delay(); //總線空閑時間保持
}
/******************************************************************************
* @ 函式名 : I2c_Ack
* @ 功 能 : I2C應答信號
* @ 參 數 : 無
* @ 回傳值 : 無
******************************************************************************/
void I2c_Ack()
{
scl = 0;
sda = 0; //SDA拉低,發出應答信號
I2c_Delay();
scl = 1;
I2c_Delay();
scl = 0;
}
/******************************************************************************
* @ 函式名 : I2c_No_Ack
* @ 功 能 : I2C非應答信號
* @ 參 數 : 無
* @ 回傳值 : 無
******************************************************************************/
void I2c_No_Ack()
{
scl = 0;
sda = 1; //SDA拉高,發出非應答信號
I2c_Delay();
scl = 1;
I2c_Delay();
scl = 0;
}
/******************************************************************************
* @ 函式名 : I2c_Wait_Ack
* @ 功 能 : I2C等待應答信號
* @ 參 數 : 無
* @ 回傳值 : 1:接收到應答信號,0:接收到非應答信號
******************************************************************************/
unsigned char I2c_Wait_Ack()
{
unsigned char ack = 0;
sda = 1;
scl = 0;
I2c_Delay();
scl = 1;
I2c_Delay();
if(sda == 0) //檢測資料線SDA是否被拉低
ack = 1;
else
ack = 0;
scl = 0;
return ack;
}
/******************************************************************************
* @ 函式名 : I2c_Write_Byte
* @ 功 能 : I2C寫位元組
* @ 參 數 : dat 要寫入的位元組資料
* @ 回傳值 : 無
******************************************************************************/
void I2c_Write_Byte(unsigned char dat)
{
unsigned char i = 0;
for(i = 0; i < 8; i++) //讀取8位
{
scl = 0;
I2c_Delay();
if(dat & 0x80) //發送最高位
sda = 1;
else
sda = 0;
scl = 1;
I2c_Delay();
dat <<= 1; //左移1位
}
scl = 0;
}
/******************************************************************************
* @ 函式名 : I2c_Read_Byte
* @ 功 能 : I2C讀位元組
* @ 參 數 : 無
* @ 回傳值 : 讀取的位元組資料
******************************************************************************/
unsigned char I2c_Read_Byte()
{
unsigned char dat = 0, i = 0;
for(i = 0; i < 8; i++) //讀取8位
{
dat <<= 1; //左移1位
scl = 0;
I2c_Delay();
scl = 1; //SCL高電平
I2c_Delay();
if(sda) //讀取SDA狀態
dat |= 0x1;
}
scl = 0;
return dat;
}
/******************************************************************************
* @ 函式名 : At24c02_Write
* @ 功 能 : AT24C02寫位元組
* @ 參 數 :
* addr 要寫資料的地址(存盤空間)
* dat 要寫入的位元組資料
* @ 回傳值 : 無
******************************************************************************/
void At24c02_Write(unsigned char addr, unsigned char dat)
{
I2c_Start();
I2c_Write_Byte(0xA0); //發送I2C設備地址
I2c_Wait_Ack(); //等待從機回應
I2c_Write_Byte(addr); //發送要寫入的記憶體地址
I2c_Wait_Ack();
I2c_Write_Byte(dat); //寫入資料
I2c_Wait_Ack();
I2c_Stop();
Delay_10us(1000); //寫周期
}
/******************************************************************************
* @ 函式名 : At24c02_Read
* @ 功 能 : AT24C02讀位元組
* @ 參 數 : addr 要讀資料的地址(存盤空間)
* @ 回傳值 : 讀取的位元組資料
******************************************************************************/
unsigned char At24c02_Read(unsigned char addr)
{
unsigned char dat = 0, i = 0;
I2c_Start();
I2c_Write_Byte(0xA0); //發送I2C設備地址
I2c_Wait_Ack(); //等待從機回應
I2c_Write_Byte(addr); //發送要寫入的記憶體地址
I2c_Wait_Ack();
I2c_Start();
I2c_Write_Byte(0xA1); //發送I2C設備地址(讀資料)
I2c_Wait_Ack(); //等待從機回應
dat = I2c_Read_Byte(); //讀取資料
I2c_Wait_Ack();
I2c_Stop();
return dat;
}
————————————————
著作權宣告:本文為CSDN博主「小輝_Super」的原創文章,遵循CC 4.0 BY-SA著作權協議,轉載請附上原文出處鏈接及本宣告,
原文鏈接:https://blog.csdn.net/weixin_43772810/article/details/122149151
普中科技的
代碼僅用來參考 I2C 具體實作程序,不能直接運行
#include"i2c.h"
/*******************************************************************************
* 函式名 : Delay10us()
* 函式功能 : 延時10us
* 輸入 : 無
* 輸出 : 無
*******************************************************************************/
void Delay10us()
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=2;a>0;a--);
}
/*******************************************************************************
* 函式名 : I2cStart()
* 函式功能 : 起始信號:在SCL時鐘信號在高電平期間SDA信號產生一個下降沿
* 輸入 : 無
* 輸出 : 無
* 備注 : 起始之后SDA和SCL都為0
*******************************************************************************/
void I2cStart()
{
SDA=1;
Delay10us();
SCL=1;
Delay10us();//建立時間是SDA保持時間>4.7us
SDA=0;
Delay10us();//保持時間是>4us
SCL=0;
Delay10us();
}
/*******************************************************************************
* 函式名 : I2cStop()
* 函式功能 : 終止信號:在SCL時鐘信號高電平期間SDA信號產生一個上升沿
* 輸入 : 無
* 輸出 : 無
* 備注 : 結束之后保持SDA和SCL都為1;表示總線空閑
*******************************************************************************/
void I2cStop()
{
SDA=0;
Delay10us();
SCL=1;
Delay10us();//建立時間大于4.7us
SDA=1;
Delay10us();
}
/*******************************************************************************
* 函式名 : I2cSendByte(unsigned char dat)
* 函式功能 : 通過I2C發送一個位元組,在SCL時鐘信號高電平期間,保持發送信號SDA保持穩定
* 輸入 : num
* 輸出 : 0或1,發送成功回傳1,發送失敗回傳0
* 備注 : 發送完一個位元組SCL=0,SDA=1
*******************************************************************************/
unsigned char I2cSendByte(unsigned char dat)
{
unsigned char a=0,b=0;//最大255,一個機器周期為1us,最大延時255us,
for(a=0;a<8;a++)//要發送8位,從最高位開始
{
SDA=dat>>7; //起始信號之后SCL=0,所以可以直接改變SDA信號
dat=dat<<1;
Delay10us();
SCL=1;
Delay10us();//建立時間>4.7us
SCL=0;
Delay10us();//時間大于4us
}
SDA=1;
Delay10us();
SCL=1;
while(SDA)//等待應答,也就是等待從設備把SDA拉低
{
b++;
if(b>200) //如果超過2000us沒有應答發送失敗,或者為非應答,表示接收結束
{
SCL=0;
Delay10us();
return 0;
}
}
SCL=0;
Delay10us();
return 1;
}
/*******************************************************************************
* 函式名 : I2cReadByte()
* 函式功能 : 使用I2c讀取一個位元組
* 輸入 : 無
* 輸出 : dat
* 備注 : 接收完一個位元組SCL=0,SDA=1.
*******************************************************************************/
unsigned char I2cReadByte()
{
unsigned char a=0,dat=0;
SDA=1; //起始和發送一個位元組之后SCL都是0
Delay10us();
for(a=0;a<8;a++)//接收8個位元組
{
SCL=1;
Delay10us();
dat<<=1;
dat|=SDA;
Delay10us();
SCL=0;
Delay10us();
}
return dat;
}
/*******************************************************************************
* 函式名 : void At24c02Write(unsigned char addr,unsigned char dat)
* 函式功能 : 往24c02的一個地址寫入一個資料
* 輸入 : 無
* 輸出 : 無
*******************************************************************************/
void At24c02Write(unsigned char addr,unsigned char dat)
{
I2cStart();
I2cSendByte(0xa0);//發送寫器件地址
I2cSendByte(addr);//發送要寫入記憶體地址
I2cSendByte(dat); //發送資料
I2cStop();
}
/*******************************************************************************
* 函式名 : unsigned char At24c02Read(unsigned char addr)
* 函式功能 : 讀取24c02的一個地址的一個資料
* 輸入 : 無
* 輸出 : 無
*******************************************************************************/
unsigned char At24c02Read(unsigned char addr)
{
unsigned char num;
I2cStart();
I2cSendByte(0xa0); //發送寫器件地址
I2cSendByte(addr); //發送要讀取的地址
I2cStart();
I2cSendByte(0xa1); //發送讀器件地址
num=I2cReadByte(); //讀取資料
I2cStop();
return num;
}
正點原子的
代碼僅用來參考 I2C 具體實作程序,不能直接運行
//
//本程式只供學習使用,未經作者許可,不得用于其它任何用途
//ALIENTEK戰艦STM32開發板
//IIC驅動 代碼
//正點原子@ALIENTEK
//技術論壇:www.openedv.com
//修改日期:2012/9/9
//版本:V1.0
//著作權所有,盜版必究,
//Copyright(C) 廣州市星翼電子科技有限公司 2009-2019
//All rights reserved
//
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, 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(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,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;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
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;
}
//在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);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/400358.html
標籤:其他
上一篇:通過STM32單片機計算并控制小車速度,通過控制速度的思想擴展到控制其它變化量
下一篇:關于HDMI之TMDS的硬體總結
