勉勉強強看懂一行程序
繼續學習中,先把開發板自帶一個例子做了些精簡,以免看得嚇人。。。。
就是這個,讓PORTD上接的4個LED分別點亮。
開始研究代碼
int main(void)
{
Init_All_Periph();
。。.。。.
看到這一行,開始跟蹤,于是又看到了下面的內容
void Init_All_Periph(void)
{
RCC_Configuration();
。。.。。.
繼續跟蹤
void RCC_Configuration(void)
{
SystemInit();
。。.。。.
這行代碼在system_stm32f10x.c中找到了。
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC-》CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC-》CFGR &= (uint32_t)0xF8FF0000;
#else
RCC-》CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC-》CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC-》CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC-》CFGR &= (uint32_t)0xFF80FFFF;
#ifndef STM32F10X_CL
/* Disable all interrupts and clear pending bits */
RCC-》CIR = 0x009F0000;
#else
/* Reset PLL2ON and PLL3ON bits */
RCC-》CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC-》CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC-》CFGR2 = 0x00000000;
#endif /* STM32F10X_CL */
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
}
這一長串的又是什么,如何來用呢?看來,偷懶是不成的了,只能回過頭去研究STM32的時鐘構成了。
相當的復雜。
系統的時鐘可以有3個來源:內部時鐘HSI,外部時鐘HSE,或者PLL(鎖相環模塊)的輸出。它們由RCC_CFGR寄存器中的SW來選擇。
SW(1:0):系統時鐘切換
由軟件置’1’或清’0’來選擇系統時鐘源。 在從停止或待機模式中返回時或直接或間接作為系統時鐘的HSE出現故障時,由硬件強制選擇HSI作為系統時鐘(如果時鐘安全系統已經啟動)
00:HSI作為系統時鐘;
01:HSE作為系統時鐘;
10:PLL輸出作為系統時鐘;
11:不可用。
////////////////////////////////////////////////////////////////////
PLL的輸出直接送到USB模塊,經過適當的分頻后得到48M的頻率供USB模塊使用。
系統時鐘的一路被直接送到I2S模塊;另一路經過AHB分頻后送出,送往各個系統,其中直接送往SDI,FMSC,AHB總線;8分頻后作為系統定時器時鐘;經過APB1分頻分別控制PLK1、定時器TIM2~TIM7;經過APB2分頻分別控制PLK2、定時器TIM1~TIM8、再經分頻控制ADC;
由此可知,STM32F10x芯片的時鐘比之于51、AVR、PIC等8位機要復雜復多,因此,我們立足于對著芯片手冊來解讀程序,力求知道這些程序代碼如何使用,為何這么樣使用,如果自己要改,可以修改哪些部分,以便自己使用時可以得心應手。
單步執行,看一看哪些代碼被執行了。
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC-》CR |= (uint32_t)0x00000001;
這是RCC_CR寄存器,由圖可見,HSION是其bit 0位。
HSION:內部高速時鐘使能
由軟件置’1’或清零。
當從待機和停止模式返回或用作系統時鐘的外部4-25MHz時鐘發生故障時,該位由硬件置’1’來啟動內部8MHz的RC振蕩器。當內部8MHz時鐘被直接或間接地用作或被選擇將要作為系統時鐘時,該位不能被清零。
0:內部8MHz時鐘關閉;
1:內部8MHz時鐘開啟。
///////////////////////////////////////////////////////////////////////
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC-》CFGR &= (uint32_t)0xF8FF0000;
這是RCC_CFGR寄存器
該行程序清零了MC0[2:0]這三位,和ADCPRE[1:0],ppre2[2:0],PPRE1[2:0],HPRE[3:0],SWS[1:0]和SW[1:0]這16位。
/*
MCO: 微控制器時鐘輸出,由軟件置’1’或清零。
0xx:沒有時鐘輸出;
100:系統時鐘(SYSCLK)輸出;
101:內部8MHz的RC振蕩器時鐘輸出;
110:外部4-25MHz振蕩器時鐘輸出;
111:PLL時鐘2分頻后輸出。
*/
/* Reset HSEON, CSSON and PLLON bits */
RCC-》CR &= (uint32_t)0xFEF6FFFF;
清零了PLLON,HSEBYP,HSERDY這3位。
/* Reset HSEBYP bit */
RCC-》CR &= (uint32_t)0xFFFBFFFF;
清零了HSEBYP位 ///???為什么不一次寫??
HSEBYP:外部高速時鐘旁路,在調試模式下由軟件置’1’或清零來旁路外部晶體振蕩器。只有在外部4-25MHz振蕩器關閉的情況下,才能寫入該位。
0:外部4-25MHz振蕩器沒有旁路;
1:外部4-25MHz外部晶體振蕩器被旁路。
所以要先清HSEON位,再清該位。
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC-》CFGR &= (uint32_t)0xFF80FFFF;
清零了:USBPRE,PLLMUL,PLLXTPR,PLLSRC共7位
/* Disable all interrupts and clear pending bits */
RCC-》CIR = 0x009F0000;
////這個暫不解讀
SetSysClock();
跟蹤進入該函數,可見一連串的條件編譯:
?
單步運行,執行的是:
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
為何執行該行呢,找到SYSCLK_PREQ_**的相關定義,如下圖所示。
這樣就得到了我們所要的一個結論:如果要更改系統工作頻率,只需要在這里更改就可以了。
可以繼續跟蹤進入這個函數來觀察如何將工作頻率設定為72MHz的。
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */
RCC-》CR |= ((uint32_t)RCC_CR_HSEON);
//開啟HSE
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC-》CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSEStartUp_TimeOut));
//等待HSE確實可用,這有個標志,即RCC_CR寄存器中的HSERDY位(bit 17),這個等待不會無限長,有個超時策略,即每循環一次計數器加1,如果計數的次數超過HSEStartUp_TimeOut,就退出循環,而這個HSEStartUp_TimeOut在stm32f10x.h中定義,
#define HSEStartUp_TimeOut ((uint16_t)0x0500) /*!《 Time out for HSE start up */
///////////////////////////////////////////////////////////////////////////////////////////////
if ((RCC-》CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
///再次判斷HSERDY標志位,并據此給HSEStatus變量賦值。
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH-》ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH-》ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH-》ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC-》CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
//找到定義: #define RCC_CFGR_HPRE_DIV1 ((uint32_t)0x00000000) /*!《 SYSCLK not divided */
/* PCLK2 = HCLK */
RCC-》CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
//找到定義:#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)0x00000000) /*!《 HCLK not divided */
/* PCLK1 = HCLK */
RCC-》CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
//找到定義:#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0x00000400) /*!《 HCLK divided by 2 */
#ifdef STM32F10X_CL
……
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC-》CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC-》CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
//以上是設定PLL的倍頻系數為9,也就是說,這個72M是在外部晶振為8M時得到的。
/* Enable PLL */
RCC-》CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC-》CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC-》CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC-》CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC-》CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
/* Go to infinite loop */
while (1)
{
}
}
}
至此,我們可以歸納幾條:
(1) 時鐘源有3個
(2) 開機時默認是HSI起作用,可以配置為所要求的任意一個時鐘
(3) 配置時必須按一定的順序來打開或都關閉一些位,并且各時鐘起作用有一定的時間,因此要利用芯片內部的標志位來判斷是否可以執行下一步。
(4) 如果外部時鐘、PLL輸出失效,系統可以自動回復到HSI(開啟時鐘安全系統)
(5) HSI的頻率準確度可以達到+/- 1%,如果有必要時,還可以用程序來調整這個頻率,可調的范圍大致在200KHz左右。
最后讓我們來感受一下勞動的果實吧--試著改改頻率看有何反應。
為查看更改后的效果,先記錄更改前的數據。將調試切換到仿真,在第一條:
Delay(0xAFFFF);
指令執行前后,分別記錄下Status和Sec
Status:2507 3606995
Sec:0.00022749 0.05028982
將振蕩頻率更改為36MHz,即
。。.
#define SYSCLK_FREQ_36MHz 36000000 //去掉該行的注釋
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
/*#define SYSCLK_FREQ_72MHz 72000000*/ //將該行加上注釋
再次運行,結果如下:
Status:2506 3606994
Sec:0.00008478 0.10036276
基本上是延時時間長了一倍。改成硬件仿真,將代碼寫入板子,可以看到LED閃爍的頻率明顯變慢了。
IO研究
前面的例子研究了時鐘,接下來就來了解一下引腳的情況
Main.c中,有關I/O口的配置代碼如下:
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure IO connected to LD1, LD2, LD3 and LD4 leds *********************/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
這幾行代碼是將GPIOD的第8,9,10和11引腳配置成輸出,并且還可以設定輸出引腳的速度(驅動能力?),這里設定為 50MHz,這應該是常用的,還有可以設置為2MHz的。那么如何將引腳設置成輸入呢?查看電路原理圖,GPIOD.0~GPIO.4是接一個搖桿的5個按鈕的,因此,下面嘗試著將它們設置成為輸入端。
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
第1行和第3行完全是照抄,第2行那個GPIO_Mode_IN_FLOATING是在stm32f10x_gpio.h中找到的。
當然是因為這里還有GPIO_Mode_Out_PP,所以猜測應該是它了。至于還有其他那么多的符號就不管了。
定義完成,編譯完全通過,那就接下來準備完成下面的代碼了。
int main(void)
{
Init_All_Periph();
while(1)
{ if( GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_0)) //1
{ GPIO_ResetBits(GPIOD, GPIO_Pin_8);
}
else
{ /* Turn on LD1 */
GPIO_SetBits(GPIOD, GPIO_Pin_8);
/* Insert delay */
}
。。.。。.
標號為1的行顯然其作用是判斷GPIOD.0引腳是0還是1。這個函數是在stm32f10x_gpio.c中找到的。
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
if ((GPIOx-》IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
雖然程序還有很多符號看不懂(沒有去查),但憑感覺它應該是對某一個引腳的狀態進行判斷,因為這個函數的類型是uint8_t,估計stm32沒有bit型函數(需要驗證),所以就用了uint8_t型了),如果是讀的端口的值,應該用uint16_t型。這一點在下面也可以得到部分的驗證:
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
這些函數是讀引腳及輸出寄存器的數據的。
再次編譯,也是順利通過,依法炮制,將其他三個引腳輸入控制LED的代碼也寫上,為保險起見,先用軟件仿真,免得反復擦寫FLASH(順便說一句,目前還沒有搞定將代碼寫入RAM及從RAM中執行)
進入仿真后打開外圍部件接口,單步執行,果然如同設想那樣運作了,單擊Pins 0后面的勾,再次運行,果然PIN8后面的勾沒了。做到這里,就感覺到用keil的好處了,這塊熟啊,幾乎沒有花時間在上面,一用就成了。
至此,按我的習慣,要翻開STM32F的數據手冊,研究一下其IO端口了。下面是數據手冊中的一段話:
-------------------------------------
每個GPI/O端口有兩個32位配置寄存器(GPIOx_CRL,GPIOx_CRH),兩個32位數據寄存器(GPIOx_IDR,GPIOx_ODR),一個32位置位/復位寄存器(GPIOx_BSRR),一個16位復位寄存器(GPIOx_BRR)和一個32位鎖定寄存器(GPIOx_LCKR)。
根據數據手冊中列出的每個I/O端口的特定硬件特征, GPIO端口的每個位可以由軟件分別配置成多種模式。
─ 輸入浮空
─ 輸入上拉
─ 輸入下拉
─ 模擬輸入
─ 開漏輸出
─ 推挽式輸出
─ 推挽式復用功能
─ 開漏復用功能
----------------------------------------------------
當然,數據手冊上關于IO端口的描述是很多很多的,我也只是大概地了解了一下,真正要設計產品時,肯定還要細看。但至少,知道了IO端口復位后處于浮空狀態,也就是其電平狀態由外圍電路決定,這很重要,如果設計工業品的話,這是必須要確定的;知道了IO引腳可以兼容5V電源;知道了在什么地方可以找到這些引腳在庫中的定義而不必看著數據手冊去控制那些位;也知道了這些引腳的一些基本操作函數(連猜帶蒙帶測試應該可以搞定大部分功能),那么我心里基本就有底啦。
最后用一段電子熒火蟲(也就是呼吸燈)的簡單程序作為結束。
int main(void)
{ uint8_t Count;
uint32_t DelayTim=“0x1000”; //基數
uint32_t ChangTim=“0x2000”; //每次變化的量
Init_All_Periph();
while(1)
{
for(Count=0;Count《16;Count++) //漸亮
{
GPIO_SetBits(GPIOD, GPIO_Pin_8); //點亮燈
Delay(DelayTim+Count*ChangTim);
GPIO_ResetBits(GPIOD, GPIO_Pin_8); //熄滅燈
Delay(DelayTim+(16-Count)*ChangTim);
}
for(Count=16;Count》0;Count--) //漸暗
{
GPIO_SetBits(GPIOD, GPIO_Pin_8); //點亮燈
Delay(DelayTim+Count*ChangTim);
GPIO_ResetBits(GPIOD, GPIO_Pin_8); //熄滅燈
Delay(DelayTim+(16-Count)*ChangTim);
}
}
}
定時器初步
接下來研究定時器。為什么開始研究定時器了呢?那個I/O還是剛剛開了頭啊,還有很多很多知識沒有掌握!不怕,方法論告訴我們:對事物的認識是螺旋式上升的,所以不要一桿子打到底,想著把I/O口的所有情況都掌握了再學下面的,那會是很困難的
STM32的定時器是什么樣子的,心里一點底也沒有,還是找個現成的例子來吧。在ST提供的庫里,有很多的例子
第一個就是它了。
把整個文件夾復制一份到自己的實驗文件夾中
在Source文件夾中再建立名為APP的文件夾,將上圖中所有源程序文件全部復制到APP文件夾中。然后將庫所提供的CMSIS文件夾和 STM32F10x_StdPeriph_Driver文件夾復制到Source文件夾中。然后按照前面的方法建立項目。
這是建好的項目的結構。
下面開始研究,首先看附帶的readme.txt文件,了解到該例子的大體用途是驗證Tim2的Output Compare Timing mode的。于是打開PDF文件,直接翻到下面的位置:
-----------------------------------------------------------------------------------------
13.3.8 輸出比較模式
此項功能是用來控制一個輸出波形或者指示何時一段給定的的時間已經到時。
當計數器與捕獲/比較寄存器的內容相同時,輸出比較功能做如下操作:
● 將輸出比較模式(TIMx_CCMRx寄存器中的OCxM位)和輸出極性(TIMx_CCER寄存器中的CCxP位)定義的值輸出到對應的管腳上。在比較匹配時,輸出管腳可以保持它的電平
(OCxM=000)、被設置成有效電平(OCxM=001)、被設置成無有效電平(OCxM=010)或進行翻轉(OCxM=011)。
● 設置中斷狀態寄存器中的標志位(TIMx_SR寄存器中的CCxIF位)。
● 若設置了相應的中斷屏蔽(TIMx_DIER寄存器中的CCXIE位),則產生一個中斷。
● 若設置了相應的使能位(TIMx_DIER寄存器中的CCxDE位,TIMx_CR2寄存器中的CCDS位選擇DMA請求功能),則產生一個DMA請求。
TIMx_CCMRx中的OCxPE位選擇TIMx_CCRx寄存器是否需要使用預裝載寄存器。
在輸出比較模式下,更新事件UEV對OCxREF和OCx輸出沒有影響。
同步的精度可以達到計數器的一個計數周期。輸出比較模式(在單脈沖模式下)也能用來輸出一個單脈沖。
輸出比較模式的配置步驟:
1. 選擇計數器時鐘(內部,外部,預分頻器)
2. 將相應的數據寫入TIMx_ARR和TIMx_CCRx寄存器中
3. 如果要產生一個中斷請求和/或一個DMA請求,設置CCxIE位和/或CCxDE位。
4. 選擇輸出模式,例如:必須設置OCxM=’011’、OCxPE=’0’、CCxP=’0’和CCxE=’1’,當計數器CNT與CCRx匹配時翻轉OCx的輸出管腳,CCRx預裝載未用,開啟OCx輸出且高電平有效。
5. 設置TIMx_CR1寄存器的CEN位啟動計數器--------------------------------------------------------------------------------
說得并不復雜,但是要弄清楚也絕非易事,況且main.c中一系列的符號究竟是什么意思也不是那么容易搞清楚的。這些東西搞不清,將來自己編程時,就算想要依葫蘆畫瓢都難。怎么辦呢?可能并沒有多少偷巧的辦法,只能是一步一步地摸索吧。
進入調試,打開Peripherals-》Timers-》Tim2,出現下面的窗口。
然后單步執行程序,并觀察界面的變化,并將這些符號作為線索,在PDF文件,stmf10x_tim.c等文件中搜索,了解相關符號的含義。
如下圖是執行到:while(1)前,也就是所有設置完成后的圖。
沿著這些線索一路追蹤,邊看源程序,邊找數據手冊,邊找相關的頭文件,其他源程序,總算大體有了個明白,下面將main.c中的部分代碼作為注釋,也算是做點記錄。
/* Includes ------------------------------------------------------------------*/
#include “stm32f10x.h”
//這個頭文件需要根據所選擇的芯片進行更改
/* Private variables ---------------------------------------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure; //定義兩個結構型變量
__IO uint16_t CCR1_Val = 49152;
__IO uint16_t CCR2_Val = 32768;
__IO uint16_t CCR3_Val = 16384;
__IO uint16_t CCR4_Val = 8192; //
ErrorStatus HSEStartUpStatus;
/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
int main(void)
{
/* System Clocks Configuration */
RCC_Configuration();
/* NVIC Configuration */
NVIC_Configuration();
/* GPIO Configuration */
GPIO_Configuration();
/* ---------------------------------------------------------------
TIM2 Configuration: Output Compare Timing Mode:
TIM2CLK = 36 MHz, Prescaler = 4, TIM2 counter clock = 7.2 MHz
CC1 update rate = TIM2 counter clock / CCR1_Val = 146.48 Hz
CC2 update rate = TIM2 counter clock / CCR2_Val = 219.7 Hz
CC3 update rate = TIM2 counter clock / CCR3_Val = 439.4 Hz
CC4 update rate = TIM2 counter clock / CCR4_Val = 878.9 Hz
--------------------------------------------------------------- */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535; //它對應TIM2_ARR
TIM_TimeBaseStructure.TIM_Prescaler = 0;
/* 它對應TIM2_PSC,相關代碼如下(stm32f10x_tim.c中):
TIMx-》PSC = TIM_TimeBaseInitStruct-》TIM_Prescaler;
但很奇怪,這里令其為0,然后再在下面設置為4??為何??
*/
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0200;
//ClockDivision是對CKD位進行設置的,但是這個必須要自己給其賦正確的值
/*在stm32f10_tim.c文件中是這樣操作的
TIMx-》CR1 |= (uint32_t)TIM_TimeBaseInitStruct-》TIM_ClockDivision |
TIM_TimeBaseInitStruct-》TIM_CounterMode;
*/
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*計數模式,st32f10x_tim.h中定義,其他可用的符號還有:
TIM_CounterMode_Up,TIM_CounterMode_Down,TIM_CounterMode_CenterAligned1
TIM_CounterMode_CenterAligned2,TIM_CounterMode_CenterAligned3
*/
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* Prescaler configuration */
TIM_PrescalerConfig(TIM2, 4, TIM_PSCReloadMode_Immediate);
/*stm32f10x_tim.c中相關代碼:
TIMx-》PSC = Prescaler;
*/
/* Output Compare Timing Mode configuration: Channel1 *///輸出比較模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; //預置值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //對CCER中CCxP的操作
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
/*對捕獲/比較使能寄存器(TIMx_CCER)進行操作,置CC1E為1 */
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* Output Compare Timing Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
//這個是將CCR2_Val的值送到TMI2_CCR2中
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* Output Compare Timing Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
/*
*/
/* Output Compare Timing Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* TIM IT enable */
TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);
對DIER寄存器操作,中斷允許配置,相關代碼如下:
而DIER寄存器如下:
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
//開啟定時器的運行
while (1);
}
解讀:
(1) 時鐘來源 CK_INT,設置的方法是讓TIMx_SMCR中的SMS[2:0]=000,最后通過TIM_Cmd(TIM2, ENABLE);函數將TIMx_CR1中的CEN置1,開啟定時器的運行;
(2) 接下來設置定時器的基本參數
(3) 然后是設置定時器的各個通道
(4) 最后開啟定時器運行
最后,將這個例子稍加修改,令其運行在我的實驗板上。
需要修改的僅是將其原來的輸出從GPIOC的第6~第9腳變為GPIOD的第8~第11腳。為此,需要改的地方有:
將藍色框內的GPIOC改為GPIOD。這個是最先寫的,但實際上一開始根本沒注意到這個地方,沒有改成GPIOD,結果一仿真,不正確,再一細查,原來端口還要配置時鐘,改過來就對了。瞧,這不驗證了上面的說法“認識事物是螺旋式上升的”學了定時器,對于I/O口和時鐘又有了更進一步的理解了。
將紅色框內的GPIO分別改成8,9,10,11,將藍色框內的GPIOC改為GPIOD。
最后,到stm32f10x_it.c中,修改相應的輸出
參考上圖紅色框內,將GPIOC改為GPIOD,將6腳改為8腳,其他部分類推。
修改好后運行,所有燈亮了,看不出效果,于是又將預分頻系數由4改為64,這樣一來,LED開始閃爍了。
評論
查看更多