catalogue
0. Cortex-M3地址空間 1. 基於標准外設庫的軟件開發 2. 基於固件庫實現串口輸出(發送)程序 3. 紅外接收實驗 4. 深入分析流水燈例程 5. GPIO再舉例之按鍵實驗 6. 串口通信(USART) 7. 庫函數開發通用流程小結 8. DMA傳輸方式 9. STM32 ADC 10. SysTick(系統滴答定時器) 11. STM32定時器
0. Cortex-M3地址空間

0x1: MDK中三種linker之間的區別
1. 采用Target對話框中的RAM和ROM地址
采用此方式,需在Linker選項卡中勾選Use Memory Layout from Target Dialog選項(選中這一項實際上是默認在Target中對Flash和RAM的地址配置,編譯鏈接時會產生一個默認的腳本文件),並且在Target中設置好RAM、ROM地址。MDK會根據Target選項中設定的RAM和ROM地址自動加載生成一個加載文件。最后鏈接器會根據此文件中的信息對目標文件進行鏈接,生成axf鏡像文件
STM32是通過同一個連續的地址空間來尋址片上ROM和片外RAM
2. 直接通過Linker選項卡中的R/O Base和R/W Base來設定鏈接信息
接器最后可根據此處指定的地址信息進行鏈接,鏈接的文件應該是順序存放了,最多RO和RW分開。此時需要注意的是應將Use Memory Layout from Target Diaglog前的勾去掉,且保證Scatter File一欄中未包含分散加載文件,並且要在Misc controls中設定鏡像文件的入口點
3. 直接采用分散加載文件
Relevant Link:
http://blog.csdn.net/mybelief321/article/details/8947424 http://www.openedv.com/thread-17087-1-1.html
1. 基於標准外設庫的軟件開發
0x1: STM32標准外設庫概述
STM32標准外設庫之前的版本也稱固件函數庫或簡稱固件庫(即操作片外固件的代碼集合),是一個固件函數包,它由程序、數據結構和宏組成,包括了微控制器所有外設的性能特征。該函數庫還包括每一個外設的驅動描述和應用實例,為開發者訪問底層硬件提供了一個中間API,通過使用固件函數庫,無需深入掌握底層硬件細節,開發者就可以輕松應用每一個外設。因此,使用固態函數庫可以大大減少用戶的程序編寫時間,進而降低開發成本。每個外設驅動都由一組函數組成,這組函數覆蓋了該外設所有功能。每個器件的開發都由一個通用API (application programming interface 應用編程界面)驅動,API對該驅動程序的結構,函數和參數名稱都進行了標准化,順便提一句,arduino之所以入門容易開發簡單就是因為我們很多時候是要"面向固件庫編程",很多復雜的外設操作都通過簡單的API調用就完成了
ST公司2007年10月發布了V1.0版本的固件庫,MDK ARM3.22之前的版本均支持該庫。2008年6月發布了V2.0版的固件庫,從2008年9月推出的MDK ARM3.23版本至今均使用V2.0版本的固件庫。V3.0以后的版本相對之前的版本改動較大
0x2: 使用標准外設庫開發的優勢
使用標准外設庫進行開發最大的優勢就在於可以使開發者不用深入了解底層硬件細節就可以靈活規范的使用每一個外設。標准外設庫覆蓋了從GPIO到定時器,再到CAN、I2C、SPI、UART和ADC等等的所有標准外設。對應的C源代碼只是用了最基本的C編程的知識,所有代碼經過嚴格測試,易於理解和使用,並且配有完整的文檔,非常方便進行二次開發和應用
0x3: STM32F10XXX標准外設庫結構與文件描述
表 5‑4 STM32F10XXX V3.4標准外設庫文件夾描述
| STM32F10x_StdPeriph_Lib_V3.4.0 |
_htmresc |
本文件夾包含了所有的html頁面資源 |
| Libraries |
CMSIS微控制器軟件接口標准(CMSIS:Cortex Microcontroller Software Interface Standard) |
是 Cortex-M 處理器系列的與供應商無關的軟件抽象層。 使用CMSIS,可以為處理器和外設實現一致且簡單的軟件接口,從而簡化軟件的重用 |
| STM32F10x_StdPeriph_Driver |
inc |
標准外設庫驅動頭文件 |
| src |
標准外設庫驅動源文件 |
|
| Project |
Examples |
標准外設庫驅動的完整例程 |
| Template |
MDK-ARM |
KEIL RVMDK的項目模板示例 |
| RIDE |
Raisonance RIDE的項目模板示例 |
|
| EWARM |
IAR EWARM的項目模板示例 |
|
| Utilities |
STM3210-EVAL |
本文件夾包含了用於STM3210B-EVAL和STM3210E-EVAL評估板的專用驅動 |
標准外設庫的第一部分是CMSIS 和STM32F10x_StdPeriph_Driver,CMSIS 是獨立於供應商的Cortex-M 處理器系列硬件抽象層,為芯片廠商和中間件供應商提供了簡單的處理器軟件接口,簡化了軟件復用工作,降低了Cortex-M 上操作系統的移植難度,並減少了新入門的微控制器開發者的學習曲線和新產品的上市時間。STM32F10x_StdPeriph_Driver則包括了分別對應包括了所有外設對應驅動函數,這些驅動函數均使用C語言編寫,並提供了統一的易於調用的函數接口,供開發者使用。Project文件夾中則包括了ST官方的所有例程和基於不同編譯器的項目模板,這些例程是學習和使用STM32的重要參考。Utilities包含了相關評估板的示例程序和驅動函數,供使用官方評估板的開發者使用,很多驅動函數同樣可以作為學習的重要參考
0x4: STM32F10xxx標准外設庫體系結構
| 文件名 |
功能描述 |
具體功能說明 |
| core_cm3.h core_cm3.c |
Cortex-M3內核及其設備文件 |
訪問Cortex-M3內核及其設備:NVIC,SysTick等 訪問Cortex-M3的CPU寄存器和內核外設的函數 |
| stm32f10x.h |
微控制器專用頭文件 |
這個文件包含了STM32F10x全系列所有外設寄存器的定義(寄存器的基地址和布局)、位定義、中斷向量表、存儲空間的地址映射等 |
| system_stm32f10x.h system_stm32f10x.c |
微控制器專用系統文件 |
函數SystemInit,用來初始化微控制器 函數Sysem_ExtMemCtl,用來配置外部存儲器控制器。它位於文件startup_stm32f10x_xx.s /.c,在跳轉到main前調用 SystemFrequncy,該值代表系統時鍾頻率 |
| startup_stm32f10x_Xd.s |
編譯器啟動代碼 |
微控制器專用的中斷處理程序列表(與頭文件一致) 弱定義(Weak)的中斷處理程序默認函數(可以被用戶代碼覆蓋) 該文件是與編譯器相關的 |
| stm32f10x_conf.h |
固件庫配置文件 |
通過更改包含的外設頭文件來選擇固件庫所使用的外設,在新建程序和進行功能變更之前應當首先修改對應的配置。 |
| stm32f10x_it.h stm32f10x_it.c |
外設中斷函數文件 |
用戶可以相應的加入自己的中斷程序的代碼,對於指向同一個中斷向量的多個不同中斷請求,用戶可以通過判斷外設的中斷標志位來確定准確的中斷源,執行相應的中斷服務函數。 |
| stm32f10x_ppp.h stm32f10x_ppp.c |
外設驅動函數文件 |
包括了相關外設的初始化配置和部分功能應用函數,這部分是進行編程功能實現的重要組成部分。 |
| Application.c |
用戶文件 |
用戶程序文件,通過標准外設庫提供的接口進行相應的外設配置和功能設計。 |
0x5: 基於CMSIS標准的軟件架構
對於ARM公司來說,一個ARM內核往往會授權給多個廠家,生產種類繁多的產品,如果沒有一個通用的軟件接口標准,那么當開發者在使用不同廠家的芯片時將極大的增加了軟件開發成本,因此,ARM與Atmel、IAR、Keil、hami-nary Micro、Micrium、NXP、SEGGER和ST等諸多芯片和軟件廠商合作,將所有Cortex芯片廠商產品的軟件接口標准化,制定了CMSIS標准。此舉意在降低軟件開發成本,尤其針對新設備項目開發,或者將已有軟件移植到其他芯片廠商提供的基於Cortex處理器的微控制器的情況。有了該標准,芯片廠商就能夠將他們的資源專注於產品外設特性的差異化,並且消除對微控制器進行編程時需要維持的不同的、互相不兼容的標准的需求,從而達到降低開發成本的目的
基於CMSIS標准的軟件架構(或者叫固件庫架構)主要分為以下4層
1. 用戶應用層 2. 操作系統及中間件接口層 3. CMSIS層: CMSIS層起着承上啟下的作用 1) 一方面該層對硬件寄存器層進行統一實現,屏蔽了不同廠商對Cortex-M系列微處理器核內外設寄存器的不同定義 2) 另一方面又向上層的操作系統及中間件接口層和應用層提供接口,簡化了應用程序開發難度,使開發人員能夠在完全透明的情況下進行應用程序開發。也正是如此,CMSIS層的實現相對復雜 4. 硬件寄存器層
0x6: 使用方式
在實際開發過程中,根據應用程序的需要,可以采取2種方法使用標准外設庫(StdPeriph_Lib)
1. 使用外設驅動: 這時應用程序開發基於外設驅動的API(應用編程接口)。用戶只需要配置文件"stm32f10x_conf.h",並使用相應的文件例如"stm32f10x_ppp.h/.c"即可 2. 不使用外設驅動: 這時應用程序開發基於外設的寄存器結構和位定義文件(需要了解單片機的大量硬件、引腳細節)
標准外設庫(StdPeriph_Lib)支持STM32F10xxx系列全部成員:大容量,中容量和小容量產品,實際開發中根據使用的STM32產品具體型號,用戶可以通過文件"stm32f10x.h"中的預處理define或者通過開發環境中的全局設置來配置標准外設庫(StdPeriph_Lib),一個define對應一個產品系列
STM32F10x_LD:STM32小容量產品
STM32F10x_MD:STM32中容量產品
STM32F10x_HD:STM32大容量產品
在庫文件中這些define的具體作用范圍是
1. 文件"stm3210f.h"中的中斷IRQ定義 2. 啟動文件中的向量表,小容量,中容量,大容量產品各有一個啟動文件 3. 外設存儲器映像和寄存器物理地址 4. 產品設置: 外部晶振(HSE)的值等 5. 系統配置函數
因此通過宏定義這種方式,可以使標准外設庫適用於不同系列的產品,同時也方便與不同產品之間的軟件移植,極大的方便了軟件的開發
0x7: 命名規范
標准外設庫中的主要外設均采用了縮寫的形式,通過這些縮寫可以很容易的辨認對應的外設。
| 縮寫 |
外設/單元 |
| ADC |
模數轉換器 |
| BKP |
備份寄存器 |
| CAN |
控制器局域網模塊 |
| CEC |
|
| CRC |
CRC計算單元 |
| DAC |
數模轉換器 |
| DBGMCU |
調試支持 |
| DMA |
直接內存存取控制器 |
| EXTI |
外部中斷事件控制器 |
| FLASH |
閃存存儲器 |
| FSMC |
靈活的靜態存儲器控制器 |
| GPIO |
通用輸入輸出 |
| I2C |
I2C接口 |
| IWDG |
獨立看門狗 |
| PWR |
電源/功耗控制 |
| RCC |
復位與時鍾控制器 |
| RTC |
實時時鍾 |
| SDIO |
SDIO接口 |
| SPI |
串行外設接口 |
| TIM |
定時器 |
| USART |
通用同步/異步收發器 |
| WWDG |
窗口看門狗 |
標准外設庫遵從以下命名規則 PPP表示任一外設縮寫,例如:ADC。源程序文件和頭文件命名都以“stm32f10x_”作為開頭,例如:stm32f10x_conf.h
外設函數的命名以該外設的縮寫加下划線為開頭。每個單詞的第一個字母都由英文字母大寫書寫,例如:SPI_SendData。在函數名中,只允許存在一個下划線,用以分隔外設縮寫和函數名的其它部分。對於函數命名
Relevant Link:
http://baike.baidu.com/link?url=X3kL65ER2yug2m-_XhgXTggAd7uH7VCnyhdaJ2ddxYt-Nj8oqB46tWDhNngqyrPnuAzzs8wJe56NzIJi-6zWWa http://www.cnblogs.com/emouse/archive/2011/11/29/2268441.html
2. 基於固件庫實現串口輸出(發送)程序
0x1: 關鍵庫函數
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) 發送函數,它告訴我們很重要的一點,那就是串口是以"位"來傳輸的 FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG) 用它來得知串口的狀態
0x2: 參考printf函數編寫串口發送函數(自定義一個完全脫庫標准C庫的新函數)
#include <stdio.h> #include <stdarg.h> /************************************************* * Function Name : USART1_printf * Description : * Input : * Output : NONE * Return : NONE *************************************************/ void USART1_printf (char *fmt, ...) { char buffer[CMD_BUFFER_LEN+1]; u8 i = 0; va_list arg_ptr; va_start(arg_ptr, fmt); vsnprintf(buffer, CMD_BUFFER_LEN+1, fmt, arg_ptr); while ((i < CMD_BUFFER_LEN) && buffer[i]) { USART_SendData(USART1, (u8) buffer[i++]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); } va_end(arg_ptr); } /************************************************* * Function Name : USART1_SendData * Description : 串口1發送 * Input : char *Buffer * Output : NONE * Return : NONE *************************************************/ void USART1_SendData(char *Buffer) { u8 Counter = 0; while( (Counter == 0) || (Buffer[Counter] != 0) ) //條件... { USART_SendData(USART1, Buffer[Counter++]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } }
0x3: Code
/*---------------------------------------------------------------------------- * Name: Hello.c * Purpose: Hello World Example * Note(s): *---------------------------------------------------------------------------- * This file is part of the uVision/ARM development tools. * This software may only be used under the terms of a valid, current, * end user licence from KEIL for a compatible version of KEIL software * development tools. Nothing else gives you the right to use this software. * * This software is supplied "AS IS" without warranties of any kind. * * Copyright (c) 2012 Keil - An ARM Company. All rights reserved. *----------------------------------------------------------------------------*/ #include <stdio.h> /* prototype declarations for I/O functions */ #include <stm32f10x.h> /* STM32F10x definitions */ #include <stm32f10x_usart.h> #include <stdarg.h> #include <string.h> #define CMD_BUFFER_LEN 64u static void delayms(int cnt){ int i; while(cnt--) for (i=0; i<7333; i++); } void USART2_printf (char *fmt, ...) { char buffer[CMD_BUFFER_LEN+1] = {0}; u8 i = 0; int len = 0; va_list arg_ptr; memset(buffer, '\x00', CMD_BUFFER_LEN+1); len = strlen(fmt) + 1; va_start(arg_ptr, fmt); vsnprintf(buffer, len, fmt, arg_ptr); while ((i < len) && buffer[i] != '\x00') { USART_SendData(USART1, (u8) buffer[i++]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) ; } va_end(arg_ptr); } extern void SER_Init(void); /* see Serial.c */ /*---------------------------------------------------------------------------- main program *----------------------------------------------------------------------------*/ int main (void) { /* execution starts here */ SER_Init (); /* initialize the serial interface */ USART2_printf ("Hello World\n"); /* the 'printf' function call */ while (1) { /* An embedded program does not stop and */ USART2_printf ("Hello World\n"); /* ... */ /* never returns. We use an endless loop. */ delayms(3000); } /* Replace the dots (...) with your own code.*/ }
使用64字節的緩沖數組保存需要發送的數據,然后通過while循環逐byte發送往Terminal終端
為了顯示方便,還可以加上sleep函數
static void delayms(int cnt){ int i; while(cnt--) for (i=0; i<7333; i++); }
需要明白的,我們從指令層面看while循環,由於指令周期、機器周期都是可時間量化的,因此可以用while來實現spin CPU空轉,即sleep
Relevant Link:
http://www.cnblogs.com/mocet/p/stm32f10x_usart_InputOutout_Function_Design_1.html
3. 紅外接收實驗
二進制脈沖碼的形式有多種,其中最為常見的是PWM碼(脈沖寬度調制碼)和PPM碼(脈沖位置調制碼,脈沖串之間的時間間隔來實現信號調制),如果要開發紅外接收設備,一定要知道紅外發射器(例如遙控器)的編碼方式和載波頻率,我們才可以選取一體化紅外接收頭和制定解碼方案
/********************************************************************************** * ***********************************************************************************/ #include "stm32f10x.h" #include "stm32f10x_exti.h" //#include "stm8s_beep.h" #include "stm32f10x_systick.h" #define LED1_0 GPIOD->BRR = 0x00000100 #define LED2_0 GPIOD->BRR = 0x00000200 #define LED3_0 GPIOD->BRR = 0x00000400 #define LED4_0 GPIOD->BRR = 0x00000800 #define LED1_1 GPIOD->BSRR = 0x00000100 #define LED2_1 GPIOD->BSRR = 0x00000200 #define LED3_1 GPIOD->BSRR = 0x00000400 #define LED4_1 GPIOD->BSRR = 0x00000800 #define IR_Hongwai_0 GPIOE->BRR = 0x00000004 //??????? #define IR_Hongwai_1 GPIOE->BSRR = 0x00000004 //??????? #define IR_Hongwai_x GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2) //???????? unsigned int TimeByte; volatile unsigned int IR_Tireafg[4] = {0, 0, 0, 0}; //unsigned int IR_xidwrit[8] = {0, 0, 0, 0, 0, 0 ,0, 0}; /* * GPIO??????? */ void GPIO_InitStructReadtempCmd(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2; //??GPIO?? GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //????????? GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //??????50MHZ GPIO_Init(GPIOE, &GPIO_InitStruct); //??????? GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //?????????? GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &GPIO_InitStruct); } /* * ????????? */ void RCC_APB2PeriphReadtempyCmd(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //??GPIOB???? RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); //??GPIOE???? RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); //??GPIOD???? RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE); //??AFIO???????? } /* * ?????????Count1 * 10us */ unsigned int IR_HongwaiRead_LSB_Cmd() { unsigned int Count1 = 0; //?????? IR_Hongwai_0; //??????? do //????? { Count1++; //?????1 Delay_10us(1); //??10us } while(IR_Hongwai_x == 0); //??????????????????????? return(Count1); //???????? } /* * ?????????Count2 * 10us */ unsigned int IR_HongwaiRead_MSB_Cmd() { unsigned int Count2 = 0; //?????? IR_Hongwai_1; //??????? do //????? { Count2++; //?????1 Delay_10us(1); //??10us } while(IR_Hongwai_x == 1); //??????????????????????? return(Count2); } /* * ???? */ int main(void) { SystemInit(); //?????????72M?? SYSTICK_InitStructReadTCmd(); //???SysTick?????? RCC_APB2PeriphReadtempyCmd(); //???????????? GPIO_InitStructReadtempCmd(); //???GPIO??????? EXTI_InitStructReadtempCmd(); //???EXTI??????? NVIC_InitStructReadtempCmd(); //???NVIC??????? while(1) { } } /* * EXTI????????? */ void EXTI2_IRQHandler(void) { unsigned char i = 0; unsigned int Countline2 = 0; IR_Hongwai_1; Countline2 = IR_HongwaiRead_LSB_Cmd(); //?????? 9ms?? if((Countline2 < 286) || (Countline2 > 305)) //??8694us ??9272us ???????? { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); //?????? 4.5ms?? if((Countline2 < 138) || (Countline2 > 155)) //??4195us ??4712us ???????? { return; } TimeByte = 0; for(i = 1; i < 14; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); //?????0.56 ?? if((Countline2 < 14) || (Countline2 > 22)) //??425us ??851us ???????? { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); //?????0.56?? if((Countline2 < 14) || (Countline2 > 65)) //??425us ??1793us ???????? { return; } if( Countline2 > 50) //???????1300us?1???0 { TimeByte |= 0x80; //?1 } } IR_Tireafg[0] = TimeByte; TimeByte = 0; for(i = 14; i < 27; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); if((Countline2 < 14) || (Countline2 > 22)) { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); if((Countline2 < 14) || (Countline2 > 65)) { return; } if( Countline2 > 50) { TimeByte |= 0x80; } } IR_Tireafg[1] = TimeByte; TimeByte = 0; for(i = 27; i < 35; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); if((Countline2 < 14) || (Countline2 > 22)) { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); if((Countline2 < 14) && (Countline2 > 65)) { return; } if( Countline2 > 50) { TimeByte |= 0x80; } } IR_Tireafg[2] = TimeByte; TimeByte = 0; for(i = 35; i < 43; i++) { TimeByte = TimeByte >> 1; Countline2 = IR_HongwaiRead_LSB_Cmd(); if((Countline2 < 14) || (Countline2 > 22)) { return; } Countline2 = IR_HongwaiRead_MSB_Cmd(); if((Countline2 < 14) || (Countline2 > 65)) { return; } if( Countline2 > 52) { TimeByte |= 0x80; } } IR_Tireafg[3] = TimeByte; //************************?????????***************************************// /* if(IR_Tireafg[0]!= 0x11A) { return; } */ //************************?????????***************************************// /* do { if(IR_Tireafg[2] == ~IR_Tireafg[3]) { flag = 0; } } while(flag == 1); */ /* if(IR_Tireafg[2] != ~IR_Tireafg[3]) { flag = 0; } */ //************************??????LED??**************************************// switch (IR_Tireafg[2]) { case 0x00: //?? 0 LED1_1; LED2_0; LED3_0; LED4_0; break; case 0x01: //?? 1 LED1_0; LED2_1; LED3_0; LED4_0; break; case 0x02: //?? 2 LED1_0; LED2_0; LED3_1; LED4_0; break; case 0x03: //?? 3 LED1_0; LED2_0; LED3_0; LED4_1; break; case 0x04: //?? 4 LED1_0; LED2_0; LED3_1; LED4_0; break; case 0x05: //?? 5 LED1_0; LED2_1; LED3_0; LED4_0; break; case 0x06: //?? 6 LED1_1; LED2_0; LED3_0; LED4_0; break; case 0x07: //?? 7 LED1_1; LED2_0; LED3_1; LED4_0; break; case 0x08: //?? 8 LED1_1; LED2_0; LED3_0; LED4_1; break; case 0x09: //?? 9 LED1_0; LED2_1; LED3_0; LED4_1; break; case 0x15: //??? LED1_0; LED2_1; LED3_1; LED4_0; break; case 0x1C: //??? LED1_0; LED2_0; LED3_0; LED4_0; break; case 0x14: //OSD? LED1_1; LED2_1; LED3_0; LED4_0; break; case 0x0E: //RECALL? LED1_0; LED2_0; LED3_1; LED4_1; break; case 0x19: //SLEEP? LED1_1; LED2_1; LED3_1; LED4_0; break; case 0x0A: //A/C? LED1_0; LED2_1; LED3_1; LED4_1; break; case 0x0F: //TV/AV? LED1_1; LED2_1; LED3_1; LED4_1; break; case 0x13: //PP? LED1_1; LED2_0; LED3_1; LED4_0; break; case 0x0C: //GAME? LED1_0; LED2_1; LED3_1; LED4_1; break; case 0x1E: //V-? LED1_1; LED2_1; LED3_1; LED4_0; break; case 0x1F: //V+? LED1_0; LED2_0; LED3_1; LED4_0; break; case 0x1B: //P+? LED1_0; LED2_0; LED3_0; LED4_1; break; case 0x1A: //P-? LED1_1; LED2_0; LED3_0; LED4_0; break; case 0x10: //MENU? LED1_0; LED2_1; LED3_0; LED4_0; break; default : break; } // Beep_lookCmd(); //?????? EXTI_ClearITPendingBit(EXTI_Line2); //??EXTI2??????? }
Relevant Link:
http://www.iqiyi.com/w_19rrdz9g91.html#curid=5449021609_7b2174ee370808596288e2209eef0b75 http://bbs.21ic.com/icview-243059-1-1.html http://wenku.baidu.com/view/2d0b4636a32d7375a417802e.html http://blog.csdn.net/houqi02/article/details/51585551 http://bbs.21ic.com/icview-649262-1-1.html http://wenku.baidu.com/link?url=F4r-R2rp-cF8lw7zSxuLYVWRoLdXeCQYt2Kf4hO9Kb7JMe1n7eOxkY-5t4Ar3990U5EmoungBQCyGFJitjsFqSSId5joGVgND6gQntg0ipO
4. 深入分析流水燈例程
想要控制LED燈,自然是要通過控制STM32芯片的IO引腳電平的高低來實現(這種和arduino是一樣的)(因為LED燈的本質就是高低電位是否導通),在STM32芯片上,IO引腳可以被軟件設置成各種不同的功能,如輸入輸出,所以又被稱為GPIO(general purpose IO),按照這種方式去思考,我們要做的事如下
1. GPIO端口引腳多:需要選定需要控制的的特定引腳 2. GPIO功能較豐富:配置需要的特定功能(配置寄存器) 3. 控制LED的亮和滅:設置GPIO輸出電壓的高低
STM32的功能實際上也是通過配置寄存器來實現的,STM32編程本質上就是面向寄存器的編程
對於GPIO端口,每個端口有16個引腳,每個引腳的模式由寄存器的4個位控制,每4位又分為兩位控制引腳配置,另外兩位控制引腳的模式以及最高速度
0x1: STM32的地址映射(stm32f10x.h)
我們來回顧下在51單片機上點亮LED是如何實現的(arduino也類似)
#include <reg52.h> int main(void){ P0 = 0; while(1); }
這背后的原理是就是地址映射,所謂地址映射,就是將芯片上的存儲器甚至IO等資源與地址建立一一對應關系(即所有東西皆地址),這樣如果某個地址對應着某個存儲器,我們就可以運用C語言的指針來尋址並修改這個地址上的內容,從而實現修改該存儲器的內容
Cortex-M3的地址映射也是類似的,Cortex-M3有32根地址線,所以它的尋址空間大小為4GB,ARM公司設計時,預先把這4GB的尋址空間大致地分配好了

