Analog to Digital Converter (ADC)#
ADC Example (ADC로 읽은 전압값의 범위에 따라 다른 색의 LED 출력)#
- 새로운 예제를 위한 프로젝트를 생성한다.
- 원하는 동작을 위해 레지스터와 메모리에 직접 접근해서 값을 써야한다.
- Board Schematic을 통해 해당 보드의 VR(가변 저항)에 대한 정보를 파악한다.
- 사용할 ADC 모듈의 동작 원리를 파악하고 메모리 맵을 분석한다.
- 분석 결과를 활용해 임베디드 프로그래밍을 한다.
1. Schematic 분석#
1-1. VR#
- Easy Module Shield V1 보드에서 ROTATION(가변 저항)은 A0핀과 연결되어 있다.
- 본 실습에서 A0 핀은 해당 보드의 PTD1 핀에 연결한다.
- Schematic에 따라, 가변 저항은 ADC0_P0 (Precision1_ADC0) 채널에 연결된다.
따라서 Easy Module Shield V1의 Rotation (VR)을 사용하기 위해, PTD1를 사용한다. - 보드에 연결된 가변 저항을 돌릴 경우 ADC0_P0에 인가되는 전압의 크기가 바뀐다.
1-2. RGB#
- Easy Module Shield V1 보드에서 RGB LED는 각각 D9, D10, D11핀과 연결되어 있다. (사진과 다름)
- 본 실습에서 RGB LED의 핀은 각각 해당 보드의 PTB17(G), PTC10(R), PTB16(B) 핀에 연결한다.
- 각 RGB 핀들은 GPIO 74, 49, 48에 연결된다.
2. Data sheet 분석#
S32K3xx 보드의 ADC는 총 3개의 그룹으로 나뉜다. 본 실습에서는 앞선 schematic 분석에서 확인했듯, ADC0_P0(Precision 0) 에 속한다.
실습에서 다루는 'S32K312'보드는 ADC_0,1을 지원하고 있으며, 최대로 분주 가능한 주파수는 120MHz이다. 본 실습에서는 24Mhz를 분주하여 사용해볼 것이다.
2-1. SIUL 설정#
-
ADC0_P0을 사용하기 위한 SIUL 관련 설정은 하지 않아도 된다.
-
RGB LED를 GPIO로 사용하기 위한 SIUL 관련 설정은 앞선 "01_MPD_S32K312_GPIO" 실습에서 다뤘으므로 자세한 내용은 생략한다. (MSCR, GPD0 설정)
2-2. MC_ME 모듈 설정#
- MC_ME (Mode Entry module's) 모듈을 통해 주변 장치의 클럭 상태를 제어한다.
- ADC0에 해당하는 MC_ME 모듈 설정을 해주어야만 해당 클럭을 사용할 수 있다.
- MC_ME base address: 402D_C000h
-
PRTN0_PCONF (Partition 0 Process Configuration Register)
- PRTN0_PCONF Register 주소: 402D_C100h (402D_C000h + 100h)
- Partition 0에 해당하는 하드웨어 프로세스의 구성을 설정한다.
- PCE bit를 설정함으로써 해당 파티션 내에서 사용되는 코어나 주변 장치에 대한 클럭을 활성화한다.
- 이미 enable 되어 있으므로 따로 설정할 필요는 없다.
-
PRTN0_PUPD (Partition 0 Process Update Register)
- PRTN0_PUPD Register 주소: 402D_C104h (402D_C000h + 104h)
- PCUD bit를 set하여 PRTN0_PCONF에서 설정한 하드웨어 프로세스를 trigger 한다.
-
PRTN0_CONFB1_CLKEN (Partition 0 COFB Set 1 Clock Enable Register)
- PRTN0_CONFB1_CLKEN Register 주소: 402D_C134h (402D_C000h + 134h)
- Partition 0에서 특정 비트에 해당하는 주변 장치의 클럭을 활성화
- ADC0에 해당하는 비트 (REQ40)를 set한다.
-
CTL_KEY (Control Key Register)
- CTL_KEY Register 주소: 402D_C000 (402D_C000h + 0h)
- 위에서 변경한 사항을 적용하려면 CTL_KEY 레지스터에 유효한 키를 입력해야 한다.
- 유효한 키 값이 입력되면 PRTN0_PCONF 및 PRTN0_PUPD에 따라 하드웨어 프로세스가 실행된다.
- 유효 제어 키 (0x5AF0)를 CLT_KEY 레지스터에 기록하고
- 제어 키의 반전된 값 (0xA50F)를 CTL_KEY 레지스터에 기록하여 프로세스 사용 준비를 완료한다.
2-3. MCR (Main Configuration Regiter) 설정#
-
ADC0_MCR Register 주소: 400A_0000h (400A_0000h + 0h)
-
ADC0_MCR Register 구조:
-
PWDN (Power Down) bit를 set 하면 ADC 아날로그 회로의 전원을 차단하여 전력 소비를 줄인다.
- ADC configuration은 Power Down state에서 벗어나고 진행되어야 한다.
- ADCLKEL bits를 통해 module clock (입력 클럭)을 분기하여 conversion clock을 생성한다.
- 실습에선 ratio = 1 (00b)로 설정한다.
-
ADCLKEL bits는 Power Down state에서 설정해야 하므로 PWDN clear 전에 하도록 한다.
-
NSTART bit는 normal conversion을 시작하는 비트이다.
-
Single converstion에서 NSTART bit를 set하여 변환을 시작하고 자동으로 clear 된다.
-
MODE bit를 통해 Normal Conversion Mode를 설정한다.
- 해당 실습에서는 Single conversion으로 설정하여 변환 후 ADC가 IDLE 상태가 되도록 한다.
- 해당 실습에서는 Single conversion으로 설정하여 변환 후 ADC가 IDLE 상태가 되도록 한다.
2-4. NCMR0 (Normal Conversion Enable For Precision Inputs Register) 설정#
-
ADC0_NCMR0 Register 주소: 400A_00A4h (400A_0000h + A4h)
-
ADC0_P0 채널을 사용하기 때문에 precision input 0에 해당하는 NCMR0[CH0]를 set한다.
- 채널을 설정함으로써 MCR[NSTART]를 통해 conversion을 시작할 수 있다.
2-5. CTR0 (Conversion Timing For Precision Inputs Register) 설정#
-
ADC0_CTR0 Register 주소: 400A_0094h (400A_0000h + 94h)
-
Precision inputs의 sample time (clock cycle)을 설정한다.
- 실습에서는 Default 값인 22 ADC clock으로설정한다.
2-6. CALBISTREG (Control And Calibration Status Register) 설정#
-
ADC0_CALBISTREG Register 주소: 400A_03A0h (400A_0000h + 3A0h)
-
RESN bits를 통해 conversion resolution을 설정한다.
- 실습에서는 Default 값인 12-bit (001b)으로 설정한다.
- 실습에서는 Default 값인 12-bit (001b)으로 설정한다.
2-7. PCDR0 (Precision Input 0 Conversion Data Register) 설정#
-
ADC0_PCDR0 Register 주소: 400A_0100h (400A_0000h + 100h + (0 x 4h))
-
Precisioin input 0의 변환데이터가 CDATA field에 저장된다.
2-8. System Clock (FIRC) 설정#
ADC는 하나의 module clock에 의해 제어된다.
내부적으로 ADC 회로는 conversion clock에 의해 제어되며, 이 clock은 module clock에서 분주된다.
Fast internal RC oscillator (FIRC)로부터 FIRC_CLK 생성하여 사용
MC_CGM 모듈에서 ADC0에 사용할 module clock(CORE_CLK) 선택
3. 프로그래밍#
3-1. ADC.h#
- ADC.h 파일에는 앞서 분석한 Schematic과 Data Sheet를 바탕으로 만들어진 Define과 함수의 원형 선언을 담은 코드들이 담겨있다.
- ADC.h
#ifndef ADC_H_ #define ADC_H_ #include <stdint.h> #define ADC0_BASE (0x400A0000UL) #define CONFIG_GPR_BASE (0x4039C000UL) #define MC_CGM_BASE (0x402D8000UL) #define ADC0_MCR (*(volatile unsigned*)(ADC0_BASE + 0x00)) #define ADC0_MSR (*(volatile unsigned*)(ADC0_BASE + 0x04)) #define ADC0_CTR0 (*(volatile unsigned*)(ADC0_BASE + 0x94)) #define ADC0_NCMR0 (*(volatile unsigned*)(ADC0_BASE + 0xA4)) #define ADC0_PCDR0 (*(volatile unsigned*)(ADC0_BASE + 0x100)) #define ADC0_CALBISTREG (*(volatile unsigned*)(ADC0_BASE + 0x3A0)) #define CONFIG_REG_GPR (*(volatile unsigned*)(CONFIG_GPR_BASE + 0x64)) #define MC_CGM_MUX_0_CSC (*(volatile unsigned*)(MC_CGM_BASE + 0x300)) #define MC_CGM_MUX_0_DC_0 (*(volatile unsigned*)(MC_CGM_BASE + 0x308)) // MC_ME Registers #define MC_ME_BASE (0x402DC000UL) #define MC_ME_PRTN0_PCONF (*(volatile uint32_t *)(MC_ME_BASE + 0x100)) #define MC_ME_PRTN0_PUPD (*(volatile uint32_t *)(MC_ME_BASE + 0x104)) #define MC_ME_PRTN0_COFB1_CLKEN (*(volatile uint32_t *)(MC_ME_BASE + 0x134)) #define MC_ME_MCTL (*(volatile uint32_t *)(MC_ME_BASE + 0x000)) #define MC_ME_KEY 0x5AF0 #define MC_ME_KEY_INV 0xA50F #define REQ40_BIT 8 #define PWDN_BIT 0 #define ADCLKSEL_BITS 1 #define NSTART_BIT 24 #define MODE_BIT 29 #define ADCSTATUS_BITS 0 #define CH0_BIT 0 #define INPSAMP_BITS 0 #define RESN_BITS 29 void Enable_FIRC_Clk(void); void ADC0_CLK_24MHZ(void); void ADC0_init(void); void adc_start(void); uint16_t read_adc0_ch0(void); #endif /* ADC_H_ */
3-2. firc_clk Enable#
- 아래의 코드들은 모두 ADC.c 파일에서 이용되는 소스코드의 함수들이다.
- Enable_FIRC_Clk() 코드
void Enable_FIRC_Clk(void) { MC_ME_PRTN0_PUPD |= 0x1; /* Trigger the hardware process */ MC_ME_PRTN0_COFB1_CLKEN |= (1 << REQ40_BIT); /* clock enable control for ADC_0 in partition 0 */ MC_ME_MCTL |= MC_ME_KEY; MC_ME_MCTL |= MC_ME_KEY_INV; }
3-3. firc_clk 분기 설정#
void ADC0_CLK_24MHZ(void) {
CONFIG_REG_GPR |= (0b11);
MC_CGM_MUX_0_CSC &= ~(0b1111 << 24);
MC_CGM_MUX_0_DC_0 |= (1 << 31);
}
3-4. ADC0를 사용하기 위한 초기 설정을 한다. (ADC0_init())#
- ADC0_MCR 레지스터를 통해 conversion clock과 conversion mode를 설정한다.
- ADC0_NCMR0 레지스터를 통해 변환 채널(CH0)을 선택한다.
- ADC0_CTR0 레지스터를 통해 ADC의 sample time을 설정한다.
- ADC0_CALBISTREG 레지스터를 통해 ADC의 해상도를 설정한다.
- ADC0_init()
void ADC0_init(void) { /* Power Down 상태에서 설정 변경 */ ADC0_MCR |= (1 << PWDN_BIT); /* 변환 클록 설정 */ ADC0_MCR &= ~(0b11 << ADCLKSEL_BITS); /* Select conversion clock 1 module clock */ /* Power Down 해제 -> Functional 상태로 변경 */ ADC0_MCR &= ~(1 << PWDN_BIT); /* 해상도 설정 (기존 값 초기화 후 12비트 설정) */ ADC0_CALBISTREG &= ~(0b111 << RESN_BITS); // 비트 초기화 ADC0_CALBISTREG |= (0b001 << RESN_BITS); // 12비트 해상도로 설정 /* 단일 변환 모드 설정 */ ADC0_MCR &= ~(1 << MODE_BIT); /* Select Single-shot(default) */ ADC0_MCR &= ~(1<<30); //WLSIDE 비트 0으로 명시적으로 초기화 , 오른쪽 정렬 ADC0_MCR &= ~(1<<11); //AVGEN 비트 0으로 명시적으로 초기화 /* 변환할 채널 선택 */ ADC0_NCMR0 |= (1 << CH0_BIT); /* Selects precision input 0 for conversion */ }
3-5. ADC0를 동작시키는 함수를 생성한다. (adc_start())#
- 장치가 올바르게 작동하기 위해 현재 conversion이 완료된 후에만 새로운 conversion 시작을 해야한다.
- ADC0_MSR 레지스터의 NSTART_BIT를 체크하여 변환이 완료되면
ADC0_MCR 레지스터의 NSTART_BIT를 set하여 해당 채널에 대해 ADC0를 동작시킨다. - adc_start() 코드
void adc_start(void) { while ((ADC0_MSR & (1 << NSTART_BIT))) {} /* Wait until completion of running/current conversion */ ADC0_MCR |= (1 << NSTART_BIT); /* Starts conversion */ }
3-6. ADC 결과값을 읽는 함수를 생성한다. (read_adc0_ch0)#
- ADC0_PCDR0 레지스터의 CDATA field에 저장되어 있는 conversion 결과값을 읽어온다.
uint16_t read_adc0_ch0(void) { return (ADC0_PCDR0 >> 3); }
3-7. LED Example을 사용하기 위한 SIUL2_init() 코드#
- VR의 입력 범위에 따라 LED 색이 바뀌는 예제이므로, Green, Red, Blue LED에 대한 GPIO 설정을 한다.
- Define for SIUL2
#include "ADC.h" // SIUL2 Registers #define SIUL2_BASE (0x40290000UL) #define SIUL2_MSCR_BASE (SIUL2_BASE + 0x240) #define SIUL2_MSCR48 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0xC0)) // BLUE, PTB16 #define SIUL2_MSCR49 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0xC4)) // GREEN, PTB17 #define SIUL2_MSCR74 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x128)) // RED, PTC10 #define SIUL2_MSCR96 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x180)) //ADC, PTD0 #define SIUL2_GPDO_BASE (SIUL2_BASE + 0x1300) #define SIUL2_GPDO48 (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x33)) //RGB - BLUE, PTB16 #define SIUL2_GPDO49 (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x32)) //RGB - RED, PTB17 #define SIUL2_GPDO74 (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x49)) //RGB - GREEN, PTC10 // MSCR Bits #define OBE_BIT 21 // Output Buffer Enable #define SSS_BITS 0 // Source Signal Select
- SIUL2_init() 코드
void SIUL2_init(void){ // GREEN_LED SIUL2_MSCR49 &= ~(0b1111 << SSS_BITS); // GPIO SIUL2_MSCR49 |= (1 << OBE_BIT); // output // BLUE_LED SIUL2_MSCR48 &= ~(0b1111 << SSS_BITS); // GPIO SIUL2_MSCR48 |= (1 << OBE_BIT); // output // RED_LED SIUL2_MSCR74 &= ~(0b1111 << SSS_BITS); // GPIO SIUL2_MSCR74 |= (1 << OBE_BIT); // output }
3-8. 동작에 따라 ‘main’ 함수를 구현한다.#
- 앞선 ADC 관련 함수들과 GPIO에 대한 함수를 실행하고 VR의 입력 범위에 따라서 서로 다른 색의 LED를 켠다.
int main(void){ Enable_FIRC_Clk(); ADC0_CLK_24MHZ(); SIUL2_init(); ADC0_init(); uint16_t adcResult = 0; while(1){ adc_start(); adcResult = read_adc0_ch0(); if(adcResult > 3000){ // B turn ON, R, G turn OFF SIUL2_GPDO48 = 1; SIUL2_GPDO49 = 0; SIUL2_GPDO74 = 0; } else if(adcResult > 2000){ // G turn ON, G, B turn OFF SIUL2_GPDO48 = 0; SIUL2_GPDO49 = 1; SIUL2_GPDO74 = 0; } else if(adcResult > 1000){ // R turn ON, R, B turn OFF SIUL2_GPDO48 = 0; SIUL2_GPDO49 = 0; SIUL2_GPDO74 = 1; } else{ // Turn off all LEDs SIUL2_GPDO48 = 0; SIUL2_GPDO49 = 0; SIUL2_GPDO74 = 0; } } }
4. 실습 결과
#
- Easy Module Sheild V1에 연결된 VR의 입력 범위에 따라서 RGB 값들이 출력됨을 확인할 수 있다.