본문 바로가기

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

[오제이 튜브의 임베디드 실전 강의] 5강 혼자서 임베디드 고수 되는 법

728x90

1. 강의 목표

WritePin부분을 HAL Driver를 사용하지 않고 순수 코딩만으로 만들어보기

 

  • HAL Driver의 코드를 분석하고 Datasheet를 보며 필요한 것들만 뽑아 코드를 만들 것
MX_GPIO_Init();
HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 0);
  • 위 두개의 코드 분석
  • Data Sheet를 보며 해당하는 주소값을 보며 코딩을 해야 함
  • Embedded Programming은 Source Code는 몇 줄 안되나 이해하고 코드로 표현을 하기까지 수많은 공부가 필요
    • 회로, 컴퓨터 등의 알아야 할 배경 지식이 너무 많음
  • 코드를 분석하기 쉬운 방법
    • 소스가 어떤 환경에서 작동하는지 이해 후 환경 구성
    • Breakpoint를 찍어가며 Debugging 진행

 

2. HAL Driver 분석

* 아래 내용부터는 기본적인 C언어 지식이 필요함

  • 위 HAL_Init -> HAL_FLASH_PREFETCH_BUFFER_ENABLE 의 (FLASH->ACR |= FLASH_ACR_PRFTBE) 분석
  • 코드는 Core -> startup_stm32f103c8tx.s 에서부터 시작 (어셈블리)
    • .s : 어셈블리어 코드의 확장자. 소문자 's' 확장자는 Compiler가 출력한 어셈블리어 코드로,
      GCC 컴파일러가 컴파일 시 전처리가 필요없는 파일로 인식한다
    • .S : 사용자가 직접 작성한 어셈블리어 코드로, GCC 컴파일러가 전처리가 필요한 파일로 인식한다
  • 여러 경로를 거쳐 main.c 의 int main에 옴

 

2.1. Debugging

1. HAL_Init()에서 Ctrl + 좌클릭 (함수 정의 부분으로 이동)

2. HAL_Init()__HAL_FLASH_PREFETCH_BUFFER_ENABLE() 에 breakpoint 찍고 debugging 시작

 

두번째 if문의 defined들 중 하나가 define되어 있으면 __HAL_FLASH_PREFETCH_BUFFER_ENABLE( );로 들어오라는 뜻

  • 위의 #if문은 Linux Driver 구현시 많이 볼 문장들 : 다양한 칩들에 따라 속성을 변경하기 위해 define 사용
  • 우리가 사용하는 칩은 defined(STM32F103xB)에 해당 : __HAL_FLASH_PREFETCH_BUFFER_ENABLE();로 진입

위 처럼 Debugger가 걸리는 쪽엔 녹색, 안걸리는 쪽엔 회색이 들어옴

 

  • Linux Driver를 vi Editor 하나로 할 땐 Grep을 통해 분석을 해나감. breakpoint도 못찍어서 불편함
    • Grep : 윈도우에서(모든 파일 중에서) 내가 원하는 문자열 찾기
      경로상에 검색한 문자열이 포함된 곳을 찾는다

 

3. (FLASH->ACR |= FLASH_ACR_PRFTBE) 의 의미 해석

  • __HAL_FLASH_PREFETCH_BUFFER_ENABLE( ) 은 define 문이므로 함수 단위로 접근 불가

우리가 해석할 statement

  • 위 statement 해석을 위해 Editor 우측 상단의 'Expression' 창에 각 변수 및 표현을 추가

 

  • FLASH : 0x40022000에 접근하는 변수

 

 

  • ACR은 변수 이름으로 접근 불가 : 주소 확인
  • 위 사진은 (FLASH->ACR |= FLASH_ACR_PRFTBE) 의 각 변수들의 (주소)값을 나타낸 것
  • 해당 statement는 아래와 같이
*(40022000) = *(40022000) | 16;
  • 40022000 주소번지에 16을 쓴다는 뜻

 

2.1.1. FLASH_TypeDef의 의미

FLASH는 ((FLASH_TypeDef *)FLASH_R_BASE) 로 정의되어 있다 (맨 밑에 설명)

 

FLASH Structure. 각각 4byte

  • 레지스터의 이름을 정해서 Structure 형태로 만듦
  • 레지스터들을 FLASH의 맴버로 만들어 접근하기 편하게 함
  • 즉 위 문장의 의미는 FLASH 주소번지의 ACR에 16을 OR 연산하는 코드
    • ACR이 FLASH 번지의 첫번째 변수이므로 FLASH와 주소가 같았던 것

 

2.1.2. uint32_t의 의미

#define uint32_t unsinged_int	// 4byte 크기​

