본문 바로가기
임베디드 엔지니어링/TMS320F28335

DSP제어(TMS320F28335) : 1ms 주기 LED Toggle 프로그램(1) - System 세팅

by Bennyziio 2019. 4. 9.
반응형

◆ GPIO Toggle 기능을 이용하여 30 MHz 오실레이터를 이용하여 150 Mhz 클럭을 설정 후 1ms 주기로 제어하는 프로그램을 구현하고자 하였습니다. 추가로 500ms에서 동작하는 것도 추가를 해 보았으며, 이를 오실로스코프에서 확인할 수 있었습니다.

클럭 설정을 이해하기 위해서는 DSP281x_SysCtrl.c를 먼저 이해해야 합니다.
칩 초기화를 우선 적으로 해야 임베디드 프로세서에서 프로그래밍 시작을 할 수 있기 때문입니다.
DSP281x_SysCtrl.c는 가장 기본적인 칩 초기화 부분을 담당하고 있으며 TI사에서 제공하는 예제 파일들을 ControlSuite등을 통해서 다운로드하여 분석하는 방법을 사용할 수도 있습니다.

그러나 정확한 칩 초기화를 위해선 칩에 대한 충분한 이해가 필요하며, 다양하고 많은 레퍼런스들을 통해서 실력을 키울 수 있을 것으로 예상되며 저 또한 노력해보고자 합니다.

우선 위 LED_Toggle 프로그램은 TI에서 제공하는 예제 Example_2833xGpioToggle.c 파일을 우선적으로 분석하고 제가 원하는 프로그램으로 수정하여 원하는 기능을 구현하였습니다. 해당 case를 기억을 더듬어 기록에 남겨보겠습니다.

main문 안을 보면

// Step 1. Initialize System Control:
// PLL, WatchDog, enable Peripheral Clocks
// This example function is found in the DSP2833x_SysCtrl.c file.
   InitSysCtrl();

위와 같이 InitSysCtrl();이란 함수를 불러오는 것을 볼 수 있으며 주석을 보면 해당 함수는 DSP2833x_SysCtrl.c에서 작성된 함수로서 이를 main에서 호출하고 있는 구조이다. 아래는 DSP2833x_SysCtrl.c 코드이다.

//###########################################################################
//
// FILE:   DSP2833x_SysCtrl.c
//
// TITLE:  DSP2833x Device System Control Initialization & Support Functions.
//
// DESCRIPTION:    Example initialization of system resources.
//
//###########################################################################
// $TI Release: F2833x/F2823x Header Files and Peripheral Examples V142 $
// $Release Date: November  1, 2016 $
// $Copyright: Copyright (C) 2007-2016 Texas Instruments Incorporated -
//             http://www.ti.com/ ALL RIGHTS RESERVED $
//###########################################################################

//
// Included Files
//
#include "DSP2833x_Device.h"     // Headerfile Include File
#include "DSP2833x_Examples.h"   // Examples Include File

//
// Functions that will be run from RAM need to be assigned to
// a different section.  This section will then be mapped to a load and
// run address using the linker cmd file.
//
#pragma CODE_SECTION(InitFlash, "ramfuncs");

//
// InitSysCtrl - This function initializes the System Control registers to a 
// known state.
// - Disables the watchdog
// - Set the PLLCR for proper SYSCLKOUT frequency
// - Set the pre-scaler for the high and low frequency peripheral clocks
// - Enable the clocks to the peripherals
//
void 
InitSysCtrl(void)
{
    //
    // Disable the watchdog
    //
    DisableDog();

    //
    // Initialize the PLL control: PLLCR and DIVSEL
    // DSP28_PLLCR and DSP28_DIVSEL are defined in DSP2833x_Examples.h
    //
    InitPll(DSP28_PLLCR,DSP28_DIVSEL);

    //
    // Initialize the peripheral clocks
    //
    InitPeripheralClocks();
}

