본문 바로가기

Study_Embedded/[오제이 튜브의 임베디드 실전 강의]

[오제이 튜브의 임베디드 실전 강의] 7강 GPIO제어 고아 먹기

728x90

- GPIO 수동 제어 2

 

지난 시간에 공부했던 MX_GPIO_Init( ) 중

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : GPIO_LED_Pin */		// 이부분 공부
  GPIO_InitStruct.Pin = GPIO_LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : GPIO_SW_Pin */
  GPIO_InitStruct.Pin = GPIO_SW_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIO_SW_GPIO_Port, &GPIO_InitStruct);

}

 

이 부분 공부

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /*Configure GPIO pin : GPIO_LED_Pin */
  GPIO_InitStruct.Pin = GPIO_LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);

 

 

 

 

GPIO_InitTypeDef GPIO_InitStruct = {0};		// GPIO_InitTypeDef는 아래처럼 구조체로 정의되어있다

typedef struct		
{
  uint32_t Pin;     	
  uint32_t Mode;    	
  uint32_t Pull;    	
  uint32_t Speed;   	
} GPIO_InitTypeDef;​	// 각 member가 4byte간격 == 크기가 4byte

 

 

  • 즉 Configure GPIO Pin 부분은 GPIO_InitTypeDef 구조체의 맴버들에 값을 대입하는 것
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /*Configure GPIO pin : GPIO_LED_Pin */
  GPIO_InitStruct.Pin = GPIO_LED_Pin;			
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;		
  GPIO_InitStruct.Pull = GPIO_PULLUP;			
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;		
  HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);	
}

 

GPIO_InitTypeDef 구조체의 맴버들에 대입되는 값들

  • GPIO_LED_PIN : PC13의 GPIO Pin
  • GPIO_LED_GPIO_Port
  • &GPIO_InitStruct는 구조체의 주소값

맴버들에 값을 대입한 후  HAL_GPIO_Init 함수로 넘어감

 

  • HAL_GPIO_Init 함수의 정의 부분
void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)	// GPIOx == GPIOC
{
  uint32_t position = 0x00u;				// 1)
  uint32_t ioposition;
  uint32_t iocurrent;
  uint32_t temp;
  uint32_t config = 0x00u;
  __IO uint32_t *configregister; 
  uint32_t registeroffset;      
    
  assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));		// 2)	
  assert_param(IS_GPIO_PIN(GPIO_Init->Pin));	
  assert_param(IS_GPIO_MODE(GPIO_Init->Mode));	
  						

  while (((GPIO_Init->Pin) >> position) != 0x00u)	// 3)
  {
    ioposition = (0x01uL << position);			
    
    iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;	
      
    if (iocurrent == ioposition)		 	
    {
      assert_param(IS_GPIO_AF_INSTANCE(GPIOx));	

      switch (GPIO_Init->Mode)		
      {
        case GPIO_MODE_OUTPUT_PP:	
        
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));	
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
          break;

        case GPIO_MODE_OUTPUT_OD:
          
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));	
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD;
          break;

        ...
        ...
        ...
      }	

      configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL     : &GPIOx->CRH;	// 4)
     
      registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
     
      // 5)
      MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));

      /*--------------------- EXTI Mode Configuration ------------------------*/
      /* Configure the External Interrupt or event for the current IO */
      if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
      {
        ...
        ...
        ...
    }
	position++;		
  }
}

 

1) 변수 선언 

uint32_t position = 0x00u;	
uint32_t ioposition;
uint32_t iocurrent;
uint32_t temp;
uint32_t config = 0x00u;
__IO uint32_t *configregister; 
uint32_t registeroffset;

 

2) Check the parameters

