日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

STM32学习——半天学完正点原子入门篇例程,STM32:学会了吗?我:学废了✨

發布時間:2023/12/10 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 STM32学习——半天学完正点原子入门篇例程,STM32:学会了吗?我:学废了✨ 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文代碼均來正點原子標準例程
聲明:本文不是教學文章,可能也不適合初學者閱讀

不知為什么,最近總蹦出有很多想法(可能是工作太閑了)一會想學這,一會想學那,這不,突然想復習一下STM32了。

我好久以前就學過正點原子的課程,還買過一些開發板,但現在手上只有一個核心板了,就暫且湊合著用吧。

我是個喜歡制定計劃的人,既然有了想法,那就得制定一個學習計劃,估摸了一下,明天要上班,現在已經中午了,所以我只有一個下午加一個晚上的時間。哎😢,工作之后發現學習的時間太少了,所以,既然是復習,那就不搞那么多彎彎繞繞了,直接針對正點原子的代碼,通過代碼學習STM32,那些啥原理的,通通給我拋到九霄云外去,以后有機會慢慢整。

文章目錄

  • 開發平臺
  • 實驗1 跑馬燈實驗
    • main()
      • delay_init() 函數
        • SysTick_CLKSourceConfig(...)
      • LED_Init() 函數
        • RCC_APB2PeriphClockCmd(...)
        • GPIO_InitTypeDef
      • delay_ms(...)函數
        • 位操作
  • 實驗2 按鍵輸入
    • main()
      • KEY_Init()函數
      • BEEP_Init()函數
      • KEY_Scan(...)函數
  • 實驗3 串口實驗
    • mian()
      • NVIC_PriorityGroupConfig
      • uart_init(...)函數
      • 串口處理全局變量
      • 重寫printf(fputc)
    • USART1_IRQHandler串口中斷函數
  • 實驗4 外部中斷實驗
    • main()
      • EXTIX_Init()函數
    • EXTIx_IRQHandler外部中斷函數
  • 實驗5 獨立看門狗實驗
    • main()
      • IWDG_Init(...)函數
      • IWDG_Feed()函數
  • 實驗6 窗口看門狗實驗[待學習]
    • main()
      • WWDG_Init(...)函數
  • 實驗7 定時器中斷實驗
    • main()
      • TIM3_Int_Init(...)函數
    • TIMx_IRQHandler()定時器中斷函數
  • 實驗8 PWM輸出實驗
    • main()
      • TIM3_PWM_Init(...)函數
  • 實驗9 輸入捕獲實驗[待學習]
    • main()
      • TIM5_Cap_Init(...)函數
    • TIM5_IRQHandler() 處理輸入捕獲
  • 實驗10 TFTLCD顯示實驗[放棄]

開發平臺

話不多說,開始整活,先準備一下硬件:

就一個核心板,太寒酸了,還好有個屏幕撐撐場面。核心板的MCU型號為STM32F103ZET6。

有了硬件,就差代碼了。
下圖是正點原子的入門篇視頻,我就按照這個順序來學一遍(沒有硬件支持的話,就只能跳過了,如OLED),寄存器版的就不考慮了,太麻煩。

雖然從教學視頻的目錄上看感覺實驗多得有些嚇人,但打開工程文件夾一看,嘿嘿,舒服了。😁,這么一點,一下午就能搞完。

就在我竊喜的時候,看了一眼時間,時間不多了,抓緊了🚐。。。

實驗1 跑馬燈實驗

main()

光看主函數,覺得他和51一樣簡單,就是初始化和設置GPIO的高低,但實際上它們有本質區別,畢竟一個是8位,一個是32位。下面我們來一行行地分析吧。

int main(void) { delay_init(); //初始化延時函數LED_Init(); //初始化LED端口while(1){GPIO_ResetBits(GPIOB,GPIO_Pin_5); //LED0對應引腳GPIOB.5拉低,亮 等同LED0=0;GPIO_SetBits(GPIOE,GPIO_Pin_5); //LED1對應引腳GPIOE.5拉高,滅 等同LED1=1;delay_ms(300); //延時300msGPIO_SetBits(GPIOB,GPIO_Pin_5); //LED0對應引腳GPIOB.5拉高,滅 等同LED0=1;GPIO_ResetBits(GPIOE,GPIO_Pin_5); //LED1對應引腳GPIOE.5拉低,亮 等同LED1=0;delay_ms(300); //延時300ms} }

delay_init() 函數

//初始化延遲函數 //SYSTICK的時鐘固定為HCLK時鐘的1/8 //SYSCLK:系統時鐘 void delay_init() {SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鐘 HCLK/8fac_us=SystemCoreClock/8000000; //為系統時鐘的1/8 fac_ms=(u16)fac_us*1000; //非OS下,代表每個ms需要的systick時鐘數 }

第一個函數delay_init(),不像51里直接用一個while實現延時,這里的延時由滴答定時器實現。Systick定時器就是系統滴答定時器,一個24 位的倒計數定時器,計到0 時,將從RELOAD 寄存器中自動重裝載定時初值。只要不把它在SysTick 控制及狀態寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick_CLKSourceConfig是一個庫函數,作用是配置滴答定時器的時鐘源。

STM32 有5個時鐘源:HSI、HSE、LSI、LSE、PLL。
①、HSI是高速內部時鐘,RC振蕩器,頻率為8MHz,精度不高。
②、HSE是高速外部時鐘,可接石英/陶瓷諧振器,或者接外部時鐘源,頻率范圍為4MHz~16MHz。
③、LSI是低速內部時鐘,RC振蕩器,頻率為40kHz,提供低功耗時鐘。WDG
④、LSE是低速外部時鐘,接頻率為32.768kHz的石英晶體。RTC
⑤、PLL為鎖相環倍頻輸出,其時鐘輸入源可選擇為HSI/2、HSE或者HSE/2。
倍頻可選擇為2~16倍,但是其輸出頻率最大不得超過72MHz。

STM32時鐘源的知識還是挺多的,我自己現在也不是很清楚(得專門抽空學學),但我知道如果沒有做配置,系統默認時鐘頻率是最高頻率——本平臺為72MHz
system_stm32f10x.c里有以下內容,先記錄一下,以后再分析。

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL) /* #define SYSCLK_FREQ_HSE HSE_VALUE */#define SYSCLK_FREQ_24MHz 24000000 #else /* #define SYSCLK_FREQ_HSE HSE_VALUE */ /* #define SYSCLK_FREQ_24MHz 24000000 */ /* #define SYSCLK_FREQ_36MHz 36000000 */ /* #define SYSCLK_FREQ_48MHz 48000000 */ /* #define SYSCLK_FREQ_56MHz 56000000 */ #define SYSCLK_FREQ_72MHz 72000000 #endif

