嵌入式作業3.1 GPIO點亮小燈

一只心耳發表於2024-04-13

目錄
  • 一、學習CH04示例程式,包括gpio.c和main.c
    • 1. 硬體分析
    • 2. 工程 GPIO-ASM-STM32L431-20231129 分析
    • 3. 工程 GPIO-Output-Component_STM32L431_20200928 分析
    • 4. 工程 GPIO-Output-DirectAddress_STM32L431_20200928 分析
    • 5. 用直接地址程式設計方式,實現紅綠藍三燈輪流閃爍
    • 6. 用呼叫構件方式,實現紅綠藍的八種組合輪流閃爍

前置資料(知識):嵌入式筆記3.1 GPIO(mcu 手冊)

一、學習CH04示例程式,包括gpio.c和main.c

1. 硬體分析

透過檢視微控制器的介面手冊,瞭解需要用到的 GPIO 埠

AHL-STM32L431介面圖(LED三色燈部分):

嵌入式作業3.1 GPIO點亮小燈

從上圖可以知道 GPIO 埠 PB7、PB8、PB9 分別控制紅、綠、藍色燈珠。

因此在編寫程式時,應對對應埠暫存器和時鐘控制進行操作。

透過二極體的方向我們可以知道小燈是低電平亮,高電平暗。

2. 工程 GPIO-ASM-STM32L431-20231129 分析

使用匯編語言編寫,作用是使藍燈亮暗交替

  • 重要宏定義

用到的宏定義:埠號地址偏移、引腳模式(方向)、引腳中斷型別、引腳外設時鐘使能控制基地址(引腳控制暫存器基地址)、GPIO暫存器基地址

//======================================================================
//檔名稱:gpio.inc
//功能概要:STM32L432RC GPIO底層驅動構件(彙編)程式標頭檔案
//版權所有:蘇州大學嵌入式與物聯網研究中心(sumcu.suda.edu.cn)
//更新記錄:2019-09-27 V2.0
//======================================================================

//埠號地址偏移量宏定義
.equ PTA_NUM,(0<<8)
.equ PTB_NUM,(1<<8)
.equ PTC_NUM,(2<<8)
.equ PTD_NUM,(3<<8)
.equ PTE_NUM,(4<<8)
.equ PTH_NUM,(7<<8)

//GPIO引腳方向宏定義
.equ GPIO_IN,(0)
.equ GPIO_OUTPUT,(1)

//GPIO引腳中斷型別宏定義
.equ RISING_EDGE,(1)   //上升沿觸發
.equ FALLING_EDGE,(2)  //下降沿觸發
.equ DOUBLE_EDGE,(3)   //雙邊沿觸發

//引腳控制暫存器基地址宏定義(只給出POR他的引腳控制暫存器PCR0的地址,其他由此計算)
.equ RCC_AHB2ENR_BASE,0x4002104C   //RCC->AHB2ENR暫存器基地址
//GPIO暫存器基地址宏定義(只給出POR他的輸出暫存器PDOR的地址,其他由此計算)
.equ GPIO_BASE,0x48000000          //GPIO暫存器基地址

用到的宏定義:指示燈埠及引腳、燈狀態

//======================================================================
//檔名稱:user.inc
//功能概要:包含工程中用到的標頭檔案
//版權所有:SD-ARM(sumcu.suda.edu.cn)
//版本更新:2019-09-01 V1.0
//======================================================================

.include "gpio.inc"

//指示燈埠及引腳定義
.equ LIGHT_RED,(PTB_NUM|7)    	//紅色RUN燈使用的埠/引腳
.equ LIGHT_GREEN,(PTB_NUM|8)  	//綠色RUN燈使用的埠/引腳
.equ LIGHT_BLUE,(PTB_NUM|9)    	//藍色RUN燈使用的埠/引腳

//燈狀態宏定義(燈亮、燈暗對應的物理電平由硬體接法決定)
.equ LIGHT_ON,0     			//燈亮
.equ LIGHT_OFF,1    			//燈暗

//串列埠宏定義
//使用者串列埠:用三芯TTL-USB串列埠線(接插線):黑線-地,接著是白線(TX),隨後是綠線(RX)
.equ UART_User, 2         //TX引腳:GEC_10;RX引腳:GEC_8(實物板上標識UART0)

//宏定義相關資料
.equ DELAY_NUM,1000000  		//延時數(約1秒),控制小燈閃爍頻率
.equ UART_BAUD,115200           //串列埠波特率

//myprintf重定義
.equ printf, myprintf

//定義原中斷處理程式名【20200317】
.type  USART2_IRQHandler, function
.global USART2_IRQHandler

//定義新中斷處理程式名【20200317】
.type  UART_User_Handler, function
.global UART_User_Handler

//建立新舊中斷處理程式名的對映【20200317】
.equ USART2_IRQHandler,UART_User_Handler
  • gpio_port_pin_resolution 函式解讀

作用:將引數 “埠號<<8|引腳號” 分離為 “埠號” 和 “引腳號”。

原始暫存器狀態:

R0
埠號<<8|引腳號

函式結束後暫存器狀態:

R0 R1
埠號 引腳號
//======================================================================
//檔名稱:gpio.s
//功能概要:STM32L432RC GPIO底層驅動構件(彙編)程式檔案
//版權所有:蘇州大學嵌入式與物聯網研究中心(sumcu.suda.edu.cn)
//更新記錄:2019-09-27 V2.0
//======================================================================
...
//======================================================================
//函式名稱:gpio_port_pin_resolution
//函式返回:無
//引數說明:r0:埠號|引腳號,例:(PTB_NUM|(5u))表示B口5腳,標頭檔案中有宏定義
//功能概要:將傳進引數r0進行解析,得出具體埠號與引腳號(如:PORTB|(5)
//          解析為PORTB與5,並將其分別賦值給r0與r1)。
//======================================================================
gpio_port_pin_resolution:
//(1)儲存現場
      push {lr}             //lr進棧(lr中為進入中斷前pc的值)
//(2)解析入口引數r0:埠號|引腳號,得到具體埠號和引腳號,並將其分別賦值給r0與r1
      mov r4,r0             //r4=r0=埠號|引腳號
      mov r5,r0             //r5=r0=埠號|引腳號
      lsr r4,#8             //邏輯右移獲得埠號,r4=埠號
      mov r0,r4             //r0=r4=埠號
      mov r6,#0x000000ff
      and r5,r6             //r5=引腳號
      mov r1,r5             //r1=r5=引腳號
//(3)恢復現場
      pop {pc}              //恢復現場,lr出棧到pc(即子程式返回)
  • gpio_init 函式分析

作用:初始化指定埠引腳作為GPIO引腳功能,並定義為輸入或輸出。若是輸出,還指定初始狀態是低電平或高電平。

步驟:

  1. 儲存現場
  2. 得到需要操作的埠號和引腳號
  3. 啟用對應埠外設時鐘控制
  4. 設定引腳模式
  5. 設定埠引腳初始狀態(電平)