//
// InitFlash - This function initializes the Flash Control registers
//                   CAUTION
// This function MUST be executed out of RAM. Executing it
// out of OTP/Flash will yield unpredictable results
//
void 
InitFlash(void)
{
    EALLOW;
    
    //
    // Enable Flash Pipeline mode to improve performance
    // of code executed from Flash.
    //
    FlashRegs.FOPT.bit.ENPIPE = 1;

    //
    //                CAUTION
    // Minimum waitstates required for the flash operating
    // at a given CPU rate must be characterized by TI.
    // Refer to the datasheet for the latest information.
    //
#if CPU_FRQ_150MHZ
    //
    // Set the Paged Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.PAGEWAIT = 5;

    //
    // Set the Random Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.RANDWAIT = 5;

    //
    // Set the Waitstate for the OTP
    //
    FlashRegs.FOTPWAIT.bit.OTPWAIT = 8;
#endif

#if CPU_FRQ_100MHZ
    //
    // Set the Paged Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.PAGEWAIT = 3;

    //
    // Set the Random Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.RANDWAIT = 3;

    //
    // Set the Waitstate for the OTP
    //
    FlashRegs.FOTPWAIT.bit.OTPWAIT = 5;
#endif
    //
    //                CAUTION
    // ONLY THE DEFAULT VALUE FOR THESE 2 REGISTERS SHOULD BE USED
    //
    FlashRegs.FSTDBYWAIT.bit.STDBYWAIT = 0x01FF;
    FlashRegs.FACTIVEWAIT.bit.ACTIVEWAIT = 0x01FF;
    EDIS;

    //
    // Force a pipeline flush to ensure that the write to
    // the last register configured occurs before returning.
    //
    asm(" RPT #7 || NOP");
}

//
// ServiceDog - This function resets the watchdog timer.
// Enable this function for using ServiceDog in the application
//
void 
ServiceDog(void)
{
    EALLOW;
    SysCtrlRegs.WDKEY = 0x0055;
    SysCtrlRegs.WDKEY = 0x00AA;
    EDIS;
}

//
// DisableDog - This function disables the watchdog timer.
//
void 
DisableDog(void)
{
    EALLOW;
    SysCtrlRegs.WDCR= 0x0068;
    EDIS;
}

//
// InitPll - This function initializes the PLLCR register.
//
void 
InitPll(Uint16 val, Uint16 divsel)
{
    //
    // Make sure the PLL is not running in limp mode
    //
    if (SysCtrlRegs.PLLSTS.bit.MCLKSTS != 0)
    {
        //
        // Missing external clock has been detected
        // Replace this line with a call to an appropriate
        // SystemShutdown(); function.
        //
        asm("        ESTOP0");
    }

    //
    // DIVSEL MUST be 0 before PLLCR can be changed from
    // 0x0000. It is set to 0 by an external reset XRSn
    // This puts us in 1/4
    //
    if (SysCtrlRegs.PLLSTS.bit.DIVSEL != 0)
    {
        EALLOW;
        SysCtrlRegs.PLLSTS.bit.DIVSEL = 0;
        EDIS;
    }

    //
    // Change the PLLCR
    //
    if (SysCtrlRegs.PLLCR.bit.DIV != val)
    {
        EALLOW;
        
        //
        // Before setting PLLCR turn off missing clock detect logic
        //
        SysCtrlRegs.PLLSTS.bit.MCLKOFF = 1;
        SysCtrlRegs.PLLCR.bit.DIV = val;
        EDIS;

        //
        // Optional: Wait for PLL to lock.
        // During this time the CPU will switch to OSCCLK/2 until
        // the PLL is stable.  Once the PLL is stable the CPU will
        // switch to the new PLL value.
        //
        // This time-to-lock is monitored by a PLL lock counter.
        //
        // Code is not required to sit and wait for the PLL to lock.
        // However, if the code does anything that is timing critical,
        // and requires the correct clock be locked, then it is best to
        // wait until this switching has completed.
        //

        //
        // Wait for the PLL lock bit to be set.
        //

        //
        // The watchdog should be disabled before this loop, or fed within
        // the loop via ServiceDog().
        //

        //
        // Uncomment to disable the watchdog
        //
        DisableDog();

        while(SysCtrlRegs.PLLSTS.bit.PLLLOCKS != 1)
        {
            //
            // Uncomment to service the watchdog
            //
            //ServiceDog();
        }

        EALLOW;
        SysCtrlRegs.PLLSTS.bit.MCLKOFF = 0;
        EDIS;
    }

    //
    // If switching to 1/2
    //
    if((divsel == 1)||(divsel == 2))
    {
        EALLOW;
        SysCtrlRegs.PLLSTS.bit.DIVSEL = divsel;
        EDIS;
    }

    //
    // NOTE: ONLY USE THIS SETTING IF PLL IS BYPASSED (I.E. PLLCR = 0) OR OFF
    // If switching to 1/1
    // * First go to 1/2 and let the power settle
    //   The time required will depend on the system, this is only an example
    // * Then switch to 1/1
    //
    if(divsel == 3)
    {
        EALLOW;
        SysCtrlRegs.PLLSTS.bit.DIVSEL = 2;
        DELAY_US(50L);
        SysCtrlRegs.PLLSTS.bit.DIVSEL = 3;
        EDIS;
    }
}