SysTick_CLKSourceConfig(…)

下面看看滴答定時器時鐘源配置的庫函數源碼,可以看出它的時鐘源只能為SysTick_CLKSource_HCLK_Div8或SysTick_CLKSource_HCLK,那么問題來了,什么是HCLK:

HCLK :AHB總線時鐘,由系統時鐘SYSCLK 分頻得到,一般不分頻,等于系統時鐘

剛剛提到系統時鐘為72M,所以SysTick_CLKSource_HCLK_Div8 就是72/8=9M。

/*** @brief Configures the SysTick clock source.* @param SysTick_CLKSource: specifies the SysTick clock source.* This parameter can be one of the following values:* @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.* @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.* @retval None*/ void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource) {/* Check the parameters */assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));if (SysTick_CLKSource == SysTick_CLKSource_HCLK){SysTick->CTRL |= SysTick_CLKSource_HCLK;}else{SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;} }

配置完了滴答定時器的時鐘,delay_init函數內還有兩行:

fac_us=SystemCoreClock/8000000; //為系統時鐘的1/8 fac_ms=(u16)fac_us*1000; //非OS下,代表每個ms需要的systick時鐘數

fac_us表示微秒的計時因子,即滴答計時器重載值為1*fac_us時,計時時間為1us(可以看后面的delay_us函數),fac_ms為fac_us的1000倍,自然就是1ms了。

  • 那么問題來了,為什么fac_us代表1us呢?

之前我們提到
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //選擇外部時鐘 HCLK/8
即滴答定時器定時器頻率為9M(72/8),9M意味著定時器1秒計數9000000,那么1毫秒計數就為9000,1微秒為9。這代表什么?計9次數為1us,這個9就是1微秒的計數因子(fac_us),即fac_us(72000000/8000000=9)代表1us。n微秒則為n * fac_us。

LED_Init() 函數

終于到了本實驗的主角——LED(GPIO)

//初始化PB5和PE5為輸出口.并使能這兩個口的時鐘 //LED IO初始化 void LED_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口時鐘GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度為50MHzGPIO_Init(GPIOB, &GPIO_InitStructure); //根據設定參數初始化GPIOB.5GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 輸出高GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽輸出GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽輸出 ,IO口速度為50MHzGPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 輸出高 }

