第16集 蜂鳴器實驗 這個實驗和流水燈是一樣的,只是將相對應的IO口拉高拉低即可控制蜂鳴器。 值得注意的是電路設計方面,根據視頻描述,STM32上電是其IO口為浮空狀態, 電平不確定,加入R38 10K 電阻,當上電時有小電流時會直接被R38流入GND, 當電流達到一定時才會進入S8050三極管。
第17集 按鍵實驗 主要是將IO口設置為輸入,然后輪訓獲取該IO口的狀態,根據狀態點亮相應的 LED 燈。 這一集還未看之前就自己先敲代碼,對比視頻上的代碼,自己寫的移植性較好,視頻上的代碼思路比較好, 而且代碼更加精簡一些。總體來說,視頻的代碼甚于我的代碼。 按鍵有兩種掃描方式: 1、長按時,只看做是一次按下 實現思路:如果此次是按下狀態則檢查上一次是否彈起狀態,只有上一次是彈起狀態時才當成按下。 2、長按時,被看作連續按下 實現思路:讀取當前按鍵所處于的狀態直接返回該狀態 總結:這兩種方式唯一不一樣且關鍵的地方在于是否需要判斷上一次是否為彈起狀態,所以要切換模式時 只需要讓這關鍵的地方失效或者生效即可,這種思路可以用在其他地方。 //按鍵處理函數 //返回按鍵值 //mode:0,不支持連續按;1,支持連續按; //0,沒有任何按鍵按下 //1,KEY0按下 //2,KEY1按下 //3,KEY2按下 //4,KEY3按下 WK_UP //注意此函數有響應優先級,KEY0>KEY1>KEY2>KEY3!! #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)//讀取按鍵0 #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//讀取按鍵1 #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)//讀取按鍵2 #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//讀取按鍵3(WK_UP) u8 KEY_Scan(u8 mode) { static u8 key_up=1; //默認為松開狀態 if(mode) // mode = 1 則讓此條件失效 key_up=1; //支持連按 if(key_up && (KEY0==0 || KEY1==0 || KEY2==0 || WK_UP==1)) { delay_ms(10); //去抖動 key_up=0; // 更新標志位 為按下狀態 if(KEY0==0) return KEY0_PRES; else if(KEY1==0) return KEY1_PRES; else if(KEY2==0) return KEY2_PRES; else if(WK_UP==1) return WKUP_PRES; } else if(KEY0==1 && KEY1==1 && KEY2==1 && WK_UP==0) { key_up=1; // 當所有都是彈起時才將標志位更新為松開狀態 } return 0;// 無按鍵按下 } 這個函數缺陷是同時只能檢測一個按鍵并且有優先級順序所限制,一種改進的方式是修改按鍵狀態標志, 每個按鍵狀態對于整數的一個位,將這些按鍵狀態合并起來,成為一個整數,判斷時再拆分起來即可。
貼出自己寫的源碼: -------------------------------------------------------------------------------
key.h -------------------------------------------------------------------------------
#ifndef __KEY_H #define __KEY_H enum en_KEY_NUMBER { eKEY_NUMBER_1, eKEY_NUMBER_2, }; enum en_KEY_STATUS { eKEY_STATUS_DOWN, eKEY_STATUS_UP, }; void KEY_Init(void); enum en_KEY_STATUS KEY_Read(enum en_KEY_NUMBER number); enum en_KEY_STATUS KEY_ReadKey1_One(void); enum en_KEY_STATUS KEY_ReadKey2_One(void); #endif -------------------------------------------------------------------------------
key.c ------------------------------------------------------------------------------- #include "public.h" #include "key.h" // 移植部分 ------------------------------------------------------------ #define KEY1_GPIO A #define KEY1_GPIO_Pin 0 #define KEY2_GPIO C #define KEY2_GPIO_Pin 13 // 上拉或下拉 #define KYE1_MODE GPIO_Mode_IPD #define KYE2_MODE GPIO_Mode_IPU // 按鍵按下時的電平狀態,這點做的比較好,可以統一起來 #define KEY1_DOWN 1 #define KEY2_DOWN 0 // 移植部分 ------------------------------------------------------------ /* 初始化按鍵 */ void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(TORCC(KEY1_GPIO) | TORCC(KEY2_GPIO), ENABLE); GPIO_InitStructure.GPIO_Mode = KYE1_MODE; GPIO_InitStructure.GPIO_Pin = TOPIN(KEY1_GPIO_Pin); GPIO_Init(TOGPIO(KEY1_GPIO), &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = KYE2_MODE; GPIO_InitStructure.GPIO_Pin = TOPIN(KEY2_GPIO_Pin); GPIO_Init(TOGPIO(KEY2_GPIO), &GPIO_InitStructure); } /* 讀取指定按鍵的狀態 長按時連續返回狀態*/ enum en_KEY_STATUS KEY_Read(enum en_KEY_NUMBER number) { enum en_KEY_STATUS status; uint8_t tmp; switch((int)number) { case eKEY_NUMBER_1: tmp = GPIO_ReadInputDataBit(TOGPIO(KEY1_GPIO), TOPIN(KEY1_GPIO_Pin)); status = (tmp == KEY1_DOWN) ? eKEY_STATUS_DOWN : eKEY_STATUS_UP; break; case eKEY_NUMBER_2: tmp = GPIO_ReadInputDataBit(TOGPIO(KEY2_GPIO), TOPIN(KEY2_GPIO_Pin)); status = (tmp == KEY2_DOWN) ? eKEY_STATUS_DOWN : eKEY_STATUS_UP; break; } return status ; } /* 讀取指定按鍵的狀態 長按時只會返回一次按下狀態*/ enum en_KEY_STATUS KEY_ReadKey1_One(void) { static enum en_KEY_STATUS oldstatus = eKEY_STATUS_UP; enum en_KEY_STATUS status; status = KEY_Read(eKEY_NUMBER_1); if(eKEY_STATUS_UP == oldstatus && eKEY_STATUS_DOWN == status) { oldstatus = eKEY_STATUS_DOWN; return eKEY_STATUS_DOWN; } if(eKEY_STATUS_UP == status) { oldstatus = eKEY_STATUS_UP; } return eKEY_STATUS_UP ; } /* 讀取指定按鍵的狀態 長按時只會返回一次按下狀態*/ enum en_KEY_STATUS KEY_ReadKey2_One(void) { static enum en_KEY_STATUS oldstatus = eKEY_STATUS_UP; enum en_KEY_STATUS status; status = KEY_Read(eKEY_NUMBER_2); if(eKEY_STATUS_UP == oldstatus && eKEY_STATUS_DOWN == status) { oldstatus = eKEY_STATUS_DOWN; return eKEY_STATUS_DOWN; } if(eKEY_STATUS_UP == status) { oldstatus = eKEY_STATUS_UP; } return eKEY_STATUS_UP ; } 第18集 C語言復習-寄存器地址名稱映射 一、C語言復習 這里將一些C語音的位操作、宏定義這些基礎知識,此處略 二、寄存器地址名稱映射 這里講解利用C語言的一些特性,實現結構體來訪問對應的寄存器地址。是一個非常有意思的技巧。 按照自己的理解,嘗試描述一遍,從設置GPIOA組的第一個pin為高電平的方式為切入點進行分析。 // 設置 GPIOA組的第1引腳為高電平 GPIO_SetBits(GPIOA, GPIO_Pin_1); // 函數的實現 void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BSRR = GPIO_Pin; } 從上面看,最終設置的是GPIOA的BSRR寄存器,那這個寄存器的地址是多少呢?GPIOx是從 GPIOA 傳進來的。 看GPIOA的定義:(STM32F10x.h) #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) // GPIOA的基址 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) // APB2總線的基址 #define PERIPH_BASE ((uint32_t)0x40000000) // 片上外設基址 1、外設基址 #define PERIPH_BASE ((uint32_t)0x40000000) STM32使用的ARM M3的內核,該內核的外設基址是從0x40000000開始。(M3權威指南 86頁) 從STM32官方的參考手冊來看也是從 0x40000000開始(STM32中文參考手冊 29頁) 所以STM32的外設基址為 0x40000000 ,即所有外設都是從 0x40000000 ~ 0x5003FFFF 2、APB2基址 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) // APB2總線的基址 外設基址為 0x40000000 + APB2偏移0x10000 = APB2的基址。 從STM32官方的參考手冊來看也是從 0x40010000開始 (STM32中文參考手冊V10 28頁) 3、GPIOA基址 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) // GPIOA的基址 從上圖看,GPIOA的基址是0x40010800,而APB2基址為 0x40010000 + GPIOA偏移0x0800 = 0x40010800 該宏完全展開后是這樣的 ( 0x40000000 + 0x10000 )+ 0x0800 = 0x40010800 4、強制轉換為 GPIO_TypeDef 類型的指針(重點,精華) #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 強制轉為GPIO_TypeDef 類型的指針,表示從 0x40010800 開始連續占用sizeof(GPIO_TypeDef) 字節的長度。
注意,它把 0x40010800 轉為 GPIO_TypeDef 類型的指針,即GPIOA的地址0x40010800。 該結構體定義如下: typedef struct { __IO uint32_t CRL; // 占用 4 個字節 即 0x40010800 ~ 0x40010804 為CRL寄存器 __IO uint32_t CRH; // 占用 4 個字節 即 0x40010804 ~ 0x40010808 為CRH寄存器 __IO uint32_t IDR; // 占用 4 個字節 即 0x40010808 ~ 0x4001080C 為IDR寄存器 __IO uint32_t ODR; // 占用 4 個字節 即 0x4001080C ~ 0x40010810 為ODR寄存器 __IO uint32_t BSRR; // 占用 4 個字節 即 0x40010810 ~ 0x40010814 為BSRR寄存器 __IO uint32_t BRR; // 占用 4 個字節 即 0x40010814 ~ 0x40010818 為BSRR寄存器 __IO uint32_t LCKR; // 占用 4 個字節 即 0x40010818 ~ 0x4001081C 為BSRR寄存器 } GPIO_TypeDef; 對應STM32的GPIO寄存器映射表,剛好完全對齊。( STM32中文參考手冊V10 129頁 ) 5、看看GPIO_Pin的定義 #define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */ #define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */ #define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */ #define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */ #define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */ #define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */ #define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */ #define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */ #define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */ #define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */ #define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */ #define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */ #define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */ #define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */ #define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */ #define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */ 仔細觀察,發現定義的宏都很有規律,展開二進制來看剛剛好是對應16位數的每一個位。 6、回頭看看 // 函數的實現 void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BSRR = GPIO_Pin; } 我們傳給GPIO_SetBits()是GPIOA和GPIO_Pin_1,GPIOx->BSRR = GPIO_Pin; 是將GPIO_Pin_1((uint16_t)0x0002) 寫入到 GPIOA(0x40010800)->BSRR(10)中。 簡單的來說,是將0x0002寫入到 0x40010810為首的寄存器中,再通俗的來說,就是將0x40010810 中的第1bin置1。置1會出現神馬情況呢?
YES,就會將GPIOA的第1 pin 置為高電平。 7、BSRR寄存器 該寄存器對應位寫1,就會讓對應的IO口置為高電平。從而完成目的。 關于結構體,還有一個技巧,是我從A20的源碼中看到的,蠻有意思的,很巧妙的將聯合體、位域、結構體融合起來,實現非常靈活的操作寄存器方式。 /* 聯合體 只占用4個字節*/ typedef union { __u32 dwval; // 這里是對寄存器整體賦值修改 struct { __u32 io_map_sel : 1 ; // default: 0; __u32 res0 : 29 ; // default: ; __u32 tcon_gamma_en : 1 ; // default: 0; __u32 tcon_en : 1 ; // default: 0; } bits; // 這里個單獨對某些寄存器進行操作 } tcon_gctl_reg_t; 因聯合體的特性,dwval與 bits 共用同一個內存空間,因結構體的特性,將每個寄存器分開,因位域的特性,得以單獨修改某一位或幾個位。 如果我要修改 tcon_gctl_reg_t 寄存器中的其中 bin31 位。那么可以這樣: #define SUNXI_LCD0_BASE 0X01C0C000 static volatile tcon_gctl_reg_t *lcd_dev; lcd_dev=(__de_lcd_dev_t *)SUNXI_LCD0_BASE; lcd_dev-> tcon_en = 1; 如果想要對這個寄存器所有位都賦值則可以這樣: lcd_dev-> dwval = 0x01;
|