引數暫存器狀態:

R0 R1 R2
埠號<<8|引腳號 引腳模式(方向) 埠引腳初始狀態
  1. 儲存現場
//======================================================================
//檔名稱:gpio.s
//功能概要:STM32L432RC GPIO底層驅動構件(彙編)程式檔案
//版權所有:蘇州大學嵌入式與物聯網研究中心(sumcu.suda.edu.cn)
//更新記錄:2019-09-27 V2.0
//======================================================================
...
//======================================================================
// 函式名稱:gpio_init
// 函式返回:無
// 引數說明:r0:(埠號|(引腳號)),例:(PTB_NUM|(5u))表示B口5腳,標頭檔案中有宏定義
//           r1:引腳方向(0=輸入,1=輸出,可用引腳方向宏定義)
//           r2:埠引腳初始狀態(0=低電平,1=高電平)
// 功能概要:初始化指定埠引腳作為GPIO引腳功能,並定義為輸入或輸出。若是輸出,
//           還指定初始狀態是低電平或高電平
// 備    注:埠x的每個引腳控制暫存器PORTx_PCRn的地址=PORT_PCR_BASE+x*0x1000+n*4
//           其中:x=0~4,對應A~E;n=0~31
//======================================================================
.type gpio_init function          //宣告gpio_init為函式型別                     
.global gpio_init                 //將gpio_init定義成全域性函式,便於晶片初始化之後呼叫
gpio_init:
//(1)儲存現場
      push {r0-r7,lr}              //儲存現場,pc(lr)入棧
      //將入口引數r1、r2轉存至r2、r3,預留出r1儲存引腳號
      mov r3,r2                    //r3=r2=埠引腳初始狀態
      mov r2,r1                    //r2=r1=引腳方向

暫存器狀態:

R0 R1 R2 R3
埠號<<8|引腳號 引腳模式(方向) 引腳模式(方向) 埠引腳初始狀態
  1. 得到需要操作的埠號和引腳號
//(2)呼叫內部函式,從入口引數r0中解析出埠號引腳號,分別放在r0和r1中
      bl gpio_port_pin_resolution  //呼叫內部解析函式,r0=埠號,r1=引腳號

暫存器狀態:

R0 R1 R2 R3
埠號 引腳號 引腳模式(方向) 埠引腳初始狀態
  1. 啟用對應埠外設時鐘控制
//(3)獲得待操作埠時鐘的RCC->AHB2ENR暫存器的地址
      ldr r7,=RCC_AHB2ENR_BASE     //r7=RCC->AHB2ENR暫存器基地址
      ldr r5,[r7]                  //r5=RCC->AHB2ENR暫存器中的內容
      mov r6,#1                    //r6=1
      lsl r6,r6,r0                 //r6=待操作RCC->AHB2ENR掩碼(為1的位由r1決定)
      orr r5,r6                    //或運算設GPIOxEN=1,GPIOx時鐘使能
      str r5,[r7]                  //將r5中的GPIOxEN值更新到RCC->AHB2ENR暫存器中
  1. 設定引腳模式

操作:

  1. 得到某GPIO埠基地址(埠模式暫存器的基地址)= GPIOA基地址 + (埠號 * 0x400)
  2. 得到對應引腳號在埠模式暫存器中需要設定的位置(掩碼)= 0b11 << (引腳號 * 2)
  3. 使用掩碼取反再和埠模式暫存器中的原始內容相與是對應引腳的模式位清零
  4. 根據初始化的需求對埠模式暫存器對應引腳進行設定
//(4)獲得待操作埠的GPIO暫存器的地址
      mov r7,r0                    //r7=r0=埠號
      ldr r4,=0x400                //r4=各埠基地址差值(0x400)
      mul r7,r7,r4                 //r7=待操作埠與A口的偏移地址
      ldr r4,=GPIO_BASE            //r4=埠GPIOA基地址(即GPIO_BASE)
      add r7,r4                    //r7=待操作埠GPIO暫存器的地址
      mov r4,#2                    //r4=2
      mov r6,r1                    //r6=r1=引腳號
      mul r6,r6,r4                 //r6=2*r1=待操作引腳左移量(GPIO_MODER_MODE_Pos)
      mov r4,#3                    //r4=3=GPIO_MODER_MODE_Msk
      lsl r4,r6                    //r4=待操作GPIO_MODER補碼(為1的位由r1決定)
      mvn r4,r4                    //r4取反
      ldr r5,[r7]                  //r5=GPIO_MODER暫存器中的內容
      and r5,r4                    //與運算設~GPIOx_MODER,GPIOx_MODER清0
//(5)根據入口引數r2設定引腳輸入輸出狀態
      cmp r2,#1                    //判斷入口引數r2的值
      bne gpio_init_2              //若要設定為輸入,轉到gpio_init_2,將GPIOx_MODER相應位為00
      //(5.1)若要設定為輸出,繼續執行,將GPIOx_MODER相應位為01
      mov r4,#1                    //r4=2
      lsl r4,r4,r6                 //r4=待操作GPIO_MODER掩碼(為1的位由r1決定)
      orr r5,r4                    //或運算設GPIOx_MODER=01,引腳被配置為GPIO輸出功能
      str r5,[r7]                  //將r5中的GPIOx_MODER值更新到該暫存器中
      ...
      //(5.2)若要設定為輸入,轉到gpio_init_2,將GPIOx_MODER相應位為00,引腳被配置為GPIO輸入功能
gpio_init_2:
      mov r4,#3                    //r4=2
      lsl r4,r4,r6                 //r4=待操作GPIO_MODER掩碼(為1的位由r1決定)
      bic r5,r5,r4
      //and r5,r4                    //或運算設GPIOx_MODER=01,引腳被配置為GPIO輸出功能
      str r5,[r7]                  //將r5中的GPIOx_MODER值更新到該暫存器中

暫存器狀態:

R0 R1 R2 R3 R7
埠號 引腳號 引腳模式(方向) 埠引腳初始狀態 待操作埠GPIO暫存器的地址
  1. 設定埠引腳初始狀態(電平)

操作:

判斷需要初始化的電平,若為高電平則使用對應埠的置位/復位暫存器的置位部分,將對應引腳使用或運算置為1,便完成了對應引腳的置位(高電平);若為低電平則使用對應埠的復位暫存器,將對應引腳使用或運算置為1,便完成了對應引腳的復位(低電平)

      cmp r3,#1                    //判斷引腳初始狀態
      bne gpio_init_1              //若為低電平,轉到gpio_init_1,將BRR相應位置1
