TIM模塊定時器向上溢出 & 輸出比較
首先我們必須肯定ST公司的實力,也承認STM32的確是一款非常不錯的Cortex-M3核單片機,但是,他的手冊實在是讓人覺得無法理解,尤其是其中的TIM模塊,沒有條理可言,看了兩天幾乎還是不知所云,讓人很是郁悶。同時配套的固件庫的說明也很難和手冊上的寄存器對應起來,研究起來非常費勁!功能強大倒是真的,但至少也應該配套一個讓人看的明白的說明吧~~
兩天時間研究了STM32定時器的最最基礎的部分,把定時器最基礎的兩個功能實現了,余下的功能有待繼續學習。
首先有一點需要注意:FWLib固件庫目前的最新版應該是V2.0.x,V1.0.x版本固件庫中,TIM1模塊被獨立出來,調用的函數與其他定時器不同;在V2.0系列版本中,取消了TIM1.h,所有的TIM模塊統一調用TIM.h即可。網絡上流傳的各種代碼有許多是基于v1版本的固件庫,在移植到v2版本固件庫時,需要做些修改。本文的所有程序都是基于V2.0固件庫。
以下是定時器向上溢出示例代碼:
C語言: TIM1模塊產生向上溢出事件
//Step1.時鐘設置:啟動TIM1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
//Step2.中斷NVIC設置:允許中斷,設置優先級
NVIC_InitStructure.NVIC_IRQChannel =TIM1_UP_IRQChannel; //更新事件
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0; //搶占優先級0
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //響應優先級1
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE; //允許中斷
NVIC_Init(&NVIC_InitStructure); //寫入設置
//Step3.TIM1模塊設置
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
//TIM1 使用內部時鐘
//TIM_InternalClockConfig(TIM1);
//TIM1基本設置
//設置預分頻器分頻系數71,即APB2=72M, TIM1_CLK=72/72=1MHz
//TIM_Period(TIM1_ARR)=1000,計數器向上計數到1000后產生更新事件,計數值歸零
//向上計數模式
//TIM_RepetitionCounter(TIM1_RCR)=0,每次向上溢出都產生更新事件
TIM_BaseInitStructure.TIM_Period = 1000;
TIM_BaseInitStructure.TIM_Prescaler = 71;
TIM_BaseInitStructure.TIM_ClockDivision = 0;
TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_BaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_BaseInitStructure);
//清中斷,以免一啟用中斷后立即產生中斷
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
//使能TIM1中斷源
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
//TIM1總開關:開啟
TIM_Cmd(TIM1, ENABLE);
}
//Step4.中斷服務子程序:
void TIM1_UP_IRQHandler(void)
{
GPIOC->ODR ^=(1<<4); //閃燈
TIM_ClearITPendingBit(TIM1, TIM_FLAG_Update); //清中斷
}
下面是輸出比較功能實現TIM1_CH1管腳輸出指定頻率的脈沖:
C語言: TIM1模塊實現輸出比較,自動翻轉并觸發中斷
//Step1.啟動TIM1,同時還要注意給相應功能管腳啟動時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//Step2. PA.8口設置為TIM1的OC1輸出口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Step3.使能TIM1的輸出比較匹配中斷
NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//Step4. TIM模塊設置
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
//TIM1基本計數器設置
TIM_BaseInitStructure.TIM_Period =0xffff; //這里必須是65535
TIM_BaseInitStructure.TIM_Prescaler =71; //預分頻71,即72分頻,得1M
TIM_BaseInitStructure.TIM_ClockDivision = 0;
TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_BaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_BaseInitStructure);
//TIM1_OC1模塊設置
TIM_OCStructInit(& TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_Toggle; //管腳輸出模式:翻轉
TIM_OCInitStructure.TIM_Pulse =2000; //翻轉周期:2000個脈沖
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable; //使能TIM1_CH1通道
TIM_OCInitStructure.TIM_OCPolarity =TIM_OCPolarity_High; //輸出為正邏輯
TIM_OC1Init(TIM1,&TIM_OCInitStructure); //寫入配置
//清中斷
TIM_ClearFlag(TIM1, TIM_FLAG_CC1);
//TIM1中斷源設置,開啟相應通道的捕捉比較中斷
TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);
//TIM1開啟
TIM_Cmd(TIM1, ENABLE);
//通道輸出使能
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
Step5.中斷服務子程序
void TIM1_CC_IRQHandler(void)
{
u16 capture;
if(TIM_GetITStatus(TIM1, TIM_IT_CC1) == SET)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1 );
capture = TIM_GetCapture1(TIM1);
TIM_SetCompare1(TIM1, capture + 2000);
//這里解釋下:
//將TIM1_CCR1的值增加2000,使得下一個TIM事件也需要2000個脈沖,
//另一種方式是清零脈沖計數器
//TIM_SetCounter(TIM2,0x0000);
}
}
關于TIM的操作,要注意的是STM32處理器因為低功耗的需要,各模塊需要分別獨立開啟時鐘,所以,一定不要忘記給用到的模塊和管腳使能時鐘,因為這個原因,浪費了我好多時間阿~~!
九九的STM32筆記(二)TIM模塊產生PWM
這個是STM32的PWM輸出模式,STM32的TIM1模塊是增強型的定時器模塊,天生就是為電機控制而生,可以產生3組6路PWM,同時每組2路PWM為互補,并可以帶有死區,可以用來驅動H橋。
下面的代碼,是利用TIM1模塊的1、2通道產生一共4路PWM的代碼例子,類似代碼也可以參考ST的固件庫中相應example
C語言: TIM1模塊產生PWM,帶死區
//Step1.開啟TIM和相應端口時鐘
//啟動GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB| \
RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD,\
ENABLE);
//啟動AFIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//啟動TIM1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
//Step2. GPIO做相應設置,為AF輸出
//PA.8/9口設置為TIM1的OC1輸出口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//PB.13/14口設置為TIM1_CH1N和TIM1_CH2N輸出口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//Step3. TIM模塊初始化
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
//TIM1基本計數器設置(設置PWM頻率)
//頻率=TIM1_CLK/(ARR+1)
TIM_BaseInitStructure.TIM_Period = 1000-1;
TIM_BaseInitStructure.TIM_Prescaler = 72-1;
TIM_BaseInitStructure.TIM_ClockDivision = 0;
TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_BaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1,&TIM_BaseInitStructure);
//啟用ARR的影子寄存器(直到產生更新事件才更改設置)
TIM_ARRPreloadConfig(TIM1, ENABLE);
//TIM1_OC1模塊設置(設置1通道占空比)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState =TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 120;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
//啟用CCR1寄存器的影子寄存器(直到產生更新事件才更改設置)
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
//TIM2_OC2模塊設置(設置2通道占空比)
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState =TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_Pulse = 680;
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
//啟用CCR2寄存器的影子寄存器(直到產生更新事件才更改設置)
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
//死區設置
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
TIM_BDTRInitStructure.TIM_DeadTime =0x90; //這里調整死區大小0-0xff
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
TIM_BDTRInitStructure.TIM_BreakPolarity =TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput =TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
//TIM1開啟
TIM_Cmd(TIM1, ENABLE);
//TIM1_OC通道輸出PWM(一定要加)
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
其實,PWM模塊還可以有很多花樣可以玩,比方在異常時(如CPU時鐘有問題),可以緊急關閉輸出,以免發生電路燒毀等嚴重事故
《九九的STM32筆記》整理(2)
這是一個綜合的例子,演示了ADC模塊、DMA模塊和USART模塊的基本使用。
我們在這里設置ADC為連續轉換模式,常規轉換序列中有兩路轉換通道,分別是ADC_CH10(PC0)和ADC_CH16(片內溫度傳感器)。因為使用了自動多通道轉換,數據的取出工作最適合使用DMA方式取出,so,我們在內存里開辟了一個u16AD_Value[2]數組,并設置了相應的DMA模塊,使ADC在每個通道轉換結束后啟動DMA傳輸,其緩沖區數據量為2個HalfWord,使兩路通道的轉換結果自動的分別落到AD_Value[0]和AD_Value[1]中。
然后,在主函數里,就無需手動啟動AD轉換,等待轉換結束,再取結果了。我們可以在主函數里隨時取AD_Value中的數值,那里永遠都是最新的AD轉換結果。
如果我們定義一個更大的AD_Value數組,并調整DMA的傳輸數據量(BufferSize)可以實現AD結果的循環隊列存儲,從而可以進行各種數字濾波算法。
接著,取到轉換結果后,根據V=(AD_Value/4096)*Vref+的公式可以算出相應通道的電壓值,也可以根據 T(℃) =(1.43 - Vad)/34*10^(-6) + 25的算法,得到片內溫度傳感器的測量溫度值了。
通過重新定義putchar函數,及包含"stdio.h"頭文件,我們可以方便的使用標準C的庫函數printf(),實現串口通信。
相關的官方例程,可以參考FWLib V2.0的ADC\ADC1_DMA和USART\printf兩個目錄下的代碼。
本代碼例子是基于萬利199的開發板EK-STM32F實現,CPU=STM32F103VBT6
#i nclude "stm32f10x_lib.h"
#i nclude "stdio.h"
#defineADC1_DR_Address ((u32)0x4001244C)
vu16 AD_Value[2];
vu16 i=0;
s16 Temp;
u16 Volt;
void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
void USART1_Configuration(void);
void ADC1_Configuration(void);
void DMA_Configuration(void);
int fputc(int ch, FILE *f);
void Delay(void);
u16 GetTemp(u16 advalue);
u16 GetVolt(u16 advalue);
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
NVIC_Configuration();
USART1_Configuration();
DMA_Configuration();
ADC1_Configuration();
//啟動第一次AD轉換
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//因為已經配置好了DMA,接下來AD自動連續轉換,結果自動保存在AD_Value處
while(1)
{
Delay();
Temp = GetTemp(AD_Value[1]);
Volt = GetVolt(AD_Value[0]);
USART_SendData(USART1,0x0c); //清屏
//注意,USART_SendData函數不檢查是否發送完成
//等待發送完成
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
printf("電壓:%d.%d\t溫度:%d.%d℃\r\n", \
Volt/100, Volt0, Temp/100, Temp0);
}
}
int fputc(int ch, FILE *f)
{
//USART_SendData(USART1, (u8) ch);
USART1->DR = (u8) ch;
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
{
}
returnch;
}
void Delay(void)
{
u32 i;
for(i=0;i<0x4f0000;i++);
return;
}
u16 GetTemp(u16 advalue)
{
u32Vtemp_sensor;
s32Current_Temp;
// ADC轉換結束以后,讀取ADC_DR寄存器中的結果,轉換溫度值計算公式如下:
// V25 - VSENSE
// T(℃) = ------------ + 25
// Avg_Slope
// V25: 溫度傳感器在25℃時的輸出電壓,典型值1.43 V。
// VSENSE:溫度傳感器的當前輸出電壓,與ADC_DR寄存器中的結果ADC_ConvertedValue之間的轉換關系為:
// ADC_ConvertedValue * Vdd
// VSENSE = --------------------------
// Vdd_convert_value(0xFFF)
// Avg_Slope:溫度傳感器輸出電壓和溫度的關聯參數,典型值4.3 mV/℃。
Vtemp_sensor = advalue * 330 / 4096;
Current_Temp= (s32)(143 - Vtemp_sensor)*10000/43 + 2500;
return(s16)Current_Temp;
}
u16 GetVolt(u16 advalue)
{
return(u16)(advalue * 330 / 4096);
}
void RCC_Configuration(void)
{
ErrorStatusHSEStartUpStatus;
//使能外部晶振
RCC_HSEConfig(RCC_HSE_ON);
//等待外部晶振穩定
HSEStartUpStatus = RCC_WaitForHSEStartUp();
//如果外部晶振啟動成功,則進行下一步操作
if(HSEStartUpStatus==SUCCESS)
{
//設置HCLK(AHB時鐘)=SYSCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
//PCLK1(APB1) = HCLK/2
RCC_PCLK1Config(RCC_HCLK_Div2);
//PCLK2(APB2) = HCLK
RCC_PCLK2Config(RCC_HCLK_Div1);
//設置ADC時鐘頻率
RCC_ADCCLKConfig(RCC_PCLK2_Div2);
//FLASH時序控制
//推薦值:SYSCLK = 0~24MHz Latency=0
// SYSCLK = 24~48MHz Latency=1
// SYSCLK = 48~72MHz Latency=2
FLASH_SetLatency(FLASH_Latency_2);
//開啟FLASH預取指功能
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
//PLL設置 SYSCLK/1 * 9 = 8*1*9 = 72MHz
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
//啟動PLL
RCC_PLLCmd(ENABLE);
//等待PLL穩定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
//系統時鐘SYSCLK來自PLL輸出
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//切換時鐘后等待系統時鐘穩定
while(RCC_GetSYSCLKSource()!=0x08);
}
//下面是給各模塊開啟時鐘
//啟動GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB| \
RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD,\
ENABLE);
//啟動AFIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//啟動USART1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//啟動DMA時鐘
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//啟動ADC1時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
}
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//PC口4567腳設置GPIO輸出,推挽 2M
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6| GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//KEY2KEY3 JOYKEY
//位于PD口的3 411-15腳,使能設置為輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_11| GPIO_Pin_12 |\
GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
//USART1_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//ADC_CH10--> PC0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
#ifdef VECT_TAB_RAM
// Set theVector Table base location at 0x20000000
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else
// Set theVector Table base location at 0x08000000
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
//設置NVIC優先級分組為Group2:0-3搶占式優先級,0-3的響應式優先級
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//串口中斷打開
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_Configuration(void)
{
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 19200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx |USART_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
void ADC1_Configuration(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //連續轉換開啟
ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel =2; //設置轉換序列長度為2
ADC_Init(ADC1, &ADC_InitStructure);
//ADC內置溫度傳感器使能(要使用片內溫度傳感器,切忌要開啟它)
ADC_TempSensorVrefintCmd(ENABLE);
//常規轉換序列1:通道10
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1,ADC_SampleTime_13Cycles5);
//常規轉換序列2:通道16(內部溫度傳感器),采樣時間>2.2us,(239cycles)
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2,ADC_SampleTime_239Cycles5);
// EnableADC1
ADC_Cmd(ADC1, ENABLE);
//開啟ADC的DMA支持(要實現DMA功能,還需獨立配置DMA通道等參數)
ADC_DMACmd(ADC1, ENABLE);
//下面是ADC自動校準,開機后需執行一次,保證精度
// EnableADC1 reset calibaration register
ADC_ResetCalibration(ADC1);
// Check theend of ADC1 reset calibration register
while(ADC_GetResetCalibrationStatus(ADC1));
// StartADC1 calibaration
ADC_StartCalibration(ADC1);
// Check theend of ADC1 calibration
while(ADC_GetCalibrationStatus(ADC1));
//ADC自動校準結束---------------
}
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&AD_Value;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//BufferSize=2,因為ADC轉換序列有2個通道
//如此設置,使序列1結果放在AD_Value[0],序列2結果放在AD_Value[1]
DMA_InitStructure.DMA_BufferSize = 2;
DMA_InitStructure.DMA_PeripheralInc =DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_HalfWord;
//循環模式開啟,Buffer寫滿后,自動回到初始地址開始傳輸
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//配置完成后,啟動DMA通道
DMA_Cmd(DMA1_Channel1, ENABLE);
}
|
| 《九九的STM32筆記》整理
這次是RTC的筆記:)
RTC這東西暈暈的,因為一個模塊涉及到了RTC,BKP,RCC多個模塊,之間的關系讓人有點模糊
入門的知識請大家看手冊,我來總結:
總之,RTC只是個能靠電池維持運行的32位定時器over!
所以,使用時要注意以下問題:
1. 上電后要檢查備份電池有沒有斷過電。如何檢查? 恩,RTC的示例代碼中已經明示:
往備份域寄存器中寫一個特殊的字符,備份域寄存器是和RTC一起在斷電下能保存數據的。
上電后檢查下這個特殊字符是否還存在,如果存在,ok,RTC的數據應該也沒丟,不需要重新配置它
如果那個特殊字符丟了,那RTC的定時器數據一定也丟了,那我們要重新來配置RTC了
這個過程包括時鐘使能、RTC時鐘源切換、設置分頻系數等等,這個可以參考FWLib\example\RTC\Calendar的代碼
在我的這個實例里,檢查備份域掉電在Init.c的RTC_Conig()中,函數內若檢測到BKP掉電,則會調用RTC_Configuration()2. 因為RTC的一些設置是保存在后備域中的,so,操作RTC的設置寄存器前,要打開后備域模塊中的寫保護功能。
3. RTC設定值寫入前后都要檢查命令有沒有完成,調用RTC_WaitForLastTask();
具體的RTC初始化代碼如下:
////////////////////////////////////////////////////////////////////////////////
// RTC時鐘初始化!
////////////////////////////////////////////////////////////////////////////////
void RTC_Configuration(void)
{
//啟用PWR和BKP的時鐘(from APB1)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP,ENABLE);
//后備域解鎖
PWR_BackupAccessCmd(ENABLE);
//備份寄存器模塊復位
BKP_DeInit();
//外部32.768K其喲偶那個
RCC_LSEConfig(RCC_LSE_ON);
//等待穩定
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
//RTC時鐘源配置成LSE(外部32.768K)
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//RTC開啟
RCC_RTCCLKCmd(ENABLE);
//開啟后需要等待APB1時鐘與RTC時鐘同步,才能讀寫寄存器
RTC_WaitForSynchro();
//讀寫寄存器前,要確定上一個操作已經結束
RTC_WaitForLastTask();
//設置RTC分頻器,使RTC時鐘為1Hz
//RTC period= RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)
RTC_SetPrescaler(32767);
//等待寄存器寫入完成
RTC_WaitForLastTask();
//使能秒中斷
RTC_ITConfig(RTC_IT_SEC,ENABLE);
//等待寫入完成
RTC_WaitForLastTask();
return;
}
void RTC_Config(void)
{
//我們在BKP的后備寄存器1中,存了一個特殊字符0xA5A5
//第一次上電或后備電源掉電后,該寄存器數據丟失,
//表明RTC數據丟失,需要重新配置
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
//重新配置RTC
RTC_Configuration();
//配置完成后,向后備寄存器中寫特殊字符0xA5A5
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
else
{
//若后備寄存器沒有掉電,則無需重新配置RTC
//這里我們可以利用RCC_GetFlagStatus()函數查看本次復位類型
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
//這是上電復位
}
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
//這是外部RST管腳復位
}
//清除RCC中復位標志
RCC_ClearFlag();
//雖然RTC模塊不需要重新配置,且掉電后依靠后備電池依然運行
//但是每次上電后,還是要使能RTCCLK???????
//RCC_RTCCLKCmd(ENABLE);
//等待RTC時鐘與APB1時鐘同步
//RTC_WaitForSynchro();
//使能秒中斷
RTC_ITConfig(RTC_IT_SEC, ENABLE);
//等待操作完成
RTC_WaitForLastTask();
}
#ifdef RTCClockOutput_Enable
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP,ENABLE);
PWR_BackupAccessCmd(ENABLE);
BKP_TamperPinCmd(DISABLE);
BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
#endif
return;
}
《九九的STM32筆記》整理3
基于STM32處理器
RTC只是個能靠電池維持運行的32位定時器over!
并不像實時時鐘芯片,讀出來就是年月日。。。
看過些網上的代碼,有利用秒中斷,在內存中維持一個年月日的日歷。
我覺得,這種方法有很多缺點:
1.斷電時沒有中斷可用
2.頻繁進中斷,消耗資源
3.時間運算復雜,代碼需要自己寫
4.不與國際接軌。。。。
so,還是用標準的UNIX時間戳來進行時間的操作吧!
什么是UNIX時間戳?
UNIX時間戳,是unix下的計時方式。。。很廢話
具體點:他是一個32位的整形數(剛好和STM32的RTC寄存器一樣大),表示從UNIX元年(格林尼治時間1970-1-10:0:0)開始到某時刻所經歷的秒數
聽起來很玄幻的,計算下:32位的數從0-0xFFFFFFFF秒,大概到2038年unix時間戳將會溢出!這就是Y2038bug
不過,事實上的標準,我們還是照這個用吧,還有二十年呢。。。
UNIX時間戳:1229544206 <==>現實時間:2008-12-17 20:03:26
我們要做的,就是把當前時間的UNIX時間戳放在RTC計數器中讓他每秒++,over
然后,設計一套接口函數,實現UNIX時間戳與年月日的日歷時間格式轉換 這樣就可以了
在RTC中實現這個時間算法,有如下好處:
1. 系統無需用中斷和程序來維持時鐘,斷電后只要RTC在走即可
2. 具體的兩種計時的換算、星期數計算,有ANSI-C的標準C庫函數實現,具體可以看time.h
3. 時間與時間的計算,用UNIX時間戳運算,就變成了兩個32bit數的加減法
4. 與國際接軌。。。
幸好是與國際接軌,我們有time.h幫忙,在MDK的ARM編輯器下有,IAR下也有
其中已經定義了兩種數據類型:unix時間戳和日歷型時間
time_t: UNIX時間戳(從1970-1-1起到某時間經過的秒數)
typedef unsigned int time_t;
struct tm: Calendar格式(年月日形式)
同時有相關操作函數
gmtime,localtime,ctime,mktime等等,方便的實現各種時間類型的轉換和計算
于是,基于這個time.h,折騰了一天,搞出了這個STM32下的RTC_Time使用的時間庫
這是我的RTC_Time.c中的說明:
本文件實現基于RTC的日期功能,提供年月日的讀寫。(基于ANSI-C的time.h)
作者:jjldc (九九)
QQ: 77058617
RTC中保存的時間格式,是UNIX時間戳格式的。即一個32bit的time_t變量(實為u32)
ANSI-C的標準庫中,提供了兩種表示時間的數據 型:
time_t: UNIX時間戳(從1970-1-1起到某時間經過的秒數)
typedef unsigned int time_t;
struct tm: Calendar格式(年月日形式)
tm結構如下:
struct tm {
int tm_sec; // 秒 seconds afterthe minute, 0 to 60
(0 - 60 allows for the occasional leap second)
int tm_min; // 分 minutes afterthe hour, 0 to 59
int tm_hour; // 時 hours since midnight, 0 to 23
int tm_mday; // 日 day of the month, 1 to 31
int tm_mon; // 月 months sinceJanuary, 0 to 11
int tm_year; // 年 years since 1900
int tm_wday; // 星期 days since Sunday, 0 to 6
int tm_yday; // 從元旦起的天數 days since January 1, 0 to 365
int tm_isdst; // 夏令時??Daylight Savings Time flag
...
}
其中wday,yday可以自動產生,軟件直接讀取
mon的取值為0-11
***注意***:
tm_year:在time.h庫中定義為1900年起的年份,即2008年應表示為2008-1900=108
這種表示方法對用戶來說不是十分友好,與現實有較大差異。
所以在本文件中,屏蔽了這種差異。
即外部調用本文件的函數時,tm結構體類型的日期,tm_year即為2008
注意:若要調用系統庫time.c中的函數,需要自行將tm_year-=1900
成員函數說明:
struct tm Time_ConvUnixToCalendar(time_t t);
輸入一個Unix時間戳(time_t),返回Calendar格式日期
time_t Time_ConvCalendarToUnix(struct tm t);
輸入一個Calendar格式日期,返回Unix時間戳(time_t)
time_t Time_GetUnixTime(void);
從RTC取當前時間的Unix時間戳值
struct tm Time_GetCalendarTime(void);
從RTC取當前時間的日歷時間
void Time_SetUnixTime(time_t);
輸入UNIX時間戳格式時間,設置為當前RTC時間
void Time_SetCalendarTime(struct tm t);
輸入Calendar格式時間,設置為當前RTC時間
外部調用實例:
定義一個Calendar格式的日期變量:
struct tm now;
now.tm_year = 2008;
now.tm_mon =11; //12月
now.tm_mday = 20;
now.tm_hour = 20;
now.tm_min = 12;
now.tm_sec = 30;
獲取當前日期時間:
tm_now = Time_GetCalendarTime();
然后可以直接讀tm_now.tm_wday獲取星期數
設置時間:
Step1. tm_now.xxx = xxxxxxxxx;
Step2. Time_SetCalendarTime(tm_now);
計算兩個時間的差
struct tm t1,t2;
t1_t = Time_ConvCalendarToUnix(t1);
t2_t = Time_ConvCalendarToUnix(t2);
dt = t1_t - t2_t;
dt就是兩個時間差的秒數
dt_tm =mktime(dt); //注意dt的年份匹配,ansi庫中函數為相對年份,注意超限
另可以參考相關資料,調用ansi-c庫的格式化輸出等功能,ctime,strftime等
|