//
// InitPeripheralClocks - This function initializes the clocks to the 
// peripheral modules. First the high and low clock prescalers are set
// Second the clocks are enabled to each peripheral. To reduce power, leave 
// clocks to unused peripherals disabled
//
// Note: If a peripherals clock is not enabled then you cannot
// read or write to the registers for that peripheral
//
void 
InitPeripheralClocks(void)
{
    EALLOW;

    //
    // HISPCP/LOSPCP prescale register settings, normally it will be set to 
    // default values
    //
    SysCtrlRegs.HISPCP.all = 0x0001;
    SysCtrlRegs.LOSPCP.all = 0x0002;

    //
    // XCLKOUT to SYSCLKOUT ratio.  By default XCLKOUT = 1/4 SYSCLKOUT
    // XTIMCLK = SYSCLKOUT/2
    //
    XintfRegs.XINTCNF2.bit.XTIMCLK = 1;
    
    //
    // XCLKOUT = XTIMCLK/2
    //
    XintfRegs.XINTCNF2.bit.CLKMODE = 1;
    
    //
    // Enable XCLKOUT
    //
    XintfRegs.XINTCNF2.bit.CLKOFF = 0;

    //
    // Peripheral clock enables set for the selected peripherals.
    // If you are not using a peripheral leave the clock off
    // to save on power.
    //
    // Note: not all peripherals are available on all 2833x derivates.
    // Refer to the datasheet for your particular device.
    //
    // This function is not written to be an example of efficient code.
    //
    SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;    // ADC

    //
    //                          *IMPORTANT*
    // The ADC_cal function, which  copies the ADC calibration values from TI 
    // reserved OTP into the ADCREFSEL and ADCOFFTRIM registers, occurs 
    // automatically in the Boot ROM. If the boot ROM code is bypassed during 
    // the debug process, the following function MUST be called for the ADC to 
    // function according to specification. The clocks to the ADC MUST be 
    // enabled before calling this function.
    // See the device data manual and/or the ADC Reference
    // Manual for more information.
    //
    ADC_cal();

    SysCtrlRegs.PCLKCR0.bit.I2CAENCLK = 1;   // I2C
    SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1;   // SCI-A
    SysCtrlRegs.PCLKCR0.bit.SCIBENCLK = 1;   // SCI-B
    SysCtrlRegs.PCLKCR0.bit.SCICENCLK = 1;   // SCI-C
    SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1;   // SPI-A
    SysCtrlRegs.PCLKCR0.bit.MCBSPAENCLK = 1; // McBSP-A
    SysCtrlRegs.PCLKCR0.bit.MCBSPBENCLK = 1; // McBSP-B
    SysCtrlRegs.PCLKCR0.bit.ECANAENCLK=1;    // eCAN-A
    SysCtrlRegs.PCLKCR0.bit.ECANBENCLK=1;    // eCAN-B

    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;   // Disable TBCLK within the ePWM
    SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1;  // ePWM1
    SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2
    SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 1;  // ePWM3
    SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 1;  // ePWM4
    SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 1;  // ePWM5
    SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 1;  // ePWM6
    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;   // Enable TBCLK within the ePWM

    SysCtrlRegs.PCLKCR1.bit.ECAP3ENCLK = 1;  // eCAP3
    SysCtrlRegs.PCLKCR1.bit.ECAP4ENCLK = 1;  // eCAP4
    SysCtrlRegs.PCLKCR1.bit.ECAP5ENCLK = 1;  // eCAP5
    SysCtrlRegs.PCLKCR1.bit.ECAP6ENCLK = 1;  // eCAP6
    SysCtrlRegs.PCLKCR1.bit.ECAP1ENCLK = 1;  // eCAP1
    SysCtrlRegs.PCLKCR1.bit.ECAP2ENCLK = 1;  // eCAP2
    SysCtrlRegs.PCLKCR1.bit.EQEP1ENCLK = 1;  // eQEP1
    SysCtrlRegs.PCLKCR1.bit.EQEP2ENCLK = 1;  // eQEP2

    SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1; // CPU Timer 0
    SysCtrlRegs.PCLKCR3.bit.CPUTIMER1ENCLK = 1; // CPU Timer 1
    SysCtrlRegs.PCLKCR3.bit.CPUTIMER2ENCLK = 1; // CPU Timer 2

    SysCtrlRegs.PCLKCR3.bit.DMAENCLK = 1;       // DMA Clock
    SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1;     // XTIMCLK
    SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1;    // GPIO input clock

    EDIS;
}