//(5.1.1)若為高電平,繼續執行,將BSRR相應位置1
      mov r5,r7                    //r5=r7=待操作埠GPIO暫存器的地址
      add r5,#0x18                 //r5=待操作埠GPIO->BSRR暫存器的地址
      ldr r4,[r5]                  //r4=待操作埠GPIO->BSRR暫存器中的內容
      mov r6,#1                    //r6=1
      lsl r6,r6,r1                 //r6=待操作GPIO_BSRR掩碼(為1的位由r1決定)
      orr r4,r6                    //或運算設GPIOx_BSRR=1
      str r4,[r5]                  //將r4中的GPIOx_BSRR值更新到該暫存器中
      bl  gpio_init_3              //跳轉到gpio_init_2
      
//(5.1.2)若為低電平,轉到gpio_init_1,將BRR相應位置1
gpio_init_1:
      mov r5,r7                    //r5=r7=待操作埠GPIO暫存器的地址
      add r5,#0x28                 //r5=待操作埠GPIO->BRR暫存器的地址
      ldr r4,[r5]                  //r4=待操作埠GPIO->BRR暫存器中的內容
      mov r6,#1                    //r6=1
      lsl r6,r6,r1                 //r6=待操作GPIO_BRR掩碼(為1的位由r1決定)
      orr r4,r6                    //或運算設GPIOx_BRR=1
      str r4,[r5]                  //將r4中的GPIOx_BRR值更新到該暫存器中
      bl  gpio_init_3
  • gpio_set 函式分析

操作與設定埠引腳初始狀態(電平)類似

也是透過置位/復位暫存器或復位暫存器對對應埠的對應引腳進行設定。

引數暫存器:

R0 R1
埠號<<8|引腳號 需要設定的埠引腳狀態

完成暫存器狀態:

R0 R1 R3 R6
埠號 引腳號 需要設定的埠引腳狀態 待操作埠GPIO暫存器的地址
//======================================================================
//檔名稱:gpio.s
//功能概要:STM32L432RC GPIO底層驅動構件(彙編)程式檔案
//版權所有:蘇州大學嵌入式與物聯網研究中心(sumcu.suda.edu.cn)
//更新記錄:2019-09-27 V2.0
//======================================================================
...
//=====================================================================
//函式名稱:gpio_set
//函式返回:無
// 引數說明:r0:(埠號|(引腳號)),例:(PTB_NUM|(5u))表示B口5腳,標頭檔案中有宏定義
//           r1:希望設定的埠引腳狀態(0=低電平,1=高電平)
//功能概要:當指定引腳被定義為GPIO功能且為輸出時,本函式設定引腳狀態
// 備    注:埠x的每個引腳控制暫存器PORTx_PCRn的地址=PORT_PCR_BASE+x*0x1000+n*4
//           其中:x=0~4,對應A~E;n=0~31
//=====================================================================
.type gpio_set function           //宣告gpio_set為函式型別                     
.global gpio_set               	  //將gpio_set定義成全域性函式,便於晶片初始化之後呼叫
gpio_set:
//(1)儲存現場
      push {r0-r7,lr}              //儲存現場,pc(lr)入棧
      //將入口引數r1轉存至r3,預留出r1儲存引腳號
      mov r3,r1                    //r3=r1=希望設定的埠引腳狀態
//------------------------------------------------------------------------
//(2)從入口引數r0中解析出埠號引腳號,分別放在r0和r1中
      bl gpio_port_pin_resolution  //呼叫內部函式,r0=埠號,r1=引腳號
      mov r5,r0                    //r5=r0=埠號
      ldr r6,=0x400                //r6=各GPIO口的基地址差值(400h)
      mul r6,r6,r5                 //r6=待操作GPIO口與GPIOA的地址偏移
//(3)根據入口引數r3,設定引腳狀態(0=低電平,1=高電平)
      cmp r3,#1                    //判斷引腳初始狀態
      bne gpio_set_1               //若為低電平,轉到gpio_set_1,將BRR相應位置1
//(3.1)若為低電平,繼續執行,將BSRR相應位置1
      ldr r5,=GPIO_BASE+0x18       //r5=第一個埠GPIO->BSRR暫存器的地址
      add r5,r6                    //r5=待操作埠GPIO->BSRR暫存器的地址
      ldr r4,[r5]                  //r4=待操作埠GPIO->BSRR暫存器中的內容
      mov r6,#1                    //r6=1
      lsl r6,r6,r1                 //r6=待操作GPIO_BSRR掩碼(為1的位由r1決定)
      orr r4,r6                    //或運算設GPIOx_BSRR=1
      str r4,[r5]                  //將r4中的GPIOx_BSRR值更新到該暫存器中
      bl  gpio_set_2               //跳轉到gpio_set_2
//(3.2)若為低電平,轉到gpio_set_1,將GPIOx_BRR相應位置1
gpio_set_1:
      ldr r5,=GPIO_BASE+0x28       //r5=第一個埠GPIO->BRR暫存器的地址
      add r5,r6                    //r5=待操作埠GPIO->BRR暫存器的地址
      ldr r4,[r5]                  //r4=待操作埠GPIO->BRR暫存器中的內容
      mov r6,#1                    //r6=1
      lsl r6,r6,r1                 //r6=待操作GPIO_BRR掩碼(為1的位由r1決定)
      orr r4,r6                    //或運算設GPIOx_BRR=1
      str r4,[r5]                  //將r4中的GPIOx_BRR值更新到該暫存器中
gpio_set_2:
//------------------------------------------------------------------------
//(4)恢復現場
      pop {r0-r7,pc}               //恢復現場,lr出棧到pc(即子程式返回)
  • main.s 部分分析

主要步驟:

  1. 呼叫GPIO初始化函式對GPIO埠引腳進行初始化。
  2. 使小燈亮暗交替迴圈,亮暗交替中停留一段時間,並列印提示資訊,更改小燈狀態用gpio_set函式實現。
//--------------------------------------------------------------------
//主函式,一般情況下可以認為程式從此開始執行(實際上有啟動過程,參見書稿)
main:
//(1)======啟動部分(開頭)主迴圈前的初始化工作======================
//(1.1)宣告main函式使用的區域性變數

//(1.2)【不變】關總中斷
    cpsid i   
//(1.3)給主函式使用的區域性變數賦初值
    
//(1.4)給全域性變數賦初值
    
//(1.5)使用者外設模組初始化
//  初始化藍燈, r0、r1、r2是gpio_init的入口引數
    ldr r0,=LIGHT_BLUE       //r0指明埠和引腳(用=,因常量>=256,需用ldr)
    movs r1,#GPIO_OUTPUT     //r1指明引腳方向為輸出
    movs r2,#LIGHT_OFF       //r2指明引腳的初始狀態為亮
    bl  gpio_init            //呼叫gpio初始化函式
//  初始化串列埠UART_User1
    movs r0,#UART_User       //串列埠號
    ldr r1,=UART_BAUD        //波特率
    bl uart_init             //呼叫uart初始化函式
//(1.6)使能模組中斷
    movs r0,#UART_User       //串列埠號
    bl  uart_enable_re_int   //呼叫uart中斷使能函式

//(1.7)【不變】開總中斷
    cpsie  i  
