找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 20760|回復(fù): 0
收起左側(cè)

學(xué)習(xí)筆記-STM32寄存器映射以及結(jié)構(gòu)體使用技巧

[復(fù)制鏈接]
ID:91350 發(fā)表于 2015-9-30 01:02 | 顯示全部樓層 |閱讀模式
            
第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;

   





評分

參與人數(shù) 1黑幣 +2 收起 理由
555觸發(fā)器 + 2

查看全部評分

回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表