CC3200之GPIO引腳分析

預備知識:
(1)volatile關鍵字:
volatile定義的變量一般爲無需開發者自己賦值,會自動改變的變量。
在普通的程序中,編譯器都具有優化功能,爲了避免浪費計算機資源,會將重複或無用的語句刪除。
大家觀察下面一段代碼。

//code1:
double gpio_input = 0printf("The GPIO Input1 is %f", gpio_input);

/*
光照傳感器開始讀取光照數據,並賦值給gpio_input
*/
printf("The GPIO Input2 is %f", gpio_input);

//code2:
volatile double gpio_input = 0printf("The GPIO Input1 is %f", gpio_input);

/*
光照傳感器開始讀取光照數據,並賦值給gpio_input
*/

printf("The GPIO Input2 is %f", gpio_input);

我們將一個GPIO INPUT的值賦值給gpio_input,第一段代碼使用double類型來定義變量,並且兩次輸出的結果均爲0;而第二次使用volatile類型來定義變量,它的兩次結果分別爲0和真實的光照數值

第一段代碼:是因爲編譯器優化功能。編譯器作爲計算機的節耗小能手,不允許一個變量沒有賦值而反覆讀取,因此它會自動過濾掉再次讀取變量地址的數值的那段代碼,直接從緩衝區中找到上次的值,而不是再一次根據變量地址訪問主存器,進而找到主存中的變量值。而外設的數值改變一般都是存儲器的數值變化,由於優化功能,光照傳感器讀取的數值無法被讀取到,造成兩次結果都是0。

[緩衝區的訪問速率高於主存,但容量小於主存。因此,一般都是先從緩存中先查找變量,如果找不到,就一邊從主存中查找,一邊將查找的數據調至緩存,供下一次使用]

第二段代碼:使用volatile關鍵字就可以告訴編譯器gpio_input這個變量他自己會變,不要把它當成壞人,直接抹殺掉,因此下一次的讀取是從主存中讀取的值,即真實的外設讀取的數值。

(2)HWREG(x)函數:
把x轉爲一個指向x的volatile unsigned long指針,然後從中取值
(3)當GPIO引腳輸出1時,需要設置引腳值爲0;反之,爲1。
原因:引腳輸出的電壓都來自開發板的板供電壓,點亮LED所需的電壓最大爲120mA,電流均來自芯片內部,如果很多LED燈,那麼就等同於電壓的疊加,此時通過芯片的電流很大,對芯片的傷害大,因此反過來,更有利於保護芯片。

切入主題

CC3200的GPIO引腳和其他arm核的處理器外設使用是一樣的步驟。

step1:設置時鐘

CC3200簡單的函數調用對想要了解開發板的初學者來說是極大地便利了,但是作爲想要進一步瞭解的“油頭”青年,只能硬着頭皮去分析功能的具體實現

  MAP_PRCMPeripheralClkEnable(PRCM_GPIOA1, PRCM_RUN_MODE_CLK);

這句話的含義是。開啓GPIOA第二組時鐘(下標從0開始)。
翻開這個函數的具體定義:

#define HWREG(x) (*((volatile unsigned long *)(x))) 
//外設寄存器地址放入內存,x爲外設寄存器地址

void
PRCMPeripheralClkEnable(unsigned long ulPeripheral, unsigned long ulClkFlags)
{
  if(ulPeripheral != PRCM_ADC)
  {
    HWREG(ARCM_BASE + PRCM_PeriphRegsList[ulPeripheral].ulClkReg) |= ulClkFlags; 
     //啓用指定的外設時鐘,對PRCM_ADC不做任何操作,同時啓動指定的外設寄存器
     //等同於 (*((volatile unsigned long *)(0x44025000 + PRCM_PeriphRegsList[ulPeripheral].ulClkReg)))
  }
  //如果是camera,爲其重新定義時鐘
  if(ulPeripheral == PRCM_CAMERA)
  {
    HWREG(ARCM_BASE + APPS_RCM_O_CAMERA_CLK_GEN) = 0x0404;
    //(*((volatile unsigned long *)(0x44025000 + 0x00000000)));
  }
}

step2:設置引腳

上一步我們對GPIOA第二組的時鐘進行了設置,由於GPIO引腳具有兩個方向,同時每組內包含8個引腳,因此,我們需要

設置組內引腳及方向

 MAP_PinTypeGPIO(PIN_64, PIN_MODE_0, false); 
 //引腳,引腳模式,開漏輸出/推輓輸出
 MAP_GPIODirModeSet(GPIOA1_BASE, 0x2, GPIO_DIR_MODE_OUT); 
 //基地址,組內號,方向

以上的code不難看出這是設置輸出引腳PIN_64,由於引腳複用,因此當對同一引腳使用不同功能時,需要設置不同的引腳模式值,具體參考引腳模式對應表。
同樣,我們看函數的定義部分

void PinTypeGPIO(unsigned long ulPin,unsigned long ulPinMode,tBoolean bOpenDrain)
{
    //設置標準開漏輸出引腳
    if(bOpenDrain)
    {
            PinConfigSet(ulPin, PIN_STRENGTH_2MA, PIN_TYPE_OD);
    }
    else
    {
            PinConfigSet(ulPin, PIN_STRENGTH_2MA, PIN_TYPE_STD);
    }

    PinModeSet(ulPin, ulPinMode);
    //設置引腳模式
}

設置GPIO引腳類型設置