概括一下配置GPIO的步驟:

  • 定義一個GPIO_InitTypeDef 成員
  • 使能GPIO對應的端口時鐘RCC_APB2PeriphClockCmd(...)
  • 配置引腳GPIO_Pin
  • 配置模式(輸入、輸出、推挽、開漏、浮空)GPIO_Mode
  • 配置IO速度(我練習時一般不太在意這一項)GPIO_Speed
  • 初始化GPIO_InitTypeDef 成員GPIO_Init(..)
  • 設置引腳高低狀態GPIO_SetBits(..)或GPIO_ResetBits(...)
  • RCC_APB2PeriphClockCmd(…)

    庫函數注釋中標明了時鐘總線上的外設,GPIOB和GPIOE都在APB2總線上

    /*** @brief Enables or disables the High Speed APB (APB2) peripheral clock.* @param RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.* This parameter can be any combination of the following values:* @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,* RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,* RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,* RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,* RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,* RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,* RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11 * @param NewState: new state of the specified peripheral clock.* This parameter can be: ENABLE or DISABLE.* @retval None*/ void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) {/* Check the parameters */assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));assert_param(IS_FUNCTIONAL_STATE(NewState));if (NewState != DISABLE){RCC->APB2ENR |= RCC_APB2Periph;}else{RCC->APB2ENR &= ~RCC_APB2Periph;} }

    如果想快速查到某外設的時鐘總線,可以參考《STM32中文參考手冊》存儲器和總線架構章節:

    GPIO_InitTypeDef

    下面是GPIO_InitTypeDef結構體定義

    /** * @brief GPIO Init structure definition */typedef struct {uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.This parameter can be any value of @ref GPIO_pins_define */GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.This parameter can be a value of @ref GPIOSpeed_TypeDef */GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.This parameter can be a value of @ref GPIOMode_TypeDef */ }GPIO_InitTypeDef;

    LED_init中,LED0的GPIO_Pin為GPIOB5,LED1為GPIOE5;
    模式都選擇了推挽輸出

    推挽輸出的最大特點是可以真正能真正的輸出高電平和低電平,在兩種電平下都具有驅動能力。

    由LED的原理圖可以知道它們為共陽極,所以默認要將IO拉高。


    其他細節感興趣的可以自己去研究😗。

    delay_ms(…)函數

    fac_ms剛剛在延時函數初始化中已經介紹,滴答定時器SysTick每計時fac_ms次,則表示1ms,所以nms*fac_ms表示計時nms毫秒。SysTick->LOAD為定時器的重載值,SysTick->VAL表示計數值,還要注意:滴答定時器是倒數計數的。SysTick->CTRL為控制寄存器,第16位可以用來檢測是否倒數到0。

    void delay_ms(u16 nms) { u32 temp; SysTick->LOAD=(u32)nms*fac_ms; //時間加載(SysTick->LOAD為24bit)SysTick->VAL =0x00; //清空計數器SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //開始倒數 do{temp=SysTick->CTRL;}while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達 SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //關閉計數器SysTick->VAL =0X00; //清空計數器 }

    位操作

    對于操作寄存器,經常要用到位操作,如SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk中,SysTick_CTRL_ENABLE_Msk表示1,SysTick->CTRL|=1的作用是將CTRL寄存器的最低位置1,而不影響其他高19位(0或任何二進制數,都會是它自己);
    而SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;的作用是將CTRL最低位置0,0x00000001按位取反后為0xfffffffe,該數與任何32位數按位與(&),都不會影響高31位,因為1和任何二進制數進行與運算都等于它自己。


    本來想寫位帶操作的,但看了看時間,就放棄了


    GPIO引腳控制函數就不提了,之前在LED_Init()函數里已經見過。
    實驗效果——紅綠燈交替閃爍。

    實驗2 按鍵輸入

    main()

    主函數中LED0、LED1和BEEP代表的是GPIO的位段(本文忽略這個概念),把它當做51里對GPIO的位操作就行了。
    與上一個實驗相比,本實驗多了按鍵模塊和蜂鳴器模塊。

    int main(void){vu8 key=0; delay_init(); //延時函數初始化 LED_Init(); //LED端口初始化KEY_Init(); //初始化與按鍵連接的硬件接口BEEP_Init(); //初始化蜂鳴器端口LED0=0; //先點亮紅燈while(1){key=KEY_Scan(0); //得到鍵值if(key){ switch(key){ case WKUP_PRES: //控制LED1翻轉 LED1=!LED1;BEEP = !BEEP;break;case KEY0_PRES: //同時控制LED0翻轉 LED0=!LED0;BEEP = !BEEP;break;}}else delay_ms(10); } }

    KEY_Init()函數

    與LED_Init()類似,配置步驟相同(配置步驟見LED_Init()介紹部分)。

    void KEY_Init(void) //IO初始化 { GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE時鐘GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//KEY0GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //設置成下拉輸入GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4//初始化 WK_UP-->GPIOA.0 下拉輸入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0設置成輸入,默認下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0 }

    開發板上有兩個按鍵,KEY_UP和KEY0,都是一端接高電平,一端接IO,所以模式設置為下拉輸入,KEY_UP對應的GPIO引腳為GPIOA0,KEY0對應的引腳為GPIOE4。IO時鐘都掛載在APB2上。


    BEEP_Init()函數

    開發板上并沒有蜂鳴器,我選擇了外接一個蜂鳴器,同樣接在PB8引腳上。初始化配置步驟和LED與KEY相同,模式為推挽輸出,由于我的蜂鳴器低電平有效,所以初始化中還需把IO電平拉高。

    //初始化PB8為輸出口.并使能這個口的時鐘 //蜂鳴器初始化 void BEEP_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB端口時鐘GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //BEEP-->PB.8 端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度為50MHzGPIO_Init(GPIOB, &GPIO_InitStructure); //根據參數初始化GPIOB.8GPIO_SetBits(GPIOB,GPIO_Pin_8);//輸出0,關閉蜂鳴器輸出}

    KEY_Scan(…)函數

    該函數中,mode表示模式,為0表示短按,為1表示長按。局部靜態變量key_up默認為1,表示按鍵處于空閑狀態(松開)。
    如果選擇短按,在按鍵處于空閑狀態時,檢測到KEY0和WK_UP中任意一個按鍵被按下,則將key_up置0,在此期間不處理其他按鍵判斷,函數返回值為按鍵值或0(無按鍵);當按鍵松開,程序再次運行到按鍵掃描函數中時,key_up置為1,按鍵再次回到空閑狀態。
    如果選擇長按,則key_up恒為1,無論是否有按鍵正處于按下狀態,每次進入KEY_Scan函數都進行按鍵判斷,這樣就實現了按鍵的長按檢測。

    u8 KEY_Scan(u8 mode) { static u8 key_up=1;//按鍵按松開標志if(mode)key_up=1; //支持連按 if(key_up&&(KEY0==1||WK_UP==1)){delay_ms(10);//去抖動 key_up=0;if(KEY0==1)return KEY0_PRES;else if(WK_UP==1)return WKUP_PRES;}else if(KEY0==0&&WK_UP==0)key_up=1; return 0;// 無按鍵按下 }

    實驗3 串口實驗

    mian()

    與前兩個實驗相比,串口實驗增加了NVIC中斷配置、串口初始化配置。main函數實現的功能為:單片機不停地向串口發送提示性數據,如果有外部設備通過串口向單片機發送數據(以“\r\n“作為結束符),單片機接收數據并返回給外部設備。

    int main(void){ u16 t; u16 len; u16 times=0;delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //LED端口初始化KEY_Init(); //初始化與按鍵連接的硬件接口while(1){if(USART_RX_STA&0x8000){ len=USART_RX_STA&0x3fff;//得到此次接收到的數據長度printf("\r\n您發送的消息為:\r\n\r\n");for(t=0;t<len;t++){USART_SendData(USART1, USART_RX_BUF[t]);//向串口1發送數據while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發送結束}printf("\r\n\r\n");//插入換行USART_RX_STA=0;}else{times++;if(times%5000==0){printf("\r\n戰艦STM32開發板 串口實驗\r\n");printf("正點原子@ALIENTEK\r\n\r\n");}if(times%200==0)printf("請輸入數據,以回車鍵結束\n"); if(times%30==0)LED0=!LED0;//閃爍LED,提示系統正在運行.delay_ms(10); }} }

    NVIC_PriorityGroupConfig

    NVIC:嵌套向量中斷控制器,NVIC_PriorityGroupConfig函數是中斷優先級的分組配置函數。

    /*** @brief Configures the priority grouping: pre-emption priority and subpriority.* @param NVIC_PriorityGroup: specifies the priority grouping bits length. * This parameter can be one of the following values:* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority* 4 bits for subpriority* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority* 3 bits for subpriority* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority* 2 bits for subpriority* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority* 1 bits for subpriority* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority* 0 bits for subpriority* @retval None*/ void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) {/* Check the parameters */assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; }

    中斷優先級分為搶占式優先級和響應優先級,搶占優先級越高的先處理,當兩個中斷向量的搶占優先級相同時,如果兩個中斷同時到達, 則先處理響應優先級高的中斷。

    如果對兩種優先級的位數分配進行分組,可以分為5組(0~4),分組配置是在寄存器SCB->AIRCR中配置:

    main函數中,分組為:

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級

    注:這個分組只是設置STM32中斷的兩種優先級可選范圍,比如0組中,沒有搶占優先級,一般情況(學習過程中)該配置設置為2組就行了。另外,這個分組是全局的,所以一個程序中只需要配置一次,多次配置可能會導致未知錯誤。

    uart_init(…)函數

    串口初始化函數里不僅有GPIO初始化,還有UART初始化和NVIC初始化。

    void uart_init(u32 bound){//GPIO端口設置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA時鐘//USART1_TX GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 //Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶占優先級3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器//USART 初始化設置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式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_Rx | USART_Mode_Tx; //收發模式USART_Init(USART1, &USART_InitStructure); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟串口接受中斷USART_Cmd(USART1, ENABLE); //使能串口1 }

    從原理圖可知串口的發送IO為GPIOA9,接收IO為GPIOA10,TX(PA9)設置為復用推挽輸出(PA9為復用引腳,可以通過設置復用推挽輸出完成USART_TX功能的配置,另外還可以通過配合復用寄存器方式實現復用,如PWM實驗),RX設置為浮空輸入。

    串口配置一般可以分為以下幾步:

  • 定義USART_InitTypeDef結構體成員
  • 使能USART外設時鐘RCC_APB2PeriphClockCmd(...),根據手冊查看對應時鐘總線(LED_init中有介紹)
  • 設置波特率USART_BaudRate,可以通過外部傳參設置
  • 設置字長USART_WordLength,一般為8字節
  • 設置停止位USART_StopBits,一般為1個停止位
  • 設置校驗方式USART_Parity
  • 設置流控制USART_HardwareFlowControl,一般無控制
  • 設置收發模式USART_Mode,設置為收發共用
  • 初始化USART_InitTypeDef成員USART_Init(...)
  • 如果要使用串口中斷,還需要配置串口中斷USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE)
  • 使能串口USART_Cmd(USARTx, ENABLE)
  • NVIC中斷配置一般步驟:

  • 定義一個NVIC_InitTypeDef結構體成員
  • 設置中斷通道NVIC_IRQChannel
  • 設置搶占優先級NVIC_IRQChannelPreemptionPriority,大小范圍由分組決定
  • 設置響應優先級NVIC_IRQChannelSubPriority
  • 使能中斷NVIC_IRQChannelCmd,ENABLE為使能
  • 初始化NVIC_InitTypeDef成員,NVIC_Init(...)
  • 串口處理全局變量

    //串口1中斷服務程序 //注意,讀取USARTx->SR能避免莫名其妙的錯誤 u8 USART_RX_BUF[USART_REC_LEN]; //接收緩沖,最大USART_REC_LEN個字節. //接收狀態 //bit15, 接收完成標志 //bit14, 接收到0x0d //bit13~0, 接收到的有效字節數目 u16 USART_RX_STA=0; //接收狀態標記

    USART_RX_BUF為程序定義的全局接收緩沖, USART_REC_LEN為緩沖最大字節數。
    USART_RX_STA為程序定義的全局狀態值,表述串口1的接收狀態,16位,0-13位為接收到的字節數,14位為1表示接收到0x0d(’\r’),15位數據為1表示接收完成。

    重寫printf(fputc)

    USART_SendData(...)是串口發送字節數據的庫函數,如果我們想發送字符串,用這個函數就不太方便,如果我們重寫fputc函數,就能直接用printf來打印字符串到串口,方法如下。
    USART_GetFlagStatus函數的作用是獲取串口狀態,USART_FLAG_TC參數表示接收完成狀態。
    【注意】使用該方式重寫fputc,一定要打開Keil的Use MicroLIB,否則程序可能無法運行。

    /*使用microLib的方法*/int fputc(int ch, FILE *f) {USART_SendData(USART1, (uint8_t) ch);while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} return ch; }

    USART1_IRQHandler串口中斷函數

    STM32的中斷服務函數名稱是固定,是通過函數名與底層綁定。

    void USART1_IRQHandler(void) //串口1中斷服務程序{u8 Res;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾){Res =USART_ReceiveData(USART1); //讀取接收到的數據if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始else USART_RX_STA|=0x8000; //接收完成了 }else //還沒收到0X0D{ if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數據錯誤,重新開始接收 } }} } }

    當串口接收到數據,中斷服務函數自動執行,先使用USART_GetITStatus(...)判斷USART1的中斷接受狀態USART_IT_RXNE,如果接收完成,使用USART_ReceiveData(...)接收串口數據,串口數據接收以1個字節為單位,當接收到回車時(’\r‘和\‘n’),完成接收,此時主函數開始處理接收到的數據。

    實驗結果


    這。。。。。。😵,才完成3個實驗啊。


    實驗4 外部中斷實驗

    STM32的中斷控制器支持19個外部中斷/事件請求:
    線0~15:對應外部IO口的輸入中斷。
    線16:連接到PVD輸出。
    線17:連接到RTC鬧鐘事件。
    線18:連接到USB喚醒事件。
    下圖為IO口對應的中斷線:

    main()

    該實驗僅僅比實驗3多了一個EXTIX_Init(),主函數沒有什么代碼,這就說明功能全搬到了外部中斷服務函數中。

    int main(void){ delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //初始化與LED連接的硬件接口BEEP_Init(); //初始化蜂鳴器端口KEY_Init(); //初始化與按鍵連接的硬件接口EXTIX_Init(); //外部中斷初始化LED0=0; //點亮LED0while(1){ printf("OK\r\n"); delay_ms(1000); }}

    EXTIX_Init()函數

    初始化函數中,配置了兩個外部中斷(EXTI0和EXIT4),對應開發板上的兩個按鍵。

    //外部中斷0服務程序 void EXTIX_Init(void) {EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;KEY_Init(); // 按鍵端口初始化RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能復用功能時鐘//GPIOE.4 中斷線以及中斷初始化配置 上升沿觸發 //KEY0GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);EXTI_InitStructure.EXTI_Line=EXTI_Line4; //KEY0EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure); //根據EXTI_InitStruct中指定的參數初始化外設EXTI寄存器//GPIOA.0 中斷線以及中斷初始化配置 上升沿觸發 PA0 WK_UPGPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); EXTI_InitStructure.EXTI_Line=EXTI_Line0;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_Init(&EXTI_InitStructure); //根據EXTI_InitStruct中指定的參數初始化外設EXTI寄存器NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按鍵WK_UP所在的外部中斷通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //搶占優先級2, NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子優先級3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按鍵KEY0所在的外部中斷通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //搶占優先級2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子優先級0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器}

    外部中斷初始化配置一般步驟:

  • 定義一個EXTI_InitTypeDef結構體成員
  • 外部中斷(復用IO,AFIO)時鐘使能,RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  • IO中斷線配置,GPIO_EXTILineConfig(...)
  • 設置中斷線EXTI_Line
  • 設置中斷模式EXTI_Mode
  • 設置觸發方式EXTI_Trigger
  • 使能中斷線EXTI_LineCmd
  • 初始化EXTI_InitTypeDef成員,
  • EXTIx_IRQHandler外部中斷函數

    當按鍵按下,IO檢測到上升沿,觸發中斷,執行對應中斷服務函數,函數中完成了LED1和LED0的亮滅控制,每次進入中斷后,必須手動清除中斷標志EXTI_ClearITPendingBit(...),不然中斷函數會一直執行。

    //外部中斷0服務程序 void EXTI0_IRQHandler(void) {delay_ms(10);//消抖if(WK_UP==1) //WK_UP按鍵{ LED1=!LED1; }EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中斷標志位 }//外部中斷4服務程序 void EXTI4_IRQHandler(void) {delay_ms(10);//消抖if(KEY0==1) //按鍵KEY0{LED0=!LED0;} EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中斷標志位 }

    實驗5 獨立看門狗實驗

    STM32看門狗
    在由單片機構成的微型計算機系統中,由于單片機的工作常常會受到來自外界電磁場的干擾,造成程序的跑飛,而陷入死循環,程序的正常運行被打斷,由單片機控制的系統無法繼續工作,會造成整個系統的陷入停滯狀態,發生不可預料的后果,所以出于對單片機運行狀態進行實時監測的考慮,便產生了一種專門用于監測單片機程序運行狀態的模塊或者芯片,俗稱“看門狗”(watchdog) 。
    STM32內置兩個看門狗,提供了更高的安全性,時間的精確性和使用的靈活性。兩個看門狗設備(獨立看門狗/窗口看門狗)可以用來檢測和解決由軟件錯誤引起的故障。當計數器達到給定的超時值時,觸發一個中斷(僅適用窗口看門狗)或者產生系統復位。
    獨立看門狗(IWDG)由專用的低速時鐘(LSI)驅動,即使主時鐘發生故障它仍有效。
    獨立看門狗適合應用于需要看門狗作為一個在主程序之外 能夠完全獨立工作,并且對時間精度要求低的場合。
    窗口看門狗由從APB1時鐘分頻后得到時鐘驅動。通過可配置的時間窗口來檢測應用程序非正常的過遲或過早操作。
    窗口看門狗最適合那些要求看門狗在精確計時窗口起作用的程序。
    ——正點原子課程PPT

    IWDG(Independent watchdog)獨立看門狗,可以用來檢測并解決由于軟件錯誤導致的故障,當計數器到達給定的超時值時,會觸發一個中斷或產生系統復位。

    main()

    新增兩個函數:IWDG_Init(...)和IWDG_Feed(),本實驗通過按鍵WKUP來喂狗,如果超過1s沒有喂狗,單片機就復位(LED一直亮說明沒復位)。

    int main(void){ delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //初始化與LED連接的硬件接口KEY_Init(); //按鍵初始化 delay_ms(500); //讓人看得到滅IWDG_Init(4,625); //與分頻數為64,重載值為625,溢出時間為1s LED0=0; //點亮LED0while(1){if(KEY_Scan(0)==WKUP_PRES){IWDG_Feed();//如果WK_UP按下,則喂狗}delay_ms(10);}; }

    IWDG_Init(…)函數

    初始化獨立看門狗,需要先用IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);使能相關寄存器的寫權限,然后用IWDG_SetPrescaler(prer)和IWDG_SetReload(rlr)設置預分頻值prer和重載值rlr,接著用IWDG_ReloadCounter()重裝載計數器(喂狗),最后使能看門狗——IWDG_Enable()

    //初始化獨立看門狗 //prer:分頻數:0~7(只有低3位有效!) //分頻因子=4*2^prer.但最大值只能是256! //rlr:重裝載寄存器值:低11位有效. //時間計算(大概):Tout=((4*2^prer)*rlr)/40 (ms). void IWDG_Init(u8 prer,u16 rlr) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //使能對寄存器IWDG_PR和IWDG_RLR的寫操作IWDG_SetPrescaler(prer); //設置IWDG預分頻值:設置IWDG預分頻值為64IWDG_SetReload(rlr); //設置IWDG重裝載值IWDG_ReloadCounter(); //按照IWDG重裝載寄存器的值重裝載IWDG計數器IWDG_Enable(); //使能IWDG }

    從庫函數里的宏定義我們可以了解到看門狗在喂狗和使能時向IWDG_KR發送的數據。

    /* KR register bit mask */ #define KR_KEY_Reload ((uint16_t)0xAAAA) #define KR_KEY_Enable ((uint16_t)0xCCCC)

    IWDG_Feed()函數

    重載計數器即為喂狗。

    //喂獨立看門狗 void IWDG_Feed(void) { IWDG_ReloadCounter();//reload }

    實驗6 窗口看門狗實驗[待學習]

    窗口看門狗(Window watchdog)通常被用來監測由外部干擾或不可預見的邏輯條件造成的應用程序背離正常的運行序列而產生的軟件故障。
    之所以稱為窗口就是因為其喂狗時間是一個有上下限的范圍(窗口),你可以通過設定相關寄存器,設定其上限時間(下限固定)。喂狗的時間不能過早也不能過晚。

    為什么要窗口看門狗?
    對于一般的看門狗,程序可以在它產生復位前的任意時刻刷新看門狗,但這有一個隱患,有可能程序跑亂了又跑回到正常的地方,或跑亂的程序正好執行了刷新看門狗操作,這樣的情況下一般的看門狗就檢測不出來了;
    如果使用窗口看門狗,程序員可以根據程序正常執行的時間設置刷新看門狗的一個時間窗口,保證不會提前刷新看門狗也不會滯后刷新看門狗,這樣可以檢測出程序沒有按照正常的路徑運行非正常地跳過了某些程序段的情況。
    ——正點原子課程PPT

    窗口看門狗以前沒認真學,現在一點概念都沒有了,先記錄下,以后再完善吧。。。

    main()

    int main(void){ delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設置中斷優先級分組為組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init();KEY_Init(); //按鍵初始化 LED0=0;delay_ms(300); WWDG_Init(0X7F,0X5F,WWDG_Prescaler_8);//計數器值為7f,窗口寄存器為5f,分頻數為8 while(1){LED0=1; } }

    WWDG_Init(…)函數

    //保存WWDG計數器的設置值,默認為最大. u8 WWDG_CNT=0x7f; //初始化窗口看門狗 //tr :T[6:0],計數器值 //wr :W[6:0],窗口值 //fprer:分頻系數(WDGTB),僅最低2位有效 //Fwwdg=PCLK1/(4096*2^fprer). void WWDG_Init(u8 tr,u8 wr,u32 fprer) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG時鐘使能WWDG_CNT=tr&WWDG_CNT; //初始化WWDG_CNT. WWDG_SetPrescaler(fprer);設置IWDG預分頻值WWDG_SetWindowValue(wr);//設置窗口值WWDG_Enable(WWDG_CNT); //使能看門狗 , 設置 counter . WWDG_ClearFlag();//清除提前喚醒中斷標志位 WWDG_NVIC_Init();//初始化窗口看門狗 NVICWWDG_EnableIT(); //開啟窗口看門狗中斷 } //窗口看門狗中斷服務程序 void WWDG_NVIC_Init() {NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; //WWDG中斷NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //搶占2,子優先級3,組2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //搶占2,子優先級3,組2 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure);//NVIC初始化 }void WWDG_IRQHandler(void) {WWDG_SetCounter(WWDG_CNT); //當禁掉此句后,窗口看門狗將產生復位WWDG_ClearFlag(); //清除提前喚醒中斷標志位LED1=!LED1; //LED狀態翻轉 }

    實驗7 定時器中斷實驗

    main()

    主函數僅僅多了定時器初始化函數TIM3_Int_Init(...)

    int main(void){ delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //LED端口初始化TIM3_Int_Init(4999,7199);//10Khz的計數頻率,計數到5000為500ms while(1){LED0=!LED0;delay_ms(200); } }

    TIM3_Int_Init(…)函數


    定時器3掛載在APB1時鐘總線上。

    //通用定時器3中斷初始化 //這里時鐘選擇為APB1的2倍,而APB1為36M //arr:自動重裝值。 //psc:時鐘預分頻數 //這里使用的是定時器3! void TIM3_Int_Init(u16 arr,u16 psc) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //時鐘使能//定時器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據指定的參數初始化TIMx的時間基數單位TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中斷,允許更新中斷//中斷優先級NVIC設置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中斷NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占優先級0級NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //從優先級3級NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器TIM_Cmd(TIM3, ENABLE); //使能TIMx }

    基本定時器初始化配置一般步驟

  • 定義一個TIM_TimeBaseInitTypeDef結構體成員
  • 使能外設時鐘,TIM3:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE)
  • 設置周期TIM_Period
  • 設置預分頻系數TIM_Prescaler
  • 設置時鐘分割TIM_ClockDivision
  • 設置計數方向TIM_CounterMode
  • 初始化TIM_TimeBaseInitTypeDef成員,TIM_TimeBaseInit(...)
  • 如果開啟中斷,則需要使能對應中斷TIM_ITConfig(TIMx,TIM_IT_Update,ENABLE );
  • 使能定時器TIM_Cmd(TIMx, ENABLE)

  • TIM3在APB1時鐘總線上,APB1的時鐘為36MHz,但TIM3的時鐘是APB1的兩倍,即72MHz。

    定時器配置完后,下一步就是了解如何設定計時的參數,main函數中定時器初始化:TIM3_Int_Init(4999,7199),TIM3的時鐘頻率為72MHz,預分頻系數為7200 - 1(定時器計時溢出后的再計一次數才觸發中斷,所以要減1),72000000 / 7200 = 10000(Hz),1 / 10000 = 0.1ms,計數值從0開始,所以4999表示計時5000次,5000 * 0.1ms = 500ms。

    TIMx_IRQHandler()定時器中斷函數

    當定時器中斷更新,單片機執行定時器的中斷服務函數,記得在函數中用TIM_ClearITPendingBit(...)來清除對應的中斷位。

    //定時器3中斷服務程序 void TIM3_IRQHandler(void) //TIM3中斷 {if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //檢查TIM3更新中斷發生與否{TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中斷標志 LED1=!LED1;} }

    實驗8 PWM輸出實驗

    PWM產生原理:在定時器一個計時周期內,計數值低于比較值時,IO輸出低電平(或高電平),當計數值高于比較值,IO輸出相反的電平,這樣就產生了所謂的PWM波,比較值CCR決定PWM的占空比,重載值ARR決定PWM的周期(頻率)。

    main()

    這個實驗一看便知道是呼吸燈了,直接分析PWM配置吧。
    main函數里用到了TIM_SetCompare2(...)函數,它的作用是設置定時器比較值,即PWM占空比。

    int main(void){ u16 led0pwmval=0;u8 dir=1; delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //LED端口初始化TIM3_PWM_Init(899,0); //不分頻。PWM頻率=72000000/900=80Khzwhile(1){delay_ms(10); if(dir)led0pwmval++;else led0pwmval--;if(led0pwmval>300)dir=0;if(led0pwmval==0)dir=1; TIM_SetCompare2(TIM3,led0pwmval); } }

    TIM3_PWM_Init(…)函數

    //TIM3 PWM部分初始化 //PWM輸出初始化 //arr:自動重裝值 //psc:時鐘預分頻數 void TIM3_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定時器3時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外設和AFIO復用功能模塊時鐘GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5 //設置該引腳為復用輸出功能,輸出TIM3 CH2的PWM脈沖波形 GPIOB.5GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO//初始化TIM3TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位//初始化TIM3 Channel2 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //選擇定時器模式:TIM脈沖寬度調制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根據T指定的參數初始化外設TIM3 OC2TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的預裝載寄存器TIM_Cmd(TIM3, ENABLE); //使能TIM3 }


    開發板上PB5對應LED1,如果要將它作為TIM3_CH2來輸出PWM波,必須先配置復用寄存器(選擇部分重映像)。步驟:

  • 使能復用時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE)
  • 使能TIM3部分重映像GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE)
  • 除此以外,PB5也需要重新配置,模式選擇復用推挽輸出。

    定時器PWM配置一般步驟

  • 定義一個TIM_OCInitTypeDef結構體成員
  • 設置比較輸出的模式TIM_OCMode為TIM_OCMode_PWM2
  • 設置輸出狀態TIM_OutputState為TIM_OutputState_Enable
  • 設置輸出極性TIM_OCPolarity
  • 初始化TIM_OCInitTypeDef成員,TIM_OC2Init(...)
  • 使能定時器輸出比較預裝載寄存器,TIM_OC2PreloadConfig(TIMx, TIM_OCPreload_Enable)

  • 此時,我內心已經絕望,原來我這么菜🥱,后面幾個實驗學起來都感覺很生疏了,當初偷的懶,現在得來償還。


    實驗9 輸入捕獲實驗[待學習]

    說實話,我都忘了什么叫輸入捕獲。

    一句話總結工作過程:通過檢測TIMx_CHx上的邊沿信號,在邊沿信號發生跳變(比如上升沿/下降沿)的時候,將當前定時器的值(TIMx_CNT)存放到對應的捕獲/比較寄存器(TIMx_CCRx)里面,完成一次捕獲。
    ——正點原子課程PPT

    main()

    main函數分析了半天,開始以為本實驗是用TIM5捕獲TIM3的輸出 ,后來發現PB5和PA0根本沒有引出IO引腳(PB5為LED1,PA0為KEY_UP),所以本實驗的功能為檢測按鍵按下的時間并打印到串口。這么說輸入捕獲和邏輯分析儀有點類似。

    extern u8 TIM5CH1_CAPTURE_STA; //輸入捕獲狀態 extern u16 TIM5CH1_CAPTURE_VAL; //輸入捕獲值 int main(void){ u32 temp=0; delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //LED端口初始化TIM3_PWM_Init(899,0); //不分頻。PWM頻率=72000/(899+1)=80KhzTIM5_Cap_Init(0XFFFF,72-1); //以1Mhz的頻率計數 while(1){delay_ms(10);TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);if(TIM_GetCapture2(TIM3)==300)TIM_SetCompare2(TIM3,0); if(TIM5CH1_CAPTURE_STA&0X80)//成功捕獲到了一次上升沿{temp=TIM5CH1_CAPTURE_STA&0X3F;temp*=65536;//溢出時間總和temp+=TIM5CH1_CAPTURE_VAL;//得到總的高電平時間printf("HIGH:%d us\r\n",temp);//打印總的高點平時間TIM5CH1_CAPTURE_STA=0;//開啟下一次捕獲}}}

    TIM5_Cap_Init(…)函數

    //定時器5通道1輸入捕獲配置TIM_ICInitTypeDef TIM5_ICInitStructure;void TIM5_Cap_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能TIM5時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA時鐘GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除之前設置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 輸入 GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉//初始化定時器5 TIM5 TIM_TimeBaseStructure.TIM_Period = arr; //設定計數器自動重裝值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //預分頻器 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //設置時鐘分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位//初始化TIM5輸入捕獲參數TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 選擇輸入端 IC1映射到TI1上TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕獲TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置輸入分頻,不分頻 TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置輸入濾波器 不濾波TIM_ICInit(TIM5, &TIM5_ICInitStructure);//中斷分組初始化NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM3中斷NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占優先級2級NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //從優先級0級NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器 TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允許更新中斷 ,允許CC1IE捕獲中斷 TIM_Cmd(TIM5,ENABLE ); //使能定時器5}

    定時器輸入捕獲配置流程一般為

  • 定義一個TIM_ICInitTypeDef結構體成員
  • 設置通道TIM_Channel
  • 設置輸入捕獲電平TIM_ICPolarity
  • 設置輸入捕獲方向TIM_ICSelection
  • 設置輸入捕獲預分頻系數TIM_ICPrescaler
  • 設置輸入捕獲濾波器TIM_ICFilter
  • 初始化TIM_ICInitTypeDef成員,TIM_ICInit(...)
  • 如果開啟捕獲中斷:TIM_ITConfig(TIMx, TIM_TI_CC1, ENABLE)
  • TIM5_IRQHandler() 處理輸入捕獲

    u8 TIM5CH1_CAPTURE_STA=0; //輸入捕獲狀態 u16 TIM5CH1_CAPTURE_VAL; //輸入捕獲值//定時器5中斷服務程序 void TIM5_IRQHandler(void) { if((TIM5CH1_CAPTURE_STA&0X80)==0)//還未成功捕獲 { if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){ if(TIM5CH1_CAPTURE_STA&0X40)//已經捕獲到高電平了{if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高電平太長了{TIM5CH1_CAPTURE_STA|=0X80;//標記成功捕獲了一次TIM5CH1_CAPTURE_VAL=0XFFFF;}else TIM5CH1_CAPTURE_STA++;} }if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕獲1發生捕獲事件{ if(TIM5CH1_CAPTURE_STA&0X40) //捕獲到一個下降沿 { TIM5CH1_CAPTURE_STA|=0X80; //標記成功捕獲到一次高電平脈寬TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 設置為上升沿捕獲}else //還未開始,第一次捕獲上升沿{TIM5CH1_CAPTURE_STA=0; //清空TIM5CH1_CAPTURE_VAL=0;TIM_SetCounter(TIM5,0);TIM5CH1_CAPTURE_STA|=0X40; //標記捕獲到了上升沿TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 設置為下降沿捕獲} } }TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中斷標志位}

    實驗10 TFTLCD顯示實驗[放棄]

    LCD,額🥱,正點原子的LCD代碼兼容了好幾款屏幕,驅動代碼3000+行,這個實驗就算了。

    今天的學習就到此為止吧,路還很長!

    已經是另一天了,從13:56到0:21,差不多10個多小時,任務失敗了,mission failed!


    總結

    以上是生活随笔為你收集整理的STM32学习——半天学完正点原子入门篇例程,STM32:学会了吗?我:学废了✨的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。