/* Check the parameters */ 			
// 괄호 안이 0이거나 1일때에 따라 메세지를 보내려 만든 부분. 지금은 메세지를 출력하는 기능 자체가 생략되어 있음   
  assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));	
  assert_param(IS_GPIO_PIN(GPIO_Init->Pin));	
  assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
  • assert_param은 debug message를 출력. 지금은 실질적 의미가 없음
  • #define IS_GPIO_ALL_INSTANCE(INSTANCE) (((INSTANCE) == GPIOA) || \
                                            ((INSTANCE) == GPIOB) || \
                                            ((INSTANCE) == GPIOC) || \
                                            ((INSTANCE) == GPIOD) || \
                                            ((INSTANCE) == GPIOE))
    - GPIOx의 그룹이 A~E인지 (유효한지) 검사
  • #define IS_GPIO_PIN(PIN)           
    (((((uint32_t)PIN) & GPIO_PIN_MASK ) != 0x00u) && ((((uint32_t)PIN) & ~GPIO_PIN_MASK) == 0x00u))
    - GPIO Pin이 유효한지 검사
  • #define IS_GPIO_MODE(MODE) (((MODE) == GPIO_MODE_INPUT)              ||\
                                ((MODE) == GPIO_MODE_OUTPUT_PP)          ||\	// 현재 GPIO_Mode	
                                ((MODE) == GPIO_MODE_OUTPUT_OD)          ||\
                                ((MODE) == GPIO_MODE_AF_PP)              ||\
                                ((MODE) == GPIO_MODE_AF_OD)              ||\
                                ((MODE) == GPIO_MODE_IT_RISING)          ||\
                                ((MODE) == GPIO_MODE_IT_FALLING)         ||\
                                ((MODE) == GPIO_MODE_IT_RISING_FALLING)  ||\
                                ((MODE) == GPIO_MODE_EVT_RISING)         ||\
                                ((MODE) == GPIO_MODE_EVT_FALLING)        ||\
                                ((MODE) == GPIO_MODE_EVT_RISING_FALLING) ||\
                                ((MODE) == GPIO_MODE_ANALOG))
    - GPIO의 mode가 유효한지 검사. 현재 mode는 GPIO_MODE_OUTPUT_PP

3) while 문

while (((GPIO_Init->Pin) >> position) != 0x00u)	// GPIO_Init->Pin 의 값이 0이랑 다르면 while문 실행
  {
    ioposition = (0x01uL << position);	

    iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;	
      
    if (iocurrent == ioposition)		
    {
      assert_param(IS_GPIO_AF_INSTANCE(GPIOx));	

      switch (GPIO_Init->Mode)		
      {
        case GPIO_MODE_OUTPUT_PP:	
          
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));	
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;	
  
          break;
	 ...
      	 ...
      	 ...
      }	// switch문이 끝나면 여기로 나옴
   
   ...
   ...
   }
      position++;
}

 

GPIO_InitStruct.Pin = GPIO_LED_Pin; 	// == 8192 (0b10 0000 0000)

 

  1. while (((GPIO_Init->Pin) >> position) != 0x00u)
     
    • 1을 position만큼 오른쪽으로 bitwise shift (position은 0에서 while문이 한번 돌때마다 1씩 증가 )
      • 10 0000 0000  (position = 0)
      • 01 0000 0000  (position = 1)
      • 00 1000 0000  (position = 2)
      •      ......
      •      ......
      • 00 0000 0001  (position = 13)
      • 00 0000 0000 -> 이때(13번 후) while문의 조건에 맞지 않으므로 (0이므로)멈춤. (position == 14)
    • Pin의 번호값만큼 while문이 돔. Pin이 몇번인지를 세기 위해 while문을 pin의 번호만큼 돌리며 1을 옆으로 옮김
  2. ioposition = (0x01uL << position);		// 1을 position만큼 왼쪽으로 bitwise shift
    
    iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;	// bitwise AND 연산
    
    if (iocurrent == ioposition)
    {
        .....
        .....
    }
    • GPIO_Init->Pin의 값은 8192로 고정. ioposition만 1이 왼쪽으로 한칸씩 옮겨지며 값이 바뀜
    • bitwise AND 연산은 연산은 둘중 하나라도 0이면 결과값이 0
      • 10 0000 0000 & 1 = 0
      • 10 0000 0000 & 10 = 0
      •      ......
      •      ......
      • 10 0000 0000 & 10 0000 0000 = 1000000000 == ioposition
    • while문이 13번 돌아 ioposition == 1000000000 == iocurrent가 되면 아래의 if else문으로 들어갈 수 있음
    • Pin이 몇번째인지를 새려는 의도
  3. assert_param(IS_GPIO_AF_INSTANCE(GPIOx));	// == (void)0; 하는게 없음
    
    /******************************* GPIO Instances *******************************/
    #define IS_GPIO_ALL_INSTANCE(INSTANCE) (((INSTANCE) == GPIOA) || \
                                            ((INSTANCE) == GPIOB) || \
                                            ((INSTANCE) == GPIOC) || \
                                            ((INSTANCE) == GPIOD) || \
                                            ((INSTANCE) == GPIOE))
    
    /**************************** GPIO Alternate Function Instances ***************/
    #define IS_GPIO_AF_INSTANCE(INSTANCE) IS_GPIO_ALL_INSTANCE(INSTANCE)
    • GPIOA ~ GPIOE중 소속되어있는게 있는지 (제대로 된 값인지) 검사
  4. switch (GPIO_Init->Mode)		
         {
           case GPIO_MODE_OUTPUT_PP: // GPIO_Init->Mode = GPIO_MODE_OUTPUT_PP 이므로 바로 여기로 들어옴
              
           	assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));	// 의미 없는 statement
           	config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;	
    
            break;
    	.....
            .....
          	.....
         }
    • main.c에서 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP 로 설정했음  
    • GPIO_MODE_OUTPUT_PP = 1이므로 GPIO_Init->Mode도 1
    • switch문의 주 목적 : config 설정 
      • main.c에서 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH == 3으로 설정했었음
    • #define  GPIO_CR_CNF_GP_OUTPUT_PP   0x00000000u /*!< 00: General purpose output push-pull  */​
       
    •  GPIO_CR_CNF_GP_OUTPUT_PP == 0. GPIO Mode가 Output Push Pull임을 의미
    • config = 3 + 0 = 3;

 

