再写TIMER+DMA驱动GPIO

    早两年前写过用DMA直接驱动GPIO的文章,当时写的只是比较原理性的,没有实例。最近在用到单线总线,上了RTOS,为了提高效率,减少内核的浪费,就想到用TIMER+DMA+GPIO去输出单线总线时序。
 

  上图是单线总线的时序,常规的方法就直接控制IO 输出,电平的间隔用延时去控制,代码如下:
  
void SC50X0B_SDA(unsigned char data)
  {
      unsigned char i;
      SC5020_SDA_Reset();
      HAL_Delay(3);
      for(i=0;i < 8;i++)
      {
        SC5020_SDA_Set();
        if(data & 0x01)
        {
          delay_us(1200);
          SC5020_SDA_Reset();
          delay_us(400);
        }
        else
        {
          delay_us(400);
          SC5020_SDA_Reset();
          delay_us(1200);
        }
        data >>= 1;
      }
      SC5020_SDA_Set();
      delay_us(200);
  }
  整个时序执行需要2.5+ 1.2+0.4 X8 = 15.3ms ,这样内核要花 15.3ms 时间去输出这个时序,如果中断频繁也会导致时序有误差,引用通信失败。尤其是上了 RTOS 的话,会占用进程太长的时间,导致其它的进程无法及时执行,或者进程被抢占,导致时序误差,通信失败。当然很多人会有说用定时器中断去控制 IO 输出也可以实现,这样就大大的提高的效率,我这里不过多的讨论。用 TIMER+DMA+GPIO 的话,全靠外设输出时序,完全不占用内核的时间,这样的效率达到最高。如下是完整的代码实现:
#define TIMEFORDMATOGPIO TIM1
#define TIMEFORDMATOGPIO_CLK __HAL_RCC_TIM1_CLK_ENABLE
#define TIMEFORDMATOGPIODAN DMA1_Channel5
TIM_HandleTypeDef  sc5080htim;
 DMA_HandleTypeDef  hdma_sc5080tim_up;
 uint32_t SC5080Data[4 *8 +1];
void SC5080_TIMx_MspInit(void);
 
void SC5080_TIMx_Init(void)
{
  sc5080htim.Instance = TIMEFORDMATOGPIO;
  sc5080htim.Init.Prescaler = 72 - 1;
  sc5080htim.Init.CounterMode = TIM_COUNTERMODE_UP;
  sc5080htim.Init.Period = 400;
  sc5080htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  SC5080_TIMx_MspInit();
  __HAL_TIM_ENABLE_DMA(&sc5080htim, TIM_DMA_UPDATE);
  HAL_TIM_Base_Init(&sc5080htim);
}
 
 
void SC5080_TIMx_MspInit(void)
{
    /* Peripheral clock enable */
    TIMEFORDMATOGPIO_CLK();
   
  hdma_sc5080tim_up.Instance = TIMEFORDMATOGPIODAN;
  hdma_sc5080tim_up.Init.Direction = DMA_MEMORY_TO_PERIPH;
  hdma_sc5080tim_up.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_sc5080tim_up.Init.MemInc = DMA_MINC_ENABLE;
  hdma_sc5080tim_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
  hdma_sc5080tim_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
  hdma_sc5080tim_up.Init.Mode = DMA_NORMAL;
  hdma_sc5080tim_up.Init.Priority = DMA_PRIORITY_VERY_HIGH;
  HAL_DMA_Init(&hdma_sc5080tim_up);
}
 
void SC5080B_Set(uint8_t data)
{
      uint8_t i;
      SC5020_SDA_Reset();
#if 0
      HAL_Delay(3);
#else   
      osDelay(3);
#endif
      for(i=0;i < 8;i++)
      {
        if(data & 0x01)
        {
          SC5080Data[i*4 +0] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +1] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +2] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +3] =   SC5020_SDA_Pin<<16;
        }
        else
        {
          SC5080Data[i*4 +0] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +1] =   SC5020_SDA_Pin<<16;
          SC5080Data[i*4 +2] =   SC5020_SDA_Pin<<16;
          SC5080Data[i*4 +3] =   SC5020_SDA_Pin<<16;
        }
        data >>= 1;
      }
            SC5080Data[i*4 +0] = SC5020_SDA_Pin;
     HAL_DMA_Start_IT(&hdma_sc5080tim_up,(uint32_t)(&SC5080Data),(uint32_t)(&(SC5020_SDA_GPIO_Port->BSRR)),4*8+1);
     HAL_TIM_Base_Start(&sc5080htim);
}
 
大概的原理就是利用定时器的update (或者 PWM )去触发对应的 DMA 通道, DMA 从内存搬到数据到 GPIO BSRR 寄存器, GPIO 对的 Pin 就会改变电平。这里用到了 TIM1 ,查手册知道 TIM1 update 对应的 DMA 通道是 DMA1 的通道 5 ,所以初始了 TIM1 DMA1_Channel5
<ignore_js_op>
  发送一个Bit 需要 1600us ,也就是 4 400us ,也就是说如果发 1 就是,定时器触发 4 DMA DMA 搬运内存到 GPIO BSRR ,只要搬到的数据到 BSRR 对应 IO 的输出 1110 GPIO 就是输出 1200us 的高电平, 400us 的低电,那 IO 就正好输出时序中的 1 8 Bit ,就是让 DMA 搬到 4X8 次,就可以整个时序。实际使用中只要调用  void SC5080B_Set(uint8_t data) ,计算当时发送时序需要搬到的数据,再启动 DMA 跟定时器,时序就会自动输出,不在需要内核的干预。当然这很容易拓展到同时最多输出 16 路的时序,效率上也一样的,大大的节省的内核的开销,让程序更加的高效。

 

转载于:https://www.cnblogs.com/mzwhhwj/p/9781449.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值