//
// CsmUnlock - This function unlocks the CSM. User must replace 0xFFFF's with 
// current password for the DSP. Returns 1 if unlock is successful.
//
#define STATUS_FAIL          0
#define STATUS_SUCCESS       1
Uint16 
CsmUnlock()
{
    volatile Uint16 temp;

    //
    // Load the key registers with the current password. The 0xFFFF's are dummy
    // passwords.  User should replace them with the correct password for the 
    // DSP.
    //
    EALLOW;
    CsmRegs.KEY0 = 0xFFFF;
    CsmRegs.KEY1 = 0xFFFF;
    CsmRegs.KEY2 = 0xFFFF;
    CsmRegs.KEY3 = 0xFFFF;
    CsmRegs.KEY4 = 0xFFFF;
    CsmRegs.KEY5 = 0xFFFF;
    CsmRegs.KEY6 = 0xFFFF;
    CsmRegs.KEY7 = 0xFFFF;
    EDIS;

    //
    // Perform a dummy read of the password locations if they match the key 
    // values, the CSM will unlock
    //
    temp = CsmPwl.PSWD0;
    temp = CsmPwl.PSWD1;
    temp = CsmPwl.PSWD2;
    temp = CsmPwl.PSWD3;
    temp = CsmPwl.PSWD4;
    temp = CsmPwl.PSWD5;
    temp = CsmPwl.PSWD6;
    temp = CsmPwl.PSWD7;

    //
    // If the CSM unlocked, return succes, otherwise return failure.
    //
    if (CsmRegs.CSMSCR.bit.SECURE == 0)
    {
        return STATUS_SUCCESS;
    }
    else 
    {
        return STATUS_FAIL;
    }
}

//
// End of file
//


위 코드를 분석해보면 우선 void InitSysCtrl(void)라는 함수를 콜 하는 것을 볼 수 있는데 이는 main() 함수에서 호출하는 칩 초기화 함수이며 이 안에는 세 개의 함수로 이루어져 있습니다. 호출하는 순서도 매우 중요하니 유심히 보시기 바랍니다.

void 
InitSysCtrl(void)
{
    //
    // Disable the watchdog
    //
    DisableDog();

    //
    // Initialize the PLL control: PLLCR and DIVSEL
    // DSP28_PLLCR and DSP28_DIVSEL are defined in DSP2833x_Examples.h
    //
    InitPll(DSP28_PLLCR,DSP28_DIVSEL);

    //
    // Initialize the peripheral clocks
    //
    InitPeripheralClocks();
}

//
// InitFlash - This function initializes the Flash Control registers
//                   CAUTION
// This function MUST be executed out of RAM. Executing it
// out of OTP/Flash will yield unpredictable results
//

void InitSysCtrl(void) 함수를 보면 3가지 함수를 콜 하는 것을 볼 수 있습니다.
가장 먼저 Watchdog 타이머를 사용하지 않도록 설정하는 DisableDog() 함수가 보이는데 이는 시스템 안전을 확보하기 위한 8비트 타이머이다. 자세한 내용은 추후 다르도록 하겠으며 간단히 설명하면 Watchdog을 해석하면 경비견으로 해석할 수 있으며 경비견에게 주기적으로 먹이를 주는데 문제가 생겨 먹이를 주지 못하면 경비견이 시스템을 리셋시켜버리는 동작을 하게 됩니다. 즉, Watchdog 타이머를 주기적으로 클리어해줘야 하는데 시스템에 문제가 생겨 클리어해주지를 못하면 시스템이 문제가 있음을 알고 리셋시켜 버립니다.

이어서 InitPll(DSP28_PLLCR,DSP28_DIVSEL)라는 PLL 설정 함수가 호출됩니다. PLL control register인 DSP28_PLLCR = 10 그리고 divide select value인 DSP28_DIVSEL = 2로 선언하였습니다.
이는 PLL의 입력 클럭을 10배로 높여 출력한 후 2로 나누는 결과를 내놓게 됩니다.

//
// Specify the PLL control register (PLLCR) and divide select (DIVSEL) value.
//
//#define DSP28_DIVSEL   0   // Enable /4 for SYSCLKOUT
//#define DSP28_DIVSEL   1 // Enable /4 for SYSCKOUT
#define DSP28_DIVSEL     2 // Enable /2 for SYSCLKOUT
//#define DSP28_DIVSEL     3 // Enable /1 for SYSCLKOUT

