Engineering Agit

[Session 02 TM4C123GXL Study 02] - GPIO 04 본문

Embedded Lab. @ Modu inst./2020 Session 02 (2020.06~)

[Session 02 TM4C123GXL Study 02] - GPIO 04

Sean_Kim95 2020. 10. 4. 17:49

◈ 본문에 들어가기 앞서

 본 게시물은 GPIO 기본적인 input, output 설정에 관한 code analysis를 다룬다. 

2020/08/22 - [Embedded Lab. @ Modu inst./2020 Session 02 (2020.06~)] - [Session 02 TM4C123GXL Study 02] - GPIO 03

 

[Session 02 TM4C123GXL Study 02] - GPIO 03

◈ 본 글은 지난 '[Session 02 TM4C123GXL Study 02] - GPIO 02'에 이어진다. 2020/08/07 - [Embedded Lab. @ Modu inst./2020 Session 02 (2020.06~)] - [Session 02 TM4C123GXL Study 02] - GPIO 02 2. Interru..

engineering-agit.tistory.com


1. TM4C123GXL Programming Model

 TM4C123GXL은 TI사의 'Tiva family of ARM Cortex-M based microcontroller'의 development board로 TM4C123GH6PM을 core로 한다. TI사에서는 Tiva serise의 개발을 위하여 'Texas Instruments TivaWare Peripheral Driver Library'를 제공한다. 이 library를 이용한 두 가지 programming model이 있는데 하나는 'Direct Register Access Model'이고 또다른 하나는 'Software Driver Model'이다. 

  • Direct Register Access Model
#include "inc/tm4c123gh6pm.h"
.
.
.
void Sys_Init()
{
    /*SysCLK*/
.
.
    /*GPIO*/
    volatile unsigned long delay;
    SYSCTL_RCGC2_R |= 0x20;           // 1) activate clock for Port F
    delay = SYSCTL_RCGC2_R;           // allow time for clock to start
    GPIO_PORTF_LOCK_R = 0x4C4F434B;  // 2) unlock GPIO Port F -> GPIOCR을 unlock.
    GPIO_PORTF_CR_R = 0x1F;           // allow changes to PF4-0
                                      // only PF0 needs to be unlocked, other bits can't be locked
    GPIO_PORTF_AMSEL_R = 0x00;        // 3) disable analog on PF
    GPIO_PORTF_PCTL_R = 0x00000000;   // 4) PCTL GPIO on PF4-0
    GPIO_PORTF_DIR_R = 0x0E;          // 5) PF4,PF0 in, PF3-1 out //0b0000,1110
    GPIO_PORTF_AFSEL_R = 0x00;        // 6) disable alt funct on PF7-0
    GPIO_PORTF_PUR_R = 0x11;          // enable pull-up on PF0 and PF4
    GPIO_PORTF_DEN_R = 0x1F;          // 7) enable digital I/O on PF4-0
.
.
.
}
.
.
.

  • Software Driver Model
#include "stdint.h"                     // Library of Standard Integer Types
#include "stdbool.h"                    // Library of Standard Boolean Types
#include "inc/hw_memmap.h"              // Macros defining the memory map of the Tiva C Series device
#include "inc/hw_types.h"               // Defines common types and macros
#include "driverlib/sysctl.h"           // Defines and macros for System Control API of DriverLib
#include "driverlib/gpio.h"             // Defines and macros for GPIO API of DriverLib
.
.
.
int main(void){
    // Set the System clock to 80MHz and enable the clock for peripheral GPIOF
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);

    // Configure the PF1, PF2 and PF3 ports as Digital Output Pins
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);

    while (true){
        // Write the output value to the GPIO PortF to control the PF1, PF2 and PF3
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1, ui8PinData);
        // Create a delay by wasting machine cycles in a loop
        SysCtlDelay(26666667);
        // Clear the Output of the GPIO PortF for PF1, PF2 and PF3
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1, 0x00);
        SysCtlDelay(26666667);

        // If the PF3 port was activated, restart the cycle by outputting to PF1
        if (ui8PinData == GPIO_PIN_3){
            ui8PinData = GPIO_PIN_1;
        } else
            // else Left Shift the ui8PinData to output to the next pin
            ui8PinData = ui8PinData << 1;
    }
}
.
.
.

 이 두 가지의 model은 'combine'될 수 있지만 'tm4c123gh6pm.h'에서 정의된 register가 'hw_.h'에서 정의 되었을 시에는 충돌이 일어날 수 있으므로 주의해야 한다. 동일한 application에 대하여 두 가지의 model로 모두 쓰여질 수 있지만, 실시간 analog data 캡쳐에 사용되는 ADC module과 같이 performance critical peripheral들의 경우에는 DRA model을 사용하고 critical하지 않은 pin configuration과 같은 것은 SD model을 사용하는 것이 좋다.  


