Skip to content

Analog to Digital Converter (ADC)#

ADC Example (ADC로 읽은 전압값의 범위에 따라 다른 색의 LED 출력)#

  1. 새로운 예제를 위한 프로젝트를 생성한다.
  2. 원하는 동작을 위해 레지스터와 메모리에 직접 접근해서 값을 써야한다.
  3. Board Schematic을 통해 해당 보드의 VR(가변 저항)에 대한 정보를 파악한다.
  4. 사용할 ADC 모듈의 동작 원리를 파악하고 메모리 맵을 분석한다.
  5. 분석 결과를 활용해 임베디드 프로그래밍을 한다.

1. Schematic 분석#

1-1. VR#

  • Easy Module Shield V1 보드에서 ROTATION(가변 저항)은 A0핀과 연결되어 있다.
    rotation_vr
  • 본 실습에서 A0 핀은 해당 보드의 PTD1 핀에 연결한다.
    ptd1_pin shield_v1
  • Schematic에 따라, 가변 저항은 ADC0_P0 (Precision1_ADC0) 채널에 연결된다.
    따라서 Easy Module Shield V1의 Rotation (VR)을 사용하기 위해, PTD1를 사용한다. adc0_p0
  • 보드에 연결된 가변 저항을 돌릴 경우 ADC0_P0에 인가되는 전압의 크기가 바뀐다.

1-2. RGB#

  • Easy Module Shield V1 보드에서 RGB LED는 각각 D9, D10, D11핀과 연결되어 있다. (사진과 다름) rgb_led
  • 본 실습에서 RGB LED의 핀은 각각 해당 보드의 PTB17(G), PTC10(R), PTB16(B) 핀에 연결한다.
  • 각 RGB 핀들은 GPIO 74, 49, 48에 연결된다.
    schematic_rgb

2. Data sheet 분석#

adc_base01

S32K3xx 보드의 ADC는 총 3개의 그룹으로 나뉜다. 본 실습에서는 앞선 schematic 분석에서 확인했듯, ADC0_P0(Precision 0) 에 속한다.

adc_base02

실습에서 다루는 'S32K312'보드는 ADC_0,1을 지원하고 있으며, 최대로 분주 가능한 주파수는 120MHz이다. 본 실습에서는 24Mhz를 분주하여 사용해볼 것이다.

2-1. SIUL 설정#

  • ADC0_P0을 사용하기 위한 SIUL 관련 설정은 하지 않아도 된다. adc0_sss

  • RGB LED를 GPIO로 사용하기 위한 SIUL 관련 설정은 앞선 "01_MPD_S32K312_GPIO" 실습에서 다뤘으므로 자세한 내용은 생략한다. (MSCR, GPD0 설정) adc0_sss

2-2. MC_ME 모듈 설정#

  • MC_ME (Mode Entry module's) 모듈을 통해 주변 장치의 클럭 상태를 제어한다.
  • ADC0에 해당하는 MC_ME 모듈 설정을 해주어야만 해당 클럭을 사용할 수 있다.

Table_260

  • MC_ME base address: 402D_C000h

mc_me_base_address

  • PRTN0_PCONF (Partition 0 Process Configuration Register)

    • PRTN0_PCONF Register 주소: 402D_C100h (402D_C000h + 100h)
    • Partition 0에 해당하는 하드웨어 프로세스의 구성을 설정한다.
    • PCE bit를 설정함으로써 해당 파티션 내에서 사용되는 코어나 주변 장치에 대한 클럭을 활성화한다.
    • 이미 enable 되어 있으므로 따로 설정할 필요는 없다.

prtn0_pconf pce

  • PRTN0_PUPD (Partition 0 Process Update Register)

    • PRTN0_PUPD Register 주소: 402D_C104h (402D_C000h + 104h)
    • PCUD bit를 set하여 PRTN0_PCONF에서 설정한 하드웨어 프로세스를 trigger 한다.

prtn0_pupd pcud

  • 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한다.

prtn0_confb1_clken req40

  • 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 레지스터에 기록하여 프로세스 사용 준비를 완료한다.

ctl_key key_bits

2-3. MCR (Main Configuration Regiter) 설정#

  • ADC0_MCR Register 주소: 400A_0000h (400A_0000h + 0h) mcr_address

  • ADC0_MCR Register 구조: mcr_structure

  • 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 전에 하도록 한다. mcr_1

  • NSTART bit는 normal conversion을 시작하는 비트이다.

  • Single converstion에서 NSTART bit를 set하여 변환을 시작하고 자동으로 clear 된다. mcr_2

  • MODE bit를 통해 Normal Conversion Mode를 설정한다.

    • 해당 실습에서는 Single conversion으로 설정하여 변환 후 ADC가 IDLE 상태가 되도록 한다. mcr_3

2-4. NCMR0 (Normal Conversion Enable For Precision Inputs Register) 설정#

  • ADC0_NCMR0 Register 주소: 400A_00A4h (400A_0000h + A4h) ncmr01 ncmr02

  • ADC0_P0 채널을 사용하기 때문에 precision input 0에 해당하는 NCMR0[CH0]를 set한다.

  • 채널을 설정함으로써 MCR[NSTART]를 통해 conversion을 시작할 수 있다. ncmr03

2-5. CTR0 (Conversion Timing For Precision Inputs Register) 설정#

  • ADC0_CTR0 Register 주소: 400A_0094h (400A_0000h + 94h) ctr01 ctr02

  • 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) calbistreg1

  • RESN bits를 통해 conversion resolution을 설정한다.

    • 실습에서는 Default 값인 12-bit (001b)으로 설정한다. calbistreg2

2-7. PCDR0 (Precision Input 0 Conversion Data Register) 설정#

  • ADC0_PCDR0 Register 주소: 400A_0100h (400A_0000h + 100h + (0 x 4h)) pcdr01 pcdr02

  • Precisioin input 0의 변환데이터가 CDATA field에 저장된다. pcdr03

2-8. System Clock (FIRC) 설정#

ADC는 하나의 module clock에 의해 제어된다.
내부적으로 ADC 회로는 conversion clock에 의해 제어되며, 이 clock은 module clock에서 분주된다.
firc1

Fast internal RC oscillator (FIRC)로부터 FIRC_CLK 생성하여 사용
MC_CGM 모듈에서 ADC0에 사용할 module clock(CORE_CLK) 선택
firc2

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())#

  1. ADC0_MCR 레지스터를 통해 conversion clock과 conversion mode를 설정한다.
  2. ADC0_NCMR0 레지스터를 통해 변환 채널(CH0)을 선택한다.
  3. ADC0_CTR0 레지스터를 통해 ADC의 sample time을 설정한다.
  4. ADC0_CALBISTREG 레지스터를 통해 ADC의 해상도를 설정한다.

  5. 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_BITset하여 해당 채널에 대해 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 값들이 출력됨을 확인할 수 있다.

result_green result_red result_blue