STM32CubeMX和HAL库踩坑记——DMA+TIM输出比较模式(续集)

DMA+TIM输出比较模式(续集)

  • 1 问题与解决方法
    • 1.1 存储空间
      • 问题1:如何减少存储空间
      • 解决方法:
      • 问题2:如何控制脉冲数量
      • 解决方法:
  • 2 部分程序
    • 数组和定时器
    • 步进电机驱动程序
  • 总结

-----时隔一日,本次在上一次的代码中加入了一点小技巧。这个项目中有两个电机驱动器设置为6400脉冲一圈,而且电机带了64减速箱,所以需要640064个脉冲才能让电机转一圈,如果按照上一次的做法,让电机转一圈至少需要在单片机开辟640064*2个16位的空间,贼恐怖的内存占用量。
----- 但是这两个电机不需要调速,而且速度固定,一个电机为另外一个电机转速的1/2,通过小技巧来修改下程序,最终使用了200+100个16位的空间来存储这些数据。

1 问题与解决方法

1.1 存储空间

问题1:如何减少存储空间

减少存储空间需要减少数组的大小,由于使用输出比较模式,就算是周期和占空比一样的脉冲,CCRX的值也要随时变化

解决方法:

1、仔细分析该模会发现定时器计数到设定最大值就会溢出,例如CCRX的增量值为10,定时器设置最大值为100,定时器计算到100的时候会溢出变成0,然后从0开始计数,利用这个规律,假如我把增量设置为5,数组设置为200,那么定时器计数最大值应该设置为1000,定时器就能从0计数到1000,然后溢出又从0开始。
2、通过1的方法就可以使用数组那200个值循环发送,这个时候需要配置DMA为循环模式,CubeMX的配置如下图所示。
STM32CubeMX和HAL库踩坑记——DMA+TIM输出比较模式(续集)_第1张图片

问题2:如何控制脉冲数量

解决方法:

这个时候打开了DMA中断和定时器通道后,DMA每次发送完数组那200个数据就会进入一次发送完成中断并且重新将数组的值从第一个元素开始发送给CCRX。这个时候可以使用一个变量,在每次进入该通道的中断就加一,等到该变量与设定数值匹配就关闭DMA和定时器通道。但是这样的脉冲数量只能以200为单位,在实际项目中合理地设置这些参数。
总的来说,数组空间小,消耗内存小,DMA中断更频繁。

2 部分程序

数组和定时器

#define step1_speed_buf_size	200	
#define step2_speed_buf_size	step1_speed_buf_size/2	
#define step3_speed_buf_size	step1_speed_buf_size/2	

//递增5
__IO u16 step1_speed_buf[step1_speed_buf_size]={																				
0x0005,	0x000A,	0x000F,	0x0014,	0x0019,	0x001E,	0x0023,	0x0028,	0x002D,	0x0032,
0x0037,	0x003C,	0x0041,	0x0046,	0x004B,	0x0050,	0x0055,	0x005A,	0x005F,	0x0064,
0x0069,	0x006E,	0x0073,	0x0078,	0x007D,	0x0082,	0x0087,	0x008C,	0x0091,	0x0096,
0x009B,	0x00A0,	0x00A5,	0x00AA,	0x00AF,	0x00B4,	0x00B9,	0x00BE,	0x00C3,	0x00C8,
0x00CD,	0x00D2,	0x00D7,	0x00DC,	0x00E1,	0x00E6,	0x00EB,	0x00F0,	0x00F5,	0x00FA,
0x00FF,	0x0104,	0x0109,	0x010E,	0x0113,	0x0118,	0x011D,	0x0122,	0x0127,	0x012C,
0x0131,	0x0136,	0x013B,	0x0140,	0x0145,	0x014A,	0x014F,	0x0154,	0x0159,	0x015E,
0x0163,	0x0168,	0x016D,	0x0172,	0x0177,	0x017C,	0x0181,	0x0186,	0x018B,	0x0190,
0x0195,	0x019A,	0x019F,	0x01A4,	0x01A9,	0x01AE,	0x01B3,	0x01B8,	0x01BD,	0x01C2,
0x01C7,	0x01CC,	0x01D1,	0x01D6,	0x01DB,	0x01E0,	0x01E5,	0x01EA,	0x01EF,	0x01F4,
0x01F9,	0x01FE,	0x0203,	0x0208,	0x020D,	0x0212,	0x0217,	0x021C,	0x0221,	0x0226,
0x022B,	0x0230,	0x0235,	0x023A,	0x023F,	0x0244,	0x0249,	0x024E,	0x0253,	0x0258,
0x025D,	0x0262,	0x0267,	0x026C,	0x0271,	0x0276,	0x027B,	0x0280,	0x0285,	0x028A,
0x028F,	0x0294,	0x0299,	0x029E,	0x02A3,	0x02A8,	0x02AD,	0x02B2,	0x02B7,	0x02BC,
0x02C1,	0x02C6,	0x02CB,	0x02D0,	0x02D5,	0x02DA,	0x02DF,	0x02E4,	0x02E9,	0x02EE,
0x02F3,	0x02F8,	0x02FD,	0x0302,	0x0307,	0x030C,	0x0311,	0x0316,	0x031B,	0x0320,
0x0325,	0x032A,	0x032F,	0x0334,	0x0339,	0x033E,	0x0343,	0x0348,	0x034D,	0x0352,
0x0357,	0x035C,	0x0361,	0x0366,	0x036B,	0x0370,	0x0375,	0x037A,	0x037F,	0x0384,
0x0389,	0x038E,	0x0393,	0x0398,	0x039D,	0x03A2,	0x03A7,	0x03AC,	0x03B1,	0x03B6,
0x03BB,	0x03C0,	0x03C5,	0x03CA,	0x03CF,	0x03D4,	0x03D9,	0x03DE,	0x03E3,	0
};