stm32f10x.h這個文件的重要的內容就是把STM32的所有寄存器進行地址映射,它就像一個大表格,我們在使用的時候就是通過宏定義進行類似查表的操作
0x2: STM32的時鍾系統(同步/驅動bit信號的傳輸)
STM32芯片為了實現低功耗,設計了一個功能完善但是卻非常復雜的時鍾系統,普通的MCU一般只要配置好GPIO的寄存器就可以使用了,但是STM32還有一個步驟,就是開啟外設時鍾
1. 時鍾樹 & 時鍾源

上圖說明了STM32的時鍾走向,從左邊開始從時鍾源一步步分配到外設時鍾,從時鍾頻率來說,又分為高速時鍾和低速時鍾
1. 高速時鍾: 是提供給芯片主體的主時鍾 2. 低速時鍾: 提供給芯片中的RTC(實時時鍾)及獨立看門狗使用
從芯片角度來說,時鍾源分為內部時鍾與外部時鍾源
1. 內部時鍾: 是由芯片內部RC振盪器產生的,起振較快,所以時鍾在芯片剛上電的時候,默認使用內部高速時鍾 2. 外部時鍾: 是由外部的晶振輸入的,在精度和穩定性在都有較大優勢,所以上電之后再通過軟件配置,轉而采用外部時鍾信號
所以,STM32有以下4個時鍾源
1. 高速外部時鍾(HSE):以外部晶振作為時鍾源,晶振頻率可取范圍為4 ~ 16MHZ,我們一般采用8MHZ的晶振 2. 高速內部時鍾(HSI):由內部RC振盪器產生,頻率為8MHZ,但不穩定 3. 低速外部時鍾(LSE):以外部晶振作為時鍾源,主要提供給實時時鍾模塊,所以一般采用32KHZ 4. 低速內部時鍾(LSI):由內部RC振盪器產生,也主要提供給實時時鍾模塊,頻率大約為40kHZ
2. HCLK、FCLK、PCLK1、PCLK2
從時鍾樹的分析,看到經過一系列的倍頻、分頻后得到了幾個與我么開發密切相關的時鍾
1. SYSCLK:系統時鍾,是STM32大部分器件的時鍾來源,主要由AHB預分頻器分配到各個部件 2. HCLK:由AHB預分頻器直接輸出得到,它是高速總線AHB的時鍾信號,提供給存儲器、DMA、Cortex內核,是Cortex內核運行的時鍾,CPU主頻就是這個信號,它的大小與STM32運算速度、數據存取速度密切相關 3. FCLK:同樣由AHB預分頻器輸出得到,是內核的"自由運行時鍾"。"自由"表現在它不來自時鍾"HCLK",因此在HCLK時鍾停止時FCLK也能繼續運行。它的存在可以保證,在處理器處於休眠時也能夠采樣到中斷和跟蹤休眠事件,它與HCLK互相同步 4. PCLK1:外設時鍾,由APB1預分配器輸出得到,最大頻率為36MHZ,提供給掛載在APB1總線上的外設 5. PCLK2:外設時鍾,由APB2預分頻器輸出得到,最大頻率可達72MHZ,提供給掛載在APB2總線上的外設
STM32的時鍾系統設計地如此復雜是出於以下幾個原因考慮
1. 因為有分頻、倍頻以及一系列外設時鍾的開關 2. 需要倍頻是考慮到電磁兼容性,如果外部提供一個72MHZ的晶振,太高的振盪頻率可能會給制作電路帶來一定的難度 3. 分頻是因為STM32既有高速外設也有低速外設,各種外設的工作頻率不盡相同,如同PC上的南北橋,把高速和低速的設備分開來管理 4. 每個外設都配備了外設時鍾的開關,每當我們不使用某個外設時,可以把這個外設時鍾關閉,從而降低STM32的整體功耗,所以當我們使用外設時,一定要記得開啟外設的時鍾
5. GPIO再舉例之按鍵實驗
0x1: GPIO的8種工作模式
在初始化GPIO的時候,根據我們的使用要求,必須把GPIO設置為相應的模式

