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