用DSP的GPIO管腳實現(xiàn)與IC卡通信
文章出處:http://www.mjagi.com 作者:北京郵電大學 張彬 人氣: 發(fā)表時間:2011年11月03日
IC卡可以分為接觸式的和非接觸式(射頻卡),本文主要討論存儲卡和智能卡(CPU卡)這兩種接觸式IC卡的結(jié)構特點和讀寫操作,詳細敘述如何使用DSP的GPIO(通用輸入輸出)管腳實現(xiàn)與各種IC卡進行通信,并給出了DSP函數(shù)實現(xiàn)。
常見與IC卡連接的都是基于單片機的系統(tǒng),但是某些應用要求IC卡讀寫終端具有較強的實時運算和控制能力,這時DSP就是最好的選擇。以TI公司的C5409為例,它的8根HPI管腳(HD0~HD7)可以配置成GPIO使用,配置方法是在復位時將HPI16管腳置高或者HPIENA管腳置低,這樣就可以通過配置DSP內(nèi)部GPIOCR和GPIOSR兩個寄存器來控制這8根GPIO管腳的輸出和輸入。GPIOCR寄存器的低8位用來控制每個GPIO管腳的方向,若管腳為輸出則對應位設為“1”,若為輸入則設為“0”(DSP復位后GPIOCR缺省值為“0”,即GPIO管腳默認為輸入)。GPIOSR寄存器的低8位用來控制每個GPIO管腳的值,若為輸出,向?qū)粚?ldquo;1”,則該管腳輸出高,寫“0”則該管腳輸出低;若為輸入,則對應位的值為管腳上輸入的值,向其寫操作無效。
下面給出了控制GPIO的函數(shù)(控制低兩位GPIO管腳,即HD0和HD1):
#define gpio_dir *(short *)0x3c
#define gpio_val *(short *)0x3d //定義兩個寄存器的地址
void gpio_setval(short i,short j) //設置GPIO的輸出
{
if(i==0) //控制HD0管腳
gpio_val=gpio_val&0xfffe; //設為0
else if(i==1)
gpio_val=gpio_val|0x0001; //設為1
if(j==0) //控制HD1管腳
gpio_val=gpio_val&0xfffd;
else if(j==1)
gpio_val=gpio_val|0x0002;
wait();
}
void gpio_setdir(short i) //控制GPIO的方向
{
if(i==1)
gpio_dir=gpio_dir|0x0001; //設為輸出
else if(i==0)
gpio_dir=gpio_dir&0xfffe; //設為輸入
}
short gpio_getval(short i) //讀入GPIO的值
{
short j;
if(i==0) j=gpio_val&0x0001;
else if(i==1) j=(gpio_val&0x0002)>>1;
return j;
}
常見的接觸式IC卡可以分為存儲卡和智能卡(又叫作CPU卡),下面分別介紹DSP如何與這兩種卡進行連接通信。
DSP和存儲卡的連接
存儲卡只具有簡單存儲功能,實際上是一片串行EEPROM的IC卡模式,以Atmel公司的AT24C16SC為例,它實質(zhì)上就是兩線串行EEPROM AT24C16,兩者接口時序基本一樣。存儲卡的管腳如圖1所示。AT24C16SC同時支持3V和5V,訪問速度分別可以達到100Kbps(3V)和400Kbps(5V);內(nèi)部容量為16Kb,分為128頁,每頁16字節(jié);雙向數(shù)據(jù)線(SDA)為OD(Open-Drain)驅(qū)動,需要加上拉電阻才能正常通信。
存儲卡的訪問時序為I2C標準時序。首先,正常通信中只有在時鐘線(SCL)為低時SDA才可變化,即在SCL為高時,SDA必須保持狀態(tài)(數(shù)據(jù)有效期),而在SCL為高時,SDA的變化表示下面兩種控制狀態(tài):開始狀態(tài):當SCL為高時,SDA由高變低表示一個開始狀態(tài),通常任何操作前均需要一個開始狀態(tài);停止狀態(tài):當SCL為高時,SDA由低變高表示一個停止狀態(tài),通常跟在每個操作后,從而將卡置于等待模式。
在讀寫中,地址和數(shù)據(jù)都是按照8位的大小進行傳輸,接收的一方需要返回一個ACK信號表示確認,這個ACK信號是在第9位的位置返回一個“0”來表示。如在讀卡的時候,DSP在收到8位后,在第9個時鐘應向卡發(fā)送“0”表示收到了正確的數(shù)據(jù),同時要求卡繼續(xù)發(fā)送下一個8位數(shù)據(jù),如果沒有這個ACK信號,則將會中止當前讀操作返回等待模式。寫卡的時候,卡在收到DSP發(fā)送的地址和數(shù)據(jù)后也應該返回ACK信號以表示收到了正確的命令。開始和停止狀態(tài)、確認信號時序如圖2所示。
一個讀寫操作的開始需要先發(fā)送一個器件地址(device address)字節(jié),該字節(jié)的高4位是“1010”,接著3位是卡的高位地址,如AT24C16SC需要有11位地址(2K字節(jié)的大小),高3位地址就是這里來指示,最后1位是讀寫控制位,若為“1”,則表示后面進行一個讀操作,若為“0”,則表示后面進行一個寫操作。
寫卡操作分為字寫和頁寫。字寫時,當發(fā)送完器件地址字節(jié)(最后1位為“0”指明寫操作)后,接著再發(fā)送一個字地址(word address)字節(jié),即為卡的低8位地址,然后就可送入一個字節(jié)的數(shù)據(jù),最后發(fā)送停止狀態(tài)。對于頁寫時,可以連續(xù)發(fā)送16個字節(jié)后再發(fā)送停止狀態(tài),需要注意的是,當頁寫時,低4位地址在卡內(nèi)部自動遞增,當?shù)竭_頁末地址時會自動返回頁首地址,所以要正確發(fā)送停止狀態(tài),否則繼續(xù)寫入的字節(jié)就會覆蓋原來的數(shù)據(jù)。
讀卡操作分為讀當前地址、讀任意地址和順序讀幾種方式。幾種方式大同小異,下面主要介紹讀任意地址的操作,另兩種方式都較簡單。讀任意地址時,在發(fā)送完器件地址字節(jié)(最后1位為“1”指明讀操作)后,發(fā)送字地址字節(jié),這一過程是裝載要讀的地址,下面再發(fā)送一個器件地址字節(jié)(同樣最后1位為“1”指明讀操作),然后便可從卡讀到一個連續(xù)8位數(shù)據(jù),然后DSP發(fā)送停止狀態(tài)(而不是ACK信號)結(jié)束讀操作。
通過以上介紹可以看出,DSP與存儲卡連接的關鍵就是如何做出SCL和SDA的時序,也就是I2C時序。用DSP的兩根GPIO分別連接存儲卡的SCL和SDA,然后同時設置兩者的高低關系并且正確改變連接SDA那根GPIO的輸入和輸出方向,我們就可以解決這個問題。例如,對于圖3這個數(shù)據(jù)有效期的時序,我們可以將兩根GPIO依次設置為:“01”、“11”、“01”,需要注意的是,改變狀態(tài)之間需要插入等待周期,因為DSP的工作時鐘很高,其GPIO的改變遠高于I2C時序的要求。
下面給出一個寫卡函數(shù):
short write_ic(short page,short addr,short length,short *buff)
//page-要訪的問高3位地址,addr-要訪問的低8位地址,length-要寫入的數(shù)據(jù)長度,通常為16,buff-要寫入的數(shù)據(jù)
{
short device_address,ack,i,loop=0;
start_ic(); //發(fā)送一個開始狀態(tài)
device_address=0xa0|(page<<1);
put_ic(device_address); //發(fā)送器件地址字節(jié),0xa0表示寫
gpio_setdir(0); //改變GPIO方向為輸入
ack=1;
do
{
ack=get_ic();
loop++;
if(loop>10000)
return 0;
}while(ack!=0); //等待讀入確認信號,否則超時退出
device_address=addr; //要訪問的低8位地址
put_ic(device_address); //發(fā)送字地址字節(jié)
gpio_setdir(0);
ack=1;
do
{
ack=get_ic();
loop++;
if(loop>10000)
return 0;
}while(ack!=0);
for(i=0;i{
put_ic(*buff++);
dir(0);
ack=1;
do
{
ack=get_ic();
}while(ack!=0);
} //寫入數(shù)據(jù)
stop_ic();
return 1;
}
void put_ic(short c)
{
short temp,i;
gpio_setdir(1); //改變GPIO為輸出
for(i=7;i>=0;i--)
{
temp=1temp=temp<<(-i);
if(temp==0)
{
gpio_setval(0,0); //設置兩根GPIO的輸出
gpio_setval(1,0);
gpio_setval(0,0);
}
else if(temp==1)
{
gpio_setval(0,1);
gpio_setval(1,1);
gpio_setval(0,1);
}
}
}
DSP和智能卡的連接
智能卡是IC卡中最高級的一種,其內(nèi)部一般有CPU、ROM、RAM和EEPROM等資源,卡內(nèi)一般都駐有智能卡操作系統(tǒng)(COS),該操作系統(tǒng)對卡進行維護和管理并解釋終端的各種命令。由于卡內(nèi)有CPU和RAM,所以可以根據(jù)需要進行一些運算和數(shù)據(jù)加密,同時卡內(nèi)的EEPROM也可以存放一些用戶資料(容量也較存儲卡大)。智能卡的管腳如圖4所示。
智能卡的操作遵循ISO7816-3規(guī)范,通信時序類似于雙向RS-232通信協(xié)議(RS-232是單向的)。首先,操作前需要對卡進行激活,激活過程必須保證智能卡的觸點接觸良好。激活的步驟為:VCC供電,RST為低,I/O設為輸入,提供CL,然后對卡進行復位。復位分為冷復位和熱復位,兩者區(qū)別在于冷復位時RST由低變高,而熱復位時RST由高變低再變高,在復位后,卡應有復位應答;接受到卡正確的復位應答后,DSP可以向卡發(fā)送命令;取卡前需要進行釋放,步驟順序與激活相反:RST變低,CLK變低,VCC掉電。
卡的復位應答可以告訴終端一些卡的原始信息,它的組成如圖5所示:
TS:初始化字節(jié),用來進行位同步和指示后續(xù)通信的編碼方式,例如0x3f表示反碼編碼,0x3b表示正常編碼;
T0:格式字節(jié),高4位用來指示是否傳輸TA1、TB1、TC1、TD1,低4位用來指示有多少個歷史字符;
TAi、TBi、TCi:接口字節(jié),用來設置操作的一些參數(shù),比如速率,保護時間,編程電壓等;
TDi:接口字節(jié),高4位用來指示是否傳輸TAi+1、TBi+1、TCi+1,低4位用來指示傳輸類型T。T=0,字符半雙工模式;T=1,塊半雙工模式,其他的T值保留。我們常用T=0即字符模式;
T1~TK:歷史字符,由制卡商提供;
TCK:校驗位,是T0~TK所有字節(jié)的異或,T=0時不傳此字節(jié)。
訪問卡的時序如圖6所示。
可以看出,1個字節(jié)幀由13位組成,1個開始位、8個數(shù)據(jù)位、1個校驗位和2個保護時間位。在外部提供時鐘方式(常用方式)下,上圖中每個位所占的時間(ETU,Elementary Time Unit)默認為外部時鐘周期的372倍,如當外部時鐘為3.57M時,1/ETU約為9600,即工作在9600速率下。保護時間默認為2個ETU,最大可以為254個ETU。
DSP與智能卡的連接跟存儲卡有些差異。首先,智能卡比存儲卡多一個RST管腳用來對CPU進行復位,可以使用DSP的一根GPIO來控制;存儲卡的CLK為通信時鐘,速度不高,沒有速率要求,可以隨時停止,只要時序關系正確即可,可以由DSP的GPIO實現(xiàn);但智能卡的CLK為卡內(nèi)CPU的工作時鐘,速度很高(通常介于1M~5M),并且要求穩(wěn)定,用DSP的GPIO來提供該時鐘是不可能的,因為DSP的GPIO的翻轉(zhuǎn)速度有限制,并且高速翻轉(zhuǎn)必定占用大量DSP系統(tǒng)資源。因此可以使用DSP的一個串口McBSP的發(fā)送時鐘CLKX來提供智能卡時鐘。同時,還需要DSP的一根GPIO來連接智能卡的I/O信號,跟智能卡一樣的是,I/O線同樣是雙向的,需要正確改變DSP的GPIO方向,但不同的是智能卡的I/O有精確的速率要求,即為CLK速率的1/372,可以采用DSP內(nèi)部的定時器來結(jié)合GPIO實現(xiàn)。
選擇DSP內(nèi)部定時器的周期為提供時鐘的串口時鐘周期的372/8倍,當每個定時器中斷到來時進行一次GPIO操作,這樣等于是8倍頻輸出和采入數(shù)據(jù),即操作的最小周期是1/8個ETU,從而確保了精度。從實現(xiàn)上看,上述過程類似于用定時器和GPIO來實現(xiàn)RS-232協(xié)議。需要注意的是,為了保證中斷響應和系統(tǒng)資源,定時器中斷程序中最好不進行GPIO操作,而只作標志位設置,GPIO操作留給讀寫函數(shù)實現(xiàn)。下面為一個讀卡函數(shù)的實現(xiàn)。
void readword_sim(unsigned short *buff,unsigned short length)
//buff-讀入的數(shù)據(jù)放的buffer,length-讀入的長度
{
short tempbit,tempbyte,i,j,bitcount,parry;
rw=0; //設置GPIO為讀
for(j=0;j{
bitcount=0;
while(!bitcount)
{
ready=0;
while(!ready);
if(simdata==0) bitcount=1;
} //讀入開始位
tempbyte=0;
bitcount=0;
for(;bitcount<11;)
{
tempbit=0;
for(i=0;i<8;i++)
{
ready=0;
while(!ready);
if(simdata==1)
tempbit=tempbit+(1<} //讀入一位的8倍采樣
if((tempbit&0x18)==0 && bitcount==0)
bitcount++;
else if(bitcount>0 && bitcount<9)
{
if((tempbit&0x18)!=0)
tempbyte=tempbyte+(1<<(bitcount-1));
bitcount++;
} //組成一個字節(jié)
else if(bitcount==9)
{
if((tempbit&0x18)!=0)
parry=1;
else parry=0;
bitcount++;
} //讀入校驗位
else if(bitcount==10) bitcount++; //保護時間
}
buff[j]=tempbyte;
}
}
shorterrupt void tshort() //定時器中斷
{
if(ready==0)
{
if(rw==0)
simdata=gpio_getval();
ready=1;
}
}
上面的程序?qū)崿F(xiàn)了智能卡的讀寫協(xié)議,余下的就是對卡發(fā)送命令即可,關于命令的結(jié)構和定義本文不作闡述。