본문 바로가기
DSP, MCU/TMS320F2838x (C28x)

TMS320F28388D ] ADC (ePWM 타이머를 사용한 Interrupt 방식)

by eteo 2022. 9. 13.
//
// Included Files
//
#include "driverlib.h"
#include "device.h"
#include "board.h"

//
// Defines
//
#define RESULTS_BUFFER_SIZE     256

//
// Globals
//
uint16_t myADC0Results[RESULTS_BUFFER_SIZE];   // Buffer for results
uint16_t index;                              // Index into result buffer
volatile uint16_t bufferFull;                // Flag to indicate buffer is full

//
// Function Prototypes
//
void initEPWM(void);

//
// Main
//



void main(void)
{
    //
    // Initialize device clock and peripherals
    //
    Device_init();

    //
    // Disable pin locks and enable internal pullups.
    //
    Device_initGPIO();

    //
    // Initialize PIE and clear PIE registers. Disables CPU interrupts.
    //
    Interrupt_initModule();

    //
    // Initialize the PIE vector table with pointers to the shell Interrupt
    // Service Routines (ISR).
    //
    Interrupt_initVectorTable();

    // 
    // Board Initialization
    // - Set up the ADC and initialize the SOC
    // - Enable ADC interrupt
    // - Signal Mode           : single-ended
    // - Conversion Resolution : 12-bit;
    //
    Board_init();

    // Set up the ePWM
    initEPWM();

    //
    // Initialize results buffer
    //
    for(index = 0; index < RESULTS_BUFFER_SIZE; index++)
    {
        myADC0Results[index] = 0;
    }

    index = 0;
    bufferFull = 0;

    //
    // Enable Global Interrupt (INTM) and realtime interrupt (DBGM)
    //
    EINT;
    ERTM;

    //
    // Loop indefinitely
    //
    while(1)
    {
        //
        // Start ePWM1, enabling SOCA and putting the counter in up-count mode
        //
        EPWM_enableADCTrigger(EPWM1_BASE, EPWM_SOC_A);
        EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_UP);

        //
        // Wait while ePWM1 causes ADC conversions which then cause interrupts.
        // When the results buffer is filled, the bufferFull flag will be set.
        //
        while(bufferFull == 0)
        {
        }
        bufferFull = 0;     // Clear the buffer full flag

        //
        // Stop ePWM1, disabling SOCA and freezing the counter
        //
        EPWM_disableADCTrigger(EPWM1_BASE, EPWM_SOC_A);
        EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_STOP_FREEZE);

        //
        // Software breakpoint. At this point, conversion results are stored in
        // myADC0Results.
        //
        // Hit run again to get updated conversions.
        //
        //ESTOP0;
    }
}

//
// Function to configure ePWM1 to generate the SOC.
//
void initEPWM(void)
{

    //
    // Disable SOCA
    //
    EPWM_disableADCTrigger(EPWM1_BASE, EPWM_SOC_A);

    //
    // Configure the SOC to occur on the first up-count event
    //
    EPWM_setADCTriggerSource(EPWM1_BASE, EPWM_SOC_A, EPWM_SOC_TBCTR_U_CMPA);
    EPWM_setADCTriggerEventPrescale(EPWM1_BASE, EPWM_SOC_A, 1);

    //
    // Set the compare A value to 1000 and the period to 1999
    // Assuming ePWM clock is 100MHz, this would give 50kHz sampling
    // 50MHz ePWM clock would give 25kHz sampling, etc. 
    // The sample rate can also be modulated by changing the ePWM period
    // directly (ensure that the compare A value is less than the period). 
    //
    EPWM_setCounterCompareValue(EPWM1_BASE, EPWM_COUNTER_COMPARE_A, 1000);
    EPWM_setTimeBasePeriod(EPWM1_BASE, 1999);

    //
    // Set the local ePWM module clock divider to /1
    //
    EPWM_setClockPrescaler(EPWM1_BASE,
                           EPWM_CLOCK_DIVIDER_1,
                           EPWM_HSCLOCK_DIVIDER_1);

    //
    // Freeze the counter
    //
    EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_STOP_FREEZE);
}

