패리펄럴과 레지스터

gidori·2024년 6월 10일

패리펄럴 개념 소개

  • MCU가 제공하는 다양한 기능 예시

GPIO, ADC, 각종 통신(Uart, SPI, CAN, I2C), PWM, Watchdog, DMA, Timer, Interrupt, DAC...

위와 같은 기능들을 제공하는 하드웨어를 페리펄럴(Peripheral / 주변기기)라고 말하며, MCU 내부에 내장되어 있습니다.

이러한 페리펄럴들은 MCU 별로 이름이나 기능 구성이 다를 수 있습니다.

Datasheet, User manual, Reference Manual에서 페리펄럴의 종류, 원리, 사용법 확인 가능

예 1) 특정 MCU에서는 SPI와 Uart가 분리되어 2개의 패리펄럴로 구성되어 있지만, 또 다른 MCU에서는 SPI+Uart가 합쳐져서 1개의 패리펄럴로 구성있음.


예 2) CAN 통신 기능을 하는 페리펄럴은 MultiCAN, MCMCAN과 같이 여러개의 종류가 존재함.


예 3) 원하는 페리펄럴이 존재하지 않더라도 Uart to CAN 모듈과 같이 특정 모듈을 붙여서 사용할 수 있음.

즉, 사용자가 원하는 기능을에 따라 적절한 페리펄럴을 구성하고 있는 MCU 선정해야 합니다.

레지스터 이야기

  • 페리펄럴 레지스터

CPU 내 레지스터가 존재하긴 하지만 여기서 말하는 레지스터는 페리펄럴 내부에 존재하는 레지스터에 대해서 다룹니다.

페리펄럴 레지스터는 페리펄럴을 컨트롤하기 위해 존재하는 메모리로 Flash나 RAM처럼 고유 주소를 가지고 있어 이 주소를 통해 메모리에 접근이 가능합니다.

  • Port 페리펄럴 예시

Port 패리펄럴에 레지스터A, 레지스터B, 레지스터C가 존재한다고 할 때, 레지스터A의 값에 1을 쓰면 특정 pin에 5V를 줄 수있다.

int main(void)
{	
	// 레지스터 A의 주소를 포인터 변수 Reg_A에 저장
	unsigned int* Reg_A = (unsigned int*)100;
    
    while(1)
    {	
    	// Reg_A의 데이터를 1로 변경 -> 불이 켜짐
    	*Reg_A = 1;
        for(int i=0i<1000;i++)
        	;
        
        // Reg_A의 데이터를 1로 변경 -> 불이 꺼짐
        *Reg_A = 2;
        for(int i=0i<1000;i++)
        	;
    }
}
  • MCU 내부 온도 관련 레지스터 예시

DTSSTAT 레지스터의 주소는 0xF02401C0이고 RESULT에 해당하는 비트필드의 값을 읽어와 MCU 내부 온도를 확인한다. (유저 메뉴얼)

int main(void)
{	
	// RESULT 주소 필드를 포인터 변수 DTSSTAT에 저장
	unsigned char* DTSSTAT = (unsigned char*)0xF02401C0;
	unsigned int* Reg_A = (unsigned int*)100;
    float temperature;
    
    while(1)
    {	
    	// DTSSTAT의 DATA 값을 읽고, 메뉴얼에 나온 공식을 이용하여 섭씨로 변환
    	temperature = (*DTSSTAT) / 7.505 - 273.15;
    }
    
    if temperature > 40
    	*Reg_A = 1;
    else
    	*Reg_A = 2;
}

레지스터-비트필드 해석하기

  • 레지스터 메뉴얼 문서 해석하기

레지스터 내부는 여러개의 비트 필드로 구성 됨
각 비트 필드 마다 서로 다른 역할을 가지고 있음

Atmega128 DataSheet

8bit로 이루어진 ADCSRA 레지스터에는 모두 다른 기능을 하는 비트 필드로 구성되어져 있으며, 각 기능은 DataSheet에 명시되어져 있다.