#define DSP28_PLLCR   10
//#define DSP28_PLLCR    9
//#define DSP28_PLLCR    8
//#define DSP28_PLLCR    7
//#define DSP28_PLLCR    6
//#define DSP28_PLLCR    5
//#define DSP28_PLLCR    4
//#define DSP28_PLLCR    3
//#define DSP28_PLLCR    2
//#define DSP28_PLLCR    1
//#define DSP28_PLLCR    0  // PLL is bypassed in this mode

현재 저는 외부 Oscillator(XCLKIN)를 사용하여 30 MHz를 입력하고 PLL을 거쳐 (30 * 100)/2 = 150 MHz의 클럭을 설정하였습니다. 이는 1/150 Mhz = 6.667ns의 주기를 가지게 됩니다.

지금까지의 과정을 위 다이어그램을 통해서 살펴볼 수 있습니다. 위 다이어그램에서 OSCCLK가 Watchdog에 공급되는 클럭이고 이는 CPU와 별개로 공급하게 되므로 CPU 감시가 가능하게 됩니다.

기본적으로 외부 클럭은 PLL을 거쳐서 CPU에 공급되지만, Bypass 모드를 사용하면 외부 클럭이 곧장 CPU로 공급되게 할 수도 있습니다. 이같이 하는 이유는 내부 PLL을 거치게 되면서 생기는 시간 지연으로 인한 시스템 전체 동기화가 맞지 않게 되는 문제를 해결하기 위해 전체 동기화가 필요할 경우에 사용하게 됩니다.

이제 PLL에 대해서 좀 더 자세하게 알아보도록 하겠습니다.
PLL은 Phase Lock Loop의 약자로서 클럭의 위상 정보를 명확히 하는 기능 및 입/출력 비율을 조절하는 기능을 맡게 됩니다. 28X DSP에 탑재된 PLL은 최저 0.5배에서 10배까지 조절이 가능합니다. PLLCR 레지스터의 DIV 비트를 가지고 이 비율을 조절할 수 있는데 아래 표를 통해 알아보도록 하겠습니다.

앞서 PLLCR을 10으로 선언했다고 하였습니다 이는 즉 DIV값을 1010으로 하겠다는 뜻입니다.
DIVSEL을 2로 선언하여 OSCCLK * 10 / 2로 설정하기 위함입니다. 그리하여 저는 (30 Mhz * 10) / 2 = 150 Mhz를 얻을 수 있게 된 것이고요.

PLL을 거쳐 안정화된 클럭은 CPU로 전달이 되고 이는 주변 회로에 공급이 됩니다. 아래 다이어그램을 보면 좀 더 이해하기 쉬울 것입니다.

SYSCLKOUT이 바로 가는 주변 회로가 있고 LOSPCP, HISPCP를 통해서 각 LSPCLK, HSPCLK로 변환되어 주변 회로로 공급되기도 합니다. 사용하지 않는 주변회로에는 클럭을 차단하여 전력 소비를 줄일 수 있고 기타 노이즈의 원인(EMI 등)을 줄일 수 있습니다. 이 작업을 마지막으로 콜 되는 함수 InitPeripheralClocks() 함수에서 담당하게 됩니다.