2. DRA model을 이용한 GPIO input/output

 위에서 언급했듯 DRA보다는 SD를 사용하는 것이 좋다. 이는 아래에서도 살펴보면 알 수 있지만, 각각의 register에 직접 접근하여 코드를 작성하는 것이므로 이는 API function들을 이용하는 SD보다 안전한 방식이 아니다. 하지만 이를 직접 작성하고 간단한 코드를 작성해보는 것이 각각의 peripheral에 대하여 면밀히 살펴볼 수 있는 기회가 될 것이므로 이를 분석해보고자 하였다.

  • HW Discription

 

 

<Fig. 2-1> On-board Switches & LED

 

 

 TM4C123GXL의 경우에 개발보드 내에 2개의 user switch들과 RGB LED가 실장되어있다. 

 

 

<Fig. 2-1-1> LED interface circuit description

 

 

 각각의 LED는 'DTC114EET1G'라는 NPN형 TR에 의해 제어되며 TR의 base에 HIGH signal을 주면 collector에서 emitter로 전류가 흐르게 된다.

 

 

<Fig. 2-1-2> Switch circuit description

 

 

 두 개의 user switch는 GND와 연결되어 있으며, switch를 누르게 되면 해당 핀에 LOW가 input으로 들어온다.


  • CCS Code
/*
 * Project name: GPIO basic I/O configuration
 * Dev. by: Sean K.
 * Data: 2020.09.28
 */

/* 
 * HW interfaces
 * PF4,0 -> Input SW1 & SW2 (Polling)
 * PF1, 2, 3 -> Output LED (Depending on switch status)
 */
 
#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"

/*variables & definitions*/
//PF1의 경우에는 0x02, PF2는 0x04, PF3은 0x08을 씀으로써 toggle이 가능하다.
#define PF1   (*((volatile unsigned long *)0x40025008))
#define PF2   (*((volatile unsigned long *)0x40025010))
#define PF3   (*((volatile unsigned long *)0x40025020))
#define PF4   (*((volatile unsigned long *)0x40025040))
#define PF0   (*((volatile unsigned long *)0x40025004))

/*func.*/
void Sys_Init();

/*sys_init*/
void Sys_Init()
{
    /*GPIO*/
    volatile unsigned long delay;
    SYSCTL_RCGC2_R |= 0x20;           // 1) activate clock for Port F
    delay = SYSCTL_RCGC2_R;           // allow time for clock to start. 3 MC 걸림.
    GPIO_PORTF_LOCK_R = 0x4C4F434B;   // 2) unlock GPIO Port F -> GPIOCR을 unlock.
    GPIO_PORTF_CR_R = 0x1F;           // allow changes to PF4-0
                                      // only PF0 needs to be unlocked, other bits can't be locked
    GPIO_PORTF_AMSEL_R = 0x00;        // 3) disable analog on PF
    GPIO_PORTF_PCTL_R = 0x00000000;   // 4) PCTL GPIO on PF4-0
    GPIO_PORTF_DIR_R = 0x0E;          // 5) PF4,PF0 in, PF3-1 out //0b0000,1110
    GPIO_PORTF_AFSEL_R = 0x00;        // 6) disable alt funct on PF7-0
    GPIO_PORTF_PUR_R = 0x11;          // enable pull-up on PF0 and PF4
    GPIO_PORTF_DEN_R = 0x1F;          // 7) enable digital I/O on PF4-0

    /*OUPUT Init.*/
    PF1 = 0x00;
    PF2 = 0x00;
    PF3 = 0x00;
}

