CH32串口智能卡模式
在 CH32 系列微控制器中,串口的智能卡模式(ISO 7816 - 3 协议)允许其与智能卡进行通信。下面将从功能、配置步骤到具体程序,详细分析该模式。
功能概述
智能卡模式是基于串口(USART)的一种特殊工作模式,遵循 ISO 7816 - 3 协议。该协议规定了智能卡与读卡器之间的电气接口和传输协议,主要用于在微控制器和智能卡之间进行数据传输。智能卡模式具备以下特点:
1)半双工通信:数据在同一时刻只能在一个方向上传输。
2)异步通信:通信双方不需要共同的时钟信号,通过起始位和停止位来同步数据传输。
3)特定的帧格式:数据帧包含起始位、数据位、奇偶校验位和停止位,并且有特定的传输速率和时钟频率要求
配置步骤
1. 使能时钟
需要使能 USART 和 GPIO 的时钟,因为 USART 用于通信,GPIO 用于连接智能卡的引脚。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
2. 配置 GPIO 引脚
配置 USART 的 Tx 和 Rx 引脚为复用功能,同时配置智能卡时钟引脚(SCK)。
GPIO_InitTypeDef GPIO_InitStructure; // 配置USART1 Tx (PA9)为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART1 Rx (PA10)为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART1 SCK (PA8)为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
3. 配置 USART 为智能卡模式
设置 USART 的工作模式、波特率、数据位、停止位、奇偶校验位等参数。
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_Clock = USART_Clock_Enable;
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_1Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init(USART1, &USART_InitStructure);
// 使能智能卡模式
USART_SmartCardCmd(USART1, ENABLE);
// 使能智能卡NACK发送
USART_SmartCardNACKCmd(USART1, ENABLE);
4. 使能 USART
USART_Cmd(USART1, ENABLE);
通信协议概述
ISO 7816 - 3 协议定义了智能卡与读卡器之间的电气接口和传输协议,采用半双工异步通信方式。通信过程主要包括 ATR(Answer To Reset)阶段和 APDU(Application Protocol Data Unit)交互阶段。
1. ATR 阶段
1.1 含义
ATR 是智能卡对复位信号的响应,用于建立通信参数,如波特率、字符格式等。
1.2 命令与数据
- 命令:在该阶段,读卡器向智能卡发送复位信号(RST),这并非严格意义上的命令字节,但它触发了智能卡的响应流程。
- 数据(ATR):智能卡返回的 ATR 是一个可变长度的字节序列,一般格式如下:
| 字段 | 含义 |
| --- | --- |
| TS | 初始字符,指示后续字符的传输方向,通常为0x3B或0x3F。 |
| T0 | 格式字节,包含历史字节数、后续字符格式信息。 |
| TAi、TBi、TCi、TDi | 可选的协议参数字节,用于定义波特率调整因子、时钟停止模式等。 |
| HIST_BYTES | 历史字节,包含卡的制造商信息、应用类型等。 |
| TCK | 校验字节(可选),用于验证 ATR 数据的完整性。 |
1.3 返回值含义
ATR 数据中的各个字段包含了智能卡的重要信息,例如:
2. APDU 交互阶段
2.1 含义
APDU 是应用层协议数据单元,用于在智能卡和读卡器之间交换应用数据和命令。分为命令 APDU 和响应 APDU。
2.2 命令 APDU
命令 APDU 有两种格式:短 APDU 和扩展 APDU,这里主要介绍短 APDU,其格式如下:
- TS 字段:
0x3B表示后续字符按 ISO/IEC 7816 - 3 标准传输;0x3F表示后续字符按其他标准传输。 - T0 字段的低 4 位表示历史字节的数量,通过解析这些字节可以获取智能卡的相关特性。

2.3 响应 APDU
响应 APDU 的格式为:

常见状态字含义

示例代码中与协议的结合
#include "ch32f10x.h"
#define BUFFER_SIZE 256
// 之前的配置函数保持不变
void USART1_SmartCard_Config(void);
void USART1_SendByte(uint8_t byte);
uint8_t USART1_ReceiveByte(void);
// 接收ATR
void ReceiveATR(uint8_t *atrBuffer, uint16_t *atrLength)
{
uint16_t index = 0;
while (1)
{
atrBuffer[index] = USART1_ReceiveByte();
if (index > 0 && atrBuffer[index - 1] == 0x0D && atrBuffer[index] == 0x0A)
{
break;
}
index++;
if (index >= BUFFER_SIZE)
{
break;
}
}
*atrLength = index;
}
// 发送APDU命令并接收响应
void SendAPDUCommand(uint8_t *command, uint16_t commandLength, uint8_t *response, uint16_t *responseLength)
{
for (uint16_t i = 0; i < commandLength; i++)
{
USART1_SendByte(command[i]);
}
uint16_t index = 0;
while (1)
{
response[index] = USART1_ReceiveByte();
if (index > 0 && index >= commandLength + 1 && response[index - 1] == 0x90 && response[index] == 0x00)
{
break;
}
index++;
if (index >= BUFFER_SIZE)
{
break;
}
}
*responseLength = index;
}
int main(void)
{
USART1_SmartCard_Config();
uint8_t atrBuffer[BUFFER_SIZE];
uint16_t atrLength;
ReceiveATR(atrBuffer, &atrLength);
uint8_t selectCommand[] = {0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00};
uint8_t response[BUFFER_SIZE];
uint16_t responseLength;
SendAPDUCommand(selectCommand, sizeof(selectCommand), response, &responseLength);