void 
InitPeripheralClocks(void)
{
    EALLOW;

    //
    // HISPCP/LOSPCP prescale register settings, normally it will be set to 
    // default values
    //
    SysCtrlRegs.HISPCP.all = 0x0001;
    SysCtrlRegs.LOSPCP.all = 0x0002;

    //
    // XCLKOUT to SYSCLKOUT ratio.  By default XCLKOUT = 1/4 SYSCLKOUT
    // XTIMCLK = SYSCLKOUT/2
    //
    XintfRegs.XINTCNF2.bit.XTIMCLK = 1;
    
    //
    // XCLKOUT = XTIMCLK/2
    //
    XintfRegs.XINTCNF2.bit.CLKMODE = 1;
    
    //
    // Enable XCLKOUT
    //
    XintfRegs.XINTCNF2.bit.CLKOFF = 0;

    //
    // Peripheral clock enables set for the selected peripherals.
    // If you are not using a peripheral leave the clock off
    // to save on power.
    //
    // Note: not all peripherals are available on all 2833x derivates.
    // Refer to the datasheet for your particular device.
    //
    // This function is not written to be an example of efficient code.
    //
    SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;    // ADC

    //
    //                          *IMPORTANT*
    // The ADC_cal function, which  copies the ADC calibration values from TI 
    // reserved OTP into the ADCREFSEL and ADCOFFTRIM registers, occurs 
    // automatically in the Boot ROM. If the boot ROM code is bypassed during 
    // the debug process, the following function MUST be called for the ADC to 
    // function according to specification. The clocks to the ADC MUST be 
    // enabled before calling this function.
    // See the device data manual and/or the ADC Reference
    // Manual for more information.
    //
    ADC_cal();

    SysCtrlRegs.PCLKCR0.bit.I2CAENCLK = 1;   // I2C
    SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1;   // SCI-A
    SysCtrlRegs.PCLKCR0.bit.SCIBENCLK = 1;   // SCI-B
    SysCtrlRegs.PCLKCR0.bit.SCICENCLK = 1;   // SCI-C
    SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1;   // SPI-A
    SysCtrlRegs.PCLKCR0.bit.MCBSPAENCLK = 1; // McBSP-A
    SysCtrlRegs.PCLKCR0.bit.MCBSPBENCLK = 1; // McBSP-B
    SysCtrlRegs.PCLKCR0.bit.ECANAENCLK=1;    // eCAN-A
    SysCtrlRegs.PCLKCR0.bit.ECANBENCLK=1;    // eCAN-B

    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;   // Disable TBCLK within the ePWM
    SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1;  // ePWM1
    SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1;  // ePWM2
    SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 1;  // ePWM3
    SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 1;  // ePWM4
    SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 1;  // ePWM5
    SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 1;  // ePWM6
    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;   // Enable TBCLK within the ePWM

    SysCtrlRegs.PCLKCR1.bit.ECAP3ENCLK = 1;  // eCAP3
    SysCtrlRegs.PCLKCR1.bit.ECAP4ENCLK = 1;  // eCAP4
    SysCtrlRegs.PCLKCR1.bit.ECAP5ENCLK = 1;  // eCAP5
    SysCtrlRegs.PCLKCR1.bit.ECAP6ENCLK = 1;  // eCAP6
    SysCtrlRegs.PCLKCR1.bit.ECAP1ENCLK = 1;  // eCAP1
    SysCtrlRegs.PCLKCR1.bit.ECAP2ENCLK = 1;  // eCAP2
    SysCtrlRegs.PCLKCR1.bit.EQEP1ENCLK = 1;  // eQEP1
    SysCtrlRegs.PCLKCR1.bit.EQEP2ENCLK = 1;  // eQEP2

    SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1; // CPU Timer 0
    SysCtrlRegs.PCLKCR3.bit.CPUTIMER1ENCLK = 1; // CPU Timer 1
    SysCtrlRegs.PCLKCR3.bit.CPUTIMER2ENCLK = 1; // CPU Timer 2

    SysCtrlRegs.PCLKCR3.bit.DMAENCLK = 1;       // DMA Clock
    SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1;     // XTIMCLK
    SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1;    // GPIO input clock

    EDIS;
}

위 함수 내용을 보면 Bit 단위로 PCLKCR 레지스터를 접근하는 것을 볼 수 있다. 학습을 위해서 Bit단위로 쪼개져 있고 all을 이용해 16비트 레지스터를 통째로 접근하면 간단하게 코딩할 수 있겠으나, 공부하는 단계에서 또한 서로 코드 리뷰의 원활함을 위해서 위와 같이 사용하는 것을 추천드리는 바입니다.(협업 시 가독률 및 이해하기 편함)

PCLKCR 레지스터 데이터 시트를 확인해 보겠습니다.

사용하고자 하는 주변 회로가 있다면, 해당 비트를 1로 설정하면 된다. 기본값은 모두 0이다.
PCLKCR의 상위 8비트는 LSPCLK에 붙어있는 주변 회로의 클럭을 공급하는 비트이며, 하위 8비트는 HSPCLK에 붙어있는 주변 회로 클럭 공급용이다.

칩을 공부하는 과정에서나 알고리즘 구축 과정에서는 모든 주변 회로에 클럭을 공급하고 이후 불필요 주변 회로의 클럭을 차단하는 방법이 유용한 방법이다.

HSPCLK와 LSPCLK를 설정하는 내용을 설명해보겠습니다. 앞에서 설명했다시피 PLL을 거친 클럭은 CPU로 공급되고 CPU는 주변회로용 클럭을 공급하는데 이중 HSPCLK와 LSPCLK로 구분되어 공급되는 클럭이 있다고 하였습니다.
HSPCLK와 LSPCLK는 각각 최대 14 분주까지 가능하다고 합니다. 아래는 HSPCLK를 설정하는 HISPCP 레지스터입니다.