/*main*/
int main()
{
    Sys_Init();

    while (1)
    {
        if (PF4 == 0x00) //When SW1 on
        {
            //PF1 on
            PF1 = 0x02;
            PF2 = 0x00;
            PF3 = 0x00;
        }
        else if (PF0 == 0x00) //When SW2 on
        {
            //PF2 on
            PF1 = 0x00;
            PF2 = 0x04;
            PF3 = 0x00;
        }
        else
        {
            //PF3 on
            PF1 = 0x00;
            PF2 = 0x00;
            PF3 = 0x08;
        }
    }
}

 

① Includes

 기본적으로 3가지의 include file이 필요하다. 

  • <stdint.h>
#ifndef _STDINT_H_
#define _STDINT_H_
.
.
.
#if defined(__MSP430__) || defined(__TMS320C55X_PLUS_BYTE__)
    typedef   signed char    int8_t;
    typedef unsigned char   uint8_t;
    typedef          int    int16_t;
    typedef unsigned int   uint16_t;
    typedef          long   int32_t;
    typedef unsigned long  uint32_t;
#elif defined(_TMS320C5XX) || defined(__TMS320C55X__)
    typedef          int    int16_t;
    typedef unsigned int   uint16_t;
    typedef          long   int32_t;
    typedef unsigned long  uint32_t;
#elif defined(_TMS320C6X) || defined(__ARM_ARCH) || defined(__ARP32__) || \
      defined(__PRU__)    || defined(__C7000__)
    typedef   signed char   int8_t;
    typedef unsigned char  uint8_t;
    typedef          short  int16_t;
    typedef unsigned short uint16_t;
    typedef          int    int32_t;
    typedef unsigned int   uint32_t;
.
.
.
#endif /* _STDINT_H_ */

 이 include file의 경우 위에서 보는 것과 같이 'Standard integer types'를 define한다. 이를 include하여 code를 작성하면 보다 직관적으로 원하는 변수의 크기를 선언할 수 있다.

  • <stdbool.h>
#ifndef __bool_true_false_are_defined
#define	__bool_true_false_are_defined	1

#include <_ti_config.h>

_TI_PROPRIETARY_PRAGMA("diag_push")
_TI_PROPRIETARY_PRAGMA("CHECK_MISRA(\"-19.4\")")
_TI_PROPRIETARY_PRAGMA("CHECK_MISRA(\"-19.11\")")

#ifndef __cplusplus

#define	false	0
#define	true	1

#define	bool	_Bool
#if __TI_PROPRIETARY_STRICT_ANSI_MACRO && 199901L > __STDC_VERSION__
typedef unsigned char _Bool;
#endif

#endif /* !__cplusplus */

_TI_PROPRIETARY_PRAGMA("diag_pop")

#endif /* __bool_true_false_are_defined */

 이 include file의 경우 위에서 보는 것과 같이 'Standard boolean types'를 define한다.

  • "inc/tm4c123gh6pm.h"