4) 

configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;	
// iocurrent(0b10 0000 0000)가 GPIO_PIN_8보다 작은가
// configregister = &GPIOx->CRH = 0x40011000

registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
// iocurrent = 8192 > GPIO_PIN_8 이므로 (position - 8u) << 2u 로 들어감
// (13 - 8 = 5 == ob0101) << 2u = ob 0001 0100 == 20 이 registeroffset에 대입

 

#define GPIO_PIN_8                 ((uint16_t)0x0100)  /* Pin 8 selected    */
// == 0b 1 0000 0000 == 256
  • GPIOC의 8Pin 이상은 값을 CRH(Configuration Register High)로, 이하는 CRL(Configuration Register High) 의 주소값을 대입
  • Pin0~7은 Low, Pin8~15는 High 

  • iocurrent > GPIO_PIN_8 = 0b1 0000 0000 이므로 configregister에는 &GPIOx -> CRH가 들어감
  • GPIOC와 Configuration Register의 주소가 동일 : GPIOC 그룹의 레지스터는 Configuration Register 부터 시작함

 

 

5) 

MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));

 

 

 

  • (GPIO_CRL_MODE0 | GPIO_CRL_CNF0) = 0011 | 1100 = 1111 == 15
  • 15 << 20 = 15,728,640 == 0b1111 0000 0000 0000 0000 0000
  • config << registeroffset == 3 << 20 = 0b1100000000000000000000 == 3145728

즉, 

MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
// 는
MODIFY_REG(1145324612, 15728640, 3145728);	// 와 같다
// 1145324612 == 0b100 0100 0100 0100 0100 0100 0100 0100
// 15728640 == 0b1111 0000 0000 0000 0000 0000
// 3145728 == 0b11 0000 0000 0000 0000 0000

 

 

#define MODIFY_REG(REG, CLEARMASK, SETMASK)  
WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
    
#define READ_REG(REG)         ((REG))		// register에 들어있는 값을 의미
    
#define WRITE_REG(REG, VAL)   ((REG) = (VAL))	// == (*configregister) = VAL;
// register에 value값을 넣음
  • VAL == (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)) = 0b100 0100 0000 0100 0100 0100 0100 0100

 

  • configregister = 0x40011004에 이미 설정된 값이 있는데, 그 값중에 변화시키고 싶은 bit가 있음
  1. READ_REG를 통해 configregister에 있는 전체 값을 읽어옴 
    • *(configregister) = 1145324612 == 0b100 0100 0100 0100 0100 0100 0100 0100
  2. 바꾸고 싶은 bit들의 위치들만 1, 나머지는 0으로 만듦 
    • CLEARMASK = 15728640 == 0b1111 0000 0000 0000 0000 0000
  3. Bitwise NOT 연산
    • ~(CLEARMASK) = ~(0b1111 0000 0000 0000 0000 0000) = 0b1111 1111 0000 1111 1111 1111 1111 1111
    • 앞의 1들은 not 연산을 하기 전에 unsinged int의 쓰지 않던 (0이던) 앞 비트들
  4. 전체 값과 NOT 연산한 값을 Bitwise AND 연산
    • ((READ_REG(REG)) & (~(CLEARMASK)) = 0b100 0100 0000 0100 0100 0100 0100 0100
  5. 위의 결과값에 SETMASK를 Bitwise OR 연산
    • SETMASK = 0b0011 0000 0000 0000 0000 0000 == 3145728
    • ((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)) = 0b100 0100 0011 0100 0100 0100 0100 0100
  6. WRITE_REG를 통해 결과값을 *(configregister)에 대입
    • 1,144,276,036 == 0b100 0100 0011 0100 0100 0100 0100 0100

 

위의 과정을 bit masking 이라 한다

* Bit Masking : 정수의 2진수 표현을 활용해 집합의 원소들을 표현하는 방법

ex) {1, 4, 3} -> {1, 4} = 110, {3} = 001, {1, 3} = 101

 

