1. 程式人生 > >STM32——使用PWM+DMA實現脈衝傳送精確控制

STM32——使用PWM+DMA實現脈衝傳送精確控制

我為什麼要寫這個程式碼。。。

之前用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 );
    DMA_Config(DMA1_Channel6, (u32)&TIM3->ARR, (u32)send_buf, 100);
    TIM3->ARR = 599 ;   /* 由於最後一項是0,所以在最後的時刻ARR會被清零,導致下一次啟動無效。*/
  DMA_InitStructure.DMA_BufferSize = DMA_CHx->CNDTR;
  DMA_Cmd(DMA_CHx, ENABLE);
}     

/*
 *進度反饋,返回剩下的資料量
 */
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個脈衝給步進驅動器,那麼我需要分多次傳送。這部分我後面會繼續做,有空會分享給小夥伴們。