#ifndef __TM4C123GH6PM_H__
#define __TM4C123GH6PM_H__
.
.
.
//*****************************************************************************
//
// GPIO registers (PORTF)
//
//*****************************************************************************
#define GPIO_PORTF_DATA_BITS_R  ((volatile uint32_t *)0x40025000)
#define GPIO_PORTF_DATA_R       (*((volatile uint32_t *)0x400253FC))
#define GPIO_PORTF_DIR_R        (*((volatile uint32_t *)0x40025400))
#define GPIO_PORTF_IS_R         (*((volatile uint32_t *)0x40025404))
#define GPIO_PORTF_IBE_R        (*((volatile uint32_t *)0x40025408))
#define GPIO_PORTF_IEV_R        (*((volatile uint32_t *)0x4002540C))
#define GPIO_PORTF_IM_R         (*((volatile uint32_t *)0x40025410))
#define GPIO_PORTF_RIS_R        (*((volatile uint32_t *)0x40025414))
#define GPIO_PORTF_MIS_R        (*((volatile uint32_t *)0x40025418))
#define GPIO_PORTF_ICR_R        (*((volatile uint32_t *)0x4002541C))
#define GPIO_PORTF_AFSEL_R      (*((volatile uint32_t *)0x40025420))
#define GPIO_PORTF_DR2R_R       (*((volatile uint32_t *)0x40025500))
#define GPIO_PORTF_DR4R_R       (*((volatile uint32_t *)0x40025504))
#define GPIO_PORTF_DR8R_R       (*((volatile uint32_t *)0x40025508))
#define GPIO_PORTF_ODR_R        (*((volatile uint32_t *)0x4002550C))
#define GPIO_PORTF_PUR_R        (*((volatile uint32_t *)0x40025510))
#define GPIO_PORTF_PDR_R        (*((volatile uint32_t *)0x40025514))
#define GPIO_PORTF_SLR_R        (*((volatile uint32_t *)0x40025518))
#define GPIO_PORTF_DEN_R        (*((volatile uint32_t *)0x4002551C))
#define GPIO_PORTF_LOCK_R       (*((volatile uint32_t *)0x40025520))
#define GPIO_PORTF_CR_R         (*((volatile uint32_t *)0x40025524))
#define GPIO_PORTF_AMSEL_R      (*((volatile uint32_t *)0x40025528))
#define GPIO_PORTF_PCTL_R       (*((volatile uint32_t *)0x4002552C))
#define GPIO_PORTF_ADCCTL_R     (*((volatile uint32_t *)0x40025530))
#define GPIO_PORTF_DMACTL_R     (*((volatile uint32_t *)0x40025534))
.
.
.
#endif

#endif // __TM4C123GH6PM_H__

 이 include file은 위에서는 그 일부분을 보여주는데 TM4C123GH6PM의 모든 register map을 define한 것이다. 위의 definition들 중에 '*'가 앞에 붙지 않은 것이 있다. 해당 definition은 'GPIO_PORTF_DATA_BITS_R'이다. 이를 사용하는 방식은 다음 사이트에서 볼 수 있다. 

e2e.ti.com/support/archive/stellaris_arm/f/471/t/158235?-h-define-question


② Port definitions

.
.
.
#define PF1   (*((volatile unsigned long *)0x40025008))
#define PF2   (*((volatile unsigned long *)0x40025010))
#define PF3   (*((volatile unsigned long *)0x40025020))
#define PF4   (*((volatile unsigned long *)0x40025040))
#define PF0   (*((volatile unsigned long *)0x40025004))
.
.
.

 각 GPIO port의 핀은 위와 같이 define되는데 이는 위의 'tm4c123gh6pm.h'에서도 볼 수 있는 형태로 정의한다. 위의 코드에서 보이는 '(*((volatile unsigned long *)))'는 흔하게 볼 수 있는 type은 아니기에 살펴보도록 하자. 'volatile unsigned long*'의 경우 type을 describe하는 부분이다. 특별히 이러한 타입은 'volatile pointer'가 unsigned long으로 선언되었음을 나타내는 것이다. 'volatile pointer'는 이러한 HW 접근방식에는 부적절한 특정 optimisation들을 disable시킨다. '(type)value'는 C에서 typecasting을 의미한다. 따라서 '(volatile unsigned long *)0x40025008'은 0x40025008을 취하고 이를 volatile unsigned long*으로 typecasting한다는 의미로 해석하면 된다. 맨 앞에 있는 '*'는 compiler에서 'dereference(역참조; 주소값을 통해 그 주소에 있는 값에 접근)'한다고 말해주는 것이다. 이를 모두 조합하여 PF1의 definition을 해석해보면, compiler에게 0x40025008 주소의 memory에 unsigned long (32bit)으로 읽고 쓰겠다는 의미이다. 

 일반적인 경우에 이렇게 각 핀을 define하면 해당 핀을 High로 만들 때는 '1'을, Low로 만들 때는 '0'을 define한 변수에 입력한다. 이런 경우에는 혹여 변경을 원하는 핀 이외의 핀의 값을 변경할 수 있는 가능성이 있어서 이를 막을 필요가 있다. TM4C123의 경우에는 각 Port의 base address에서 2bit 떨어진 address부터 각 핀(0~7)이 지정되어 있다. 이를 datasheet에서는 다음과 같이 언급하고 있다.

