GPIO, ADC, 각종 통신(Uart, SPI, CAN, I2C), PWM, Watchdog, DMA, Timer, Interrupt, DAC...
위와 같은 기능들을 제공하는 하드웨어를 페리펄럴(Peripheral / 주변기기)라고 말하며, MCU 내부에 내장되어 있습니다.
이러한 페리펄럴들은 MCU 별로 이름이나 기능 구성이 다를 수 있습니다.
예 1) 특정 MCU에서는 SPI와 Uart가 분리되어 2개의 패리펄럴로 구성되어 있지만, 또 다른 MCU에서는 SPI+Uart가 합쳐져서 1개의 패리펄럴로 구성있음.
예 2) CAN 통신 기능을 하는 페리펄럴은 MultiCAN, MCMCAN과 같이 여러개의 종류가 존재함.
예 3) 원하는 페리펄럴이 존재하지 않더라도 Uart to CAN 모듈과 같이 특정 모듈을 붙여서 사용할 수 있음.
즉, 사용자가 원하는 기능을에 따라 적절한 페리펄럴을 구성하고 있는 MCU 선정해야 합니다.
CPU 내 레지스터가 존재하긴 하지만 여기서 말하는 레지스터는 페리펄럴 내부에 존재하는 레지스터에 대해서 다룹니다.
페리펄럴 레지스터는 페리펄럴을 컨트롤하기 위해 존재하는 메모리로 Flash나 RAM처럼 고유 주소를 가지고 있어 이 주소를 통해 메모리에 접근이 가능합니다.
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++)
;
}
}

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;
}
레지스터 내부는 여러개의 비트 필드로 구성 됨
각 비트 필드 마다 서로 다른 역할을 가지고 있음

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

16bit로 이루어진 DACCTL 레지스터에는 사용하지 않는 RESERVED 영역을 제외하고 총 4개의 기능을 하는 비트 필트로 구성되어져 있으며, 특정 주소 필드틑 4개의 bit를 사용한다.
Set : 값이 1이 되는 것
Clear/Reset : 값이 0이 되는 것
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를 확인


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


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

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++)
;
}
}
#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++)
;
}
}
}
바로 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 char로 char와 동일
5. DDRA = (*(char *)(0x3A))
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++)
;
}
}
#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 |
|---|---|
| STM32 | HAL Driver SW |
| 아두이노 | Driver SW |
| TI | C2000ware |
| Infineon Aurix | ILLD SW |
디버깅 & 문제해결 능력
문제가 발생했을 때, MCU 내부 동작을 이해한 상태로 레지스터 값을 확인하며 디버깅하는 것이 중요하며, SW에서 문제가 발생했는 지, HW에서 문제가 발생했는 지 빠르게 확이할 수 있음.
Driver SW에서 제공되지 않은 MCU의 기능
MCU상에서는 제공하는 기능이지만, Driver SW에서는 제공하지 않는 기능은 직접 구현하여야 한다.
SW 최적화