//顯示hello_information定義的字串
    ldr r0,=hello_information     //待顯示字串首地址
    bl  printf                    //呼叫printf顯示字串
    
    //bl .   //在此打樁(.表示當前地址),理解發光二極體為何亮起來了?
    
//(1)======啟動部分(結尾)=======================================

//(2)======主迴圈部分(開頭)=====================================
main_loop:                         //主迴圈標籤(開頭)
//(2.1)主迴圈次數變數mMainLoopCount+1
        ldr r2,=mMainLoopCount     //r2←變數mMainLoopCount的地址
        ldr r1, [r2]               //r1←變數mMainLoopCount的內容
        movs r3,#1                 //r3←1 
        add r1,r3                  //變數+1
        str r1,[r2]                //放回地址中
//(2.2)未達到主迴圈次數設定值,繼續迴圈
        ldr r2,=MainLoopNUM
        cmp r1,r2
        blO  main_loop             //未達到,繼續迴圈
//(2.3)達到主迴圈次數設定值,執行下列語句,進行燈的亮暗處理
//(2.3.1)清除迴圈次數變數
        ldr r2,=mMainLoopCount     //r2←mMainLoopCount的地址
        movs r1,#0
        str r1,[r2]    
//(2.3.2)如燈狀態標誌mFlag為'L',燈的閃爍次數+1並顯示,改變燈狀態及標誌    
        //判斷燈的狀態標誌
        ldr r2,=mFlag           
        ldr r6,[r2]
        cmp r6,#'L'            
        bne main_light_off        //mFlag不等於'L'轉
        //mFlag等於'L'情況
        ldr r3,=mLightCount       //燈的閃爍次數mLightCount+1
        ldr r1,[r3]
        movs r4,#1
        add r1,r4                
        str r1,[r3]
        ldr r0,=light_show3       //顯示“燈的閃爍次數mLightCount=”
        bl  printf                
        ldr r0,=data_format       //顯示燈的閃爍次數值
        ldr r2,=mLightCount
        ldr r1,[r2]
        bl  printf    
        ldr r2,=mFlag             //燈的狀態標誌改為'A'
        movs r7,#'A'
        str r7,[r2]             
        ldr r0,=LIGHT_BLUE        //亮燈
        ldr r1,=LIGHT_ON
        bl  gpio_set          
        ldr r0, =light_show1      //顯示燈亮提示
        bl  printf    
        //mFlag等於'L'情況處理完畢,轉
        b main_exit  
//(2.3.3)如燈狀態標誌mFlag為'A',改變燈狀態及標誌
main_light_off:
        ldr r2,=mFlag             //燈的狀態標誌改為'L'        
        movs r7,#'L'
        str r7,[r2]   
        ldr r0,=LIGHT_BLUE        //暗燈
        ldr r1,=LIGHT_OFF
        bl  gpio_set  
        ldr r0, =light_show2      //顯示燈暗提示
        bl  printf    
main_exit:
    b main_loop                   //繼續迴圈
//(2)======主迴圈部分(結尾)=====================================

3. 工程 GPIO-Output-Component_STM32L431_20200928 分析

本工程使用C語言編寫,與上面彙編工程一樣是使藍燈亮暗交替,其函式的實現也與彙編的實現類似。

主要用到的函式有:main、gpio_get_port_pin、gpio_init、gpio_set

  • 一些重要的宏(gpio.h和user.h)
//===========================================================================
//檔名稱:gpio.h
//功能概要:GPIO底層驅動構件標頭檔案
//版權所有:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20190520-20200221
//晶片型別:STM32
//===========================================================================

#ifndef  GPIO_H       //防止重複定義(_GPIO_H  開頭)
#define  GPIO_H

#include "mcu.h"   //包含公共要素標頭檔案
#include "string.h"

// 埠號地址偏移量宏定義
#define PTA_NUM    (0<<8)
#define PTB_NUM    (1<<8)
#define PTC_NUM    (2<<8)
#define PTD_NUM    (3<<8)
#define PTE_NUM    (4<<8)
#define PTH_NUM    (7<<8)
// GPIO引腳方向宏定義
#define GPIO_INPUT  (0)      //GPIO輸入
#define GPIO_OUTPUT (1)      //GPIO輸出
// GPIO引腳拉高低狀態宏定義
#define PULL_UP    (0x01u)   //拉高
#define PULL_DOWN  (0x02u)   //拉低
// GPIO引腳中斷型別宏定義
#define RISING_EDGE  (1)     //上升沿觸發
#define FALLING_EDGE (2)     //下降沿觸發
#define DOUBLE_EDGE  (3)     //雙邊沿觸發
// GPIO輸出速率宏定義
#define LOW_SPEED       (0x00u)  //低速
#define MSDIUM_SPEED    (0x01u)  //中速
#define HIGH_SPEED      (0x02u)  //高速
#define VERY_HIGH_SPEED (0x03u)  //超高速

//======================================================================
//檔名稱:user.h(user標頭檔案)
//製作單位:SD-Arm(sumcu.suda.edu.cn)
//更新記錄:20181201-20200830
//概要說明:(1)包含用到的標頭檔案,為達到應用程式的可移植性,具體硬體要接
//              晶片哪個引腳,需要在這裡宏定義,目的是實現對具體硬體物件
//              程式設計,而不是對晶片的引腳程式設計
//          (2)中斷處理程式名字在此宏定義,以便isr.c可移植
//======================================================================
#ifndef USER_H   //防止重複定義(USER_H 開頭)
#define USER_H

//(1)【固定】檔案包含
#include "printf.h"
#include "gpio.h"
#include "gec.h"
//(2)【變動】指示燈埠及引腳定義—根據實際使用的引腳改動
//指示燈埠及引腳定義
#define  LIGHT_RED    (PTB_NUM|7)  //紅燈,(GEC_56)
#define  LIGHT_GREEN  (PTB_NUM|8)  //綠燈,(GEC_55)
#define  LIGHT_BLUE   (PTB_NUM|9)  //藍燈,(GEC_54)

//燈狀態宏定義(燈亮、燈暗對應的物理電平由硬體接法決定)
#define  LIGHT_ON       0    //燈亮
#define  LIGHT_OFF      1    //燈暗

//(3)【變動】UART可用模組定義
#define UART_Debug   UART_3   //用於程式更新,無法被使用
#define UART_User    UART_2   //使用者串列埠(黑-GND;白-TX;綠-RX)

//(4)【變動】中斷服務函式宏定義
#define UART_User_Handler   USART2_IRQHandler  //使用者串列埠中斷函式

#endif    //防止重複定義(USER_H 結尾)
  • gpio.c前置陣列分析
//===========================================================================
//檔名稱:gpio.c
//功能概要:GPIO底層驅動構件原始檔
//版權所有:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20181201-20200221
//晶片型別:STM32
//===========================================================================

#include "gpio.h"