이 내용을 보면 ADDR[9:2], 즉, base address에서 0x04(0b0100)~0x200(0b0010.0000.0000) 값을 각각 더하면 각 핀이 mapping 되어 있는 address이다. 예를 들어, PF0은 PORTF의 base address(APB 이용시)인 0x4002.5000에 0x04를 더한 0x4002.5004에 map되어 있으므로 이 주소로 PF0를 define하면 된다. 

  다시 위에서 언급한 문제를 TM4C123의 경우에 어떻게 해결했는지 알아보자. PF2를 예를 들어서 말하자면 다음과 같다. 위의 코드와 같이 PF2라는 이름을 해당핀이 map되어 있는 address에 붙여주면 이제 이 변수명에 입력하는 값은 해당 address에 입력이 된다. 이 값이 내부적인 처리에 의해 Data_R에 적용이 되는데 이때 다른 bit의 값은 건들면 안되고 PF2의 bit인 LSB에서 3번째 핀의 값만 바꿀 수 있어야한다. 따라서 PF2의 mapped register의 LSB에서 3번째 bit만을 건들여주면 이 값이 내부적 처리를 통해 Data_R의 다른 bit는 건들이지 않고 해당 bit만을 바꾸게 된다. 이 때문에 위의 코드에서 각각의 핀을 독립적인 변수로 지정했음에도 불구하고 마치 Data_R에 값을 쓰는 것 처럼 값을 넣는 것이다(0x01, 0x02, 0x04, 0x08, ..., 0x80). 이는 'PF2 = 0x0f'를 하더라도 3번째 bit의 값만 입력이 되므로 나머지 1, 2, 4번째 bit의 값이 '1'인 것은 Data_R에 아무런 영향을 미치지 않게된다(추후에 CCS의 debugging을 통해 실험적 체크 진행할 예정). 


③ Port Configuration

.
.
.
void Sys_Init()
{
    /*GPIO*/
    volatile unsigned long delay;
    SYSCTL_RCGC2_R |= 0x20;           // 1) activate clock for Port F
    delay = SYSCTL_RCGC2_R;           // allow time for clock to start.
    GPIO_PORTF_LOCK_R = 0x4C4F434B;   // 2) unlock GPIO Port F -> GPIOCR을 unlock.
    GPIO_PORTF_CR_R = 0x1F;           // allow changes to PF4-0
                                      // only PF0 needs to be unlocked, other bits can't be locked
    GPIO_PORTF_AMSEL_R = 0x00;        // 3) disable analog on PF
    GPIO_PORTF_PCTL_R = 0x00000000;   // 4) PCTL GPIO on PF4-0
    GPIO_PORTF_DIR_R = 0x0E;          // 5) PF4,PF0 in, PF3-1 out //0b0000,1110
    GPIO_PORTF_AFSEL_R = 0x00;        // 6) disable alt funct on PF7-0
    GPIO_PORTF_PUR_R = 0x11;          // enable pull-up on PF0 and PF4
                                      // Since the switches are connected to GND.
    GPIO_PORTF_DEN_R = 0x1F;          // 7) enable digital I/O on PF4-0
.
.
.
}
.
.
.

 Sys_Init() 함수의 내용 중에 위의 code 부분이 PORTF의 configuration sequence이다. 각 register의 내용은 Datasheet에서 살펴볼 수 있다. GPIO의 특징은 이전 게시물을 참고하면 좋다.