//递增10
__IO u16 step2_speed_buf[step2_speed_buf_size]={
0x000A, 0x0014, 0x001E, 0x0028, 0x0032, 0x003C, 0x0046, 0x0050, 0x005A, 0x0064,
0x006E, 0x0078, 0x0082, 0x008C, 0x0096, 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00C8,
0x00D2, 0x00DC, 0x00E6, 0x00F0, 0x00FA, 0x0104, 0x010E, 0x0118, 0x0122, 0x012C,
0x0136, 0x0140, 0x014A, 0x0154, 0x015E, 0x0168, 0x0172, 0x017C, 0x0186, 0x0190,
0x019A, 0x01A4, 0x01AE, 0x01B8, 0x01C2, 0x01CC, 0x01D6, 0x01E0, 0x01EA, 0x01F4,
0x01FE, 0x0208, 0x0212, 0x021C, 0x0226, 0x0230, 0x023A, 0x0244, 0x024E, 0x0258,
0x0262, 0x026C, 0x0276, 0x0280, 0x028A, 0x0294, 0x029E, 0x02A8, 0x02B2, 0x02BC,
0x02C6, 0x02D0, 0x02DA, 0x02E4, 0x02EE, 0x02F8, 0x0302, 0x030C, 0x0316, 0x0320,
0x032A, 0x0334, 0x033E, 0x0348, 0x0352, 0x035C, 0x0366, 0x0370, 0x037A, 0x0384,
0x038E, 0x0398, 0x03A2, 0x03AC, 0x03B6, 0x03C0, 0x03CA, 0x03D4, 0x03DE, 0
};


__IO u16 step3_speed_buf[step3_speed_buf_size]={
0x000A, 0x0014, 0x001E, 0x0028, 0x0032, 0x003C, 0x0046, 0x0050, 0x005A, 0x0064,
0x006E, 0x0078, 0x0082, 0x008C, 0x0096, 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00C8,
0x00D2, 0x00DC, 0x00E6, 0x00F0, 0x00FA, 0x0104, 0x010E, 0x0118, 0x0122, 0x012C,
0x0136, 0x0140, 0x014A, 0x0154, 0x015E, 0x0168, 0x0172, 0x017C, 0x0186, 0x0190,
0x019A, 0x01A4, 0x01AE, 0x01B8, 0x01C2, 0x01CC, 0x01D6, 0x01E0, 0x01EA, 0x01F4,
0x01FE, 0x0208, 0x0212, 0x021C, 0x0226, 0x0230, 0x023A, 0x0244, 0x024E, 0x0258,
0x0262, 0x026C, 0x0276, 0x0280, 0x028A, 0x0294, 0x029E, 0x02A8, 0x02B2, 0x02BC,
0x02C6, 0x02D0, 0x02DA, 0x02E4, 0x02EE, 0x02F8, 0x0302, 0x030C, 0x0316, 0x0320,
0x032A, 0x0334, 0x033E, 0x0348, 0x0352, 0x035C, 0x0366, 0x0370, 0x037A, 0x0384,
0x038E, 0x0398, 0x03A2, 0x03AC, 0x03B6, 0x03C0, 0x03CA, 0x03D4, 0x03DE, 0
};