결론 : 

MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));

위 코드는 

*(0x40011004) = (*(0x40011004) & ~(15 << 20)) | (3 << 20);

를 의미한다. 이는 

 /*Configure GPIO pin : GPIO_LED_Pin */
  GPIO_InitStruct.Pin = GPIO_LED_Pin;			
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;	
  GPIO_InitStruct.Pull = GPIO_PULLUP;			
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;	
  HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);

이 부분이 위 코드와 같다는 것을 의미한다

 

 

 

데이터 시트 중 Register boundary address부분

  • GPIO 제어를 하려면 GPIO Port A ~ G (0x4001 0800 ~ 0x4001 23FF) 에 접근을 해야 함
  • 우리가 접근한 주소는 GPIO Port C의 주소 (0x4001 1000 ~ 0x4001 13FF)

한 GPIO Port에 대한 Register map

typedef struct		// 위의 설계에 따라 구조체 구성을 함
{	
  __IO uint32_t CRL;
  __IO uint32_t CRH;
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;
  • GPIOx = 모든 GPIO Port들이 위의 구조를 가지고 있다는 뜻
  • 이번 강의에선 저 register들 중 CRH를 다룸

 

Port configuration register low / high

  • GPIO 0 ~ 7은 low, 8 ~ 15는 high
  • 4비트가 (MODE 2bit + CNFO 2 bit) GPIOx번을 설정함 
  • configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
    // 드라이버 설계자가 위의 데이터 시트를 보고 코드로 해석을 해놓은 것

    아래의 코드 또한 위의 데이터 시트를 토대로 만들어진 설정값들
  • /* Definitions for bit manipulation of CRL and CRH register */
    #define  GPIO_CR_MODE_INPUT         0x00000000u /*!< 00: Input mode (reset state)  */
    #define  GPIO_CR_CNF_ANALOG         0x00000000u /*!< 00: Analog mode  */
    #define  GPIO_CR_CNF_INPUT_FLOATING 0x00000004u /*!< 01: Floating input (reset state)  */
    #define  GPIO_CR_CNF_INPUT_PU_PD    0x00000008u /*!< 10: Input with pull-up / pull-down  */
    #define  GPIO_CR_CNF_GP_OUTPUT_PP   0x00000000u /*!< 00: General purpose output push-pull  */
    #define  GPIO_CR_CNF_GP_OUTPUT_OD   0x00000004u /*!< 01: General purpose output Open-drain  */
    #define  GPIO_CR_CNF_AF_OUTPUT_PP   0x00000008u /*!< 10: Alternate function output Push-pull  */
    #define  GPIO_CR_CNF_AF_OUTPUT_OD   0x0000000Cu /*!< 11: Alternate function output Open-drain  */
    
    /* defgroup GPIO_speed_define  GPIO speed define, brief GPIO Output Maximum frequency */
    #define  GPIO_SPEED_FREQ_LOW              (GPIO_CRL_MODE0_1) /*!< Low speed */
    #define  GPIO_SPEED_FREQ_MEDIUM           (GPIO_CRL_MODE0_0) /*!< Medium speed */
    #define  GPIO_SPEED_FREQ_HIGH             (GPIO_CRL_MODE0)   /*!< High speed */

 

  • 위처럼 ARM Core의 설계 내용이 담긴 데이터 시트를 토대로 source code를 작성함
  • 간단한 설정값만 넣으면 사용할 수 있게끔 다양한 변수들을 고려하며 library를 설계
  • 덕분에 STM32 IDE는 설정값만 조절하면 위 같은 소스 코드들이 생성되므로 편리
  • I2C, SPI등의 ARM Core를 통해 다루는 모든 것들이 위처럼 데이터 시트를 토대로 코드가 작성되어 있음

 

  • 보통 임베디드 개발자가 새로운 기능을 제어하는 과정 
    1. 데이터 시트를 보며 어떤 기능이 하는 역할을 공부 (이론 공부)
    2. 현재 사용중인 MCU에서 그 기능을 어떻게 제어하는지 공부
    3. 공부한 제어방법대로 소스 코드 작성
  • 하지만 위 방법은 시간이 오래 걸리므로, 샘플 소스가 있다면 그 코드의 실행결과를 분석하며 역으로 데이터 시트에서 추적을 하는 방법으로 시간을 단축할 수 있음

 

 

 

 

 

 


위 내용의 모든 출처는 유튜버 '[오제이 튜브]OJ Tube' 님께 있습니다.

https://www.youtube.com/watch?v=n5yUMV3fJuM&list=PLz--ENLG_8TNjRg1OtyFBvUyV4PHaKwmu&index=10