TI TMS320C28003C Reference Maunal

16bit로 이루어진 DACCTL 레지스터에는 사용하지 않는 RESERVED 영역을 제외하고 총 4개의 기능을 하는 비트 필트로 구성되어져 있으며, 특정 주소 필드틑 4개의 bit를 사용한다.

메뉴얼문서를 볼 때 놓치기 쉬운 몇가지

  • 참고 용어 설명

Set : 값이 1이 되는 것
Clear/Reset : 값이 0이 되는 것

  • R/W 속성에 대하여

R/W -> pointer 로 접근하여 읽고 쓰기가 가능
R -> pointer 로 접근하여 읽고만 가능
W -> pointer 로 접근하여 쓰기만 가능

0 Field에 비트 필드는 R Type으로 선언되어 있으나, 해당 값을 변경하면 정상적으로 동작하지 않음.

void main(void)
{
	unsigned int* KSRTCLR = 0xFF0000EC;
    // 읽기만 가능한 비트 필드에 쓰기 적용
    *(KSRTCLR) = 1;
    
    while(1)
    {
    	//...
    }
}
  • 특이한 비트 필드들

STATUS 레지스터의 TXF 비트 필드는 RW 모두 가능하지만, "Writing 0 has no effect" 0을 쓰면 효과가 없다고 명시되어 있음.
하지만, FLAGSCLEAR 레지스터의 TXC 비트 필드에 1을 쓰면 TXF 비트가 CLEAR 즉, 0을 쓸 수 있다.

rc_w1은 어떤 역할을 하는지 명시되어 있지 않음. 이 때는 Reference Manual의 약어 list를 확인

  • Write Protection 속성에 대하여

USC 혹은 SUPLEV 비트 필드를 변경하기 위해서는 CPWC 먼저 1을 쓴 뒤 변경해야한다.

실제 데이터시트와 함께 MCU SW 구현 개념 잡기기

  • 목표 : LED 제어
  1. 핀 선정
    Atmega128의 PA1 pin 선정

  1. DataSheet 확인
    DDRx 레지스터 안에 있는 DDxn 값에 따라 pin의 방향(input/output)이 설정된다.
    DDxn에 1이 쓰여지면 Pxn 핀은 ouput모드가 된다.

DDRx, DDxn이 무슨 뜻인가?
x는 A, B, C ...., n은 1, 2, 3 .... 을 의미
따라서, PA1(=PORTA1) 핀을 ouput 모드로 설정하고 싶다면 DDRA 레지스터에의 DDA1 비트 필트에 1을 써야한다.

  1. C언어 작성

int main(void)
{	
	// PORT_A, DDR_A 비트 필트 포인터 변수 선언
	unsigned char* PORT_A = (unsigned char*) 0x3B;
    unsigned char* DDR_A = (unsigned char*) 0x3A;
	
    // DDR_A1에 1을 선언함으로써 Write 모드
    *(DDR_A) = 2;
    
    while (1)
    {	
    	// PORT_A에 1을 선언함으로써 출력 On
    	*(PORT_A) = 2; // 이진법 변환 -> 00000010
        for(int i=0;i<100<i++)
        ;
        
        // PORT_A에 1을 선언함으로써 출력 Off
        *(PORT_A) = 0; // 이진법 변환 -> 00000000
    	for(int i=0;i<100<i++)
        ;
    }
}

레지스터 셋팅과 Atmega128 IDE에 대하여

  • LED 제어를 Atmega128 IDE에서 작성하면 아래의 코드와 같다.
#include <avr/io.h>

int main(void)
{
	DDRA = 2;
    
    while(1)
    {
    	PORTA = 2;
        
        while(1)
        {
        	PORTA = 2;
            for(int i=0;i<100<i++)
        	;
            
            PORTA = 1;
            for(int i=0;i<100<i++)
        	;
        }
    }
}
  • 왜 주소를 명시하지 않고도 LED 제어를 할 수 있을까?