0x2: 循環掃描方式檢測按鍵是否按下
我們可以采取中斷響應、或者循環掃描方式去檢測對應的GPIO輸入端口(引腳)是否有指定的bit數據來實現按鍵的檢測
#ifndef __KEY_H #define __KEY_H #include "stm32f10x.h" #define S1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) #define S2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) #define S3 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) #define S4 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) void KEY_Init(void); #endif #include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" int main(void) { uint8_t j; //¶¨Òå±äÁ¿ LED_Init();//LED³õʼ»¯ KEY_Init();//°´¼ü³õʼ»¯ SysTick_Init();//ÑÓʱ³õʼ»¯ while (1) { if(!S1) { Delay_ms(10); if(!S1) { while(!S1);//µÈ´ý°´¼üÊÍ·Å LED2_REV; } } //////////////////////////////////////////// if(!S2) { Delay_ms(10); if(!S2) { while(!S2); LED3_REV; } } ////////////////////////////////////////// if(!S3) { Delay_ms(10); if(!S3) { while(!S3); LED2_REV; LED3_REV; } } /////////////////////////////////////////// if(S4) { Delay_ms(10); if(S4) { while(S4); for(j=0;j<10;j++) { LED2_REV; LED3_REV; Delay_ms(100); } } } } }
0x3: 外部中斷觸發方式檢測按鍵是否按下
還可以使用EXIT(External Interrupt 外部中斷)的方式,即通過GPIO檢測輸入脈沖,引起中斷事件,打斷原來的代碼執行流程,進入到中斷服務函數中進行處理,處理完后再返回到中斷之前的代碼中執行。我們知道,STM32的所有GPIO口可以用作外部中斷源的輸入端
1. STM32的中斷和異常
Cortex內核具有強大的異常響應系統,它把能夠打斷當前代碼執行流程的事件分為異常(exception)和中斷(interrupt),並把它們用一個表(中斷向量表)管理起來
1. 編號為-3 ~ 6的稱為內核異常: 不能被設置優先級,如復位(Reset)、不可屏蔽中斷(NMI)、硬錯誤(HardFault) 2. 7以上的則稱為外部中斷: 這些中斷的優先級是可以自行設置的
需要注意的是,不同型號的STM32芯片,中斷向量表稍有區別,最好的方法的是從啟動文件startup_stm32f10x_hd.s中查找,在啟動文件中,已經有相應芯片可用的全部中斷向量,而且在編寫中斷服務函數時,需要從啟動文件中定義的中斷向量表查找中斷服務函數名
2. NVIC中斷控制器(Nested Vectored Interrupt Controller)
NVIC是屬於Cortex內核的器件,不可屏蔽中斷(NMI)和外部中斷都由它來處理,但是SYSTICK不是由NVIC來控制的。嵌套向量中斷控制器(NVIC)和處理器核的接口緊密相連,可以實現低延遲的中斷處理和有效地處理晚到的中斷。嵌套向量中斷控制器管理着包括核異常等中斷
3. NVIC結構體成員
ST庫已經把NVIC封裝了庫函數的形式
uint8_t NVIC_IRQChannel
FunctionalState NVIC_IRQChannelCmd
uint8_t NVIC_IRQChannelPreemptionPriority
uint8_t NVIC_IRQChannelSubPriority
STM32的中斷向量具有兩個屬性
1. 搶占屬性: 搶占是指打斷其他中斷的屬性,即因為有這個屬性才會出現嵌套中斷 2. 響應屬性: 響應屬性則是在搶占屬性相同的情況下,當兩個中斷向量的搶占優先級相同時,如果連個中斷同時到達,則優先處理響應優先級較高的中斷
STM32單片機上的所有IO端口都可以配置為EXIT中斷模式,用來捕捉外部信號,可以配置為下降沿中斷、上升沿中斷和上升下降沿中斷這3種模式,通過這種方式,我們可以很方便地通過中斷方式來接收外設接收到的信號數據
4. EXIT外部中斷
STM32的所有GPIO都引入到EXIT外部中斷線上,使得所有的GPIO都能作為外部中斷的輸入源

