STM32輸出一定個數佔空比可調的單脈衝信號

軟件環境:Keil5

硬件環境:STM32F103C8T6

最近有個項目需要用到STM32F0產生一定數量不同佔空比的單脈衝信號,初步構思了一下。以前配置一些傳感器即根據時序圖寫脈衝序列就是用簡單的延時模擬單個脈衝信號,這種方法在工程項目中太浪費CPU資源。定時器產生的PWM波又是連續的,如果能讓連續的PWM波變成單個的,那麼就可以簡單的通過寫CCRx寄存器產生單脈衝信號,所以可以開一個定時器捕獲PWM脈衝的上升沿,在第二個上升沿的時候關閉定時器。這個方案其實也想試試,但是如果數據量很大的情況下,每一個字節的發送就要進出中斷8次,感覺不是很好。

結合STM32F0系列喫緊的資源,初步選用F0上面的大概篩選了3種方法去實現我們需要的單脈衝信號!

PlanA : 利用單脈衝信號

翻了翻手冊,是有一個寄存器去開啓定時器單脈衝模式的。
在這裏插入圖片描述
即開啓了這個寄存器以後,計數器可以自動地在產生下一個更新時間UEV時停止。完美的滿足了我們的需求,控制簡單,佔用內存資源少。下面上代碼:

#include "pwm.h"
//TIM3CH1 <--> PA6 
extern u16 DMA_Buff[8];

void PWM_Init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO , ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE);
	
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	TIM_TimeBaseInitStructure.TIM_ClockDivision =  0;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = arr;
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);
	TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);
	TIM_SelectOnePulseMode(TIM3,TIM_OPMode_Single);//開啓單脈衝模式 第二次調用SetCompare一直產生不了波形,初步懷疑這個函數產生了某個事件,沒有清除,導致的。
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
}
int main(void)
{		
	delay_init();	    	 //延時函數初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 	 //設置NVIC中斷分組2:2位搶佔優先級,2位響應優先級
	uart_init(115200);	 //串口初始化爲115200
	LED_Init();			     //LED端口初始化
	PWM_Init(1000-1,72-1); //TIM3CH1 
	TIM_SetCompare1(TIM3,500);	
	while(1)
	{
	}	 	
}

這個方法的關鍵在於設置好定時器的PWM模式後,開啓TIM_SelectOnePulseMode(TIM3,TIM_OPMode_Single);這個函數,打開定時器單脈衝模式,然後設置一次TIM_SetCompare1(TIM3,500);開啓脈衝傳輸。仿真後的波形圖爲:
在這裏插入圖片描述
改變TIM_SetCompare1(TIM3,x)x的值即可獲得不同佔空比的單脈衝信號!但是測試的時候,重複調用TIM_SetCompare1這個函數發現並不能產生第二個波形,頓時陷入了自我懷疑,初步懷疑是某個事件沒有清楚導致PWM通道被強制關閉了,重新初始化TIM3定時器倒是可以發送新的脈衝信號,但是每一個脈衝信號都要重新配置一遍定時器這點實在是讓人不能接受,遂放棄這個方案。在這裏插入圖片描述
用高級定時器可以通過設置這個RCR寄存器產生多個脈衝在這裏插入圖片描述
沒有試這個方案,看起來並不能改變每個脈衝的佔空比。

PlanB :DMA+TIM 利用定時器和DMA去完成(親測成功)

這個方案是網上看到的比較主流的產生一定數量不同佔空比的單脈衝信號,利用了定時器輸出比較通道產生PWM波,然後將DMA與該通道的CCRx寄存器“綁定”,DMA把內存中數組的值發送給“綁定”的CCRx寄存器,以產生不同佔空比的信號,當DMA傳輸完成時,進入DMA傳輸完成中斷關閉定時器即可。每次發送DMA時在開啓定時器。

