第16集 蜂鳴器實驗 這個實驗和流水燈是一樣的,只是將相對應(yīng)的IO口拉高拉低即可控制蜂鳴器。 值得注意的是電路設(shè)計方面,根據(jù)視頻描述,STM32上電是其IO口為浮空狀態(tài), 電平不確定,加入R38 10K 電阻,當(dāng)上電時有小電流時會直接被R38流入GND, 當(dāng)電流達(dá)到一定時才會進(jìn)入S8050三極管。
第17集 按鍵實驗 主要是將IO口設(shè)置為輸入,然后輪訓(xùn)獲取該IO口的狀態(tài),根據(jù)狀態(tài)點亮相應(yīng)的 LED 燈。 這一集還未看之前就自己先敲代碼,對比視頻上的代碼,自己寫的移植性較好,視頻上的代碼思路比較好, 而且代碼更加精簡一些。總體來說,視頻的代碼甚于我的代碼。 按鍵有兩種掃描方式: 1、長按時,只看做是一次按下 實現(xiàn)思路:如果此次是按下狀態(tài)則檢查上一次是否彈起狀態(tài),只有上一次是彈起狀態(tài)時才當(dāng)成按下。 2、長按時,被看作連續(xù)按下 實現(xiàn)思路:讀取當(dāng)前按鍵所處于的狀態(tài)直接返回該狀態(tài) 總結(jié):這兩種方式唯一不一樣且關(guān)鍵的地方在于是否需要判斷上一次是否為彈起狀態(tài),所以要切換模式時 只需要讓這關(guān)鍵的地方失效或者生效即可,這種思路可以用在其他地方。 //按鍵處理函數(shù) //返回按鍵值 //mode:0,不支持連續(xù)按;1,支持連續(xù)按; //0,沒有任何按鍵按下 //1,KEY0按下 //2,KEY1按下 //3,KEY2按下 //4,KEY3按下 WK_UP //注意此函數(shù)有響應(yīng)優(yōu)先級,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; //默認(rèn)為松開狀態(tài) 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; // 更新標(biāo)志位 為按下狀態(tài) 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; // 當(dāng)所有都是彈起時才將標(biāo)志位更新為松開狀態(tài) } return 0;// 無按鍵按下 } 這個函數(shù)缺陷是同時只能檢測一個按鍵并且有優(yōu)先級順序所限制,一種改進(jìn)的方式是修改按鍵狀態(tài)標(biāo)志, 每個按鍵狀態(tài)對于整數(shù)的一個位,將這些按鍵狀態(tài)合并起來,成為一個整數(shù),判斷時再拆分起來即可。
貼出自己寫的源碼: -------------------------------------------------------------------------------
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 // 按鍵按下時的電平狀態(tài),這點做的比較好,可以統(tǒng)一起來 #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); } /* 讀取指定按鍵的狀態(tài) 長按時連續(xù)返回狀態(tài)*/ 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 ; } /* 讀取指定按鍵的狀態(tài) 長按時只會返回一次按下狀態(tài)*/ 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 ; } /* 讀取指定按鍵的狀態(tài) 長按時只會返回一次按下狀態(tài)*/ 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語言復(fù)習(xí)-寄存器地址名稱映射 一、C語言復(fù)習(xí) 這里將一些C語音的位操作、宏定義這些基礎(chǔ)知識,此處略 二、寄存器地址名稱映射 這里講解利用C語言的一些特性,實現(xiàn)結(jié)構(gòu)體來訪問對應(yīng)的寄存器地址。是一個非常有意思的技巧。 按照自己的理解,嘗試描述一遍,從設(shè)置GPIOA組的第一個pin為高電平的方式為切入點進(jìn)行分析。 // 設(shè)置 GPIOA組的第1引腳為高電平 GPIO_SetBits(GPIOA, GPIO_Pin_1); // 函數(shù)的實現(xiàn) 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; } 從上面看,最終設(shè)置的是GPIOA的BSRR寄存器,那這個寄存器的地址是多少呢?GPIOx是從 GPIOA 傳進(jìn)來的。 看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) // 片上外設(shè)基址 1、外設(shè)基址 #define PERIPH_BASE ((uint32_t)0x40000000) STM32使用的ARM M3的內(nèi)核,該內(nèi)核的外設(shè)基址是從0x40000000開始。(M3權(quán)威指南 86頁) 從STM32官方的參考手冊來看也是從 0x40000000開始(STM32中文參考手冊 29頁) 所以STM32的外設(shè)基址為 0x40000000 ,即所有外設(shè)都是從 0x40000000 ~ 0x5003FFFF 2、APB2基址 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) // APB2總線的基址 外設(shè)基址為 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、強(qiáng)制轉(zhuǎn)換為 GPIO_TypeDef 類型的指針(重點,精華) #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 強(qiáng)制轉(zhuǎn)為GPIO_TypeDef 類型的指針,表示從 0x40010800 開始連續(xù)占用sizeof(GPIO_TypeDef) 字節(jié)的長度。
注意,它把 0x40010800 轉(zhuǎn)為 GPIO_TypeDef 類型的指針,即GPIOA的地址0x40010800。 該結(jié)構(gòu)體定義如下: typedef struct { __IO uint32_t CRL; // 占用 4 個字節(jié) 即 0x40010800 ~ 0x40010804 為CRL寄存器 __IO uint32_t CRH; // 占用 4 個字節(jié) 即 0x40010804 ~ 0x40010808 為CRH寄存器 __IO uint32_t IDR; // 占用 4 個字節(jié) 即 0x40010808 ~ 0x4001080C 為IDR寄存器 __IO uint32_t ODR; // 占用 4 個字節(jié) 即 0x4001080C ~ 0x40010810 為ODR寄存器 __IO uint32_t BSRR; // 占用 4 個字節(jié) 即 0x40010810 ~ 0x40010814 為BSRR寄存器 __IO uint32_t BRR; // 占用 4 個字節(jié) 即 0x40010814 ~ 0x40010818 為BSRR寄存器 __IO uint32_t LCKR; // 占用 4 個字節(jié) 即 0x40010818 ~ 0x4001081C 為BSRR寄存器 } GPIO_TypeDef; 對應(yīng)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 */ 仔細(xì)觀察,發(fā)現(xiàn)定義的宏都很有規(guī)律,展開二進(jìn)制來看剛剛好是對應(yīng)16位數(shù)的每一個位。 6、回頭看看 // 函數(shù)的實現(xiàn) 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會出現(xiàn)神馬情況呢?
YES,就會將GPIOA的第1 pin 置為高電平。 7、BSRR寄存器 該寄存器對應(yīng)位寫1,就會讓對應(yīng)的IO口置為高電平。從而完成目的。 關(guān)于結(jié)構(gòu)體,還有一個技巧,是我從A20的源碼中看到的,蠻有意思的,很巧妙的將聯(lián)合體、位域、結(jié)構(gòu)體融合起來,實現(xiàn)非常靈活的操作寄存器方式。 /* 聯(lián)合體 只占用4個字節(jié)*/ 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; // 這里個單獨對某些寄存器進(jìn)行操作 } tcon_gctl_reg_t; 因聯(lián)合體的特性,dwval與 bits 共用同一個內(nèi)存空間,因結(jié)構(gòu)體的特性,將每個寄存器分開,因位域的特性,得以單獨修改某一位或幾個位。 如果我要修改 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;
|