用DSP的GPIO管脚实现与IC卡通信
文章出处:http://www.singbon.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;
}
}
上面的程序实现了智能卡的读写协议,余下的就是对卡发送命令即可,关于命令的结构和定义本文不作阐述。