바로 avr/io.h 헤더파일에 모두 정의되어 있다.
여러 헤더파일이 중첩되어 있고 이를 해석하면 아래와 같음.

#define DDRA _SFR_IO8(0x1A)
#define _SFR_IO8(id_addr) _MMIO_BYTE((id_addr) + _SFR_OFFSET)
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_OFFSET 0x20

1. DDRA = _SFR_IO8(0x1A)
2. DDRA = _MMIO_BYTE((0x1A) + _SFR_OFFSET)
3. DDRA = _MMIO_BYTE((0x1A) + 0x20) = _MMIO_BYTE(0x3A)\
4. DDRA = (*(volatile uint8_t *)(0x3A))
volatile은 컴파일 시 컴파일러가 자동으로 명령어를 최적화 하는 것을 막는 것을 의미하며 생략해도 무방
uint8_t은 8bit unsigned charchar와 동일
5. DDRA = (*(char *)(0x3A))

Driver SW(HAL SW)

기존 코드(A)

int main()
{
	unsigned int* GPIOA_MODER = (unsigned int*)0x48000000;
    unsigned int* GPIOA_ODR = (unsigned int*)0x48000014;
    
    *(GPIOA_MODER) = 4;
    
    while(1)
    {
    	*(GPIOA_ODR) = 2;
    	for(int i=0;i<100<i++)
    		;
            
		*(GPIOA_ODR) = 2;
		for(int i=0;i<100<i++)
	    	;
	}  
}

수정(B)

#define GPIOA 0
#define GPIOA 1

#define GPIO_Output 1
#define GPIO_Input 0

#define GPIO_High 1
#define GPIO_Low 1

void Set_Gpio_Mode(int PinName, int PinNumber, int mode);
void Set_Gpio_Output(int PinName, int PinNumber, int level);

int main()
{
	Set_Gpio_Mode(GPIOA, 1, GPIO_OUTPUT);
    
    while(1)
    {
    	Set_Gpio_Output(GPIOA, 1, GPIO_High);
    	for(int i=0;i<100<i++)
    		;
            
		Set_Gpio_Output(GPIOA, 1, GPIO_Low);
		for(int i=0;i<100<i++)
	    	;
	}  
}

A -> B로 변경되면서 코드가 더욱 알기 쉽게 바뀐 것을 확인할 수 있다. 이러한 것을 사용자가 직접 구현하는 것이 아닌, MCU를 제작하는 회사가 이를 제공하는 것을 Driver SW라고 한다.

회사Driver SW
STM32HAL Driver SW
아두이노Driver SW
TIC2000ware
Infineon AurixILLD SW

그럼... 굳이 MCU 내부 동작을 알아야 하나? Dirver SW만 있어도 모든 개발을 할 수 있을 것 같은데?

그럼에도.. MCU 내부 동작을 공부해야 하는 이유

  • 디버깅 & 문제해결 능력
    문제가 발생했을 때, MCU 내부 동작을 이해한 상태로 레지스터 값을 확인하며 디버깅하는 것이 중요하며, SW에서 문제가 발생했는 지, HW에서 문제가 발생했는 지 빠르게 확이할 수 있음.

  • Driver SW에서 제공되지 않은 MCU의 기능
    MCU상에서는 제공하는 기능이지만, Driver SW에서는 제공하지 않는 기능은 직접 구현하여야 한다.

  • SW 최적화

디버거를 통한 레지스터 확인

  • 디버깅을 할 시, SFR -> 내부 페리펄럴 -> 레지스터 값을 확인할 수 있으므로 이를 활용하여 MCU SW를 효율적으로 진행할 수 있다.
출처 : 인프런-신입사원에게 들려주는 - MCU SW 직무 기초 개념완성

0개의 댓글