FM4442IC卡通信协议(SLE4442)
基于STM32F103CBT6的IC卡协议
1.原理图设计
其中GPIOA15为JTAG引脚,固需要把JTAG功能禁用!!!
代码如下:
__HAL_RCC_AFIO_CLK_ENABLE();//在stm32f1xx_hal_gpio.c文件中
__HAL_AFIO_REMAP_SWJ_NOJTAG();
/* Check the parameters */
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_SWJ_NOJTAG();
/* Configure the port pins */
2.IC卡存储区介绍
1、棕色数据为出场数据 表明卡类型等无法修改。
2、粉色数据为写保护区 即写入后无法修改。
3、绿色数据为厂商标识 可以联系厂商在出场时写入。
4、白色数据为正常存储区 可多次写入与擦除。
3.IC芯片手册
1.IC卡引脚图
2.IC卡复位与应答时序
3.命令模式时序
4.数据输出时序
5.处理模式时序
6.中止时序
4.程序.c
代码如下:
/*
*********************************************************************************************************
头文件
*********************************************************************************************************
*/
#include "stm32f1xx_hal.h"
#include "ic.h"
/*
*********************************************************************************************************
全局变量定义
*********************************************************************************************************
*/
unsigned char WRC_Structure;
/*
*********************************************************************************************************
函数名称:ic_Init
功 能:初始化ic卡的引脚
输 入:无
输 出:无
注 意:PA15为 JTAG脚调试脚,需禁用JTAG功能
*********************************************************************************************************
*/
void ic_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/*
*********************************************************************************************************
函数名称:Delay_Us
功 能:实现us级延时
输 入:无
输 出:无
*********************************************************************************************************
*/
void Delay_Us(unsigned char i)
{
for(;i!=0;i--)
__NOP();
}
/*
*********************************************************************************************************
函数名称:Start_IC_IC
功 能:IC卡开始时序
输 入:无
输 出:无
*********************************************************************************************************
*/
void Start_IC(void)
{
SDA_OUT();
SCL = 0; //钳住总线,准备发送数据
SLE_DATA = 1;
SCL = 1;
Delay_Us(10); //起始条件建立时间大于4.7us,延时
SLE_DATA = 0; //发送起始信号
Delay_Us(10); //起始条件锁定时间大于4us
SCL = 0; //钳住总线,准备发送数据
Delay_Us(10);
}
/*
*********************************************************************************************************
函数名称:Stop_IC_IC
功 能:IC卡停止时序
输 入:无
输 出:无
*********************************************************************************************************
*/
void Stop_IC(void)//结束命令模式
{
SDA_OUT();
SLE_DATA = 0;
Delay_Us(10);
SCL = 1;
Delay_Us(10); //结束条件建立时间大于4us,延时
SLE_DATA = 1; //发送起始信号
Delay_Us(10);
}
/*
*********************************************************************************************************
函数名称:WR_Byte
功 能:写字节函数
输 入:unsigned char txd --- 要写入的数据
输 出:无
*********************************************************************************************************
*/
void WR_Byte (unsigned char txd)
{
unsigned char i = 0;
SDA_OUT();
for(i=0;i<8;i++)
{
if(txd&0x01) SLE_DATA = 1;
else SLE_DATA = 0;
Delay_Us(10);
SCL = 1;
Delay_Us(10);
SCL = 0;
txd >>= 1;
}
}
/*
*********************************************************************************************************
函数名称:RD_Byte
功 能:读字节函数
输 入:无
输 出:receive --- 读取到的数据
*********************************************************************************************************
*/
unsigned char RD_Byte (void)
{
unsigned char i = 0,receive = 0;
SDA_IN();//置数据线为输入方式
for(i=0;i<8;i++)
{
SCL = 0; //置时钟线为低准备接收数据位
Delay_Us(10);
SCL = 1; //置时钟线为高使数据线上数据有效
Delay_Us(10);
receive >>= 1;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15)) receive|=0X80; //读数据位,接收的数据位放入x
}
SCL = 0;
Delay_Us(10);
SDA_OUT();
return receive;
}
/*
*********************************************************************************************************
函数名称:byte_read_4442_a
功 能:读保护位字节函数
输 入:无
输 出:cTemp --- 读取到的数据
*********************************************************************************************************
*/
unsigned char byte_read_4442_a(void)
{
unsigned char cTemp = 0x00,i = 0x00;
SLE_DATA = HIGH;
SDA_IN();
for(i=0;i<8;i++)
{
cTemp*=2;
SCL = 1;
Delay_Us(10);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15) == HIGH)
cTemp++;
SCL = 0;
Delay_Us(10);
}
return cTemp;
}
/*
*********************************************************************************************************
函数名称:RstCard
功 能:使IC卡FM4442复位
输 入:无
输 出:0 --- 复位成功
1 --- 复位失败,卡为无效卡或卡已损坏
*********************************************************************************************************
*/
unsigned char RstCard(void)//复位
{
unsigned char i = 0;
unsigned char ReadBuf[4] = {0};
SDA_OUT();
RST = LOW;
SCL = LOW;
Delay_Us(10);
RST = HIGH;//产生复位时序
Delay_Us(10);
SCL = HIGH;
Delay_Us(20);
SCL = LOW;
Delay_Us(10);
RST = LOW;
for(i = 0; i < 4; i++)
{
ReadBuf[i] = RD_Byte();
}
SCL = HIGH;
Delay_Us(5);
SCL = LOW;
SLE_DATA = HIGH;
if ((ReadBuf[0] == SLE4442ID1) && (ReadBuf[1] == SLE4442ID2) && (ReadBuf[2] == SLE4442ID3) && (ReadBuf[3] == SLE4442ID4))
{
return 0; //复位值正确,返回复位成功
}
else
{
return 1; //复位值错误,返回复位失败
}
}
/*
*********************************************************************************************************
函数名称:Wait_IC_IC
功 能:等待时序
输 入:无
输 出:0 --- 等待成功
1 --- 等待失败
*********************************************************************************************************
*/
unsigned char Wait_IC(void)
{
unsigned char i = 0;
SDA_OUT();
SLE_DATA = 0;
while(i<255)
{
i++;
SCL = 0;
Delay_Us(10);
SCL = 1;
Delay_Us(10);
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15))
{
SDA_OUT();
return 0;//内部处理模式已经完成了.
}
}
SDA_OUT();
return 1;//等待失败.
}
/*
*********************************************************************************************************
函数名称:Break_IC_IC
功 能:终止时序
输 入:无
输 出:无
*********************************************************************************************************
*/
void Break_IC(void)
{
SCL = 0;
Delay_Us(10);
SCL = 1; //发出中止操作的时序
Delay_Us(10);
SCL = 0;
}
/*
*********************************************************************************************************
函数名称:WR_Cmd
功 能:写命令到ic卡
输 入:unsigned char cmd --- 命令
unsigned char addr --- 写入地址
unsigned char dat --- 写入数据
输 出:无
*********************************************************************************************************
*/
void WR_Cmd(unsigned char cmd,unsigned char addr,unsigned char dat)
{
Start_IC(); //开始
WR_Byte(cmd); //发送命令
WR_Byte(addr); //写地址
WR_Byte(dat); //写数据
Stop_IC(); //停止
}
/*
*********************************************************************************************************
函数名称:Read
功 能:写命令到ic卡
输 入:unsigned char area --- 读地址范围
unsigned char addr --- 读取起始地址
unsigned char dat --- 读到的数据
输 出:无
*********************************************************************************************************
*/
void Read_IC(unsigned char area, unsigned char addr, unsigned char len, unsigned char *buf)
{
unsigned char i = 0;
RstCard();
WR_Cmd(area,addr,0);
for (i=0;i<len;i++) //读取数据
{
buf[i] = RD_Byte();
}
}
/*
*********************************************************************************************************
函数名称:Write
功 能:写命令到ic卡
输 入:unsigned char area --- 写入地址范围
unsigned char addr --- 写入起始地址
unsigned char *buf --- 写入数据
输 出:无
*********************************************************************************************************
*/
unsigned char Write_IC(unsigned char area,unsigned char addr,unsigned char len,unsigned char *buf)
{
unsigned char i = 0,sta = 0;
RstCard();
for(i=0;i<len;i++)
{
WR_Cmd(area|0X08,addr+i,buf[i]);//发送命令 0X38/0X39/0X3C 地址+数据
sta = Wait_IC(); //发送操作脉冲
if(sta) Break_IC(); //超时了
}
return sta;
}
/*
*********************************************************************************************************
函数名称:ReadCm
功 能:从主存储器中读出数据块并存入ReadBuf中
输 入:unsigned char StarAddr --- 开始地址(0 - 255)
unsigned char ByteNum --- 要读出的字节数(1 - 256)
unsigned char *RecBuf --- 接收数据缓冲区
输 出: 0 --- 读成功
1 --- 表示无效卡或者卡损坏
*********************************************************************************************************
*/
unsigned char ReadCm(unsigned char StarAddr,unsigned char ByteNum,unsigned char *RecBuf)
{
unsigned char i = 0;
if(StarAddr>255)
return 1;
if((256-StarAddr) < ByteNum)
return 1;
if (RstCard() == 1) /* 使卡复位*/
return 1; /* 如果复位失败返回1 */
WR_Cmd(READ_MRAM,StarAddr,0x00); /* 发送读主存命令 */
for(i = 0; i < ByteNum; i++)
{
*RecBuf = RD_Byte(); /* 从主存中读出ByteNum个字节并存入RecBuf中 */
RecBuf++;
}
return 0;
}
/*
*********************************************************************************************************
函数名称: WriteCm
功 能: 把存在WriteBuf中的ByteNum个字节的数据存入主存储器中
输 入: unsigned char StarAdr --- 开始地址(0 - 255)
unsigned char ByteNum --- 要写入的字节数(1 - 256)
unsigned char *WriteBuf --- 写入数据缓冲区
输 出: 0 --- 写成功
1 --- 表示无效卡或者卡损坏
注 意:要校验密钥正确,才能对卡的主存储器写入数据
*********************************************************************************************************
*/
unsigned char WriteCm(unsigned char StarAdr,unsigned char ByteNum,unsigned char *WriteBuf)
{
volatile unsigned char writebuf[ByteNum];
unsigned char i = 0;
volatile unsigned char sta = 0;
if(StarAdr>255)
return 1;
if((256-StarAdr) < ByteNum)
return 1;
if (RstCard() == 1) /* 使卡复位*/
return 1; /* 如果复位失败返回1 */
for(i = 0; i < ByteNum; i++)
{
writebuf[i] = *WriteBuf;
WR_Cmd(WRITE_MRAM,StarAdr,writebuf[i]); //发送写主存储区命令
sta = Wait_IC(); //发送操作脉冲
StarAdr++; //写入字节地址加1
WriteBuf++; //写入缓冲区指针加1
}
return 0;
}
/****************************************************************
函数名称:SendClock
功 能:发送处理脉冲
输 入:
输 出:
*****************************************************************/
void SendClock(void)
{
SDA_IN();
SCL=0; //产生一个脉冲,做为处理的工始
Delay_Us(10);
SCL=1;
Delay_Us(10);
do
{
SCL=0;
Delay_Us(10);
SCL=1;
Delay_Us(10);
}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15) == 0); //不断产生处理脉冲,驱动IC卡内部处理,直到IC卡将IO口拉为高电平
}
/*
*********************************************************************************************************
函数名称:ReadPm
功 能:读保护存储器,并把保护存储器的4字节的内容存在RecBuf中
输 入:unsigned char *RecBuf --- 接收数据缓冲区,长度为4个字节
输 出:0 --- 读成功
1 --- 表示无效卡或者卡损坏
*********************************************************************************************************
*/
unsigned char ReadPm(unsigned char *RecBuf)
{
unsigned char i = 0;
if (RstCard() == 1) /* 使卡复位*/
return 1; /* 如果复位失败返回1 */
WR_Cmd(READ_PRAM,0x00,0x00); //读保护存储器命令
for(i = 0; i < 32; i++) //读保护存储器4个字节
{
*RecBuf = RD_Byte();
RecBuf++; //接收缓冲区指针加1
}
return 0;
}
/*
*********************************************************************************************************
函数名称:WritePm
功 能:写保护数据存储器
输 入:unsigned char Start_ICAdr --- 起始地址(0 - 31)
unsigned char ByteNum --- 写入的字节数(1 - 32)
unsigned char *WriteBuf --- 写入数据缓冲区
输 出:0 --- 写成功
1 --- 表示无效卡或者卡损坏
注 意: 首先校验密钥正确,才能对卡的保护存储器写入数据,
其次写入的数据必须与卡中对应的数据相等才能写入
即 WriteCm();WritePm();
*********************************************************************************************************
*/
unsigned char WritePm(unsigned char Start_ICAdr,unsigned char ByteNum,unsigned char *WriteBuf)
{
unsigned char i,sta = 0;
if(Start_ICAdr>31)
return 1;
if(ByteNum>(32-Start_ICAdr))
return 1;
if (RstCard() == 1) /* 使卡复位*/
return 1; /* 如果复位失败返回1 */
for (i = 0; i < ByteNum; i++)
{
WR_Cmd(WRITE_PMEM,Start_ICAdr,*WriteBuf); //写入一个字节
sta = Wait_IC(); //发送操作脉冲
if(sta) Break_IC(); //超时了
Start_ICAdr++; //写入字节地址加1
WriteBuf++; //写入缓冲区指针加1
}
return 0;
}
/*
*********************************************************************************************************
函数名称:ReadPsw
功 能:读加密存储器
输 入:unsigned char *Psw --- 用于装读到的加密存储器内容,长度为4个字节
Psw第一字节: 错误计数器值
Psw第二字节到第四字节: 卡的密码值
输 出:0 --- 读成功
1 --- 表示无效卡或者卡损坏
注 意: 必须校验密码正确才能读到正确的密钥值,否则读到的密码值为00 00 00
*********************************************************************************************************
*/
unsigned char ReadPsw(unsigned char *Psw)
{
unsigned char i = 0;
if (RstCard() == 1) /* 使卡复位*/
return 1; /* 如果复位失败返回1 */
WR_Cmd(READ_PSCR,0,0); //读加密存储器命令
for(i = 0; i < 4; i++) //读加密存储器的四个字节
{
*Psw = RD_Byte();
Psw ++;
}
return 0;
}
/*
*********************************************************************************************************
函数名称:CheckPsw
功 能:校验密码,把Psw中1,2,3字节的内容分别与加密存储器的1,2,3字节比较
输 入:unsigned char *Psw --- 待校验的密码值,长度为3个字节
输 出: 3: 核对密码成功
0: 卡已报废
1: 只剩一次校验机会,校验密码失败
2: 只剩二次校验机会,校验密码失败
4: 卡为无效卡或已损坏
*********************************************************************************************************
*/
unsigned char CheckPsw(unsigned char *psw)
{
unsigned char ReadBuf[4];
unsigned char i = 0,count;
volatile unsigned char Psw[3];
for(count=0;count<3;count++)
{
Psw[count] = *psw;
psw++;
}
if(ReadPsw(ReadBuf) == 1) //读加密存储器
return 4;
if(ReadBuf[0]==0x07) //0x07
{
WR_Cmd(WRITE_SAFE_MEM,0,0x03); //将EC写为0x03
Wait_IC(); //发送操作脉冲
}
else if(ReadBuf[0]==0x06 || ReadBuf[0]==0x05 || ReadBuf[0]==0x03)
{
WR_Cmd(WRITE_SAFE_MEM,0,0x01); //将EC写为0x01
Wait_IC(); //发送操作脉冲
}
else if(ReadBuf[0]==0x01 || ReadBuf[0]==0x02 || ReadBuf[0]==0x04)
{
WR_Cmd(WRITE_SAFE_MEM,0,0x00); //将EC写为0x00
Wait_IC(); //发送操作脉冲
}
WR_Cmd(SLE_PSC_CHK,1,Psw[0]);//校验密码的第一个字节
Wait_IC(); //发送操作脉冲
WR_Cmd(SLE_PSC_CHK,2,Psw[1]);//校验密码的第二个字节
Wait_IC(); //发送操作脉冲
WR_Cmd(SLE_PSC_CHK,3,Psw[2]);//校验密码的第三个字节
Wait_IC(); //发送操作脉冲
//
WR_Cmd(WRITE_SAFE_MEM,0,0XFF); //擦除错误计数器
Wait_IC(); //发送操作脉冲
WR_Cmd(READ_PSCR,0,0); //读加密存储器命令
for(i = 0;i < 4;i++)
{
ReadBuf[i] = RD_Byte(); //读加密存储器的四个字节
}
if(ReadBuf[0] == 0x07) return 3; //剩下3次校验机会,校验密码成功
else if(ReadBuf[0] == 0x06 || ReadBuf[0] == 0x05 || ReadBuf[0] == 0x03) return 2;//剩下2次校验机会,校验密码失败
else if(ReadBuf[0] == 0x04 || ReadBuf[0] == 0x02 || ReadBuf[0] == 0x01) return 1;//剩下1次校验机会,校验密码失败
else return 0; //剩下0次校验机会,卡报废
}
/*
*********************************************************************************************************
函数名称:SetPsw
功 能:修改加密存储器中的密码
输 入:NewPsw -- 新密码缓冲区,长度为3个字节
输 出:0 --- 读成功
1 --- 表示无效卡或者卡损坏
注 意: 必须校验密码成功后才能修改密码, 否则密码写不进卡中
*********************************************************************************************************
*/
unsigned char SetPsw(unsigned char *pNewPsw)
{
unsigned char i,count;
volatile unsigned char NewPsw[3];
for(count=0;count<3;count++)
{
NewPsw[count] = *pNewPsw;
pNewPsw++;
}
if (RstCard() == 1) /* 使卡复位*/
return 1; /* 如果复位失败返回1 */
for (i = 0; i < 3; i++)
{
WR_Cmd(WRITE_SAFE_MEM,i + 1,NewPsw[i]); //发送新的密码值
Wait_IC(); //发送操作脉冲
// NewPsw++; //密码值缓冲区指针加1
}
return 0;
}
/*
*********************************************************************************************************
函数名称:CheckC_Type
功 能:监测卡片类型
输 入:NewPsw -- 新密码缓冲区,长度为3个字节
输 出:0 --- 读成功
1 --- 表示无效卡或者卡损坏
注 意: 必须校验密码成功后才能修改密码, 否则密码写不进卡中
*********************************************************************************************************
*/
void CheckC_Type(void)
{
unsigned char i = 0;
unsigned char ReadBuf[4] = {0};
SDA_OUT();
RST = LOW;
SCL = LOW;
Delay_Us(10);
RST = HIGH;//产生复位时序
Delay_Us(10);
SCL = HIGH;
Delay_Us(20);
SCL = LOW;
Delay_Us(10);
RST = LOW;
for(i = 0; i < 4; i++)
{
ReadBuf[i] = RD_Byte();
}
SCL = HIGH;
Delay_Us(5);
SCL = LOW;
SLE_DATA = HIGH;
if ((ReadBuf[0] == SLE4442ID1) && (ReadBuf[1] == SLE4442ID2) && //SLE4442卡
(ReadBuf[2] == SLE4442ID3) && (ReadBuf[3] == SLE4442ID4))
{
WRC_Structure = SLE4442;
}
else
{
WRC_Structure = 0xFF;
}
}
5.程序.h
代码如下:
/*
*********************************************************************************************************
*********************************************************************************************************
*/
#ifndef _IC_H
#define _IC_H
/*
*********************************************************************************************************
头文件
*********************************************************************************************************
*/
#include "stm32f1xx_hal.h"
/*
*********************************************************************************************************
宏定义
*********************************************************************************************************
*/
#define HIGH 1
#define LOW 0
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
//IO方向设置
#define SDA_IN() {GPIOA->CRH&=0X0FFFFFFF;GPIOA->CRH|=0X80000000;}
#define SDA_OUT() {GPIOA->CRH&=0X0FFFFFFF;GPIOA->CRH|=0X30000000;}
#define RST PAout(11)
#define SCL PAout(12)
#define SLE_DATA PAout(15)
/*
*********************************************************************************************************
此处为IC卡保护区00~03单元的值,用于识别卡,这四个字节是SLE4442的标识,
如果 IC卡保护区00~03单元的值 不是这四个字节,表明该卡不是SLE4442卡
或卡已损坏
*********************************************************************************************************
*/
#define SLE4442ID1 0xA2
#define SLE4442ID2 0x13
#define SLE4442ID3 0x10
#define SLE4442ID4 0x91
/*
*********************************************************************************************************
卡类型编号
*********************************************************************************************************
*/
#define SLE4442 0x77
#define SLE4428 0x78
/*
*********************************************************************************************************
指令定义
*********************************************************************************************************
*/
#define READ_MRAM 0x30 //读主存储区
#define WRITE_MRAM 0x38 //写主存储区
#define READ_PRAM 0x34 //读保护存储区
#define WRITE_PMEM 0x3C //写保护存储区
#define READ_PSCR 0x31 //读安全存储器
#define WRITE_SAFE_MEM 0x39 //写安全存储器
#define SLE_PSC_CHK 0x33 //校验安全代码
/*
*********************************************************************************************************
全局定义
*********************************************************************************************************
*/
extern unsigned char WRC_Structure;
extern unsigned char *p_ic_sta;
/*
*********************************************************************************************************
函数声明
*********************************************************************************************************
*/
void ic_Init(void);
unsigned char RstCard(void);
void CheckC_Type(void);
unsigned char CheckPsw(unsigned char *psw);//校验密码
unsigned char ReadPsw(unsigned char *Psw);//读密码
unsigned char ReadCm(unsigned char StarAddr,unsigned char ByteNum,unsigned char *RecBuf);//读主存储区
unsigned char WriteCm(unsigned char StarAdr,unsigned char ByteNum,unsigned char *WriteBuf);//写主存储区
unsigned char WritePm(unsigned char StartAdr,unsigned char ByteNum,unsigned char *WriteBuf);//写保护区
unsigned char ReadPm(unsigned char *RecBuf);//读保护区
unsigned char SetPsw(unsigned char *pNewPsw);//修改密码
#endif
总结
1、通过在main.c中调用即可实现对应功能。
2、IC卡读取数据不需要校验密码,在写入数据需校验密码。
(注意:IC卡初始密码为0xff,0xff,0xff 校验错误三次后将锁死 无法写入数据)
3、PA15作为JTAG调试接口,所以要将其禁用。