雖然看起來感覺不是很難,但是調試過程真心折磨人,我也是在出錯了很多次以後,才意識到這個方案有兩種解決問題的方法。一種是通過 更新事件+DMA“綁定”CCRx寄存器 ,即通過改變輸出比較的閾值(CCRx)改變信號佔空比,然後通過更新事件觸發DMA計數次數從而產生中斷,另一種是通過 輸出比較事件+DMA“綁定”ARR寄存器,即通過改變定時器的計數次數(ARR)的值改變信號佔空比,然後通過每次輸出比較事件的產生觸發DMA計數從而產生中斷。

簡單來說,就是第一種用了TIM3_UP這個DMA通道,即DMA1CH3,將定時器與該通道綁定,每一次計數到ARR時,就會產生更新事件來增加DMA計數。該方法對應的庫函數爲:
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);
DMA1_CH3_Init(DMA1_Channel3,(u32)&TIM3->CCR1,(u32)DMA_Buff,8);

第二種用了TIM3_CH1這個通道,即對應DMA1CH6,通過DMA與ARR寄存器綁定,而CCRx寄存器的值不改變,來改變每個脈衝的佔空比,同時每一個脈衝會產生一個輸出比較事件,該事件會增加DMA的計數。該方法對應的庫函數爲:
TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);
DMA1_CH3_Init(DMA1_Channel6,(u32)&TIM3->ARR,(u32)DMA_Buff,8);

下面先上第一種方法代碼:

//定時器配置函數 >< >< >< >< >< >< >< >< >< >< >< >< 
#include "pwm.h"
//TIM3CH1 <--> PA6 
extern u16 DMA_Buff[8];

void PWM_Init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO , ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 , ENABLE);
	
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
	TIM_TimeBaseInitStructure.TIM_ClockDivision =  0;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = arr;
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = DMA_Buff[0];
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	
	//TIM相關初始化!
	TIM_OC1Init(TIM3,&TIM_OCInitStructure);	
	TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); //使能定時器更新事件增加DMA計數
	//TIM_DMACmd(TIM3, TIM_DMA_CC1 ,ENABLE);
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
}

//DMA配置函數!@!!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@
#include "dma.h"
#include "usart.h"

DMA_Tx_Achieve_Flag dmaTx_Flag ;

/*
	初始化DMA1通道6 <--> TIM3CH1 <--> PA6
	函數思路:通過DMA從內存拿出數組傳遞給TIMx->CCR1,改變PWM波的佔空比
	傳遞完成後進入DMA傳輸完成中斷,關閉定時器。
*/
void DMA1_CH3_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 perAddr,u32 memAddr,u16 ndtr)
{
	DMA_InitTypeDef DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	__NOP();
	
	DMA_DeInit(DMAy_Channelx);//軟件復位後在進行查忙判斷,不然會卡死!!用while的壞處。。
	while ( 0 != DMA_GetCurrDataCounter(DMAy_Channelx)){}//等待DMA可配置
	//printf("DMA Init Success!@!");
		
	/******************************************************************************/	
	DMA_InitStructure.DMA_BufferSize = ndtr;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;/* 數據傳輸方向,從內存讀取發送到外設 */
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 
	DMA_InitStructure.DMA_MemoryBaseAddr = memAddr;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /*正常模式,即由程序控制發送數據*/
	DMA_InitStructure.DMA_PeripheralBaseAddr = perAddr;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /*半字,即16字節,和數組對應*/
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_Init(DMAy_Channelx,&DMA_InitStructure);
	/******************************************************************************/
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
	/******************************************************************************/
		
	/******************************************************************************/
	DMA_ITConfig(DMAy_Channelx,DMA_IT_TC,ENABLE);
	DMA_ClearITPendingBit(DMA1_IT_TC3); //這裏比較噁心,想換通道還要改這裏的標誌位!
	/******************************************************************************/	
}

//開啓一次DMA傳輸
void MYDMA_Enable(DMA_Channel_TypeDef* DMA_CHx,u16 myndtr)
{ 
	
	DMA_Cmd(DMA_CHx, DISABLE );  //關閉USART1 TX DMA1 所指示的通道
	//printf("完成了一次DMA傳輸!");
 	DMA_SetCurrDataCounter(DMA_CHx,myndtr);//DMA通道的DMA緩存的大小
 	DMA_Cmd(DMA_CHx, ENABLE);  //使能USART1 TX DMA1 所指示的通道
	TIM_Cmd(TIM3,ENABLE);
}