//GPIO口基地址放入常數資料組GPIO_ARR[0]~GPIO_ARR[5]中
GPIO_TypeDef * GPIO_ARR[] =
       {(GPIO_TypeDef *)GPIOA_BASE,(GPIO_TypeDef *)GPIOB_BASE,
		(GPIO_TypeDef *)GPIOC_BASE,(GPIO_TypeDef *)GPIOD_BASE,
		(GPIO_TypeDef *)GPIOE_BASE,(GPIO_TypeDef *)GPIOH_BASE};

//====定義擴充套件中斷IRQ號對應表====
IRQn_Type table_irq_exti[7] = {EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn,
		EXTI3_IRQn, EXTI4_IRQn, EXTI9_5_IRQn, EXTI15_10_IRQn};

IRQn_Type table_irq_exti暫時還用不到,先不分析


GPIO_TypeDef:

作用:儲存GPIO埠暫存器(地址)內容的結構體。

其中每個元素都表示具體暫存器中的內容,例如:GPIO_TypeDef * GPIOB_BASE = 0x48000400 那麼 GPIOB_BASE -> BSRR 就會是GPIOB埠的置位/復位暫存器的內容,因為 BSRR 是結構 GPIO_TypeDef 的第 7 個32 位無符號的元素,那麼 &(GPIOB_BASE -> BSRR) 就會等於 GPIOB_BASE + (6 * 0x04)

定義位置:stm32l431xx.h

嵌入式作業3.1 GPIO點亮小燈

GPIOx:

作用:宣告GPIOx埠外設。

定義位置:stm32l431xx.h

嵌入式作業3.1 GPIO點亮小燈

GPIOx_BASE:

作用:GPIOx埠的基地址。

定義位置:stm32l431xx.h

嵌入式作業3.1 GPIO點亮小燈

AHB2PERIPH_BASE:

作用:AHB2匯流排外設基地址。

定義位置:stm32l431xx.h

嵌入式作業3.1 GPIO點亮小燈

PERIPH_BASE:

作用:外設基地址。

定義位置:stm32l431xx.h

嵌入式作業3.1 GPIO點亮小燈

  • gpio_get_port_pin 分析

與彙編類似用於將埠號和引腳號分離

//=====================================================================
//函式名稱:gpio_get_port_pin
//函式返回:無
//引數說明:port_pin:埠號|引腳號(如:(PTB_NUM)|(9) 表示為B口9號腳)
//       port:埠號(傳指帶出引數)
//       pin:引腳號(0~15,實際取值由晶片的物理引腳決定)(傳指帶出引數)
//功能概要:將傳進引數port_pin進行解析,得出具體埠號與引腳號,分別賦值給port與pin,返回。
//       (例:(PTB_NUM)|(9)解析為PORTB與9,並將其分別賦值給port與pin)。
//=====================================================================
void gpio_get_port_pin(uint16_t port_pin,uint8_t* port,uint8_t* pin)
{
	*port = (port_pin>>8);
	*pin = port_pin;
}
  • gpio_init 分析

操作與彙編類似:

  1. 得到對應埠號和引腳號(呼叫gpio_get_port_pin)
  2. 使能相應GPIO埠外設時鐘
  3. 設定對應埠引腳模式
  4. 設定引腳初始狀態(呼叫gpio_set)
//=====================================================================
//函式名稱:gpio_init
//函式返回:無
//引數說明:port_pin:(埠號)|(引腳號)(如:(PTB_NUM)|(9) 表示為B口9號腳)
//         dir:引腳方向(0=輸入,1=輸出,可用引腳方向宏定義)
//         state:埠引腳初始狀態(0=低電平,1=高電平)
//功能概要:初始化指定埠引腳作為GPIO引腳功能,並定義為輸入或輸出,若是輸出,
//         還指定初始狀態是低電平或高電平
//=====================================================================
void gpio_init(uint16_t port_pin, uint8_t dir, uint8_t state)
{
	GPIO_TypeDef *gpio_ptr;    //宣告gpio_ptr為GPIO結構體型別指標
	uint8_t port,pin;    //宣告埠port、引腳pin變數
	uint32_t temp;       //臨時存放暫存器裡的值
	//根據帶入引數port_pin,解析出埠與引腳分別賦給port,pin
	gpio_get_port_pin(port_pin,&port,&pin);
	//根據port,給區域性變數gpio_ptr賦值(GPIO基地址)
	if(7 == port) //GPIOH
		gpio_ptr = GPIO_ARR[port-2];
	else
		gpio_ptr = GPIO_ARR[port];

	//使能相應GPIO時鐘
	RCC->AHB2ENR |= (RCC_AHB2ENR_GPIOAEN<<(port * 1u));

	//清GPIO模式暫存器對應引腳位
	temp = gpio_ptr->MODER;
	temp &= ~(GPIO_MODER_MODE0 << (pin * 2u));

	if(dir == 1)  //定義為輸出引腳
	{
		temp |= (GPIO_OUTPUT << (pin * 2u));
		gpio_ptr->MODER = temp;
		gpio_set(port_pin,state);    //呼叫gpio_set函式,設定引腳初始狀態
	}
	else  //定義為輸入引腳
	{
		temp |= (GPIO_INPUT << (pin * 2u));
		gpio_ptr->MODER = temp;
	}
}

RCC_AHB2ENR_GPIOAEN:

嵌入式作業3.1 GPIO點亮小燈

RCC:

外設宣告(基地址)

嵌入式作業3.1 GPIO點亮小燈

RCC_TypeDef:

RCC暫存器(地址)內容的結構體

嵌入式作業3.1 GPIO點亮小燈
嵌入式作業3.1 GPIO點亮小燈

RCC_BASE:

RCC暫存器基地址

嵌入式作業3.1 GPIO點亮小燈

AHB1PERIPH_BASE:

AHB1匯流排外設基地址

image-20240413132602117

  • gpio_set分析

與彙編程式碼類似的透過對應埠號和引腳號的置位/復位暫存器將引腳狀態設定為高電平,透過對應埠號和引腳號的復位暫存器將引腳狀態設定為低電平。

//=====================================================================
//函式名稱:gpio_set
//函式返回:無
//引數說明:port_pin:(埠號)|(引腳號)(如:(PTB_NUM)|(9) 表示為B口9號腳)
//          state:希望設定的埠引腳狀態(0=低電平,1=高電平)
//功能概要:當指定埠引腳被定義為GPIO功能且為輸出時,本函式設定引腳狀態
//=====================================================================
void gpio_set(uint16_t port_pin, uint8_t state)
{
	//區域性變數宣告
	GPIO_TypeDef *gpio_ptr;    //宣告port_ptr為GPIO結構體型別指標(首地址)
	uint8_t port,pin;        //宣告埠port、引腳pin變數
	//根據帶入引數port_pin,解析出埠與引腳分別賦給port,pin
	gpio_get_port_pin(port_pin,&port,&pin);
	//根據port,給區域性變數gpio_ptr賦值(GPIO基地址)
	if(7 == port) //GPIOH
		gpio_ptr = GPIO_ARR[port-2];
	else
		gpio_ptr = GPIO_ARR[port];

	//根據state,設定對應引腳狀態
	if(1 == state)    //高電平(該引腳對應置位暫存器置1)
		gpio_ptr->BSRR = (uint32_t)(1u<<pin);
	else              //低電平(該引腳對應重置暫存器置1)
		gpio_ptr->BRR = (uint32_t)(1u<<pin);
}
  • main分析