//
// adcA1ISR - ADC A Interrupt 1 ISR
//
__interrupt void adcA1ISR(void)
{
    //
    // Add the latest result to the buffer
    //
    myADC0Results[index++] = ADC_readResult(ADCARESULT_BASE, ADC_SOC_NUMBER0);

    //
    // Set the bufferFull flag if the buffer is full
    //
    if(RESULTS_BUFFER_SIZE <= index)
    {
        index = 0;
        bufferFull = 1;
    }

    //
    // Clear the interrupt flag
    //
    ADC_clearInterruptStatus(myADC0_BASE, ADC_INT_NUMBER1);

    //
    // Check if overflow has occurred
    //
    if(true == ADC_getInterruptOverflowStatus(myADC0_BASE, ADC_INT_NUMBER1))
    {
        ADC_clearInterruptOverflowStatus(myADC0_BASE, ADC_INT_NUMBER1);
        ADC_clearInterruptStatus(myADC0_BASE, ADC_INT_NUMBER1);
    }

    //
    // Acknowledge the interrupt
    //
    Interrupt_clearACKGroup(INT_myADC0_1_INTERRUPT_ACK_GROUP);
}

 

 

board.c

//...

void ADC_init(){
	//myADC0 initialization

	// ADC Initialization: Write ADC configurations and power up the ADC
	// Configures the analog-to-digital converter module prescaler.
	ADC_setPrescaler(myADC0_BASE, ADC_CLK_DIV_4_0);
	// Configures the analog-to-digital converter resolution and signal mode.
	ADC_setMode(myADC0_BASE, ADC_RESOLUTION_12BIT, ADC_MODE_SINGLE_ENDED);
	// Sets the timing of the end-of-conversion pulse
	ADC_setInterruptPulseMode(myADC0_BASE, ADC_PULSE_END_OF_CONV);
	// Powers up the analog-to-digital converter core.
	ADC_enableConverter(myADC0_BASE);
	// Delay for 1ms to allow ADC time to power up
	DEVICE_DELAY_US(500);

	// SOC Configuration: Setup ADC EPWM channel and trigger settings
	// Disables SOC burst mode.
	ADC_disableBurstMode(myADC0_BASE);
	// Sets the priority mode of the SOCs.
	ADC_setSOCPriority(myADC0_BASE, ADC_PRI_ALL_ROUND_ROBIN);
	// Start of Conversion 0 Configuration
	// Configures a start-of-conversion (SOC) in the ADC and its interrupt SOC trigger.
	// 	  	SOC number		: 0
	//	  	Trigger			: ADC_TRIGGER_EPWM1_SOCA
	//	  	Channel			: ADC_CH_ADCIN0
	//	 	Sample Window	: 15 SYSCLK cycles
	//		Interrupt Trigger: ADC_INT_SOC_TRIGGER_NONE
	ADC_setupSOC(myADC0_BASE, ADC_SOC_NUMBER0, ADC_TRIGGER_EPWM1_SOCA, ADC_CH_ADCIN0, 15U);
	ADC_setInterruptSOCTrigger(myADC0_BASE, ADC_SOC_NUMBER0, ADC_INT_SOC_TRIGGER_NONE);
	// ADC Interrupt 1 Configuration
	// 		SOC/EOC number	: 0
	// 		Interrupt Source: enabled
	// 		Continuous Mode	: disabled
	ADC_setInterruptSource(myADC0_BASE, ADC_INT_NUMBER1, ADC_SOC_NUMBER0);
	ADC_enableInterrupt(myADC0_BASE, ADC_INT_NUMBER1);
	ADC_clearInterruptStatus(myADC0_BASE, ADC_INT_NUMBER1);
	ADC_disableContinuousMode(myADC0_BASE, ADC_INT_NUMBER1);

}