2020/07/28 - [Embedded Lab. @ Modu inst./2020 Session 02 (2020.06~)] - [Session 02 TM4C123GXL Study 02] - GPIO 01

 

[Session 02 TM4C123GXL Study 02] - GPIO 01

◈ 본문에 들어가기 앞서  GPIO는 1. Datasheet 2. edX강좌 내용 으로 구성된다. 이번 포스트에서는 GPIO 핀의 기본적인 특징을 언급할 것이다. #1 GPIO module features  GPIO module은 6개의 물리적 GPIO bloc..

engineering-agit.tistory.com

  • SYSCTL_RCGC2_R

 

 

<Fig. 2-2> SYS_RCGC reg. description

 

 

 Register의 명칭을 살펴보면 SYSCTL은 'SYStem ConTroL'을, RCGC는 'Run mode Clock Gating Control'를 의미한다. 이 류의 register는 총 3가지가 있고 각각 RCGC0, RCGC1, 그리고 RCGC2이다. 각각의 register에서 [5:0] bit의 값만 aviliable하고 [31:6] bit들은 사용할 수 없고 값을 쓰면 안된다. [5:0] bit들은 system clock을 각각 F~A 6개의 port에서 사용할 수 있도록 개발자가 제어한다(0:disable, 1:enable). 일반적인 input/output으로 사용할 때에는 RCGC2의 bit를 이용하여 제어한다. RCGC0과 RCGC2에 대해서는 추후에 알아보도록 하자. 

 Port F를 사용할 것이므로 'SYSCTL_RCGC2_R |= 0x20;' 즉, 해당 register의 5번 bit를 set한다. 해당 register의 값이 입력한 대로 적용이 되려면 일정한 시간이 걸리므로 delay를 사용하여 rest한다.

  • GPIO_PORTF_LOCK_R

 이 register는 GPIO_PORTF_CR_R reg.에 write access를 할 수 있게 한다. 이 register에 0x4C4F434B를 쓰면 unlock되며 CR_R에 값을 쓸 수 있게 된다. 이외의 값을 쓰면 lock되고 0x0000.0001 값으로 reset된다. 

  • GPIO_PORTF_CR_R

 

 

<Fig. 2-3> GPIO_PORTF_CR_R reg. description

 

 

 이 register는 위의 LOCK_R을 통해 enable된다. 위에서 볼 수 있듯 [7:0] bit들에만 값을 쓸 수 있다. 각각의 bit는 각 port의 8개의 핀을 의미한다. 이 register의 bit를 '1'로 set하면 해당 핀에 상응하는 GPIOAFSEL, GPIOPUR 혹은 GPIOPDR, GPIODEN register의 bit에 값을 쓸 수 있다. 

  • GPIO_PORTF_PCTL_R

 이 register는 GPIOAFSEL, 즉, PORT의 special function을 사용하는 경우에 사용하는 register이다. 따라서 이 값은 0x0000.0000으로 설정해주어야한다. 추후에 special function을 사용할 때 자세히 알아보도록 하자.

  • GPIO_PORTF_DIR_R

 이 register는 핀의 input/output(Direction)을 결정하는 register이다. [7:0] bit들만 사용할 수 있으며 각 bit들은 각각의 port의 핀들에 대응된다. 이 값이 '1'로 set되면 해당 핀이 output으로 설정되고, '0'으로 clear되면 input으로 설정된다.

  • GPIO_PORTF_PUR_R

 PF0와 PF4는 스위치를 눌렀을 때 GND로 연결되므로 누르지 않았을때 floating 상태가 될 수 있다. 따라서 내부 pull-up을 이용하는 방식을 사용하는데 이때 해당 register를 set한다. 이에 상응하는 GPIO_PORTF_PDR_R의 bit는 자동으로 clear된다. 이때의 pull-up은 weak하다고 하는데 이는 내부에 pull-up register를 상당히 큰 값으로 가져갈 때 이렇게 부른다.


 

 

 

Comments