void PinConfigSet(unsigned long ulPin,unsigned long  ulPinStrength,unsigned long ulPinType)
{
  	unsigned long ulPad;
 	ulPad = g_ulPinToPadMap[ulPin & 0x3F];

    //將0x4402E144置1,允許輸入。
    //但是這個寄存器0x4402E144是幹什麼的不太清楚
    HWREG(0x4402E144) &= ~((0x80 << ulPad) & (0x1E << 8));

    //計算寄存器地址,參考寄存器地址手冊 	
    ulPad = ((ulPad << 2) + PAD_CONFIG_BASE);

    //找到寄存器地址,並向寄存器中寫入設置值,參考每個引腳不同的配置值對應不同的功能
    HWREG(ulPad) = ((HWREG(ulPad) & ~(PAD_STRENGTH_MASK | PAD_TYPE_MASK)) |(ulPinStrength | ulPinType ));
  }
}

GPIO方向設置

void
GPIODirModeSet(unsigned long ulPort, unsigned char ucPins,
               unsigned long ulPinIO)
{
    ASSERT(GPIOBaseValid(ulPort)); //判斷GPIO基地址是否合法
    ASSERT((ulPinIO == GPIO_DIR_MODE_IN) || (ulPinIO == GPIO_DIR_MODE_OUT));     //判斷GPIO方向參數是否合法,輸入爲0,輸出爲1    
    HWREG(ulPort + GPIO_O_GPIO_DIR) = ((ulPinIO & 1) ?
                                  (HWREG(ulPort + GPIO_O_GPIO_DIR) | ucPins) :
                                  (HWREG(ulPort + GPIO_O_GPIO_DIR) & ~(ucPins)));
    //HWREG(ulPort + GPIO_O_GPIO_DIR)-》拿到GPIO組地址,ucPins拿到組內地址
}

至此,我們完成了配置。

step3:寄存器編程

CC3200官方自帶的GPIO使用例程爲流水燈

首先是函數GPIO_IF_LedConfigure,此函數中的GPIO_IF_GetPortNPin函數爲主要的設置函數,此函數是
根據給定的GPIO值計算出引腳的GPIO基地址值和組內引腳值

GPIO_IF_LedConfigure(LED1|LED2|LED3);//同時配置3個GPIO引腳

void GPIO_IF_GetPortNPin(unsigned char ucPin, unsigned int *puiGPIOPort, unsigned char *pucGPIOPin)
{
    *pucGPIOPin = 1 << (ucPin % 8);    //組內序號從0開始,按此計算得到組內位權
    *puiGPIOPort = (ucPin / 8);       //每組有8個引腳,計算得出所屬的GPIO組
    *puiGPIOPort = ulReg[*puiGPIOPort];    //取GPIO基地址值
}

拿到了引腳的GPIO基地址值和組內引腳值,接下來我們就需要針對具體GPIO引腳進行0/1的設置了,針對
流水燈中的一個燈的具體設置進行分析
其他設置是相同的步驟

    while(1)
    {
        MAP_UtilsDelay(8000000);
        GPIO_IF_LedOn(MCU_RED_LED_GPIO); //gpio9引腳置1,點亮LED
        MAP_UtilsDelay(8000000);
        GPIO_IF_LedOff(MCU_RED_LED_GPIO);//gpio9引腳置0,關閉LED
    }

進入GPIO_IF_LedOn函數體內部,其主要作用的是MAP_GPIOPinWrite函數,其主要功能爲
設置GPIO引腳值,點燈

GPIO_IF_Set(GPIO_LED1, g_uiLED1Port, g_ucLED1Pin, 1);

void GPIO_IF_Set(unsigned char ucPin, unsigned char ucGPIOValue)
{
	...........
    ucGPIOValue = ucGPIOValue << (ucPin % 8);//gpio值爲1時,ucGPIOValue 值爲0;反之,值爲1
    ...........
}


void GPIOPinWrite(unsigned long ulPort, unsigned char ucPins, unsigned char ucVal)
{
    ASSERT(GPIOBaseValid(ulPort));
    HWREG(ulPort + (GPIO_O_GPIO_DATA + (ucPins << 2))) = ucVal;  //ucVal表示要配置gpio組內的第幾個引腳的位值0/1,將對應的位值寫入ucPins指定的輸出引腳
}

GPIO_IF_LedOff函數也是相同的配置

延時函數UtilsDelay


    __asm(     //_asm表示內聯彙編,即在C/C++裏使用匯編指令
    	  "    .sect \".text:UtilsDelay\"\n"
          "    .clink\n"
          "    .thumbfunc UtilsDelay\n"
          "    .thumb\n"
          "    .global UtilsDelay\n"
          "UtilsDelay:\n"
          "    subs r0, #1\n"   //r0-1;注意sub r0,r0,#1<——>r0=r0-1放到r0 
          "    bne.n UtilsDelay\n"    //16位的BNE指令,“不相等(或不爲0)跳轉指令”,如果r0不爲0就跳轉到後面指定的地址,繼續執行;爲0,則繼續執行下面語句
          "    bx lr\n");    //轉到lr中存放的地址處

lr:連接寄存器(Link Register, LR),在ARM體系結構中LR的特殊用途有兩種:一、保存子程序返回地址;二、當異常發生時,LR中保存的值等於異常發生時PC的值減4(或者減2),因此在各種異常模式下可以根據LR的值返回到異常發生前的相應位置繼續執行。
這段彙編還有點不太懂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章