2017年9月1日 星期五

ARM結構指標的操作~以STM32L1XX為例

使用ARM CPU時,通常很常使用CMSIS已經包好的Library,至於其內容如何操作不得而知,這邊要來說明一下其中如何運作,現在以GPIO_Init(GPIOA, &GPIO_InitStructure); 這樣的程式為範例來trace code如何運作,主要會針對指標或結構的操作來說明!

首先先去了解一下GPIO的Register map






GPIO_Init函式實作如下
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
  uint32_t tmpreg = 0x00;
  
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));

  /* -------------------------Configure the port pins---------------- */
  /*-- GPIO Mode Configuration --*/
  for (pinpos = 0x00; pinpos < 0x10; pinpos++)
  {
    pos = ((uint32_t)0x01) << pinpos;

    /* Get the port pins position */
    currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    if (currentpin == pos)
    {
      /* Use temporary variable to update MODER register configuration, to avoid 
         unexpected transition in the GPIO pin configuration. */
      tmpreg = GPIOx->MODER;
      tmpreg &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
      tmpreg |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
      GPIOx->MODER = tmpreg;

      if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
      {
        /* Check Speed mode parameters */
        assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));

        /* Use temporary variable to update OSPEEDR register configuration, to avoid 
          unexpected transition in the GPIO pin configuration. */
        tmpreg = GPIOx->OSPEEDR;
        tmpreg &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
        tmpreg |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));
        GPIOx->OSPEEDR = tmpreg;

        /*Check Output mode parameters */
        assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));

        /* Use temporary variable to update OTYPER register configuration, to avoid 
          unexpected transition in the GPIO pin configuration. */
        tmpreg = GPIOx->OTYPER;
        tmpreg &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos));
        tmpreg |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
        GPIOx->OTYPER = tmpreg;
      }

      /* Use temporary variable to update PUPDR register configuration, to avoid 
         unexpected transition in the GPIO pin configuration. */
      tmpreg = GPIOx->PUPDR;
      tmpreg &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
      tmpreg |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
      GPIOx->PUPDR = tmpreg;
    }
  }
}

重點是必須了解為何要宣告兩個指標GPIO_TypeDef* GPIOx及GPIO_InitTypeDef* GPIO_InitStruct做傳入參數來使用,因此找出GPIO_TypeDef及GPIO_InitTypeDef結構定義如下,
typedef struct
{
  __IO uint32_t MODER;        /*!< GPIO port mode register,                     Address offset: 0x00      */
  __IO uint16_t OTYPER;       /*!< GPIO port output type register,              Address offset: 0x04      */
  uint16_t RESERVED0;         /*!< Reserved,                                    0x06                      */
  __IO uint32_t OSPEEDR;      /*!< GPIO port output speed register,             Address offset: 0x08      */
  __IO uint32_t PUPDR;        /*!< GPIO port pull-up/pull-down register,        Address offset: 0x0C      */
  __IO uint16_t IDR;          /*!< GPIO port input data register,               Address offset: 0x10      */
  uint16_t RESERVED1;         /*!< Reserved,                                    0x12                      */
  __IO uint16_t ODR;          /*!< GPIO port output data register,              Address offset: 0x14      */
  uint16_t RESERVED2;         /*!< Reserved,                                    0x16                      */
  __IO uint16_t BSRRL;        /*!< GPIO port bit set/reset low registerBSRR,    Address offset: 0x18      */
  __IO uint16_t BSRRH;        /*!< GPIO port bit set/reset high registerBSRR,   Address offset: 0x1A      */
  __IO uint32_t LCKR;         /*!< GPIO port configuration lock register,       Address offset: 0x1C      */
  __IO uint32_t AFR[2];       /*!< GPIO alternate function low register,        Address offset: 0x20-0x24 */
#if defined (STM32L1XX_HD) || defined (STM32L1XX_XL)
  __IO uint16_t BRR;          /*!< GPIO bit reset register,                     Address offset: 0x28      */
  uint16_t RESERVED3;         /*!< Reserved,                                    0x2A                      */
#endif 

} GPIO_TypeDef;

typedef struct
{
  uint32_t GPIO_Pin;              /*!< Specifies the GPIO pins to be configured.
                                       This parameter can be any value of @ref GPIO_pins_define */

  GPIOMode_TypeDef GPIO_Mode;     /*!< Specifies the operating mode for the selected pins.
                                       This parameter can be a value of @ref GPIOMode_TypeDef */

  GPIOSpeed_TypeDef GPIO_Speed;   /*!< Specifies the speed for the selected pins.
                                       This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOOType_TypeDef GPIO_OType;   /*!< Specifies the operating output type for the selected pins.
                                       This parameter can be a value of @ref GPIOOType_TypeDef */

  GPIOPuPd_TypeDef GPIO_PuPd;     /*!< Specifies the operating Pull-up/Pull down for the selected pins.
                                       This parameter can be a value of @ref GPIOPuPd_TypeDef */

}GPIO_InitTypeDef;


而仔細觀察GPIO_TypeDef及GPIO_InitTypeDef會發現結構內宣告的內容全部都在上述GPIO register map圖片當中,
實際在執行GPIO的規劃程式如下,GPIO_Init(GPIOA, &GPIO_InitStructure);就是最後做GPIO設定的程式,
void GPIO_Config(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;

        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB |\                             
        RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOH ,ENABLE);
 
/* Configure GPIOA pins */
       GPIO_InitStructure.GPIO_Pin = BKL_BTN;
       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
       GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
       GPIO_Init(GPIOA, &GPIO_InitStructure);
}

返回去看一下void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)這個初始化GPIO的Library,如前述宣告兩個指標GPIO_TypeDef* GPIOx及GPIO_InitTypeDef* GPIO_InitStruct做傳入參數來使用,在
GPIO_Init(GPIOA, &GPIO_InitStructure)執行時GPIO_TypeDef*會指向GPIOA及GPIO_InitTypeDef*會指向GPIO_InitStructure,因此必須繼續挖出GPIOA是什麼,接著找出相關定義如下:
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)

由於GPIO屬於AHB Bus,因此找到定義如下,並查看上述圖片table 5.可以了解PERIPH_BASE的address是0x40000000,而GPIOA在AHBPERIPH的offset是0x20000,
#define GPIOA_BASE            (AHBPERIPH_BASE + 0x0000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

總結
GPIO_Init(GPIOA, &GPIO_InitStructure)的使用就是GPIO_TypeDef*指標指向GPIOA的位址,然後透過底層的Driver去設定GPIOA的硬體,而GPIO_InitTypeDef*指向GPIO_InitStructure,GPIO_InitStructure則會設定IO相關工作模式,例如輸出入等等,由於GPIO_InitStructure只是一個結構變數,因此需要用取址運算元"&"去指向它,相關細節可能要更加了解硬體底層的工作才能了解,能了解最好,如都不懂的話只要懂得指標相關原理也是可以寫得出來!







STM32於IAR 9.3以後的版本將uart導向至printf

首先,請先參考如以下IAR提供的"在IAR Embedded Workbench中实现打印输出技術資料" https://www.iar.com/cn/knowledge/support/technical-notes/general/implementing-printf-ou...