PSAM嵌入式驱动——GD32模拟01

前言

本部分内容是前一篇《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时钟实现

  • 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时钟中断实现

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中断实现

前面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)卡交互结束反激活,以便下次访问卡
等等,一个个函数实现慢慢来,下节继续。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值