각 레지스터는 4byte의 크기 보유

  • ACR을 세팅하는 레지스터, KEYR을 세팅하는 레지스터 OPTKERYR을 세팅하는 레지스터등등이 0x40022000 부터 4byte씩 있다
  • RESERVED는 사용하지 않는 공간 
  • FLASH -> OBR |= 4; : FLASH의 OBR에 4를 넣음 

 

2.1.3. __IO의 의미

#define __IO volatile
  • 최적화 방지를 위해 사용 
  • Compiler는 compile시 프로그램의 실행 속도를 최대화하거나 프로그램이 차지하는메모리의 양을최소화하기 위해
    컴파일러 최적화(optimizing compiler)를 한다
  • 참고 : https://youngseong.tistory.com/125
 

[RIS] 1/5 (__attribute, stdint.h, ros::Timer)

모터 드라이버 MD200T의 Source Code를 분석하며 공부한 내용 1) __attribute__ 위 사진에 있는 __attribute__는 Unix/Linux의 GCC Compiler에 존재하는 메커니즘으로, compiler가 program에서 사용될 함수들을 더 주의해

youngseong.tistory.com

  • volatileCV Qualifier로, 변수 선언문 앞에 붙이면 해당 변수에 대해서는 어떠한 최적화 처리도 하지 않음
    • CV Qualifier : 변수의 성질을 바꾸는 역할을 하는 Keyword (Ex) Const, volatile)

access and modification semantics for variables and objects:

2.1.3.1. 최적화 방지를 하는 이유

int a = 3; 

a = 7; 
a = 8; 
a = 12;

