在消费领域,随处都可以看到智能卡的应用,比如常见的有手机SIM卡、带金属触点的银行卡、天然气充值IC卡等。MM32F013x的UART智能卡模式符合ISO7816-3标准,支持智能卡异步协议,本文是针对在MM32F013x上实现UART智能卡的功能应用。
1.智能卡协议介绍
根据协议,IC卡的操作信息交互大概流程如下图:
- 接口设备(也叫读卡设备,下同)能够控制IC卡各IO引脚使其激活。
- 接口设备给IC卡发送复位信号使卡复位启动。
- IC卡要向接口设备发送复位应答信号,将通信中必要的相关信息告知接口设备。
- 接口设备对IC卡进行一次热复位,IC卡进行复位应答。
- 接口设备发起一个 PPS 交互指令,选择要与IC卡通信的协议和相关参数。
- 根据选择的协议(T=0 或 T=1)进行数据的通信。
1.1 IC卡触点定义
注释:
接口设备上的IC卡座有一个开关,当卡插入,就会顶住卡座开关,使开关闭合,通过检测开关状态可以知道IC卡是否插入接口设备。
1-5MHz:A类
1-4MHz:B类
1.2 IC卡操作的过程
当卡的触点连接到接口设备的触点时,电路才能运行。卡和接口设备之间的交互有以下步骤:
- 接口设备要激活 IC 卡;
- 卡和接口设备之间的信息交互由卡的冷复位应答所发起;
- 接口设备与卡按照对应协议进行数据交换;
- 接口设备对卡去激活,即停用;
在由接口设备发起的去激活动作结束之后,才能断开卡和接口设备之间的物理连接。
1.2.1 卡激活
为了与一个已经物理的连接的卡发起交互,接口设备需要通过下面的操作激活卡。
- RST 置成 L;
- VCC 上电;
- 接口设备上 I/O 置成接收模式;
- 在 A 类操作条件下,Vpp 应该置为停止状态;在 B 类操作条件下,不接 Vpp;
- CLK 将要被给一个时钟信号。
1.2.2 冷复位
激活结束后(RST 在 L 状态,VCC 上电,接口设备 I/O 在接收模式,CLK 有一个适合的并且稳定的时钟信号),卡已经准备好冷复位。在冷启动之前,卡的内部状态没有定义。接口设备将发出一个冷复位信号,并从 IC 卡得到一个复位应答,过程如下:
- 从 Ta时刻起,接口设备施加 CLK;
- 在 Ta后不超过 200 个时钟周期内,IC 卡必须将其 I/O 线置为接收方式。由于接口设备也必须在这段期限内置其 I/O 线驱动器为接收方式,所以 I/O 线在 Ta后最迟不超过 200 个时钟周期的时间内置为高电平。为此,接口设备的 I/O 触点应经过一个上拉电阻接到 VCC;
- 接口设备应从Ta开始保持RST端为低电平状态至少400个时钟周期,并在Tb将其置为高电平;
- IC卡上I/O的复位应答将在Tb时刻后的400至40000个时钟周期内开始;
- 如果IC卡在RST被拉高后的40000个时钟周期内没有应答,则RST将被拉低并且卡将被去激活。
1.2.3 热复位
IC卡对终端的复位应答有着规定的规格和内容(详见后述),如果终端收到的复位应答不符合规定要求时,终端将启动一个热复位并从IC卡获得复位信号。过程如下:
- 在 VCC 和 CLK 信号保持稳定后,接口设备通过拉低 RST 发起热复位,RST低电平至少要持续 400个时钟周期;
- 在Tc之后的最多200周期内,IC卡和接口设备都必须置I/O为接收方式,即I/O线在Tc后最迟不超过200个时钟周期内置为高电平;
- 接口设备在 RST 保持低电平大于 400 个时钟周期后,将其拉高;
- IC卡上I/O的复位应答将在T后的400至 40,000 个时钟周期内开始;
- 如果 IC 卡在 RST 被拉高后的 40000 个时钟周期内没有应答,RST 将被拉低并且卡将被去激活。
1.2.4 时钟停止
- 对于支持时钟停止的卡,当接口设备认为没有来自卡的传输并当 I/O 已经保持在 H 至少 1860 时钟周期(延时 tg),在 VCC 上电并且 RST 在 H 时,接口设备可以对于 CLK(Te)进行时钟停止。
- 当时钟停止时(在 Te\~Tf),根据已被确定好的时钟指示器 X ,CLK 将要继续保持在在 H 或是在 L。时钟指示器 X 的值应在复位应答序列 TA(i)当中设置。
- 在 Tf,接口设备重启时钟,在至少 700 时钟延时后(th),在 I/O 的信息交换可以继续。
1.2.5 去激活
当信息交换被完成或者被终止(例如卡没有应答,或检测到卡的移动),接口设备将按照下面的序列停用 IC 卡:
- 将 RST 置 L
- 将 CLK 置 L(除非时钟停止在 L 状态)
- 将 VPP 去激活
- 将 I/O 置 L
- 将 VCC 去激活
2.IC卡异步字符格式
在字符传输之前,I/O 应该处于高电平。一个字符由10个连续的时段组成,每一个时段为高电平或低电平。
- 第一个时段 m1 为低电平,为起始时段,用于字符帧的同步
- 中间 8 个时段(2 到 9)为要发送的一个字符
- 最后一个时段 m10 为奇偶校验位
每一个时段持续时间为一个 etu。如果在一个时段 mn(第n位)的结尾状态改变,那么这个字符的起始边沿到mn的结束边沿之间的延时为(n±0.2)etu。
注释:
etu = Fi/(Fclk*Di)
其中Fclk是CLK引脚时钟频率(1\~4MHz),Di = 1,Fi=372
为了适配UART的9600波特率,可以设置MCU输出3.6MHz的PWM送入到IC卡的CLK引脚。这样etu=103.3us,与UART的9600误差在1%内,可以正常通信。
接受方对 I/O 进行定期采样,采样时间不少于 0.2etu。接收方必须在((n-1)+0.5±0.2)个 etu 内确认相应的时段 mn。
两个连续字符的起始边沿之间的延时至少为 12etu。这包括一个字符的持续时间和保护时间。在保护时间内卡和接口设备都处于接收模式,即高电平状态。
在复位应答期间,两个被卡发送的连续字符的起始边沿之间的延时不能超过 9600etu。这个最大值被称为是初始等待时间。
此处我们只针对物理层最基本的通信进行讲解,不对应用层协议深入讲解。ISO7816的协议层讲解有一百多页,有兴趣的朋友,可以网上下载ISO7816的协议深入研究。
3.协议和参数选择
上面降到了ISO7816 标准IC卡的控制过程,但是不同的IC卡可能使用的协议不相同,但都符合ISO7816规范,接口设备可以发送协议和参数选择指令(PPS),选择要和 IC 卡进行通信的协议和相关的参数。此处不详细讲解,可以参看ISO7816相关资料。
PPS协议如下:
- IFD 发送一个 PPS 请求给 ICC;
- 如果 ICC 收到一个错误的请求,则不响应;
- 如果 ICC 受到一个正确的请求,则返回一个 PPS 响应,否则将超过初始等待时间;
- 如果 ICC 超出初始等待时间,则 IFD 复位或者拒绝 ICC;
- 如果 IFD 收到一个错误的应答,则复位或者拒绝 ICC;
- 如果 PPS 交换失败,则 IFD 复位或者拒绝 ICC。
3.1 异步半双工字符传输协议
3.1.1 在复位应答命令的结构和处理
命令由接口设备启动,它是以 5 个字节的报头通知卡做什么,并且允许在卡发出的过程字节的控制下传输数据字节。
接口设备通过5个连续的字节为包头,这5个字节指定为CLA,INS,P1,P2,P3。
- CLA 为指令类别,值 FF 保留;
- INS 为指令类别中的指令代码;
- P1、 P2 是一个完成指令代码的参考符;
- P3 对指令期间被传输的数据字节(D1…Dn)的数目 n 编码;
在一个5字节包头传输后,接口设备等待一个过程字节。
3.1.2 过程字节
过程字节的值将指明接口设备请求的动作。有3种类型的过程字节:
CLA 为指令类别,值FF保留
在ACK中,除了 6X 和 9X 以外,在 ACK 字节中的七个最高有效位(B8 到 B2)全部等于 INS字节中的相应位或者与之补位。
SW1 的值为‘6X’或‘9X’但不包括 60
在每一个过程字节中,卡可以用一个ACK或NULL字节来把这个命令继续进行下去,或以适当的不应答表示不赞同,或用结束序列SW1-SW2结束这个命令。
3.1.3 NULL字节
NULL 请求不影响数据传出,仅等待一个过程字节。
3.1.4 确认字节
ACK 被用作数据字节传输的控制。
如果ACK与INS的异或等于00 或 FF,Vpp 应该设置并保持在停止状态。
如果 ACK 与 INS 异或等于 01 或 FE, Vpp 应该设置并保持在编程状态。
假如 ACK 的 b8 到 b2 位等于 INS 的相应位,所有剩余的字节将随后传送。
如果 ACK 的 b8 到 b2 位等于 INS 的相应位的补码,那么仅下一个字节将被传输。
3.1.5 状态字节
接收到SW1以后,接口设备将等待一个SW2字节的传输。对SW2的至无限制。结束命令SW1 SW2在命令的结尾给出卡的状态。置为9000只是处理正常完成。
SW1 的有效位的高 4 位等于 6 时, SW1 的含义与应用无关。
4.UART智能卡配置寄存器描述
首先看下智能卡模式相关寄存器UART->SCR:
- 设置SCEN位,选择智能卡模式;
- 设置HDSEL位,使能单线半双工模式;
- 设置UART为8位数据位,偶校验,波特率9600,1.5个停止位;
- 在ISO7816协议里,IC卡接收字符与字符之间需要一定的间隔时间,我们把这个间隔时间称为保护时间,可以设置SCFCNT[7:0]选择保护时间的位数;
- 设置SCAEN,当接收IC卡发来的数据发生校验错误时,接收器会给出一个NACK 信号,否则就不会发送 NACK 。
当与智能卡相连接时,UART 的 TX 驱动一根智能卡的双向线。为了做到这点,RX 必须和TX 连接到相同的 I/O 口。在发送开始位和数据字节期间,发送器的输出使能位 TXEN 被置起,在发送停止位期间被释放 (弱上拉),因此在发现校验错误的情况下接收器可以将数据线拉低。如果 TXEN 不被使用,在停止位期间 TX 被拉到高电平:这样的话,只要 TX 配置成开漏,接收器也可以驱动这根线。
下图给出的例子说明了数据线上在有校验错误和没有校验错误两种情况下的信号。
智能卡是一个单线半双工通信协议
- 从发送移位寄存器把数据发送出去,要被延时最小 1/2 波特时钟。在正常操作时,一个满的发送移位寄存器将在下一个波特时钟沿开始向外移出数据。在智能卡模式里,此发送被延迟 1/2 波特时钟。
- 如果在接收一个设置为 0.5 或 1.5 个停止位的数据帧期间,检测到一奇偶校验错误,在完成接收该帧后 (即停止位结束时),发送线被拉低一个波特时钟周期。这是告诉智能卡发送到 UART 的数据没有被正确地接收到。此 NACK 信号 (拉低发送线一个波特时钟周期) 在发送端将产生一个帧错误 (发送端被配置成 1.5 个停止位)。应用程序可以根据协议处理重新发送数据。如果设置了SCAEN控制位,发生校验错误时接收器会给出一个NACK信号,否则就不会发送NACK。
- TXC 标志的置起可以通过编程保护时间寄存器得以延时。在正常操作时,当发送移位寄存器变空并且没有新的发送请求出现时, TXC 被置起。在智能卡模式里,空的发送移位寄存器将触发保护时间计数器开始向上计数,直到保护时间寄存器中的值。 TXC 在这段时间被强制拉低。当保护时间计数器达到保护时间寄存器中的值时, TXC 被置高。
- 标志的撤销不受智能卡模式的影响。
- 如果发送器检测到一个帧错误 (收到接收器的 NACK 信号),发送器的接收功能模块不会把 NACK 当作起始位检测。根据 ISO 协议,接收到的 NACK 的持续时间可以是 1 或 2波特时钟周期。
- 在接收器这边,如果一个校验错误被检测到,并且 NACK 被发送,接收器不会把 NACK检测成起始位。
5.相关软件的实现
5.1 底层软件部分
我们从上述讲到的“IC卡的操作过程”共6步逐一实现。
5.1.1 卡激活
//调用ISO7816_Activate(ENABLE)激活卡
void ISO7816_Activate(FunctionalState state)
{
if(state != DISABLE) //IC卡激活
{
ICCard_Rst_L; //将 RST 置 L
ICCard_VCC_ON; //VCC 上电
ISO7816_ClockStart(); //CLK时钟启动
}
else //IC卡去激活
{
ICCard_Rst_L; //将 RST 置 L
ISO7816_ClockStop_Low(); //将 CLK 置 L
ICCard_IO_L(); //将 I/O 置 L
ICCard_VCC_OFF; //将 VCC 去激活
}
}
5.1.2 冷复位
void ISO7816_Reset(void) //冷复位
{
ICCard_VCC_ON; //给IC卡VCC上电
ISO7816_ClockStart(); //CLK时钟启动
ICCard_IO_Release(); //clk开启后200个clk周期内将MCU的I/O配置成接收模式
ICCard_Rst_L; //将 RST 置 L
delay_ms(1);
ICCard_Rst_H; //RST置低至少400个时钟周期
}
5.1.3 热复位(带电复位IC卡)
void ISO7816_Restart(void) //热复位
{
//注意热复位本就在VCC已经打开的前提下,为防止VCC没有打开产生误操作
ICCard_VCC_ON; //给IC卡VCC上电
ISO7816_ClockStart(); //CLK时钟启动
ICCard_IO_Release(); //MCU的I/O配置成接收模式
ICCard_Rst_L;
delay_ms(1);
ICCard_Rst_H; //RST置低至少400个时钟周期
}
CLK时钟停止(一种停止保持高,另一种是停止保持低)
void ISO7816_ClockStop_High(void) //时钟停止,并保持在高电平
{ TIM3_PWM_Stop_High();
}
void ISO7816_ClockStop_Low(void) //时钟停止,并保持在低电平
{ TIM3_PWM_Stop_Low();
}
5.1.4 去激活
和“卡激活”共用一个函数,只是传的参数不一样,调用ISO7816\_Activate(DISABLE)实现去激活卡。
5.2 配置MM32F013x的UART智能卡模式
卡智能卡是一个单线半双工模式,当接收到IC卡传来的数据出现奇偶校验错误时,会在规定时刻自动把I/O拉低,需要使能ISO7816自动应答功能,在ISO7816中还有字符之间有保护时间,需要设置UART的保护计数器与之匹配。
下面是MM32F013x的UART智能卡初始化:
void Uart_ISO7816_Config(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
UART_InitTypeDef UART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_1);
UART_InitStructure.UART_BaudRate = bound;
UART_InitStructure.UART_WordLength = UART_WordLength_8b;
UART_InitStructure.UART_StopBits = UART_StopBits_1_5;
UART_InitStructure.UART_Parity = UART_Parity_Even; //偶校验
UART_InitStructure.UART_HardwareFlowControl= UART_HardwareFlowControl_None;
UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(UART1, &UART_InitStructure);
NVIC_ConfigInit();
UART_ClearITPendingBit( UART1, UART_IT_RXIEN);
UART_ITConfig( UART1, UART_IT_RXIEN, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
UART_HalfDuplexCmd(UART1,ENABLE); //使能单线半双工模式
UART_SetGuardTime(UART1,2); //2bit保护计数器
UART_SmartCardNACKCmd(UART1,ENABLE); //使能ISO7816自动应答
UART_SmartCardCmd(UART1,ENABLE); //ISO7816使能
UART_Cmd(UART1, ENABLE);
}
void UART1_IRQHandler(void)
{ if(UART_GetITStatus( UART1, UART_IT_RXIEN) )
{
UART_ClearITPendingBit( UART1, UART_IT_RXIEN);
uartInfo.buf[ uartInfo.len ++ ] = UART_ReceiveData(UART1);
}
}
UART部分配置好了之后,需要配置CLK的PWM。前面已经计算了,CLK的频率为3.6MHz时,IC卡的etu时间与UART的9600波特率下的位是对应的,所以设置MM32F013x的TIM3\_CH1(PB4脚)输出3.6M占空比50%的PWM。MM32F013x主频跑72MHz。在main函数调用TIM3\_PWM\_Init(19,0)配置PB4输出3.6MHz的PWM给到IC卡的CLK。
void TIM3_GPIO_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void TIM3_PWM_Init(u16 arr, u16 psc)
{ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM3_GPIO_Init();
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
//Setting Clock Segmentation
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
///TIM Upward Counting Mode
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCStructInit(&TIM_OCInitStructure);
//Select Timer Mode: TIM Pulse Width Modulation Mode 2
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//Setting the Pulse Value of the Capture Comparison Register to be Loaded
TIM_OCInitStructure.TIM_Pulse = 10;
//Output polarity: TIM output is more polar
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_CtrlPWMOutputs(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
void TIM3_PWM_Stop_High(void)
{ TIM_Cmd(TIM3, DISABLE);
TIM3->CNT = 2;
}
void TIM3_PWM_Stop_Low(void)
{TIM_Cmd(TIM3, DISABLE);
TIM3->CNT = 12;
}
void TIM3_PWM_Restart(void)
{ TIM_Cmd(TIM3, ENABLE);
}
6.实验现象
针对上述讲解,下面对IC卡进行实例操作一遍,加深大家对IC卡的一个操作过程的印象。
首先对IC卡进行复位,调用ISO7816\_Restart()函数,IC卡会发复位应答。下图是复位应答波形:
通道0:IC卡的RST脚
通道1:IC卡的CLK输入脚(3.6MHz)
通道2:IC卡的IO脚
TS:为起始字符
用于设置其后各字符的解码规则。
执行两项功能:
向终端提供了一个已知的位组合模型,以便于同步;
提示所用逻辑约定,以便对后继的字符进行解释。
IC 卡必须以下列二值之一来回送 TS
反向约定(H)LHHLLLLLLH,其值为“3F”。用1表示低电平,m2 为最高有效位;
正向约定(H)LHHLHHHLLH,其值为“3B”。用1表示高电平,m2 为最低有效位;
T0:格式字符
格式字符T0由两个部分组成,高半字节(b5~b8)表示后续字符TA1到TD1是否存在,b5-b8位设置为逻辑“1”表示TA1到TD1存在,相应的,低半字节(b1~b4)表明可选历史字符的数目(0~15),如下所示:
T0的基本响应代码:
基本响应:
* 选择T=0时,IC卡应回送T0=0x6X,表示字符TB1和TC1存在。
* 选择T=1时,IC卡应回送T0=0xEX,表示字符TB1、TC1和TD1存在。
其中X表示历史字符的数目。兼容终端能够接收满足以上条件的所有值。
TA1\~TC3:接口字符
上面对IC卡复位,IC卡已经有复位应答,可以看到协议格式为T=0。下面就对IC卡进行一次命令的传输。
UartSendByte(0xA0);
UartSendByte(0xB0);
UartSendByte(0x00);
UartSendByte(0x00);
UartSendByte(0x0A);
delay_ms(100);//等待IC卡回复
发了命令后,看看逻辑分析仪抓取的波形如下:
上面是一条命令和IC卡回复的完整波形,下面先对命令放大,可以看出是一个“字符传输协议”的5字节包头,命令是“READ BINARY” 从文件开始的数据单元中被读的第1个字节的偏移。
下面是IC卡接收到指令后,返回的数据。
IC卡操作完后,记得调用ISO7816\_Activate(DISABLE)函数,将IC卡去激活。
那么MM32F013x的UART 智能卡模式(ISO7816)底层驱动讲到此处,关于更深入的应用,有兴趣的可以根据ISO7816的协议,亲自动手测一测。