迴圈藍燈亮暗交替(中間有停頓)

//======================================================================
//檔名稱:main.c(應用工程主函式)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:見本工程的..\01_Doc\Readme.txt
//移植規則:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含總標頭檔案

//----------------------------------------------------------------------
//宣告使用到的內部函式
//main.c使用的內部函式宣告處

//----------------------------------------------------------------------
//主函式,一般情況下可以認為程式從此開始執行(實際上有啟動過程,參見書稿)
int main(void)
{
//(1)======啟動部分(開頭)==========================================
//(1.1)宣告main函式使用的區域性變數
	uint32_t mMainLoopCount;  //主迴圈次數變數
	uint8_t  mFlag;           //燈的狀態標誌
	uint32_t mLightCount;     //燈的狀態切換次數

//(1.2)【不變】關總中斷
	DISABLE_INTERRUPTS;

//(1.3)給主函式使用的區域性變數賦初值
    mMainLoopCount=0;    //主迴圈次數變數
	mFlag='A';           //燈的狀態標誌
	mLightCount=0;       //燈的閃爍次數

//(1.4)給全域性變數賦初值
   
//(1.5)使用者外設模組初始化
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);	//初始化藍燈
   
//(1.6)使能模組中斷
   
//(1.7)【不變】開總中斷
	ENABLE_INTERRUPTS;
	
	printf("------------------------------------------------------\n");   
    printf("金葫蘆提示:構件法輸出控制小燈亮暗   \n");
    printf("    第一次用構件方法點亮的藍色發光二極體,\n");
    printf("    這是進行應用程式設計的第一步,可以在此基礎上,\n");
    printf("   “照葫蘆畫瓢”地繼續學習實踐。\n");
    printf("    例如:改為綠燈;調整閃爍頻率等。\n");
    printf("------------------------------------------------------\n"); 
    
    //asm ("bl .");
    
    //for(;;) {  }     //在此打樁,理解藍色發光二極體為何亮起來了?
    
        
//(1)======啟動部分(結尾)==========================================

//(2)======主迴圈部分(開頭)========================================
	for(;;)   //for(;;)(開頭)
	{
//(2.1)主迴圈次數變數+1
        mMainLoopCount++;
//(2.2)未達到主迴圈次數設定值,繼續迴圈
		if (mMainLoopCount<=12888999)  continue;
//(2.3)達到主迴圈次數設定值,執行下列語句,進行燈的亮暗處理
//(2.3.1)清除迴圈次數變數
		mMainLoopCount=0; 
//(2.3.2)如燈狀態標誌mFlag為'L',燈的閃爍次數+1並顯示,改變燈狀態及標誌
		if (mFlag=='L')                    //判斷燈的狀態標誌
		{
			mLightCount++;  
			printf("燈的閃爍次數 mLightCount = %d\n",mLightCount);
			mFlag='A';                       //燈的狀態標誌
			gpio_set(LIGHT_BLUE,LIGHT_ON);  //燈“亮”
			printf(" LIGHT_BLUE:ON--\n");   //串列埠輸出燈的狀態
		}
//(2.3.3)如燈狀態標誌mFlag為'A',改變燈狀態及標誌
		else
		{
			mFlag='L';                       //燈的狀態標誌
			gpio_set(LIGHT_BLUE,LIGHT_OFF); //燈“暗”
			printf(" LIGHT_BLUE:OFF--\n");  //串列埠輸出燈的狀態
		}
	}  //for(;;)結尾
//(2)======主迴圈部分(結尾)========================================
}   //main函式(結尾)

給出 gpio set(LIGHT RED,LIGHT OFF); 語句中LIGHT REDLIGHT OFF的值是多少?貼出每一步的查詢截圖。

值:LIGHT RED——((1<<8)|9) = 0x100000009;LIGHT OFF——1

嵌入式作業3.1 GPIO點亮小燈
嵌入式作業3.1 GPIO點亮小燈
嵌入式作業3.1 GPIO點亮小燈

4. 工程 GPIO-Output-DirectAddress_STM32L431_20200928 分析

如果上面的彙編和使用構件的工程都看懂了,看這個就沒啥意思啦

//====================================================================
//檔名稱:main.c(應用工程主函式)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:2017.08, 2020.05
//功能描述:見本工程的<01_Doc>資料夾下Readme.txt檔案
//====================================================================

#define GLOBLE_VAR
#include "includes.h"      //包含總標頭檔案

//----------------------------------------------------------------------
//宣告使用到的內部函式
//main.c使用的內部函式宣告處