void INTERRUPT_init(){
	
	// Interrupt Setings for INT_myADC0_1
	Interrupt_register(INT_myADC0_1, &adcA1ISR);
	Interrupt_enable(INT_myADC0_1);
}

//...

 

가변저항을 사용해 테스트 해보았다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

하나씩 살펴보자.

 

ADC 설정

 

 

 

 

ADC clock은 SYSCLK 으로 부터 공급받는데 ADCCTL2 레지스터에 의해 분주한다.

ADC_setPrescaler(myADC0_BASE, ADC_CLK_DIV_4_0);

위 함수로 프리스케일 설정하고 ADCCLK 는 Datasheet 7.11.2.3.x를 확인했을 때 MAX 50MHz를 넘으면 안된다.

 

 

이 ADCCLK는 ADC가 동작하는데 기반이되는 클럭이며, ADC가 입력채널에서 아날로그 데이터를 취득해서 그 변환결과를 결과 저장 레지스터에 기록하는데까지 걸리는 시간, 즉 변환시간을 결정짓는다. 그리고 이 값은 SOC (Start of Conversion : 변환시작신호) 의 주기인 샘플링 주기와는 별개이다.

 

 

예를 들어, ePWM 모듈의 타이머 이벤트를 이용해서 매 20KHz 주기마다 ADC에 SOC를 준다고 가정할 때, ADCCLK가 느려지던 빨라지던 샘플링 주기는 20KHz 이다. 다만, ADCCLK가 빨라지면 매 20kHz 주기에서 SOC 이후 변환 결과가 결과저장용 레지스터에 기록되는데까지 걸리는 시간이 단축될 것이다.

 

 

 

 

 

 

 

 

 

 

그리고 12 bit Resolution, Single-Ended Mode로 설정됐는데,

시그널 모드에는 Single-ended 모드와 Differential 모드 두 개가 있다.

 

 

Single-ended 모드에선 signal pin 이 하나이고 VREFLO(GND) 기준에서의 전위차로 시그널 전압을 측정한다.

Differential모드는 signal pin 한 쌍이 필요한데 하나는 positive input 핀이고 하나는 negative input 핀이어서 둘 사이의 차이로 전압을 측정한다. 

 

TRM

 

 

 

그리고 Differential mode 는 핀이 2개가 필요하니 Single-ended mode일 때에 비해 사용할 수 있는 채널이 절반으로 줄어든다. 

 

 

 

 

ADC 값 12-bit Resolution의 경우 0-4095 사이의 값으로 표현되고 아래의 공식으로 계산한다. 

 

 

TRM

 

 

16-bit Resolution의 경우 0-65535 사이의 값으로 표현되고 아래의 공식으로 계산한다. 

 

 

 

 

 

기준 전압은 VREFHI 핀, VREFLO 핀인데 일반적인 경우 VREFHI는 VDDA(3.3v) VREFLO는 VSSA(GND)이다. 이 내용은 데이터시트에서 찾을 수 있다. 근데 아마 이전 28x 코어 DSP 시리즈는 기본 입력범위가 0-3V였었나보다.

 

 

 

 

 

 

 

SOC 설정

 

 

 

 

 

SOC는 ADC를 시작하게 하는 트리거 역할의 신호를 뜻한다. SOC0에서 SOC15까지 있으며 각각 SOCx의 ADCSOCxCTL 레지스터를 조작해서 트리거 소스, 입력 채널, 샘플 획득 window duration을 결정할 수 있다.

 

TRM

 

 

 

먼저 트리거 소스는 다음의 중 하나를 선택할 수 있다.

 

 

1. Software only : ADCSOCFRC1 비트를 software에서 1로 쓰는 것으로 SOC

2. CPU Timer 0/1/2 TINTx

3. GPIO - ADCEXTSOC (외부 인터럽트 신호와 동기화)

4. ePWM 에 맞춰 동기화 (ePWMx-ADCSOC-A/B ) : ePWM 모듈의 Event Trigger SOCA/SOCB pulse generator에서 생성되는 신호로 SOC

 

이 중 여기서는 4번 방식을 사용한다.

 

 

 

 

