河南馳誠電氣股份有限公司 朱 斌 張 磊 怯肇乾
意法半導體STM(STMicroelectronics)的Cortex-M系列微控制器MCU(Micro Control Unit),性價比高,應用廣泛。然而,其片內(nèi)集成的硬件IIC總線(Inter-Integrated Circuit)接口運用,幾乎是共認的“雞肋”。此類MCU外接IIC設備,如數(shù)據(jù)隨機存儲器RAM(Ramdom Access Memory)、傳感器Sensor、液晶顯示模塊LCM()等,很多工程師,寧可用通用輸入輸出端口GPIO(General Purpose Input Output)模擬實現(xiàn)IIC通信,也不用其片內(nèi)集成的IIC接口。
選用片內(nèi)IIC接口,即使采用STM提供的庫驅(qū)動函數(shù),程序卡死或跑飛,是“家常便飯”。GPIO模擬IIC通信,盡管容易實現(xiàn),但它耗用MCU資源多,形成的IIC時序不規(guī)范,而且為保證IIC時序的完整性而在通信期間關閉系統(tǒng)中斷又會嚴重影響嵌入式軟件體系的整體調(diào)度性能。
GPIO模擬IIC“得不償失”,閑置片內(nèi)IIC“棄之可惜”。深入研究STM-MCU-IIC時序,借鑒GPIO模擬和STM庫函數(shù)通信經(jīng)驗,充分運用并簡化片內(nèi)IIC驅(qū)動通信,勢在必行。
選用片內(nèi)IIC接口,采用STM-IIC驅(qū)動庫函數(shù)或自編驅(qū)動,進行IIC總線主從通信,通常MCU為主,設備為從,很容易捕捉發(fā)現(xiàn):發(fā)送數(shù)據(jù)沒有問題,接收多個數(shù)據(jù)沒有問題,讀入1個數(shù)據(jù),IIC總線停止。對于RAM等外設的訪問,多讀幾個數(shù)據(jù)很容易回避讀入1個數(shù)據(jù)時的“尷尬”,但必需讀入1個字節(jié)的寄存器時,就不得不“面對”了。
圖1下面給出的是示波器測量空氣質(zhì)量傳感器CSS811的配置與狀態(tài)回讀時序,CSS811作為從機,地址為0x5A,對CSS811內(nèi)部寄存器0x01地址寫入0x38完成配置,之后對狀態(tài)寄存器0x00回讀1 字節(jié),即:發(fā)送0x01、0x38完成配置,發(fā)送0x00啟動狀態(tài)讀,之后讀入1 字節(jié)。
圖1說明,按1個字讀,沒有結果;按2個以上字節(jié)讀,數(shù)據(jù)線拉低,致使IIC總線停止。
圖1 片內(nèi)IIC原始收發(fā)時序展示圖Dig.1 Transmit & receive Timing Diagram for Primitive IIC on Chip
圖2 STM-CortexMx-MCU-IIC主發(fā)送器傳送序列及其說明圖Dig.2 STM-CortexMx-MCU-IIC Master Sender Timing Diagram
圖3 STM-CortexMx-MCU-IIC主接收器傳送序列及其說明圖Dig.3 STM-CortexMx-MCU-IIC Slaver Sender Timing Diagram
深入研究各類STM-CortexMx-MCU參考手冊的IIC總線控制器,以7位地址主機收發(fā)為例,歸納IIC操作控制時序,如圖2、3所示。
對比圖2、3的傳送序列和片內(nèi)IIC驅(qū)動的現(xiàn)狀,可以看到:IIC發(fā)送和多字節(jié)接收,各個操控時刻點和內(nèi)容非常明確,所以很容易編程實現(xiàn);而單字節(jié)接收的操控時刻點EV6_1極難把握,幾乎沒有辦法及時發(fā)出清除位和產(chǎn)生停止位。在發(fā)出“從設備地址slvAddr+讀r”后,產(chǎn)生停止位,就會收不到任何數(shù)據(jù);在收好數(shù)據(jù)后產(chǎn)生停止位,就出現(xiàn)數(shù)據(jù)線拉低的IIC總線停止現(xiàn)象。
確定單字節(jié)接收的操控時刻點EV6_1,準確給出操控指令,是片內(nèi)IIC驅(qū)動程序設計的關鍵。
針對單字節(jié)數(shù)據(jù)接收,先標記“發(fā)出從設備地址slvAddr+讀r”,再在收到數(shù)據(jù)標識并且有“發(fā)出從設備地址slvAddr+讀r”標識后“控制發(fā)送停止位并清除發(fā)出從設備地址slvAddr+讀r標識,之后讀入數(shù)據(jù)。跟蹤試驗,問題解決,并且不響應2個以上數(shù)據(jù)的接收。綜合設計恰當?shù)钠瑑?nèi)IIC驅(qū)動程序流程如圖4所示,這里采用GPIO模擬IIC的中斷關閉經(jīng)驗,在中斷中進行IIC數(shù)據(jù)收發(fā),并且設置IIC收發(fā)中斷事件優(yōu)先級僅次于系統(tǒng)調(diào)度時鐘,以確保不因其它事件插入時間過長而破壞IIC操控時序的完整性。
圖4 恰當?shù)钠瑑?nèi)IIC驅(qū)動程序流程圖Dig.4 Fitting Driver Flow Chart for IIC on Chip
驅(qū)動程序有4個部分啟動準備、從機訪問、數(shù)據(jù)接收和數(shù)據(jù)發(fā)送,前2部分是操控,后2部分是收發(fā),字節(jié)流形式,每次進入僅完成1個部分操作,不同于傳統(tǒng)的軟件流。
主要實現(xiàn)程序代碼如下,篇幅限制,略去初始化部分:
unsigned char slvAddr = 0x07; // 主機欲尋址的從機地址
unsigned char IIC2_Buf[Iic2BufSize]; // IIC2: 收發(fā)數(shù)據(jù)緩沖
int IIC2_DtaPrt;
char IIC2_MsgFlag,IIC2_EvtFlag = 0; // 接收標識: 公共信息[0-是/1-非],事件
short IIC2_DataCts; // 收發(fā)計數(shù)器
char IIC2_Mode; // 收發(fā)標識: 0--收,1--發(fā)
char IIC2_Read(unsigned int count) // IIC2數(shù)據(jù)接收(指定數(shù)量,返回標識公共信息標識)
{ IIC2_Mode = 0;IIC2_DtaPrt = 0;
IIC2_DataCts = count;
IIC2_MsgFlag = 0;IIC2_EvtFlag = 0;
IIC2_CR1 |= 1 << 8; // 啟動IIC控制傳輸[主模式]
if(IIC2_DataCts>1) IIC2_CR1 |= 1 << 10; // 允許應答
while(IIC2_DataCts) ; // 等待數(shù)據(jù)接收完成(中斷方式)
return IIC2_MsgFlag;
}
void IIC2_Write(unsigned int count) // IIC2數(shù)據(jù)發(fā)送(指定數(shù)據(jù)數(shù)量)
{ IIC2_Mode = 1;IIC2_DtaPrt = 0;
IIC2_DataCts = count;IIC2_EvtFlag = 0;
IIC2_CR1 |= 1 << 8; // 啟動IIC控制傳輸[主模式]
IIC2_CR1 |= 1 << 10; // 允許應答
while(IIC2_DataCts) ; // 等待數(shù)據(jù)接收完成(中斷方式)
IIC2_CR1 |= 1 << 9; // 停止IIC控制傳輸[主模式]
}
void Iic2Evt_Process(void) // IIC2數(shù)據(jù)收發(fā)處理
{ unsigned int iicSt = IIC2_SR2; // 獲得IIC2工作狀態(tài)
iicSt <<= 16;iicSt |= IIC2_SR1;
if((iicSt&0x00030001)==0x00030001) // 主機收發(fā): 啟動位已經(jīng)發(fā)出,準備SLA+W/R
{ if(IIC2_Mode) IIC2_DR = slvAddr << 1; // 寫W
else IIC2_DR = (slvAddr << 1) | 1; // 讀R
}
if(!IIC2_Mode) // 主機接收
{ if((iicSt&0x00030002)==0x00030002) // 已經(jīng)發(fā)出SLA+R,收到了ACK
{ if(IIC2_DataCts==1) // 只有一個字節(jié),NACK
{ IIC2_CR1 &= ~(1 << 10);
IIC2_CR1 &= ~(1 << 9);
IIC2_EvtFlag |= 1;
}
}
else if((iicSt&0x00030040)==0x00030040) // 數(shù)據(jù)接收
{ if(IIC2_EvtFlag&1)
{ IIC2_CR1 |= 1 << 9;
IIC2_EvtFlag &= ~1;
}
IIC2_Buf[IIC2_DtaPrt++] = IIC2_DR;
IIC2_DataCts--;
if(IIC2_DataCts==1) // 最后字節(jié),NACK
{ IIC2_CR1 &= ~(1 << 10);
IIC2_CR1 |= 1 << 9;
}
}}
if(IIC2_Mode) // 主機發(fā)送
{ if((iicSt&0x00070082)==0x00070082) // 已經(jīng)發(fā)出SLA+W,收到了ACK
IIC2_EvtFlag |= 1;
else if((IIC2_EvtFlag&1)&& // 第一個數(shù)據(jù)發(fā)送
((iicSt&0x00070080)==0x00070080))
{ IIC2_DR = IIC2_Buf[IIC2_DtaPrt++];
IIC2_DataCts--;IIC2_EvtFlag &= ~1;
}
else if((iicSt&0x00070084)==0x00070084) // 數(shù)據(jù)已經(jīng)傳輸,收到了ACK
{ if(IIC2_DataCts) // 繼續(xù)發(fā)送數(shù)據(jù)
{ IIC2_DR = IIC2_Buf[IIC2_DtaPrt++];
IIC2_DataCts--;
}
}}
}
所有驅(qū)動代碼,不過200行,相對GPIO模擬IIC和STM-IIC庫函數(shù),得到了最大的簡化。
圖5是示波器捕捉的前述空氣質(zhì)量傳感器CSS811(從地址0x5A)配置后的狀態(tài)查詢過程時序,先發(fā)送指令0x00,再做1字節(jié)數(shù)據(jù)接收。
圖5 空質(zhì)傳感器的1字節(jié)狀態(tài)指令讀取時序圖Dig.5 1 Byte State-Receiving Timing Diagram for Air-Sensor
圖6 是示波器捕捉的溫濕度傳感器CTH21(從地址0x40)的溫度讀取時序,發(fā)送指令0xE3后做3字節(jié)數(shù)據(jù)接收。
圖6 溫濕度傳感器的3字節(jié)數(shù)據(jù)讀取時序Dig.6 3 Bytes Data-Receiving Timing Diagram for Temperature-Humidity-Sensor
圖7 是示波器捕捉的CTH21測量的電池供電時序狀況,發(fā)送指令0xE7后做1字節(jié)數(shù)據(jù)接收。
結合圖5~7和程序仿真跟蹤,可以看出設計驅(qū)動程序,很好實現(xiàn)了各種數(shù)據(jù)的收發(fā),無論單個數(shù)據(jù)還是多個數(shù)據(jù)讀寫。之后,運用到空質(zhì)測控終端,從數(shù)個月的連續(xù)運行的結果看,新設計的片內(nèi)IIC驅(qū)動,非常給力的。
圖7 電池供電傳感器的1字節(jié)數(shù)據(jù)讀取時序Dig.1 1 Byte State-Receiving Timing Diagram for Bettery-Sensor
經(jīng)過深入的查閱分析思考和反復的測量鑒定與跟蹤仿真,找準了片內(nèi)IIC接口驅(qū)動程序合適的操控點并及時發(fā)出了操控指令,在借鑒GPIO模擬操控的基礎上,終于實現(xiàn)了STM-MCU-IIC硬件接口全面可靠高效的驅(qū)動,既提升了功能和性能,還充分簡化了驅(qū)動設計。仔細觀察IIC接收時序,無論單字節(jié)接收或多字節(jié)接收,無意中在最后都多發(fā)了一個字節(jié)的操作時鐘,這個字節(jié)數(shù)據(jù)進入了接收寄存器,只是沒有理會它罷了,是“得意”中的“敗筆”,有待徹底去除,以求“完美”。