定时器的配置只给出初始化的,其他的和上一篇文章一样

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 71;	//设置脉冲频率
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 1000;	//这个值根据数值的大小和增量设置
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_OC_Init(&htim1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

步进电机驱动程序

下面是步进电机的配置

//电机x
typedef enum
{
	step_motor1,
	step_motor2,
	step_motor3,
}STEP_MOTORX_TypeDef;

//方向
typedef enum
{
	cw,		
	ccw,	
}DIR_TypeDef;

//使能
typedef enum
{
	DIS,
	EN,
}EN_TypeDef;

void set_motor_run(STEP_MOTORX_TypeDef step_motorx,DIR_TypeDef dir,u32 position,EN_TypeDef state);

/**
  * 函数功能: 设置步进电机
  * 输入参数: setp_motorx:电机x
  *						dir:方向
  *						position:设置位置
  *						state:使能
  * 返 回 值: 无
  * 说    明: 
  */
void set_motor_run(STEP_MOTORX_TypeDef step_motorx,DIR_TypeDef dir,u32 position,EN_TypeDef state)
{
	step_prmt[step_motorx].set_en=state;
	
	//使能和失能
	if(state==DIS)
	{
		step_prmt[step_motorx].set_pos=0;
		
		//失能电机
		if(step_motorx==step_motor1)
		{
			HAL_TIM_OC_Stop_DMA(&htim1,TIM_CHANNEL_2);	//关闭DMA通道和定时器通道2
			STEP1_DIS;
		}
		else if(step_motorx==step_motor2)
		{
			HAL_TIM_OC_Stop_DMA(&htim1,TIM_CHANNEL_2);	//关闭DMA通道和定时器通道3
			STEP2_DIS;
		}
		else if(step_motorx==step_motor3)
		{
			HAL_TIM_OC_Stop_DMA(&htim1,TIM_CHANNEL_2);	//关闭DMA通道和定时器通道4
			STEP3_DIS;
		}
	}
	else
	{
		//初始化参数
		step_prmt[step_motorx].set_dir=dir;
		step_prmt[step_motorx].set_pos=position;
		
		step_prmt[step_motorx].cur_pos=0;

		//启动定时器中断和电机
		if(step_motorx==step_motor1)
		{
			//方向
			if(dir==cw)
				STEP1_CW;
			else 
				STEP1_CCW;
				
			HAL_TIM_OC_Start_DMA(&htim1,TIM_CHANNEL_2,(uint32_t*)step1_speed_buf,step1_speed_buf_size); 	//开启定时器1 CH2
			STEP1_EN;
		}
		else if(step_motorx==step_motor2)
		{
			//方向
			if(dir==cw)
				STEP2_CW;
			else 
				STEP2_CCW;

			step_prmt[step_motorx].set_pos+=position;
			
			HAL_TIM_OC_Start_DMA(&htim1,TIM_CHANNEL_3,(uint32_t*)step2_speed_buf,step2_speed_buf_size); 	//开启定时器1 CH3
			STEP2_EN;
		}
		else if(step_motorx==step_motor3)
		{
			//方向
			if(dir==cw)
				STEP3_CW;
			else 
				STEP3_CCW;
				
			HAL_TIM_OC_Start_DMA(&htim1,TIM_CHANNEL_4,(uint32_t*)step3_speed_buf,step3_speed_buf_size); 	//开启定时器1 CH4
			STEP3_EN;
		}
	}

}

下面是中断的处理
先是stm32f1xx_it.c文件的处理,用变量来指示哪个通道中断了。
小提示:用户自己的代码区要写到注释里有BEGIN和END的中间行,不然下次用CubeMX输出工程的时候会把用户代码覆盖。

/**
* @brief This function handles DMA1 channel3 global interrupt.
*/
void DMA1_Channel3_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel3_IRQn 0 */
  flag_DMA_CH2_TCIF=1;
  /* USER CODE END DMA1_Channel3_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_tim1_ch2);
  /* USER CODE BEGIN DMA1_Channel3_IRQn 1 */
	
  /* USER CODE END DMA1_Channel3_IRQn 1 */
}

/**
* @brief This function handles DMA1 channel4 global interrupt.
*/
void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
	flag_DMA_CH4_TCIF=1;

  /* USER CODE END DMA1_Channel4_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_tim1_ch4_trig_com);
  /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */

  /* USER CODE END DMA1_Channel4_IRQn 1 */
}

/**
* @brief This function handles DMA1 channel6 global interrupt.
*/
void DMA1_Channel6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel6_IRQn 0 */
	flag_DMA_CH3_TCIF=1;

  /* USER CODE END DMA1_Channel6_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_tim1_ch3);
  /* USER CODE BEGIN DMA1_Channel6_IRQn 1 */

  /* USER CODE END DMA1_Channel6_IRQn 1 */
}

下面是回调函数处理

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	if(htim==&htim1)
	{
		if(flag_DMA_CH2_TCIF)
		{
			flag_DMA_CH2_TCIF=0;

			step_prmt[0].cur_pos++;
			//达到目标位移
			if(step_prmt[0].cur_pos==step_prmt[0].set_pos)
			{
				HAL_TIM_OC_Stop_DMA(&htim1,TIM_CHANNEL_2);
			}
		}
	  if(flag_DMA_CH3_TCIF)
		{
			flag_DMA_CH3_TCIF=0;

			step_prmt[1].cur_pos++;
			//达到目标位移
			if(step_prmt[1].cur_pos==step_prmt[1].set_pos)
			{
				HAL_TIM_OC_Stop_DMA(&htim1,TIM_CHANNEL_3);
			}
			
		}
		if(flag_DMA_CH4_TCIF)
		{
			flag_DMA_CH4_TCIF=0;
			
			step_prmt[2].cur_pos++;
			//达到目标位移
			if(step_prmt[2].cur_pos==step_prmt[2].set_pos)
			{
				HAL_TIM_OC_Stop_DMA(&htim1,TIM_CHANNEL_4);
			}
		}	
	}
			
}

总结

这种用法很好地解决了DMA传输数据耗内存的问题,但是缺点也有很多,这样用循环传输每次循环的内容都是一样的,不能用于步进电机加减速,而且脉冲数量控制分辨率不高,但是对于发送数量大、频率和占空比一样的脉冲是一种很好的技巧。

你可能感兴趣的:(STM32)