HSPCLK 비트 영역이 분주 비율을 설정하게 합니다. 리셋 후 기본값은 001로 세팅되어 있습니다. 즉 2 분주이다.
제가 세팅해둔 외부 오실레이터 30 MHz가 장착되어 PLL을 10배로 세팅되어 있다면 CPU 클럭은 150 MHz가 됩니다. 이때 HISPCP 레지스터를 기본값으로 두면, HSPCLK가 클럭을 공급하는 ADC, Event Manager의 구동 클럭은 75 MHz가 되는 것입니다.

아래는 LSPCLK를 설정하는 LOSPCP 레지스터를 알아보도록 하겠습니다.

LOSPCP의 리셋 후 기본값은 010으로 4 분주이다. CPU 클럭이 LOSPCP를 거치게 되면 37.5 MHz가 되어 주변회로로 공급되게 됩니다.
주변회로에 맞는 속도로 공급되기 위해 HISPCP와 LOSPCP 레지스터 설정이 필요하고 그래서 이것을 칩 초기화 단계에서 포함되어 있다는 것을 반드시 이해하고 넘어가시길 바랍니다.

다음으로 저는 내부 메모리가 아닌 Flash 메모리를 이용하여 펌웨어 업로드를 하였습니다. 플래시 메모리를 이용하면 고속 수행을 가능하게 하기 때문입니다. 우선 Flash 메모리 설정을 위한 코드를 살펴보도록 하겠습니다.

void 
InitFlash(void)
{
    EALLOW;
    
    //
    // Enable Flash Pipeline mode to improve performance
    // of code executed from Flash.
    //
    FlashRegs.FOPT.bit.ENPIPE = 1;

    //
    //                CAUTION
    // Minimum waitstates required for the flash operating
    // at a given CPU rate must be characterized by TI.
    // Refer to the datasheet for the latest information.
    //
#if CPU_FRQ_150MHZ
    //
    // Set the Paged Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.PAGEWAIT = 5;

    //
    // Set the Random Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.RANDWAIT = 5;

    //
    // Set the Waitstate for the OTP
    //
    FlashRegs.FOTPWAIT.bit.OTPWAIT = 8;
#endif

#if CPU_FRQ_100MHZ
    //
    // Set the Paged Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.PAGEWAIT = 3;

    //
    // Set the Random Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.RANDWAIT = 3;

    //
    // Set the Waitstate for the OTP
    //
    FlashRegs.FOTPWAIT.bit.OTPWAIT = 5;
#endif
    //
    //                CAUTION
    // ONLY THE DEFAULT VALUE FOR THESE 2 REGISTERS SHOULD BE USED
    //
    FlashRegs.FSTDBYWAIT.bit.STDBYWAIT = 0x01FF;
    FlashRegs.FACTIVEWAIT.bit.ACTIVEWAIT = 0x01FF;
    EDIS;

    //
    // Force a pipeline flush to ensure that the write to
    // the last register configured occurs before returning.
    //
    asm(" RPT #7 || NOP");
}

메모리는 소자나, 설계 방식에 의해 읽기와 쓰기 속도차가 발생합니다. Flash 메모리는 비휘발성 메모리로서 휘발성 메모리인 RAM에 비해 속도가 느립니다. RAM의 경우에는 150 MHz로 동작이 가능하지만, 플래시는 그렇지 못하고 겨우 20 ~ 50 MHz 정도로 동작이 가능하다.
따라서 램에서 코드를 수행할 때와 다른 상황이 발생하게 됩니다. 고속을 가진 CPU가 데이터를 상대적으로 저속인 플래시로부터 정확하게 가져오려면 상대적으로 빠르다 보니 명령을 주고 기다려서 받는 과정이 필요하게 됩니다. 이를 TI에서는 플래시 파이프라인(Flash Pipeline)이라는 회로를 통해 가능하게 하였습니다.

Flash Pipeline은 명령어 처리에 쓰이는 파이프라인과 비슷한데, 다음에 수행할 코드를 플래시로부터 미리 가져와서 대기시켜 놓게 됩니다(Prefetch). 캐시 메모리도 비슷한 구조를 가집니다.

위 다이어그램이 Flash Pipeline이며 이 것을 사용하기 위해서는 우선

//
    // Enable Flash Pipeline mode to improve performance
    // of code executed from Flash.
    //
    FlashRegs.FOPT.bit.ENPIPE = 1;

해당 코드를 통해서 Enable 시켜줘야 합니다.
이어서 대기시간을 설정하기 위한 코드가 필요한 데 이것은 아래와 같습니다. (150 MHz 기준)