printf(%d\n", a);
  • Compiler는 위의 코드 중 미사용되는 부분을 삭제하여

 

int a = 12;

printf(%d\n", a);
  • 이처럼 최적화를 시킴. 위의 경우에선 효율적이지만 GPIO를 제어할 때, 

 

*a = 0x03;
*a |= 1;	// a를 킴
*a |=0;		// a를 끔
*a |= 1;	// while문 안에서 키고 끄고를 반복
*a |=0;
*a |= 1;
*a |=0;
  • 위의 코드를 최적화하면

 

*a = 0x03;

*a |=0;
  • 가 되는데, 이러면 의도한 대로 동작을 하지 못함

 

a = 3;

while( ){
	if(a){
    statement;		// a가 특정 상황일 때 하는 동작
    }
}
  • 위와 같이 a의 환경에 따라 동작할 것을 지정했는데. a가 소스코드상 3에서 바뀌지 않을 때
  • a를 메모리에 넣지 않고 CPU 레지스터에 넣고 씀 (자주 쓴다고 생각해서)
  • 이렇게 되면 GPIO값에 따라 a가 변해야 되는데 변하지 않음

 

  • 위와 같은 상황을 방지하기 위해 #define __IO volatile 로 최적화 방지. volatile이 최적화 하지 말라는 뜻
    1. 최적화를 위한 코드 삭제 (중간 과정 삭제) 방지
    2. 어떤 변수를 자주 사용한다고 CPU 레지스터에 넣는 것을 방지하고 원래대로 메모리에 넣어서 사용하게 함

 

2.2. 결론

(FLASH->ACR |= FLASH_ACR_PRFTBE) == *(40022000) |= 16;
  • 위 코드의 왼쪽 부분은 오른쪽 동작을 하며
voltile int unsigned int* ACR = 40022000
*ACR |= 16;
  • 위와 같이 대체할 수 있다

 

2.2.1. FLASH_ACR_PRFTBE의 의미

  • 1을 FLASH_ACR_PRFTBE_Pos(4) 만큼 shift 이동 (bitwise shift) : 1 << 4;
  • 1을 옆으로 4칸 밀면 16이 된다 (16진수)

 

2.2.1.1. 1을 옆으로 4칸 이동하는 것의 의미 : Datasheet  확인

  • 강의 자료중 레퍼런스 메뉴얼을 킨 후 Ctrl + F로 ACR 검색 : 56페이지 

IDE에서 찾았던 구조체 그대로 나와있음

 

 

  • FLASH_ACR Register가 Prefetch와 Half Cycle Access를 활성화/비활성화 및
    CPU 주파수에 따라 Flash Memory Access Time을 제어 함을 알 수 있음
    • 아래와 같이 define 문의 이름에 'PREFETCH'가 들어감으로 Prefetch 기능에 연관이 있음을 유추 가능

 

 

  • 1을 옆으로 4칸 이동시키는 것의 의미는 60페이지에서 확인할 수 있다 
    1을 왼쪽으로 4칸 bitwise shift : 1이 PRFTBE로 이동
     
  • ACR : Access Control Register를 의미
  • voltile int unsigned int* ACR = 40022000 *ACR |= 16;
  • 즉  위 코드는 Prefetch buffer enable 레지스터를 제어하는 것
  • 1이 해당 레지스터에 입력됐으므로 Prefetch 활성화

 

2.2.1.2. Prefetch에 대한 이해

  • OS(Window)는 보조기억장치 (HDD, SSD)에 저장. 주기억장치(RAM)에 저장 시 전원을 끄면 데이터가 사라지므로
    • 부팅시 보조기억장치에 있는 메모리가 RAM으로 복사된 후 그 복사된 OS를 CPU가 실행시킴
  • Prefetch : SSD 보급 전 HDD를 읽는 속도를 높이기 위해 HDD의 데이터를 미리 메모리(RAM)에 가져오는 기술
    윈도우 부팅시 사용하는 시스템 파일과 자주 사용하는 응용 프로그램들의 정보 등을 먼저 읽어들여서 부팅이나 프로그램을 빠르게 실행되도록 도와주는 기능이다
  • pf 파일에는 실제 메모리로 읽어오는 파일이 하드디스크 어디에 있는지 그 주소가 저장되어 있어 실제 사용시에는 memory mapping만을 수행하여 사용할 수 있도록 해 실행속도를 향상시킴
  • 하지만 SSD의 보급 이후, SSD의 속도가 HDD가 Prefetch를 사용하는 것에 비해 훨씬 빨라서 SSD 사용시에는 Prefetch의 사용이 자동으로 비활성화된다

실제 Prefetch file들

 

2.3. 결론

#define __HAL_FLASH_PREFETCH_BUFFER_ENABLE()    (FLASH->ACR |= FLASH_ACR_PRFTBE)
  • 위의 코드는 아래와 같은 의미이고
voltile int unsigned int* reg = 40022000
*reg |= 16;
  • 이 코드는 Flash Access Control Register의 Prefetch buffer enable bit를 활성화 시킨다 

 

2.4. 마무리

  • HAL 드라이버 분석을 할 수 있으면 임베디드 고수가 될 수 있다
  • 이번 강의 내용은 선생님 혼자서 터득한 것. 선생님이 공부하신데로 공부하면 그만큼은 할 수 있다
    • 프로그래밍은 결국 혼자서 공부하는 시간이 중요. 강의를 그대로 따라하는게 아닌 앞으로의 공부 방향과 선생님의 공부 방식을 참고하여 내 공부 방식을 연구하는 것이 중요
  • 노하우가 중요한 것이 아닌 궁금증을 가지고 무조건 한다는 의지와 함께 그 궁금증을 푸는 것이 중요. 그러면 노하우는 따라옴. 안될 때도 있으나 그럴 땐 좌절하지 말고 잠시 넘어가고 나중에 다시 해보기
  • 이번 강의 내용들은 컴퓨터에 대한 지식들이 있어야 이해하기 쉽다
  • 학부생일땐 컴퓨터에 대한 지식을 많이 쌓아야 함
  • 웹상의 최적화 관련 문제는 결국 OS와 운영체제와 연결이 됨
  • C언어를 하면 하드웨어와 밀접한 언어를 공부하는 것이라 하드웨어와 익숙해져 분석에 용이해 진다 : 학부과정에 C언어가 있는 이유

  • 이번 강에서는 __HAL_FLASH_PREFETCH_BUFFER_ENABLE() 문장 하나를 해석
  • 이런 과정은 시간과 연구가 필요함. 즉 혼자 씹어먹는 시간이 필요함

 

3. etc

3.1. Grep을 이용한 __HAL_FLASH_PREFETCH_BUFFER_ENABLE( ) 의 FLASH 추적

FLASH 위에서 Ctrl + 좌클릭
FLASH_R_BASE는 Ctrl + 좌클릭 해도 안나옴 - 그랩 사용 필요

 

그랩 사용 : Ctrl + H 후 Search
FLASH_R_BASE 가 있는 곳으로 이동
그랩으로 AHBPERIPH_BASE 찾기
AHBPERIPH_BASE가 있는 곳으로 이동
PERIPH_BASE 가 있는 곳으로 이동

  • PERIPH_BASE = 0x40000000UL
#define FLASH               ((FLASH_TypeDef *)FLASH_R_BASE)

#define FLASH_R_BASE          (AHBPERIPH_BASE + 0x00002000UL)
// UL = Unsigned Long
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x00020000UL)

#define PERIPH_BASE           0x40000000UL
// 결과는 FLASH는 0x40022000의 메모리 번지에 접근한다는 뜻

 

 

 

 

 

 


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

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

 

참고 자료 : 

https://jakupsil.tistory.com/33

 

어셈블리 파일 확장자 .s와 .S는 다르다.

1주일도 넘게 멈춘 진도 최근 RTOS 공부를 시작하면서 어셈블리어로 작성된 코드를 다루게 되었습니다. 제대로 배운 적은 없지만, 많이 어려운 내용이 아니라서 그래도 따라갈 만하다고 생각했었

jakupsil.tistory.com