STM32——使用PWM+DMA实现脉冲发送精确控制

太多小伙伴问我要代码,实在是无暇回复,这里我直接把网盘发出来吧,需要的小伙伴自取就可以了:
https://pan.baidu.com/s/1CwizNXizIqGcEvI0bvMtWA 提取码: w36x

我为什么要写这个代码。。。

之前用stm32写过脉冲发送的代码,用来控制步进电机,但是缺点明显,之前是用定时器中断做的,所以一但控制的电机多起来,MCU资源占用就很大,这在大多数情况下是不可接受的,更不用说多轴联动了。
最近做的步进电机CAN总线控制系统,就想顺便重新写驱动。希望做到占用很少的MCU资源,实现脉冲发送的精确控制。既然是用来控制步进电机,那么脉冲的数量频率一定要可控,要不然怎么实现电机的加减速曲线。于是就想到了DMA。

DMA (直接存储器访问)
DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。(资料来自百度百科)

在记忆里,STM32的数据手册中有提到PWM有DMA触发的模式。那么这一次终于有用武之地了。

show me your code !!!

main.c

#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "dma.h"
#include "timer.h"
#define size 100

extern u16 DMA1_MEM_LEN;
extern DMA_InitTypeDef DMA_InitStructure;  
u16 send_buf[size];
 
int main(void)
{	
	int i;
	int feedback;
	delay_init();	
	uart_init(115200);
	KEY_Init();
	DMA_Config(DMA1_Channel6, (u32)&TIM3->ARR, (u32)send_buf, size);
	TIM3_PWM_Init(599,7199);
	for(i = 0; i < size; ++i)
	{
		if(i != size - 1)
		send_buf[i] = 100 + 10 * i;
		else
		send_buf[i] = 0;
	}
	DMA_Enable(DMA1_Channel6);
	while(1)
	{
			feedback = DMA_send_feedback(DMA1_Channel6);
			if(feedback != 0)
			{
				printf("-> ");
				printf("%d\r\n", DMA_send_feedback(DMA1_Channel6));
			}
			if(KEY_Scan(0) == 1)
			{
				DMA_Enable(DMA1_Channel6);
			}
	}
}

main.c中的send_buf[size]是控制信息的来源,你想要如何发送脉冲,全靠这个buffer
这里写图片描述
这是send_buf的数据组成,size 决定了发送脉冲的数量information决定了脉冲的频率。至于脉冲的脉宽,可以在timer.c中的初始化函数中修改。

dma.c

#include "dma.h"

extern u16 send_buf[100];
extern 	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;	 /* 保存DMA每次数据传送的长度 */

/*
 *DMA1的各通道配置
 *这里的传输形式是固定的,这点要根据不同的情况来修改
 *从存储器->外设模式/8位数据宽度/存储器增量模式
 *DMA_CHx:DMA通道CHx
 *cpar:外设地址
 *cmar:存储器地址
 *cndtr:数据传输量
 */
void DMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
	
 	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	/* 使能DMA钟源 */
	delay_ms(5);
	
  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_HalfWord;  /* 数据宽度为16位 */
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /* 数据宽度为16位 */
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  /* 工作在正常模式 */
	DMA_InitStructure.DMA_Priority = DMA_Priority_High; /* DMA通道 x拥有中优先级 */
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  /* DMA通道x没有设置为内存到内存传输 */
	DMA_Init(DMA_CHx, &DMA_InitStructure);  	
} 

/* 开启一次DMA传输 */
void DMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
	DMA_Cmd(DMA_CHx, DISABLE );
	TIM3->ARR = 2;	/* 由于最后一项是0,所以在最后的时刻ARR会被清零,导致下一次启动无效。*/
 	DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);
  	DMA_Cmd(DMA_CHx, ENABLE);
	TIM_Cmd(TIM3, ENABLE);  /* 使能TIM3 */
	TIM3->EGR = 0x00000001;	/* 由于最后一次ARR值为0,这是为了停止定时器对io口的操作,但是不要忽略了一点:CNT并没有停止计数,而且是不会再停下来,如果没有手动操作的话,所以需要在每次dma使能时加上一句,将EGR里的UG位置1,清零计数器 */
}	  

/*
 *进度反馈,返回剩下的数据量
 */
u16 DMA_send_feedback(DMA_Channel_TypeDef* DMA_CHx)
{
	return DMA_CHx->CNDTR;
} 

dma.h

#ifndef __DMA_H
#define	__DMA_H	   
#include "sys.h"
#include "delay.h"
#include "dma.h"
#include "timer.h"
#include "usart.h"
#include "stm32f10x_dma.h"

void NVIC_Configuration(void);				    					    

void DMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr);//配置DMA1_CHx

void DMA_Enable(DMA_Channel_TypeDef*DMA_CHx);//使能DMA1_CHx

u16 DMA_send_feedback(DMA_Channel_TypeDef* DMA_CHx);

void DMA1_Channel6_IRQHandler(void);
#endif

timer.c

#include "timer.h"
#include "led.h"
#include "usart.h"
 
 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
/*
 *TIM3 PWM部分初始化 
 *PWM输出初始化
 *arr:自动重装值
 *psc:时钟预分频数
 */
void TIM3_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	/* 使能定时器3时钟 */
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  /* 使能GPIO外设 */   
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; /* TIM_CH1*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  /* 复用推挽输出 */
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);/* 初始化GPIO */
 
   /* 初始化TIM3 */
	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的时间基数单位 */
	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; /* 选择定时器模式:TIM脉冲宽度调制模式1 */
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; /* 比较输出使能 */
	//TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  /* 使能TIM3在CCR1上的预装载寄存器*/
	TIM_OCInitStructure.TIM_Pulse= 100;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; /* 输出极性:TIM输出比较极性高 */
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);  /* 根据T指定的参数初始化外设TIM3 OC1 */
	//TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);	/* 如果是要调节占空比就把这行去掉注释,然后注释掉下面那行,再把DMA通道6改为DMA通道3 */
	TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);
	TIM_Cmd(TIM3, ENABLE);  /* 使能TIM3 */
	
}

timer.h

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"

void TIM3_PWM_Init(u16 arr,u16 psc);

#endif

看看运行效果

这里写图片描述
垃圾示波器,玩玩就好。。。。
篇幅限制,结果是最终将会发送100个脉冲,频率慢慢改变。但是在这个有一点需要提醒小伙伴们注意一下,send_bufsize并不是要多大有多大的,比较STM32的RAM有限,没办法一下子给你分配那么多空间,如果你强行分配的话,那么在编译过程中一定会报错,我在测试的时候,分配到3W+的时候已经差不多是极限了,但是这仅仅只是一个demo程序,在实际应用的时候不可能把RAM都给send_buf,所以如果想要发送大量的脉冲的话,比如我想发409600个脉冲给步进驱动器,那么我需要分多次发送。这部分我后面会继续做,有空会分享给小伙伴们。

©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页