//----------------------------------------------------------------------
//主函式,一般情況下可以認為程式從此開始執行(實際上有啟動過程見書稿)
int main(void)
{
    //(1)======啟動部分(開頭)==========================================
    //(1.1)宣告main函式使用的區域性變數
    uint32_t mMainLoopCount;  //主迴圈使用的記錄主迴圈次數變數
    uint8_t  mFlag;            //主迴圈使用的臨時變數
    
    //(1.2)【不變】關總中斷
    DISABLE_INTERRUPTS;

    
    //(1.3)給主函式使用的區域性變數賦初值
    mMainLoopCount = 0;     //主迴圈使用的記錄主迴圈次數變數
    mFlag='A';              //主迴圈使用的臨時變數:藍燈狀態標誌
    
    //(1.4)給全域性變數賦初值
    
    //(1.5)使用者外設模組初始化
    // B口9腳(藍燈,低電平點亮)
    //(1.5.1)宣告變數
    volatile uint32_t* RCC_AHB2;    //GPIO的B口時鐘使能暫存器地址
    volatile uint32_t* gpio_ptr;    //GPIO的B口基地址
    volatile uint32_t* gpio_mode;   //引腳模式暫存器地址=口基地址
	volatile uint32_t* gpio_bsrr;   //置位/復位暫存器地址
	volatile uint32_t* gpio_brr;    //GPIO位復位暫存器
	//(1.5.2)變數賦值
    RCC_AHB2=(uint32_t*)0x4002104C;   //GPIO的B口時鐘使能暫存器地址
	gpio_ptr=(uint32_t*)0x48000400;   //GPIO的B口基地址
	gpio_mode=gpio_ptr;    //引腳模式暫存器地址=口基地址
    gpio_bsrr=gpio_ptr+6;  //置位/復位暫存器地址
    gpio_brr=gpio_ptr+10;  //GPIO位復位暫存器
    //(1.5.3)GPIO初始化
    //(1.5.3.1)使能相應GPIOB的時鐘
    *RCC_AHB2|=(1<<1);       //GPIOB的B口時鐘使能
    //(1.5.3.1)定義B口9腳為輸出引腳(令D19、D18=01)方法如下:
    *gpio_mode &= ~(3<<18);  //0b11111111111100111111111111111111; 
    *gpio_mode |=(1<<18);    //0b00000000000001000000000000000000;
    //(思考:為什麼這樣賦值?答案見本檔案末尾註①)
    
    //(1.6)使能模組中斷

    //(1.7)【不變】開總中斷
    ENABLE_INTERRUPTS;
    
    printf("-----------------------------------------------------\r\n"); 
    printf("金葫蘆提示:直接地址方式進行GPIO輸出\r\n"); 
    printf("    這個程式設計有點難以看懂,使用構件程式設計就簡單多了,\r\n");
    printf("    但是構件製作要經過這一關,因此,我們把構件製作與\r\n");
    printf("    基於構件的程式設計分成不同過程。學習嵌入式系統,\r\n");
    printf("    以理解GPIO、UART、定時器、Flash、ADC、...\r\n");
    printf("    知識要素為出發點,學會正確運用構件進行應用程式設計,\r\n");
    printf("    理解和掌握2~3個簡單構件的製作方法即可。\r\n");
    printf("----------------------------------------------------\r\n"); 
    
    //for(;;) {  }     //在此打樁,理解藍色發光二極體為何亮起來了?
    
    //(1)======啟動部分(結尾)==========================================
    
    //(2)======主迴圈部分(開頭)=========================================
    for(;;)     //for(;;)(開頭)
    {
        
        //(2.1)主迴圈次數+1,並判斷是否小於特定常數
        mMainLoopCount++;                         //+1
        if (mMainLoopCount<=6556677)  continue;   //如果小於特定常數,繼續迴圈
        //(2.2)主迴圈次數超過特定常數,燈狀態進行切換(這樣燈會閃爍)
        mMainLoopCount=0;      //清主迴圈次數
        //切換燈狀態
        if (mFlag=='A')   //若燈狀態標誌為'A'
        {
		    *gpio_brr|=(1<<9);     //設定燈“亮”
            printf("藍燈:亮\r\n");  //透過除錯串列埠輸出燈的狀態
            mFlag='L';             //改變狀態標誌
        }
        else                   //否則,若燈狀態標誌不為'A'    
        {
		    *gpio_bsrr|=(1<<9);     //設定燈“暗”
            printf("藍燈:暗\r\n");   //透過除錯串列埠輸出燈的狀態
            mFlag='A';              //改變狀態標誌
        }	
    }     //for(;;)結尾
    //(2)======主迴圈部分(結尾)========================================
}

5. 用直接地址程式設計方式,實現紅綠藍三燈輪流閃爍

  • 程式碼:

程式碼的話我覺得很簡單,就是增加了對埠B引腳7、8的控制,具體操作和單純控制引腳9一樣。

//====================================================================
//檔名稱:main.c(應用工程主函式)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:2017.08, 2020.05
//功能描述:見本工程的<01_Doc>資料夾下Readme.txt檔案
//====================================================================

#define GLOBLE_VAR
#include "includes.h"      //包含總標頭檔案

//----------------------------------------------------------------------
//宣告使用到的內部函式
//main.c使用的內部函式宣告處

//----------------------------------------------------------------------
//主函式,一般情況下可以認為程式從此開始執行(實際上有啟動過程見書稿)
int main(void)
{
    //(1)======啟動部分(開頭)==========================================
    //(1.1)宣告main函式使用的區域性變數
    uint32_t mMainLoopCount;  //主迴圈使用的記錄主迴圈次數變數
    uint8_t  mFlag;            //主迴圈使用的臨時變數
    uint8_t  cFlag;				//主迴圈使用的記錄上一次亮燈的顏色
    
    //(1.2)【不變】關總中斷
    DISABLE_INTERRUPTS;

    
    //(1.3)給主函式使用的區域性變數賦初值
    mMainLoopCount = 0;     //主迴圈使用的記錄主迴圈次數變數
    mFlag='A';              //主迴圈使用的臨時變數:燈狀態標誌
    cFlag='B';
    
    //(1.4)給全域性變數賦初值
    
    //(1.5)使用者外設模組初始化
    // B口7、8、9腳(三色全暗,高電平)
    //(1.5.1)宣告變數
    volatile uint32_t* RCC_AHB2;    //GPIO的B口時鐘使能暫存器地址
    volatile uint32_t* gpio_ptr;    //GPIO的B口基地址
    volatile uint32_t* gpio_mode;   //引腳模式暫存器地址=口基地址
	volatile uint32_t* gpio_bsrr;   //置位/復位暫存器地址
	volatile uint32_t* gpio_brr;    //GPIO位復位暫存器
	//(1.5.2)變數賦值
    RCC_AHB2=(uint32_t*)0x4002104C;   //GPIO的B口時鐘使能暫存器地址
	gpio_ptr=(uint32_t*)0x48000400;   //GPIO的B口基地址
	gpio_mode=gpio_ptr;    //引腳模式暫存器地址=口基地址
    gpio_bsrr=gpio_ptr+6;  //置位/復位暫存器地址
    gpio_brr=gpio_ptr+10;  //GPIO位復位暫存器
    //(1.5.3)GPIO初始化
    //(1.5.3.1)使能相應GPIOB的時鐘
    *RCC_AHB2|=(1<<1);       //GPIOB的B口時鐘使能
    //(1.5.3.1)定義B口7、8、9腳為輸出引腳方法如下:
    *gpio_mode &= ~(0b111111<<14);  //0b11111111111100000011111111111111;清零
    *gpio_mode |=(1<<14);    //0b00000000000000000100000000000000;
    *gpio_mode |=(1<<16);    //0b00000000000000010000000000000000;
    *gpio_mode |=(1<<18);    //0b00000000000001000000000000000000;
    // (1.5.4)初始化三色小燈全暗
    *gpio_bsrr &=(7<<7);
    //(1.6)使能模組中斷

    //(1.7)【不變】開總中斷
    ENABLE_INTERRUPTS;
    //(1)======啟動部分(結尾)==========================================
    
    //(2)======主迴圈部分(開頭)=========================================
    for(;;)     //for(;;)(開頭)
    {
        //(2.1)主迴圈次數+1,並判斷是否小於特定常數
        mMainLoopCount++;                         //+1
        if (mMainLoopCount<=6556677)  continue;   //如果小於特定常數,繼續迴圈
        //(2.2)主迴圈次數超過特定常數,燈狀態進行切換(這樣燈會閃爍)
        mMainLoopCount=0;      //清主迴圈次數
        //切換燈狀態
        if (mFlag=='A')   //若燈狀態標誌為'A'
        {
        	if(cFlag=='B')
        	{
        		*gpio_brr|=(1<<7);     //設定燈“紅”
            	printf("小燈狀態:紅燈\r\n");  //透過除錯串列埠輸出燈的狀態
            	cFlag='R';             //改變顏色標誌
            	mFlag='L';             //改變狀態標誌
        	}
        	else if(cFlag=='R')
        	{
        		*gpio_brr|=(1<<8);     //設定燈“綠”
            	printf("小燈狀態:綠燈\r\n");  //透過除錯串列埠輸出燈的狀態
            	cFlag='G';             //改變顏色標誌
            	mFlag='L';             //改變狀態標誌
        	}
        	else if(cFlag=='G')
        	{
        		*gpio_brr|=(1<<9);     //設定燈“藍”
            	printf("小燈狀態:藍燈\r\n");  //透過除錯串列埠輸出燈的狀態
            	cFlag='B';             //改變顏色標誌
            	mFlag='L';             //改變狀態標誌
        	}
        }
        else                   //否則,若燈狀態標誌不為'A'    
        {
        	*gpio_brr&=~(7<<7); 	//復位暫存器7、8、9位清空
		    *gpio_bsrr|=(7<<7);     //設定燈“暗”
            printf("小燈狀態:暗\r\n");   //透過除錯串列埠輸出燈的狀態
            mFlag='A';              //改變狀態標誌
        }	
    }     //for(;;)結尾
    //(2)======主迴圈部分(結尾)========================================
}
  • 執行效果:

板子的執行情況在部落格上不太好放出來,反正效果就是和執行提示資訊中的一樣就是了

嵌入式作業3.1 GPIO點亮小燈

6. 用呼叫構件方式,實現紅綠藍的八種組合輪流閃爍

  • 程式碼:

自認為很簡單,把上面的幾個樣例都看懂了就真的沒啥可說的了。

//======================================================================
//檔名稱:main.c(應用工程主函式)
//框架提供:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20191108-20200419
//功能描述:見本工程的..\01_Doc\Readme.txt
//移植規則:【固定】
//======================================================================
#define GLOBLE_VAR
#include "includes.h"      //包含總標頭檔案

//----------------------------------------------------------------------
//宣告使用到的內部函式
//main.c使用的內部函式宣告處

//----------------------------------------------------------------------
//主函式,一般情況下可以認為程式從此開始執行(實際上有啟動過程,參見書稿)
int main(void)
{
//(1)======啟動部分(開頭)==========================================
//(1.1)宣告main函式使用的區域性變數
	uint32_t mMainLoopCount;	//主迴圈次數變數
	uint8_t  mFlag;          	//燈的狀態標誌
	uint32_t mLightCount;    	//燈的狀態切換次數
	uint8_t  cFlag;				//燈的顏色標誌

//(1.2)【不變】關總中斷
	DISABLE_INTERRUPTS;

//(1.3)給主函式使用的區域性變數賦初值
    mMainLoopCount=0;   //主迴圈次數變數
	mFlag='A';          //燈的狀態標誌
	cFlag=0;           	//燈的顏色標誌
	mLightCount=0;      //燈的閃爍次數

//(1.4)給全域性變數賦初值
   
//(1.5)使用者外設模組初始化
	gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);	//初始化藍燈
	gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);	//初始化藍燈
	gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);	//初始化藍燈
   
//(1.6)使能模組中斷
    
   
//(1.7)【不變】開總中斷
	ENABLE_INTERRUPTS;
   
//(1)======啟動部分(結尾)==========================================

//(2)======主迴圈部分(開頭)========================================
	for(;;)   //for(;;)(開頭)
	{
//(2.1)主迴圈次數變數+1
        mMainLoopCount++;
//(2.2)未達到主迴圈次數設定值,繼續迴圈
		if (mMainLoopCount<=12888999)  continue;
//(2.3)達到主迴圈次數設定值,執行下列語句,進行燈的亮暗處理
//(2.3.1)清除迴圈次數變數
		mMainLoopCount=0; 
//(2.3.2)如燈狀態標誌mFlag為'L',燈的閃爍次數+1並顯示,改變燈狀態及標誌
		if (mFlag=='A')                    //判斷燈的狀態標誌
		{
			mLightCount++;  
			printf("\n燈的閃爍次數 mLightCount = %d\n",mLightCount);
			mFlag='L';                      //燈的狀態標誌
			cFlag=(cFlag % 7) + 1;			//計算當前小燈要顯示的顏色
			if(cFlag == 1)
				printf("小燈狀態:紅色\n");
			else if(cFlag == 2)
				printf("小燈狀態:綠色\n");
			else if(cFlag == 3)
				printf("小燈狀態:黃色\n");
			else if(cFlag == 4)
				printf("小燈狀態:藍色\n");
			else if(cFlag == 5)
				printf("小燈狀態:紫色\n");
			else if(cFlag == 6)
				printf("小燈狀態:青色\n");
			else if(cFlag == 7)
				printf("小燈狀態:白色\n");
			
			if(cFlag == 1 || cFlag == 3 || cFlag == 5 || cFlag == 7)
			{
				gpio_set(LIGHT_RED,LIGHT_ON);  	//紅燈“亮”
				printf(" LIGHT_RED:ON--\n");   	//串列埠輸出燈的狀態
			}
			if(cFlag == 2 || cFlag == 3 || cFlag == 6 || cFlag == 7)
			{
				gpio_set(LIGHT_GREEN,LIGHT_ON);  	//綠燈“亮”
				printf(" LIGHT_GREEN:ON--\n");   	//串列埠輸出燈的狀態
			}
			if(cFlag == 4 || cFlag == 5 || cFlag == 6 || cFlag == 7)
			{
				gpio_set(LIGHT_BLUE,LIGHT_ON);  	//藍燈“亮”
				printf(" LIGHT_BLUE:ON--\n");   	//串列埠輸出燈的狀態
			}
		}
//(2.3.3)如燈狀態標誌mFlag為'A',改變燈狀態及標誌
		else
		{
			mFlag='A';                       	//燈的狀態標誌
			printf("\n小燈狀態:滅\n");
			gpio_set(LIGHT_RED,LIGHT_OFF); 	//紅燈“暗”
			printf(" LIGHT_RED:OFF--\n");  	//串列埠輸出燈的狀態
			gpio_set(LIGHT_GREEN,LIGHT_OFF); 	//綠燈“暗”
			printf(" LIGHT_GREEN:OFF--\n");  	//串列埠輸出燈的狀態
			gpio_set(LIGHT_BLUE,LIGHT_OFF); 	//藍燈“暗”
			printf(" LIGHT_BLUE:OFF--\n");  	//串列埠輸出燈的狀態
		}
	}  //for(;;)結尾
//(2)======主迴圈部分(結尾)========================================
}   //main函式(結尾)
  • 執行效果:

小燈會迴圈亮暗交替,每次小燈亮的顏色都會變換,顏色變換順序是紅、綠、黃、藍、紫、青、白。

嵌入式作業3.1 GPIO點亮小燈
嵌入式作業3.1 GPIO點亮小燈
嵌入式作業3.1 GPIO點亮小燈

相關文章