1. PA0 ~ PG0連接到EXIT0 2. PA1 ~ PG1連接到EXIT1 3. PA2 ~ PG0連接到EXIT2 ... 15. PA15 ~ PG15連接到EXIT15
PAx ~ PGx端口的中斷事件都連接到EXITx,即同一時刻EXITx只能響應一個端口的事件觸發,不能同時時間響應所有GPIO端口的事件,但可以分時復用。它可以配置為上升沿觸發、下降沿觸發、雙邊觸發。EXIT最普通的應用就是接上一個按鍵,並設置為下降沿觸發,用中斷來檢測按鍵
在編程時要注意,不同開發板的引腳定義都是不同的,不同開發板的代碼直接移植過程會存在代碼不可用的現象,需要根據實際情況做適當修改
6. 串口通信(USART)
STM32的串口非常強大,它不僅支持最基本的通用串口同步、異步通信,還具有LIN總線功能(局域互聯網)、IRDA功能(紅外通信)、SmartCard功能
0x1: 串口工作過程
從下至上,我們看到串口外設主要由三個部分組成,分別是波特率控制、收發控制、數據存儲傳輸
1. 波特率控制
波特率,即每秒傳輸的二進制位數,用b/s(bps)表示,通過對時鍾的控制可以改變波特率。在配置波特率時,我們向波特率寄存器USART_BRR寫入參數,修改了串口時鍾的分頻值USARTDIV(USARTDIV = DIV_Mantissa + (DIV_Fraction / 16))。USARTDIV是對串口外設的時鍾源進行分頻的
1. USART1: 掛載在APB2總線上,時鍾源為fpclk2 2. USART2: 掛載在APB1上,時鍾源為fpclk1
串口的時鍾源經過USARTDIV分頻后分別輸出為發送器時鍾、接收器時鍾,控制發送和接收
2. 收發控制
圍繞着發送器和接收器控制部分,有很多寄存器
1. CR1 2. CR2 3. CR3 4. SR
3. 數據存儲轉移
收發控制器根據我們的寄存器配置,對數據存儲轉移部分的"移位寄存器"進行控制,串口收發就是通過移位寄存器實現的
1. 當我們需要發送數據時,內核或DMA外設把數據從內存(變量)寫入到數據寄存器TDR后,發送控制器將適時地自動把數據從TDR轉移到移位寄存器時,會產生發送寄存器TDR已空事件TXE,當數據從移位寄存器全部發送出去時,會產生數據發送完成事件TC,這些事件可以在狀態寄存器中查詢到 2. 接收數據是一個逆過程,數據從串口線Rx一位一位地輸入到接收移位寄存器,然后自動地轉移到接收數據寄存器RDR,最后用內核指令或DMA讀取到內存(變量)中
0x2: 串口輸出函數重定向
1. printf()函數重定向
默認情況下,STM32固件庫的printf是輸出到標准輸出中,我們如果希望其輸出到串口終端中,需要把printf()重定向到串口中,即需要自己重寫C的庫函數,當linker檢查到用戶編寫了與C庫函數相同名字的函數時,優先采用用戶編寫的函數。為了實現重定向printf()函數,我們需要重寫fputc()這個C標准庫函數,因為printf()在C標准庫函數中實質是一個宏,最終是調用了fputc()這個函數
#ifdef __GNUC__ /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__ */ PUTCHAR_PROTOTYPE { /* Place your implementation of fputc here */ /* e.g. write a character to the USART */ USART_SendData(USART1, (uint8_t) ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} return ch; }
當使用printf()時,它先調用這個fputc()函數,然后使用ST庫的串口發送函數USART_SendData(),把數據轉移到發送數據寄存器TDR,觸發我們的串口向PC發送一個相應的數據,調用完USART_SendData()后,要使用while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}不斷檢查串口發送是否完成的標志位TC,在這段while循環檢測的延時中,串口外設已經由發送控制器以及根據我們的配置把數據從移位寄存器一位一位地通過串口線Tx發送出去了
2. USART1_printf()函數
7. 庫函數開發通用流程小結
0x1: 初始化
1. GPIO_InitTypeDef GPIO_InitStructure: 用來配置GPIO 2. NVIC_InitTypeDef NVIC_InitStructure: 用來配置NVIC 3. EXIT_InitTypeDef EXIT_InitStructure: 用來配置EXIT 4. USART_InitTypeDef USART_InitStructure: 用來配置USART
使用ST庫對外設進行初始化,一般有以下步驟
1. 定義一個xxx_InitTypeDef類型的初始化結構體 2. 根據使用需要,向這些初始化結構體的成員賦值特定的控制參數 3. 填充好結構體后,把這個結構體作為輸入參數調用相應的外設庫函數xxx_Init(),從而實現向寄存器寫入控制參數,並配置好外設
對於需要時鍾同步的外設,還需要開啟(使能)對應總線上的時鍾
RCC_AHBPeriphClockCmd(RCC_AHBPerip_DMA1, ENABLE);
對於需要中斷回調的外設,還可以配置中斷callback函數
DMA_Init(DMA1_Channal4, &DMA_InitStructure); DMA_Cmd(DMA1_Channal4, ENABLE); //配置DMA發送完成后產生中斷 DMA_ITConfig(DMA1_Channal4, DMA_IT_TC, ENABLE);
0x2: 數據輸入輸出
對外設的使用,一般涉及其輸入和輸出數據,ST官方庫中有一類函數專門為此而編寫的
GPIO的輸入輸出函數
GPIO_ReadOutputDataBit()
GPIO_ReadInputData
GPIO_SetBits
USART的收發數據函數
USART_ReceiveData
USART_SendData
這些函數控制相應外設數據寄存器DR的內容,達到控制輸入輸出的目的,使用的方法也是類似的
1. 通過輸入參數,向函數指定要使用的是什么外設,如用(GPIOA、GPIO_Pin_5)選定PA5進行控制,用USART1來指定使用串口1外設 2. 若向外輸出數據,則調用Output或Send函數,把將要輸出的數據變量作為函數的輸入參數 3. 若為接收外部數據,則調用Read或Receive函數,讀取函數的返回值來得到外部輸入數據
0x3: 狀態位、標志位
當我們需要知道外設的工作狀態時,就涉及一系列標志檢查的ST官方庫函數了
1. 事件
當外設完成了某些工作或出現某些狀態的時候,會觸發一些事件,這些事件會在狀態寄存器SR中,以不同的寄存器位來記錄,這些寄存器位稱為相應的事件標志位
2. 標志位的檢查與清除
假如我們把串口的發送完成事件、接收寄存器非空事件(串口接收到數據)都配置為可觸發中斷,因為它們觸發的都是串口中斷,所以中斷時都是進入到同一個串口中斷服務函數中處理的,那么我們這個串口服務函數中,就要區分事件源類型的,例如USART_GetFlagStatus、EXIT_GetFlagStatus,檢查的值來自外設的xxx_SR寄存器
3. 外設函數的分類
8. DMA傳輸方式
DMA(Direct Memory Access 直接存儲器存取)是一種可以大大減輕CPU工作量的數據存取方式,因而被廣泛使用,STM32的DMA則是以類似外設的形式添加到Cortex內核之外的。在傳統硬件系統中,主要由CPU(內核)、外設、內存(SRAM)、總線等結構組成,數據經常要在內存與外設之間轉移,或從外設A轉移到外設B。在轉移數據的過程中會占用CPU十分寶貴的資源,DMA正是為CPU分擔了數據轉移的工作,因為DMA的存在CPU能夠被解放出來,它可以在DMA轉移數據的過程中同時進行數據運算、響應中斷,大大提高效率
0x1: DMA工作分析
從上圖可以看到,STM32內核、存儲器、外設、DMA的連接最終都通過各種各樣的線連接到總線矩陣之中,硬件結構之間的數據轉移都經過總線矩陣的協調,使各個外設都能夠和諧地使用總線來傳輸數據
1. 在不使用DMA的情況下,內核通過DCode經過總線矩陣協調,使用AHB把外設ADC采集的數據讀取到內核,然后內核DCode再通過總線矩陣協調,把數據存放到內存SRAM中 2. DMA正好可以取代這樣的工作,由DMA控制器的DMA總線與總線矩陣協調,使用AHB把外設ADC的數據經由DMA通道存放到內存SRAM,在這個數據傳輸的過程中,不需要內核的全稱參與,所以內核可以同時進行數據運算。而且,DMA方式是點到點的數據轉移
要使用DMA,需要確定一系列的控制參數,如外設數據的地址、內存地址、傳輸方向等,在開啟DMA傳輸前還要先發出DMA請求
0x2: DMA實例之串口通信
我們知道,硬件外設本來就是一個異步工作方式,外設工作的時候,除了轉移數據,實質上是不需要內核干預的,而數據轉移的工作交給了DMA,所以在串口發送數據的時候,內核同時還可以進行其他操作,如點亮LED燈
9. STM32 ADC
ADC(Analog to Digital Converter 模/數轉換器),在模擬信號需要以數字形式處理、存儲或傳輸時,模數轉換器幾乎必不可少。配套STM32開發板用的是STM32F103VET6,屬於增強型CPU,它有18個通道,可測量16個外部和2個內部信號源。各通道的A/D轉換可以單次、連續、掃描或間斷模擬執行。ADC的結果可以左對齊或右對齊方式存儲在16位數據寄存器中
0x1: STM32的ADC主要技術指標
1. 分辨率: 12位分辨率,不能直接測量負電壓,所以沒有符號位,即最小量化單位LSB = Vref/ 212 2. 轉換時間: 轉換時間是可編程的,采樣一次至少要用14個ADC時鍾周期,而ADC的時鍾頻率最高為14MHz,也就是說,它的采樣時間最短為1us,足以勝任中、低頻數字示波器的采樣工作 3. ADC類型: STM32的ADC是逐次比較型ADC 4. 參考電壓范圍: 2.4V <= Vref+ <= 3.6V
0x2: ADC工作過程分析
所有的器件都是圍繞中間的模擬至數字轉換器部分(ADC部件)展開的
1. 它的左端為Vref+、Vref-參考電壓 2. ADCx_IN0 ~ ADCx_IN15為ADC的輸入信號通道,即某些GPIO引腳,輸入信號經過這些通道被送到ADC部件,ADC部件需要受到觸發信號才開始進行轉換,如EXIT外部觸發、定時器觸發、也可以使用軟件觸發 3. ADC部件接收到觸發信號后,在ADCCLK時鍾的驅動下對輸入通道的信號進行采樣,並進行模數轉換,其中ADCCLK是來自ADC預分頻器的 4. ADC部件轉換后的數值被保存到一個16位的規則通道數據存儲器(或注入通道數據寄存器)之中,我們可以通過CPU指令或DMA把它讀取到內存(變量) 5. 模數轉換之后,可以觸發DMA請求、或者觸發ADC的轉換結束回調事件 6. 如果配置了模擬看門狗,並且采集得的電壓大於閾值,會觸發看門狗中斷
0x3: ADC數據采集實例(DMA方式)
使用ADC時常常需要不間斷采集大量的數據,在一般的器件中會使用中斷進行處理,但使用中斷的效率還是不夠高,在STM32中,使用ADC時往往采用DMA傳輸方式,由DMA把ADC外設轉換的數據直接傳輸到SRAM中(跳過CPU的中轉),再進行處理,甚至直接把ADC的數據轉移到串口發送給上位機
10. SysTick(系統滴答定時器)
0x1: SysTick(操作系統的心跳)
SysTick定時器被捆綁在NVIC中,用於產生SysTick異常(異常號15),滴答中斷對操作系統尤其重要,例如
1. 操作系統可以為多個任務分配不同數目的時間片,確保沒有一個任務能霸占系統 2. 或者將每個定時器周期的某個時間范圍賜予特定的任務
操作系統提供的各種定時功能都與這個滴答定時器有關,因此,需要一個定時器來產生周期性的中斷,而且最好還讓用戶程序不能隨意訪問它的寄存器,以維持操作系統"心跳"的節律。Cortex-M3在內核部分包含了一個簡單的定時器: SysTick。該定時器的時鍾源可以是內部時鍾(FCLK、CM3上的自由運行時鍾),或者是外部時鍾(CM3處理器上的STCLK信號)
SysTick定時器能產生中斷,CM3為它專門預留了一個異常類型,並且在向量表中有專門的中斷處理函數void SysTick_Handler(void),它使操作系統和其他系統軟件在CM3器件間的移植變得簡單多了,因為在所有CM3產品間,SysTick的處理方式都是相同的,SysTick定時器除了能服務於操作系統外,還能用於其他目的,如作為一個鬧鍾、用作測量時間等
0x2: SysTick工作分析
SysTick是一個24位的定時器,即一次最多可以計數2^24個時鍾脈沖,這個脈沖計數值被保存到當前計數值寄存器STK_VAL(SysTick current register)中,只能向下計數,每接收到一個時鍾脈沖STK_VAL的值就向下減1,直至0,當STK_VAL的值被減至0時,由硬件自動把重載寄存器STK_LOAD(SysTick reload value register)中保存的數據加載到STK_VAL,重新向下計數。
當STK_VAL的值被計數至0時,觸發異常,就可以在中斷服務函數中處理定時事件了。要使SysTick進行以上工作必須要進行SysTick配置,它的控制很簡單,只有三個控制位和一個標志位,都位於寄存器STK_CTRL(SysTick control and status register)中
1. Bit0: ENABLE: 為SysTick的使能位,此位為1的時候使能SysTick定時器,此位為0的時候關閉SysTick定時器 2. Bit1: TICKINT: 為異常觸發使能位,此位為1的時候並且STK_VAL計數至0時會觸發SysTick異常,此位被配置為0的時候不觸發異常 3. Bit2: CLKSOUCE: 為SysTick的時鍾選擇位,此位為1時SysTick的時鍾為AHB時鍾,此位為0時SysTick始終為AHB/8(AHB的8分頻) 4. Bit16: COUNTFLAG: 為計數為0標志位,若STK_VAL計數至0,此標志位會被置1
0x3: 使用SysTick精確延時實驗分析
比起使用while循環一定次數來大概估測延時時間,當我們需要精確延時時,就可以利用SysTick實現,理論上它的最小計時單位為AHB的時鍾周期,即1/72000000秒,72分之一的微秒,足以滿足大部分應用的需求
11. STM32定時器
區別於SysTick一般只用於系統時鍾的計時,STM32的定時器外設功能更加強大,STM32一共有8個16位的定位是
1. TIM6/TIM7是基本定時器 2. TIM2/TIM3/TIM4/TIM5是通用定時器 3. TIM1/TIM8是高級定時器
這些定時器使STM32具有定時、信號的頻率測量、信號的PWM測量、PWM輸出、三相6步電機控制及編碼接口等功能,都是專門為工控領域定做的
0x1: 基本定時器
基本定時器TIM6/TIM7只具備最基本的定時功能,就是累加的時鍾脈沖數超過預定值時,能觸發中斷或觸發DMA請求。由於在芯片內部與DAC外設相連,可通過觸發輸出驅動DAC,也可以作為其他通用定時器的時鍾基准
0x2:通用定時器
相比之下,通用定時器TIM2 ~ TIM5就比基本定時器復雜的多了,除了基本的定時,它主要用在測量輸入脈沖的頻率、脈沖寬與輸出PWM脈沖的場合,還具有編碼器的接口
0x3: 高級定時器
TIM1和TIM8是兩個高級定時器,它們具有基本、通用定時器的所有功能,還具有三相6步電機的接口、剎車的功能(break function)及用於PWM驅動電驢的死區時間控制,使得它非常適合於點擊的控制
在進行STM32編程的時候,我們會發現一點,STM32需要你去了解很多底層的硬件細節、GPIO、中斷callback的設置、中斷向量例程的設置、外設時鍾的設置和使能,它們往往是成對出現、並配置使用的,這涉及到了大量的細節
Copyright (c) 2016 LittleHann All rights reserved