//進入這裏就說明我們已經成功發送了一個字節的數據給到外設,那麼我們在這裏就可以;/
//關閉定時器,防止產生過多的脈衝,而且進入這裏以後,說明當前DMA沒有在發送數據,那麼我們就可以;/
//在這裏做一個判斷,去告訴外界當前DMA狀態!
//如果不加DMA發送完成中斷,在DMA發送完成中斷中關閉定時器,那麼就會在發送第8個數據後持續發送第8個數據!
void DMA1_Channel3_IRQHandler(void)
{
	if(SET == DMA_GetITStatus(DMA1_IT_TC3))
	{
		//dmaTx_Flag = Tx_OK;
		//printf("DMA發送完成!");
		
		TIM_Cmd(TIM3,DISABLE);
		DMA_ClearITPendingBit(DMA1_IT_TC3);
	}
}

//主函數!@!!@!@!@!@!@!@!@!@!@!@!@!@!@!@!@
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
#include "pwm.h"
#include "dma.h"

u16 DMA_Buff[8] = {100,200,300,400,500,600,700,800};

/*
目的:生成固定數量不同佔空比的脈衝,可驅動彩燈IC,數碼管IC
PlanA : 利用單脈衝   失敗
PlanB : DMA+TIM     成功
PlanC : 主從定時器   待測試(較高級)
*/

int main(void)
{		
	delay_init();	    	 //延時函數初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 	 //設置NVIC中斷分組2:2位搶佔優先級,2位響應優先級
	uart_init(115200);	 //串口初始化爲115200
	LED_Init();			     //LED端口初始化
	PWM_Init(1000-1,72-1); //TIM3CH1 
	
	//TIM_SetCompare1(TIM3,500);
	
	DMA1_CH3_Init(DMA1_Channel3,(u32)&TIM3->CCR1,(u32)DMA_Buff,8);
	//DMA1_CH3_Init(DMA1_Channel6,(u32)&TIM3->ARR,(u32)DMA_Buff,8);
	
	MYDMA_Enable(DMA1_Channel3,8);//將DMA_Buff的數據發送到CCR1上面
	
	while(1)
	{
	}	 	
}

打開虛擬示波器可見:
在這裏插入圖片描述
產生了8個,佔空比依次增加的單脈衝信號,通過改變數組的大小和對應的CCRx的值,即可增加至40甚至更多個單脈衝信號。

當然也可以產生連續的多個信號:
在這裏插入圖片描述
每開啓一次DMA傳輸,便將數組數據傳輸到對應的寄存器,最好有一定間隔或者在中斷中進行判斷一下。

在函數中註釋的兩部分分別是:
//TIM_DMACmd(TIM3, TIM_DMA_CC1 ,ENABLE);
//DMA1_CH3_Init(DMA1_Channel6,(u32)&TIM3->ARR,(u32)DMA_Buff,8);
這兩個即爲第二種方法,顯示的波形和上圖沒差,這裏就不展示了。

在調試的過程中,我錯誤的以爲我用了定時器輸出比較通道,就要把DMA綁定到CCRx寄存器,但其實應該綁定到DMA1CH3,這上面有TIM3_UP,即通過更新事件的次數(到達ARR的次數)來改變DMA計數的值。否則出現的波形數量會少於數組設置的大小。在這裏插入圖片描述
在這裏插入圖片描述

其實這些方法的本質,就是決定什麼時候DMA傳輸完成了進入中斷了,因爲只有及時進入中斷關閉定時器,纔不會產生多餘的脈衝信號,這也是這個方案較大的弊端把,我在中斷里加了一個printf作爲DEBUG結果生生多了4個波形!

PlanC:通過主從定時器模式+PlanA

看手冊的時候發現手冊裏記錄一種產生單個脈衝信號的方法:
在這裏插入圖片描述
看了一下這個方法應該是和項目用法匹配度最高的,記錄一下,有時間的時候測試!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章