新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 工程師STM32單片機學習基礎手記(2):從勉強看懂一行程序到IO口研究

        工程師STM32單片機學習基礎手記(2):從勉強看懂一行程序到IO口研究

        作者: 時間:2012-10-22 來源:網絡 收藏

         強看懂

          繼續中,先把開發板自帶一個例子做了些精簡,以免看得嚇人。。。。
          
          就是這個,讓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 HSN bit */
          RCC-》CR |= (uint32_t)0x00000001;
          /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
          #ifndef F10X_CL
          RCC-》CFGR = (uint32_t)0xF8FF0000;
          #else
          RCC-》CFGR = (uint32_t)0xF0FF0000;
          #endif /* F10X_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 F10X_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的時鐘構成了。
          相當的復雜。

        本文引用地址:http://www.104case.com/article/170756.htm

         系統的時鐘可以有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 HSN bit */
          RCC-》CR |= (uint32_t)0x00000001;
          120S52109-1.jpg
          這是RCC_CR寄存器,由圖可見,HSN是其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的好處了,這塊熟啊,幾乎沒有花時間在上面,一用就成了。

        單片機相關文章:單片機教程


        單片機相關文章:單片機視頻教程


        單片機相關文章:單片機工作原理


        分頻器相關文章:分頻器原理
        塵埃粒子計數器相關文章:塵埃粒子計數器原理
        晶振相關文章:晶振原理
        鎖相環相關文章:鎖相環原理

        上一頁 1 2 下一頁

        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 丰台区| 鄱阳县| 井冈山市| 阿尔山市| 西青区| 长岭县| 宣武区| 汉川市| 昌平区| 阳西县| 萨嘎县| 肥西县| 涪陵区| 宁河县| 陇川县| 苏尼特左旗| 大足县| 斗六市| 且末县| 扎兰屯市| 邮箱| 凤山市| 安康市| 紫云| 平安县| 裕民县| 新宁县| 洪湖市| 华安县| 沛县| 奎屯市| 南川市| 民县| 张家口市| 富川| 靖安县| 武乡县| 蒲城县| 九台市| 合山市| 新巴尔虎右旗|