前言
本部分内容是前一篇《PSAM嵌入式驱动——原理》的后续篇,本节主要是以GD32F103为平台,模拟实现驱动部分。
一、背景
项目中以GD32F103为平台,
- 主频72M
- GPIO引脚四个:PSAM_VDD, PSAM_CLK, PSAM_RST, PSAM_IO
二、CLK和IO中断实现
1. CLK时钟实现
- 上节提到ISO中要求A类1-5MHz, B类1-4MHz, 占空比为 40%至60%,默认的频率是3.579MHz,这个频率经过卡内部分频器分频之后正好是9600bps。
原因如下:
在数据 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.
一般模拟中多采用4MHz,
对于MCU而言,通过主时钟分频或PWM都可以,
本项目中采用PWM,占空比50%,比较简单,
但对于用逻辑分析仪抓数时,此时对应的波特率就要调整为:4000000 / 372 = 10752
且别忘了是偶校验,如下图。
此部分代码比较简单了,如下:
void sam_clk_init(void)
{
timer_parameter_struct timer_initpara;
timer_oc_parameter_struct timer_ocinitpara;
rcu_periph_clock_enable(PSAM_CLK_GPIO_CLK);
gpio_init(PSAM_CLK_GPIO_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, PSAM_CLK_GPIO_PIN);
// Sam_clk timer_0 PWM 4MHz output.
rcu_periph_clock_enable(RCU_TIMER0);
timer_deinit(TIMER0);
// SystemCoreClock = 72MHz
// prescaler = (72M / 24M) - 1;
// period = (24M / 4M) - 1;
timer_struct_para_init(&timer_initpara);
timer_initpara.period = 5;
timer_initpara.prescaler = (SystemCoreClock / 24000000) - 1;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER0, &timer_initpara);
timer_channel_output_struct_para_init(&timer_ocinitpara);
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_channel_output_config(TIMER0, TIMER_CH_0, &timer_ocinitpara);
timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, (timer_initpara.period + 1) / 2);
timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER0, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE);
timer_primary_output_config(TIMER0, ENABLE);
timer_auto_reload_shadow_enable(TIMER0);
timer_enable(TIMER0);
}
示波器波形如下:主要是主频和占空比
2. IO时钟实现
前面提到基本时间单位etu, 9600对就103us, 那么可以看出基本单位是以us为整数倍,那么一般IO时钟就以1us为字节最小调整单位,因而设置IO中断设置为1MHz来作为接收发送基本单位。
代码如下(示例):
void sam_io_clk_adj(uint8_t channel, uint8_t time_mode, uint16_t etu)
{
uint16_t prescaler;
uint32_t us_cnt = 0;
uint16_t period;
sam_data.timer_mode = time_mode;
prescaler = 0;
us_cnt = ((smc_param[channel].fi / smc_param[channel].di) / 4) * etu;
if(us_cnt > 0xffff)
{
prescaler = 7200 * 2 - 1; //us_cnt=period*prescaler/48
period = us_cnt / 200;
}
else
{
prescaler = 72 - 1;
period = us_cnt;
}
timer_deinit(TIMER1);
timer_prescaler_config(TIMER1, prescaler, TIMER_PSC_RELOAD_NOW); //时钟分频系数72,所以定时器时钟为1M
timer_autoreload_value_config(TIMER1, period - 1);
timer_flag_clear(TIMER1, TIMER_FLAG_UP);
timer_interrupt_enable(TIMER1, TIMER_INT_UP);
timer_enable(TIMER1);
}
io时钟中断实现如下:
void TIMER1_IRQHandler(void)
{
sam_clk_handler();
timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP);
}
基中sam_clk_handler()需要实现如下功能:
1)对数据逐bit接收;
2)对数据逐bit发送;
3)冷复位结束,就是实现
上一篇中提到的将RST置为H, 因为冷复位要求是以clk为单位;
4)接收数据时的超时计数;
前面IO时钟中断是实现接收一个字节中的bit位及校验,
那么发送时是主动发送,没有问题,接收时呢?
接收时机就需要在IO中断中实现,
这个依赖于,发送完成后,进入接收状态,此时既要将IO设置为输入,
且要将接收每个字节的第一个bit位开始做为中断来触发一个字节的接收启动,
因此只需要设置IO为下降沿触发即可,
触发后,要及时停止,因为一个字节只需要触发一次即可;
void EXTI5_9_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(PSAM_1_IOI_EXTI_LINE))
{
sam_io_handler();
exti_interrupt_flag_clear(PSAM_1_IOI_EXTI_LINE);
}
}
三、复位实现
ISO中,冷复位时序要求如下:
如图所表达意思为:
1)CLK要在上电之后开始;
2)RST要在CLK之后200到400个周期之间拉高
3)IO要在RST拉高之后400到40000周期之间设置为输入接收
4)最后就是接收周期设置,由于1etu =(F/D) * (1/f) ,卡上电时默认F = 372, D = 1
标准时钟为3.579MHz,
对应周期为:103us
而前面我们设置是4MHz, 所以此处对应为:4 * 103 / 3.579 = 115us
此处代码实现如下:
int16_t sam_cold_reset(uint8_t channel, struct SMC_PARAM *smc_param)
{
smc_param->state = ICC_COLD_RESET;
convert_channel(channel);
if(SAM1_CH == channel || SAM2_CH == channel)
{
sam_io_out_intSet(channel, DISABLE);
sam_io_timer_stop();
sam_rst_out(channel, PIN_LOW);
sam_clk_out(channel, DISABLE);
sam_io_dir(channel, DIR_OUT);
sam_vcc_out(channel, smc_param->voltage);
delay_ms(1);
sam_clk_out(channel, ENABLE);
delay_us(100);
sam_io_dir(channel, DIR_IN);
sam_atr_process(channel, smc_param);
/*115etu = 372 * 115 = 42780clk*/
sam_io_timer_start(channel, RST_LOW_TIMER, 115);
}
else
{
return -1;
}
return 0;
}
一般而言如果冷复位失败,就需要启动复位来获取ATR,且在后续的APDU交互中,如果 出现异常,还是需要热复位的。
ISO中热复位要求如下:
如此,少了电源和CLK要求,这个就比较简单了,直接看实现就好。
int16_t sam_warm_reset(uint8_t channel, struct SMC_PARAM *smc_param)
{
smc_param->state = ICC_WARM_RESET;
convert_channel(channel);
if(SAM1_CH == channel || SAM2_CH == channel)
{
sam_io_out_intSet(channel, DISABLE);
sam_io_timer_stop();
sam_atr_process(channel, smc_param);
sam_rst_out(channel, PIN_LOW);
/*115etu = 372 * 115 = 42780clk*/
sam_io_timer_start(channel, RST_LOW_TIMER, 115);
}
else
{
return -1;
}
return 0;
}
总结
至此,基本最重要的时钟和冷热复位已实现,后面还需要实现:
0)参数初始化
1)字节的接收
2)字节的发送
3)T0字节发送
3)T1块发送
4)大小端字节发送集成
5)卡交互结束反激活,以便下次访问卡
等等,一个个函数实现慢慢来,下节继续。