그리고 이외에도 SOCx Trigger는 Software only로 설정하고 SOCx Interrupt Trigger를 ADCINT-1/2로 설정하는 방법도 있다.

ADC 블락에는 INT가 4채널까지 있는데 그중에 SOC trigger를 할 수 있는 것은 ADCINC-1/2 뿐이다.

 

 

 

 

 

 

 

 

 

다음 입력 채널을 설정한다.

 

Datasheet

 

 

 

TMS320F28388D 는 4개의 ADC 모듈이 있고 물리적인 ADC핀이 24개 있다. 그중 ADCIN14/ADCIN15 핀은 ADC-A/B/C/D와 모두 연결되어 있어서 필요에 의해 사용할 수 있다.

 

참고로 ADC-A에는 Temp Sensor가 연결되어 있고 변환공식은 예제로 제공된다.

 

 

 

 

 

 

 

 

 

 

 

그리고 sysconfig 툴에서 SOC Configuration 아래 Sample Window와 Sample Time을 계산해 표시해주고 있다.

 

아날로그 신호는 디지털 신호로 변환하는 동안 시간이 필요하기 때문에 어느 순간 신호를 샘플링(Sample)하고 저장(Hold)해 두어야한다.

따라서 위의 Acquisition Window (S-H time)는 ADC가 특정 아날로그 입력 채널에서 값을 취득해서 가지고 있는 시간 즉, 입력 신호 전압을 캐패시터에 충전하는 기간을 말한다.

S-H 시간이 너무 짧을 경우 안정화되지 않은 아날로그 값이 ADC 에 전달되고 ADC 결과 품질에 영향을 줄 수 있다. 반대로 S-H 시간을 길게 가져갈 경우 변환 결과 품질에는 긍정적인 영향을 주지만, 아날로그 값 취득부터 결과 값 저장까지의 변환시간이 길어지는 단점을 가지게 된다.

 

위의 캡쳐에서는 Sample Window [SYSCLK counts] 값이 15로 설정되었는데 SYSCLK가 200MHz여서 5ns 니까 샘플 타임이 75ns가 된다.

Minimum Sample and Hold time은 데이터시트에 나와있다. (12-bit Single-Ended는 75ns, 16-bit Single-Ended는 320ns) 그리고 이 값은 1 ADCCLK보단 길어야 한다.

 

 

 

 

 

 

 

 

 

ADC Interrupt 설정

 

sysconfig 파일에서 인터럽트 설정 부분

 

 

 

아래 ADC 모듈의 블락 다이어그램을 보면 Itnerrupt Block이 4개(1~4)있고, EOCx[15:0](End of Conversion)를 ADCINTx의 Trigger로 사용할 수 있다.

 

그리고 Conversion 완료 신호를 받고 호출된 ISR안에서 변환된 값을 획득해 버퍼에 저장하는 행동을 할 수 있다.

 

 

 

 

그리고 다른 Continuous SOC 예제를 보니까 이런 것도 있었다.

 

SOC0~15까지 사용하는데,

 

SOC8~15의 트리거 소스를 INT1로 설정한다.

SOC0~7의 트리거 소스를 INT2로 설정한다.

INT1의 인터럽트 소스를 EOC6으로 설정한다.

INT2의 인터럽트 소스를 EOC14로 설정한다.

INT3의 인터럽트 소스를 EOC7로 설정한다.

INT4의 인터럽트 소스를 EOC15로 설정한다.

 

이렇게 했을때 최초 SOC0~7 트리거만 직접 해주면 인터럽트에 의해 EOC와 SOC가 영원히 반복되고, INT3 플래그가 섰을 때 SOC0~7 변환값을 획득, INT4 플래그가 섰을 때 8~15 변환값을 획득할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

ePWM 설정

 

