千呼万唤始出来——IO口模拟ISO7816(PSAM卡)协议

单片机:stm32f103c8t6  

实现方式:纯IO口模拟(RST、I/O、CLK均为普通IO口)

实现功能:接触式CPU卡、PSAM、SIM读(写)

网上有很多相关的代码,有的是用自带的智能卡接口,有的使用了外部晶振和分频电路,多数都没讲清具体的软件实现方式,其中最为诟病的就是:没有一篇帖子讲明CLK的模拟方式,大多数代码里只有RST和I/O,对CLK只字未提?商业机密?

这是ISO7816手册上对CLK要求的原话:

CLK提供时钟信号至少在复位应答期间,时钟f的频率值应在以下范围内:
1-5MHz:A类
1-4MHz:B类

其实这也讲明了CLK的要求,而不是类似模拟SPI那种需要同步给时钟,此处的时钟类似一个外部晶振电路给卡提供基准频率。

嵌入智能卡的芯片没有内部时钟发生器,这样就需要外部设备为芯片提供时钟。时钟是提供数据传输速率的基准。它是通过 C3 触点提供给卡的。卡时钟的最低频率为 1MHz,而在激活和冷复位期间,时钟频率的最大频率为5MHz。在没有特殊说明的情况下,稳定工作时的时钟信号的占空比为 40%至60%。当时钟从一个频率切换到另一个被卡支持的频率时,应当注意最短时钟周期的占空比不能小于 40%。当切换时钟时,不应有数据的交换发生。在下面的两个时刻可以切换时钟:复位应答后,卡在等待一个字符时;PPS 交换后,卡在等待字符时。建议在这两个时刻进行时钟的切换。施加到触点的时钟并不需要与提供给芯片内部的时钟信号完全一样,因为芯片的内部含有可选的时钟分频器。本文的 7816 接口包含有一个 8 位的寄存器,它用来设置芯片时钟的分频系数。『这段话来自某位同学的论文

所以对于7816协议卡的CLK,只需要外部提供1~4M频率(B类卡)的稳定时钟即可,而不需要在发送数据时同步送出,默认的频率是3.579M,因为这个频率经过卡内部分频器分频之后正好是9600bps。至于为什么要得到9600bps,这个放在后面讲。首先讲下,stm32单片机如何产生f=3.579M的方波。

最简单的方式就是利用定时器产生PWM:

//TIM4_PWM_Init(20-1,0);//3.4Mhz 50%Duty  输出  

void TIM4_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);// 使能TIM4时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);  //使能GPIOB时钟
	                                                                     	

    //设置该引脚为复用输出功能,输出TIM4 CH1的PWM脉冲波形
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM_CH1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//PB6

	
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 80K
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 10; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM4, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx

  //TIM_CtrlPWMOutputs(TIM4,ENABLE);	//MOE 

	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);  //CH1预装载使能	 
	
	TIM_ARRPreloadConfig(TIM4, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_Cmd(TIM4, ENABLE);  //使能TIM4
 
   
}

psc = 0 ,预分频系数为0,即定时器不分频,输入时钟为72Mhz,

arr = 20-1,定时器周期为20,定时器的CNT值达到20即会清0,重新开始计数;

TIM_Pulse = 10 ,比较寄存器的值为10,即CNT达到10便会将当前已经使能通道引脚(此处是PB6)翻转一次,由于arr = 20 ,所以PWM的占空比就是50%,定时器周期=((1+psc) * (1+arr) )/72= 20/72 ≈0.278us = 3.6Mhz

数据I/O引脚:

在数据 I/O 上,一位数据所持续的时间叫做“基本时间单位”,简写为 etu。etu是由 F 
和 D 共同决定的,这两个值是在复位应答中给出的,F 为时钟分频因子,D为波特率调整因子。其大小为 F/D 个时钟周期,这里的时钟指的是 CLK 触点上的时钟,即

1etu =(F/D) * (1/f)   ,卡上电时默认F = 372, D = 1,所以1etu = 372/3.579Mhz= 103us,也就是每一位是103us,对应波特率为9600.

                
 在这种情况下可以直接利用串口发送和接收数据,网上很多代码也都是这种使用方式:(格式:1位起始位+8位数据+1位偶校验位)

当然也可以不用串口,直接读取IO口,按位接收数据,每次读完延时103us再读取下一位,依次读完一个字节数据和奇偶位即可。我就是直接读取IO口方式接收的:

unsigned char  Psam_RecvByte(u8 cChannel)				
{
	unsigned char i,data=0;
	
	unsigned int  j=0;
	SAM_DATA_DIR(GP_IO_Data[cChannel],GP_Pin_Data[cChannel],GPIO_Mode_IN_FLOATING);	//DATA脚配置为输入
	//INTX_DISABLE();					//close EA	
	while((SAM_DATA_GET[cChannel]()!=0) /*&& (j++<65000)*/ );//等待IO口为低,超时退出  起始位
  //delay_us(GCDELAYTIME);
	for(i=0; i<8; i++)					
	{	
		data >>= 1;        
		delay_us(GCDELAYTIME);
		if(SAM_DATA_GET[cChannel]() != 0)	
		data |= 0x80;
	}
	delay_us(GCDELAYTIME);
	delay_us(GCDELAYTIME/2);
	return data;
}

下面是调试通过的7816协议时序图 :采用热复位方式,卡片返回ATR


 

 RST信号:

卡片热复位代码: 

void Psam_Reset(u8 cChannel)
{

	Set_SAM_RST[cChannel]();//拉高RST
	delay_ms(20);
	Clr_SAM_RST[cChannel]();//拉低RST
	delay_ms(10);
	Set_SAM_RST[cChannel]();//拉高RST 完成热复位
}
发布了44 篇原创文章 · 获赞 70 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_24835087/article/details/93738304