//
    // Set the Paged Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.PAGEWAIT = 5;

    //
    // Set the Random Waitstate for the Flash
    //
    FlashRegs.FBANKWAIT.bit.RANDWAIT = 5;

FBANKWAIT이라는 레지스터를 사용하여 대기시간을 설정하게 됩니다. CPU가 얼마나 기다려야 하는지를 설정해주게 됩니다.
PAGEWAIT은 플래시 메모리를 페이지 단위로 읽을 때 필요한 대기시간 값을 설정합니다. 플래시 메모리의 1페이지는 2048비트(128 워드)로 이루어져 있는데 각각의 비트 영역이 적용되는 방식으로 페이지를 처음 접근할 경우에는 Random Access라 하여 RANDWAIT의 적용을 받는다. 이후 읽기 작업이 실행되면 이를 Paged Access라고 하여 PAGEWAIT 적용을 받게 됩니다.
RANDWAIT는 Flash Random Read Wait States를 설정하는 비트로 플래시 메모리 전역에 걸쳐 주소를 마구잡이로 설정하여 값을 읽는 과정에서 필요한 값입니다. 최소 1 이상이고 시스템 클럭에 맞추어 설정해주면 됩니다. 우리는 이를 5로 지정하였는데 150 MHz로 CPU가 동작할 때 최소 5 사이클은 기다려줘야 플래시 메모리와 정상적인 대화가 가능하기 때문입니다.
플래시 파이프 라인 모드를 사용할 때에는, RANDWAIT 값이 PAGEWAIT 값보다 반드시 커야 합니다.. 아래 표를 참조하시기 바랍니다.

SYSCLKOUT(MHz) SYSCLKOUT(ns) PAGE WAIT-STATE RANDOM WAIT STATE
150 6.67 5 5
120 8.33 4 4
100 10 3 3
75 13.33 2 2
50 20 1 1
30 33.33 1 1
25 40 0 1
15 66.67 0 1
4 250 0 1

시스템 클럭이 150 MHz일 때, 필요한 대기 값(WAIT)은 5로 나와있음을 확인할 수 있습니다. 이와 같은 답을 구하는 공식은 은 아래와 같습니다.

PARAMETER MIN MAX UNIT
ta(fp) Paged Flash access time 36  
ta(fr) Random Flash access time 36  
ta(OTP) OTP access time 60  

대기 값을 설정하고 나면 아래와 같은 코드를 작성해주어야 합니다.

//
    //                CAUTION
    // ONLY THE DEFAULT VALUE FOR THESE 2 REGISTERS SHOULD BE USED
    //
    FlashRegs.FSTDBYWAIT.bit.STDBYWAIT = 0x01FF;
    FlashRegs.FACTIVEWAIT.bit.ACTIVEWAIT = 0x01FF;
    EDIS;

위 코드는 플래시가 sleep mode에서 standby mode로 또는 standby mode에서 active mode로 전환할 때 필요한 대기시간을 설정해주는 코드로서 목적은 전력 절감을 위해서입니다.

코드수가 많지 않다면 플래시 메모리 내용 전부를 램에 올려서 사용할 수 있고 이럴 경우 플래시는 sleep mode로 해두면 좋습니다. 만일 sleep mode에 있는 플래시 메모리를 건드리게 되면 바로 반응하지 않고 대기시간이 필요한데 이를 위 코드에서 제공하게 되는 것입니다.

이러한 상태천이를 지정하는 레지스터가 있는데 이는 바로 FPWR입니다. 그러나 현재는 리비전이 바뀌어 천이에 필요한 시간 조절이 불가능하게 바뀌었기 때문에 레지스터 기본값을 그대로 써야 합니다.

마지막 인라인 어셈블리 명령어는 아래와 같이 작성이 되어 있는데 이는 명령어 처리용 파이프 라인을 깨끗이 비우기 위해 8사이클 공회전하는 내용으로 플래시 설정을 새롭게 fetch 되는 명령어부터 적용하기 위해서입니다.

//
    // Force a pipeline flush to ensure that the write to
    // the last register configured occurs before returning.
    //
    asm(" RPT #7 || NOP");


지금까지가 InitSysCtrl()함수에 대한 간략한 설명이었습니다. 이외 Watchdog 설정용 함수와 CsmUnlock() 함수 등도 있는데 이는 저도 아직 제대로 공부가 안되어 이번 작성에 제외하였고 차츰 공부하면서 추가할 예정입니다.
이상입니다.

반응형

댓글