// Function to configure ePWM1 to generate the SOC.
void initEPWM(void)
{
    // Disable SOCA
    EPWM_disableADCTrigger(EPWM1_BASE, EPWM_SOC_A);
    
    // Configure the SOC to occur on the first up-count event
    EPWM_setADCTriggerSource(EPWM1_BASE, EPWM_SOC_A, EPWM_SOC_TBCTR_U_CMPA);
    EPWM_setADCTriggerEventPrescale(EPWM1_BASE, EPWM_SOC_A, 1);

    // Set the compare A value to 1000 and the period to 1999
    // Assuming ePWM clock is 100MHz, this would give 50kHz sampling
    // 50MHz ePWM clock would give 25kHz sampling, etc.
    // The sample rate can also be modulated by changing the ePWM period
    // directly (ensure that the compare A value is less than the period).
    EPWM_setCounterCompareValue(EPWM1_BASE, EPWM_COUNTER_COMPARE_A, 1000);
    EPWM_setTimeBasePeriod(EPWM1_BASE, 1999);

    // Set the local ePWM module clock divider to /1
    EPWM_setClockPrescaler(EPWM1_BASE,
                           EPWM_CLOCK_DIVIDER_1,
                           EPWM_HSCLOCK_DIVIDER_1);

    // Freeze the counter
    EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_STOP_FREEZE);
}

// Start ePWM1, enabling SOCA and putting the counter in up-count mode
EPWM_enableADCTrigger(EPWM1_BASE, EPWM_SOC_A);
EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_UP);

 

 

 

 

initEPWM() 함수를 보면 먼저 EPWM_setADCTriggerSource() 함수에서 adcSOCType 은 EPWM_SOC_A 로 하고 socSource 를 타이머가 증가할 때 CMPA와 동일한 순간으로 설정하고 있다.

 

 

 

 

 

 

그리고 period 는 1999, CMPA는 1000 로 설정하고 있다. 주석에도 나와있듯이 ePWM clock이 100MHz면 샘플링 주기가 50KHz가된다. (Sampling rate for ADC = TBCLK for ePWM = EPWMCLK / Period = 100,000,000 / 2,000)

 

 ePWM clock 은 따로 설정하지 않는 한 디폴트가 100MHz고 TRM에서 설명을 찾아볼 수 있다.

 

디폴트 값을 변경하려면 SysCtl_setEPWMClockDivider()를 통해 할 수 있다.

 

 

 

 

 

 

EPWM_setClockPrescaler 설정함수를 살펴보면 더 정확히 나와있다.

 

TBCLK(Time base Clock) = EPWMCLK/(highspeedPrescaler * pre-scaler)

 

//*****************************************************************************
//
//! Set the time base clock and the high speed time base clock count pre-scaler
//!
//! \param base is the base address of the EPWM module.
//! \param prescaler is the time base count pre scale value.
//! \param highSpeedPrescaler is the high speed time base count pre scale
//!        value.
//!
//! This function sets the pre scaler(divider)value for the time base clock
//! counter and the high speed time base clock counter.
//! Valid values for pre-scaler and highSpeedPrescaler are EPWM_CLOCK_DIVIDER_X,
//! where X is 1,2,4,8,16, 32,64 or 128.
//! The actual numerical values for these macros represent values 0,1...7.
//! The equation for the output clock is:
//!   TBCLK = EPWMCLK/(highSpeedPrescaler * pre-scaler)
//!
//! \b Note: EPWMCLK is a scaled version of SYSCLK. At reset EPWMCLK is half
//!          SYSCLK.
//!
//! \return None.
//
//*****************************************************************************
static inline void
EPWM_setClockPrescaler(uint32_t base, EPWM_ClockDivider prescaler,
                       EPWM_HSClockDivider highSpeedPrescaler)

 

 

 

 

 

만약, 1ms 간격으로 SOC트리거를 준다고 하면 어떻게 설정할까?

컴페어 밸류 레지스터가 16bit이므로 먼저 EPWM_HSClockDivider를 10으로, EPWM_ClockDivider를 1로 해서 10MHz를 얻고 Period를 9999, CMPA도 9999로 설정하면 되겠다.

 

 

 

 

 

 

참고 : https://cafe.naver.com/timcu/2543