DMA+TIM3通用定时器输出PWM波形驱动TM1814

项目中用到了TM1814,这是芯片的简单介绍。在这里插入图片描述
在这里插入图片描述
可以看出0码1码对于占空比时序有比较严格的要求,0码1码周期均在1.25us-2.5us,低电平时间的典型值分别为360ns和720ns。原先采用输出时序的方式为io输出高低电平,中间的延时用_NOP_函数代替,但这种方式效率低下,资源占用多,并且在放松帧数据时得关闭所有中断,包括滴答定时器(否则会产生300ns延时导致0码被识别成1码,帧数据混乱),这样对与跑RTOS整个系统都会造成严重后果。
故后来采用DMA+定时器的方式输出0 1码,它的原理是利用TIMx通用定时器的PWM模式,简单的说TIM3->CNT会不停累加,当计数到TIM3->CCR3时会将通道对应的波形翻转,当计数到TIM3->ARR时会发生上溢出,于是继续从0开始计数。(详细解释可以参考其他博客)
在这里插入图片描述
与1814相连的引脚为PC8,根据规格书可以看出
在这里插入图片描述
在这里插入图片描述
它对应的通道为TIM3_CH3,为DMA1_CH2,一开始我确实以为是这么配置的于是我的配置代码为:

void TIM3_CH3_PWM_Init(u16 arr,u16 psc)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟
    GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE); //Timer3完全重映射  必须要加
 
    //设置该引脚为复用输出功能,输出TIM3 CH3的PWM脉冲波形	GPIOC.8
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH2
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO
	
    //初始化TIM3
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
    //初始化TIM3 Channel3 PWM模式
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
		//TIM_OCInitStructure.TIM_Pulse = 20;
    TIM_OC3Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2
		TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存
				
//		TIM3->CR1 |= 4; 
//		TIM3->CR2 |= 8; 
//		TIM3->DIER |= 16384; 
		TIM_DMACmd(TIM3,TIM_DMA_Update,ENABLE);            // 开启 TIM3 UPDATE 触发 DMA  ,即 DMA2_Stream1 TIM_DMA_CC3|TIM_DMA_Update|TIM_DMA_Trigger
		TIM_Cmd(TIM3, ENABLE);	//打开TIM3
		//TIM_DMACmd(TIM3,TIM_DMA_CC3,ENABLE);            // 开启 TIM3 UPDATE 触发 DMA  ,即 DMA2_Stream1
}
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA传输
	
  DMA_DeInit(DMA_CHx);   //将DMA的通道1寄存器重设为缺省值

	DMA1_MEM_LEN=cndtr;
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpar;  //DMA外设基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = cmar;  //DMA内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
	DMA_InitStructure.DMA_BufferSize = cndtr;  //DMA通道的DMA缓存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA_CHx, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器  	
} 

```c
#define SEND_BUF_SIZE 3	//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.

u8 SendBuff[SEND_BUF_SIZE] = {20,30,40};	//发送数据缓冲区
const u8 TEXT_TO_SEND[] = {"ALIENTEK Elite STM32F1 DMA 串口实验"};
int main(void)
{
  u16 i;
  u8 t = 0;
  u8 j, mask = 0;
  float pro = 0; //进度
  delay_init();	    	 //延时函数初始化
  uart_init(115200);	 	//串口初始化为115200
  LED_Init();		  		//初始化与LED连接的硬件接口
  KEY_Init();				//按键初始化
	TIM3_CH3_PWM_Init(100,0);
  MYDMA_Config(DMA1_Channel2, (u32)&TIM3->CCR3, (u32)SendBuff, SEND_BUF_SIZE); 
  i = 0;
  while(1)
  {
      MYDMA_Enable(DMA1_Channel2);//开始一次DMA传输!
    i++;
    delay_ms(10);
    if(i == 20)
    {
      LED0 = !LED0; //提示系统正在运行
      i = 0;
    }
  }
}

但发现PC8引脚上的波形时钟为高电平,没有变化,如果给TIM3一个初始脉宽,TIM_OCInitStructure.TIM_Pulse = 20;
则会输出固定占空比的波形,这样看来像是DMA没有启动一样。这个问题困扰了几天,反复调试依然没有用,知道后来我看到了一篇博文,地址为:https://blog.csdn.net/qq_21793157/article/details/88798467
才发现如果要产生定时器更新(上溢出)产生DMA搬运,则应注意定时器通道TIM3_UP即对应DMA1_CH3和函数TIM_DMACmd(TIM3,TIM_DMA_Update,ENABLE);,而TIM3_CH3对应DMA1_CH2以及函数TIM_DMACmd(TIM3,TIM_DMA_CC3,ENABLE);
还有别忘了把这个函数设为DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 故困扰了我很久的问题终于解决了!!!

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要使用 DMA定时器 (TIM) 驱动 WS2812 LED 灯带,你可以按照以下步骤进行配置: 1. 配置 GPIO:选择一个合适的 GPIO 引脚,用于连接 WS2812 灯带的数据引脚。在 STM32CubeMX 或者手动设置寄存器的方式下,将该引脚配置为推挽输出模式。 2. 配置 DMA:配置 DMA 以实现数据的传输。选择一个合适的 DMA 通道,并将其配置为从内存到外设的传输方向。设置传输数据的大小为 8 位。 3. 配置定时器 (TIM):选择一个合适的定时器,并将其配置为 PWM 模式。将定时器的周期设置为 WS2812 的周期,通常为 1.25us。配置比较匹配单元 (CCU) 以产生适当的高电平和低电平时间。 4. 编写发送函数:编写一个函数,用于生成并发送数据到 WS2812 灯带。使用 DMA 将生成的数据传输到 GPIO 输出寄存器。 在发送函数中,你可以使用 TIM 的比较匹配中断来触发 DMA 传输。每次比较匹配中断时,将下一个像素的数据加载到 DMA 的内存中,并启动传输。在 DMA 完成传输后,可以在 DMA 完成中断中关闭 TIM,以确保传输完成。 这种方法可以减少 CPU 的负载,因为数据传输和定时信号的生成都由 DMA 和 TIM 完成。同时,确保根据 WS2812 的协议要求设置适当的延时。 请注意,以上步骤仅提供了一个大致的思路,具体的实现细节可能因硬件配置和需求而有所不同。建议参考 STM32F103ZET6 的参考手册和相应的库文档,以获取更详细的配置和代码示例。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值