Skip to content

진짜 S32K312#

Timer Example (일정 주기마다 LED toggle)#

  1. 새로운 예제를 위한 프로젝트를 생성한다.
  2. 원하는 동작을 위해 레지스터와 메모리에 직접 접근해서 값을 써야 한다.
  3. 해당 보드가 여러 Timer 모듈을 포함하고 있기 때문에 각 Timer 모듈에 대한 정보를 파악한다.
  4. 여러 Timer 모듈중 Timer Interrupt를 사용하기에 적합한 Timer 모듈을 선택한다.
  5. 사용할 Timer 모듈의 동작 원리를 파악하고 메모리맵을 분석한다.
  6. 분석 결과를 활용해 임베디드 프로그래밍을 한다.

1. 여러 Timer 모듈에 대한 정보 파악#

해당 보드는 여러 종류의 Timer 모듈을 포함하고 있으므로, 각 모듈에 대한 정보를 파악한 후 사용하기 적절한 Timer 모듈을 선택해야 한다.

(1) STM (System Timer Module)#

(2) SWT (Software Watchdog Timer)#

(3) PIT (Periodic Interrupt Timer)#

(4) RTC (Real-Time Clock)#

2. 사용할 Timer 모듈 선택#

  • Timer Interrupt를 사용하기에 적합한 Periodic interrupt timer (PIT)를 사용할 모듈로 선택한다.
  • 대표적인 특징

    • PIT는 최대 4개의 타이머 채널을 포함하며, 각 타이머는 32비트 길이를 가진다.
    • S32K312의 경우 PIT_0, PIT_1만 지원

      .png)

    • PIT_0

      • PIT_0의 경우 독립된 클럭에서 동작하는 Real Time Interrupt (RTI)를 지원한다.
      • Lifetime Timer: PIT_0는 두 개의 채널을 연결하여 64비트 타이머로 사용할 수 있다.

3. PIT 모듈 동작 원리 파악#

3.1. PIT Block Diagram#

  • RTI의 경우 독립된 RTI 오실레이터 클럭에서 동작한다.
  • Timer Channel은 Peripheral Clock에 맞춰 동작한다.
  • Load_Value:
    • 타이머가 카운트다운을 시작할 값을 로드한다.
  • Triggers 및 Interrupts:
    • 타이머가 트리거 신호를 생성하거나 인터럽트를 발생시킨다.

3.2. 동작 원리#

  • PIT Timer의 경우 Peripheral bus clock, PIT RTI의 경우 Independent RTI osillator clock에 맞춰 동작한다.
  • PIT 모듈이 동작하면(TCTRLn[TEN] =1 또는 RTI_TCTRLn[TEN]= 1), 타이머 값은 Clock의 1 clock마다 1씩 감소한다.
  • 타이머는 LDVALn[TSV] 또는 RTI_LDVALn[TSV]에 저장되어 있는 start value값으로 초기화된다.
  • 타이머 값이 0이 되면 Timer Interrupt Flag (TFLGn[TIF]또는RTI_TFLGn[TIF])가 Set되고, 타이머가 interrupt를 발생시킨다. (이전 인터럽트의 플래그를 지우기 전까지 새로운 인터럽트를 생성하지 않는다.)
  • 이후, 타이머 값은 LDVALn[TSV] 또는 RTI_LDVALn[TSV]에 저장되어 있는 start value값으로 다시 초기화되고 위의 동작을 반복한다.
  • interrupt 발생주기는 다음과 같다.

    \[ 𝐼𝑛𝑡𝑒𝑟𝑟𝑢𝑝𝑡 𝑃𝑒𝑟𝑖𝑜𝑑 = (𝑇imerStartValue+1) × 𝐶𝑙𝑜𝑐𝑘 𝑃𝑒𝑟𝑖𝑜𝑑 = (TimerStartValue+1) / (𝐶𝑙𝑜𝑐𝑘 𝐹𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦) \]
  • RTI 타이머는 RTI clock domain에 동기화되므로, 타이머 값을 읽을 때 몇 클럭 지연이 있을 수 있다.

3.2.1 Chained timers 및 Lifetimer (참고)#

Chained timers#

  • Chained Timers는 두 개의 타이머를 연결하여 32비트를 초과하는 타이머 주기를 지원한다.
  • 타이머 n이 체인 모드로 설정되면, 타이머 n-1이 만료될 때마다 타이머 n이 감소한다.
  • 타이머 0 또는 RTI 타이머는 다른 타이머와 체인할 수 없다.

Lifetimer#

  • Lifetimer는 두 개의 타이머를 연결하여 64비트 타이머로 사용하는 기능이다.
  • 타이머 1타이머 0에 체인하여 구성된다.
  • Lifetimer는 장시간 동작에 적합하며, 현재 값은 LTMR64H(상위 32비트)와 LTMR64L(하위 32비트) 레지스터에서 읽을 수 있다.

4. Data Sheet 분석#

PIT 모듈 설정#

  1. System Clock 및 분기 Clock을 설정한다(Clock 설정).
  2. PIT 모듈의 Interrupt를 사용하기 위한 NVIC 설정을 한다(NVIC 설정).
  3. PIT 모듈에서 사용할 Timer 및 RTI를 설정한다(PIT 초기화).
  4. Handler가 호출되면 Timer Interrupt Flag를 clear해준다. (Timer Interrupt Flag 설정).

4.1. Clock 설정#

  • Timer들은 AIPS_SLOW_CLK, RTI는 SIRC_CLK에 맞춰 동작한다.
  • 둘 다 기본적으로 활성화된 시스템 클럭이므로 특별한 설정 없이도 잘 동작한다.

4.1.1. AIPS_SLOW_CLK#

AIPS_SLOW_CLK의 최대 주파수는 30MHz이다.

4.1.2. SIRC_CLK#

SIRC_CLK의 주파수는 32kHz이다.

4.1.3. PIT Clock#

  • 실습에서 사용할 PTI0의 경우 Partition 0에 해당하며, MC_ME.PRTN0_COFB1_CLKEN[REQ44]를 set 하여 PIT0 clock을 enable 해줄 필요가 있다.

4.1.4. Clock 설정#

  1. Partition 0 Process Configuration Register (PRTN0_PCONF)

    MC_ME_PRTN0_PCONF 주소: 402DC100h (MC_ME_BASE + 100h)

    • PCE bit를 Set 하여 Partition clock enable한다.
  2. Partition 0 Process Update Register (PRTN0_PUPD)

    MC_ME_PRTN0_PUPD 주소: 402DC104h (MC_ME_BASE + 104h)

    • PCUD bit를 set하여 hardware process를 trigger 시킨다.
    • PRTN0_PCONFPRTN0_PUPD 레지스터는 CTL_KEY 레지스터에 유효한 키 조합이 작성될 때 하드웨어 프로세스를 실행하도록 설정된다.
  3. Partition 0 COFB Set 1 Clock Enable Register (PRTN0_COFB1_CLKEN)

    MC_ME_PRTN0_COFB1_CLKEN 주소: 402DC134h (MC_ME_BASE + 134h)

    • REQ44 bit를 set하여 PIT0의 clock을 enable한다.
    • Control Key Register (CTL_KEY)

    MC_ME_MCTL 주소: 402DC000h (MC_ME_BASE + 0h)

    • 이 레지스터 시스템의 전원 모드 및 상태를 변경하는 데 사용된다.
    • 특정 모드(예: RUN0, STOP, STANDBY 등)로 전환하기 위해 두 번의 쓰기 작업을 요구한다.
      • 첫 번째 쓰기: 모드 전환 요청. - 키 값(0x5AF0)로 검증
      • 두 번째 쓰기: 요청 확인 - 반전된 키 값(0xA50F)로 검증

    • MC_ME_MCTL = MC_ME_KEY | (0x4 << 16);
      • 0x4RUN0 모드를 의미한다.
      • << 16으로 상위 16비트에 모드 값을 저장한다.
      • 이 명령은 "RUN0 모드로 전환하라"는 요청을 의미한다.
    • MC_ME_MCTL = MC_ME_KEY_INV | (0x4 << 16);
      • 이 명령은 첫 번째 요청을 확인하는 단계로, "RUN0 모드로 전환 요청을 실행하라"는 의미이다.
//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

void EnablePITClock(void) {
    // 1. Enable Partition 0 (if not already enabled)
    MC_ME_PRTN0_PCONF |= 0x1;  // Enable Partition 0
    MC_ME_PRTN0_PUPD |= 0x1;   // Request update

    // 2. Enable PIT clock
    MC_ME_PRTN0_COFB1_CLKEN |= (1 << 12); // Enable clock for PIT0 (REQ44)

    // 3. Apply the changes using mode control
    MC_ME_MCTL = MC_ME_KEY | (0x4 << 16);     // RUN0 mode
    MC_ME_MCTL = MC_ME_KEY_INV | (0x4 << 16); // Confirmation key
}

4.2. NVIC 설정#

  • NVIC 모듈에서 사용하고자 하는 interrupt의 우선순위를 정하고 enable해주어야 interrupt를 사용할 수 있다.
  • 각 모듈의 interrupt에 대한 설정 위치(메모리 주소)가 정해져 있기 때문에 사용하고자 하는 interrupt를 선택적으로 설정할 수 있다.

  • 실습에서 사용할 PIT0의 경우 IRQ 번호가 96번이다.
  • 따라서, NVIC 모듈에서 96번째 Interrupt에 대한 우선순위를 정해주고 Enable 설정을 해주어야 한다.

  • NVIC Interrupt Set-Enable Registers (ISER)의 96번째 비트를 Set하여 PIT0에 대한 Interrupt를 Enable한다.

    NVIC_ISER3 Register 주소: E000E100Ch (NVIC_ISER_BASE + Ch)

    1. NVIC Interrupt Clear-Pending Registers (ICPR)의 96번째 비트를 Set하여 Pending 된 Interrupt를 제거한다.

    NVIC_ICPR3 Register 주소: E000E280Ch (NVIC_ICPR_BASE + Ch)

    1. NVIC Interrupt Priority Register (IPR)의 96번째 바이트를 설정하여 PIT0에 대한 Interrupt 우선순위를 설정한다.

    NVIC_IPR96 Register 주소: E000E460h (NVIC_IPR_BASE + 60h)

// MSCR Bits
#define OBE_BIT             21
#define SSS_BITS            0

//NVIC Registers
#define NVIC_ISER_BASE      (0xE000E100UL)
#define NVIC_ISER3          *((volatile unsigned*)(NVIC_ISER_BASE + 0xC))
#define NVIC_ICPR_BASE      (0xE000E280)
#define NVIC_ICPR3          *((volatile unsigned*)(NVIC_ICPR_BASE + 0xC))
#define NVIC_IPR_BASE       (0xE000E400)
#define NVIC_IPR96          *((volatile unsigned char*)(NVIC_IPR_BASE + 0x60))

void NVIC_init(void){
   NVIC_ICPR3 |= (1<<(96 % 32)); //pending state를 벗어남
   NVIC_ISER3 |= (1<<(96 % 32)); //interrupt 활성화
   NVIC_IPR96 = 10;
}

4.3. PIT 초기화#

  • 초기화 순서는 다음과 같다.
    • MCR Register의 FRZ_BIT를 set하여 debug mode에 타이머가 멈추도록 한다.
    • LDVALn Register를 통해 Timer Start Value를 설정한다.
    • TCTRLn Register를 통해 Chain 사용 여부를 설정하고, TIE, TEN bit를 set하여 Timer와 Timer interrupt를 enable한다.
    • MCR Register의 RTI_MDIS_BIT, MDIS_BIT, FRZ_BIT를 clear하여 타이머를 동작시킨다.

4.3.1. MCR Register#

PIT_MCR 주소: 400B0000h (PIT_0_BASE + 0h)

  • MCR Register의 FRZ_BIT를 set하여 debug mode에 타이머가 멈추도록 한다.
  • MCR Register의 RTI_MDIS_BIT, MDIS_BIT, FRZ_BIT를 clear하여 타이머를 동작시킨다.

  • PIT0의 MCR Register는 초기 값이 0000 0006이므로 처음에 따로 Disable하기 위해 MDIS bit를 set할 필요없다.

4.3.2. LDVALn Register#

PIT_RTI_LDVAL 주소: 400B00F0h (PIT_0_BASE + F0h)

PIT_LDVAL1 주소: 400B0110h (PIT_0_BASE + 110h)

  • LDVALn Register를 통해 Timer Start Value를 설정한다.
  • RTI의 경우 clock 주파수가 32kHz, Timer1의 경우 clock 주파수가 24MHz이다.
    • RTI interrupt 주기가 0.5초가 되도록 RTI_LDVAL의 TSV 값을 16000-1로 설정한다.
    • Timer1 interrupt 주기가 2초가 되도록 LDVAL1의 TSV 값을 48000000-1로 설정한다.

4.3.3. TCTRLn Register#

PIT_RTI_TCTRL 주소: 400B00F8h (PIT_0_BASE + F8h)

PIT_TCTRL1 주소: 400B0118h (PIT_0_BASE + 118h)

  • TIE, TEN bit를 set하여 Timer와 Timer interrupt를 enable한다.
//PIT Registers
#define PIT_0               (0x400B0000UL)
#define PIT_MCR             (*(volatile unsigned *)(PIT_0))
#define PIT_RTI_LDVAL       (*(volatile unsigned*)(PIT_0 + 0xF0))
#define PIT_RTI_TCTRL       (*(volatile unsigned*)(PIT_0 + 0xF8))
#define PIT_LDVAL1          (*(volatile unsigned*)(PIT_0 + 0x110))
#define PIT_TCTRL1          (*(volatile unsigned*)(PIT_0 + 0x118))

//MCR Bits
#define RTI_MDIS_BIT        2
#define MDIS_BIT            1
#define FRZ_BIT             0

//TCTRL Bits
#define TIE_BIT             1
#define TEN_BIT             0

void PIT0_init(){
    PIT_MCR |= (1<<FRZ_BIT);

    // RTI
    PIT_RTI_LDVAL = (16000-1); // 0.5s
    PIT_RTI_TCTRL |= (1<<TIE_BIT) | (1<<TEN_BIT); // Enable RTI and START

    // Timer 1
    PIT_LDVAL1 = (48000000-1); // 2s
    PIT_TCTRL1 |= (1<<TIE_BIT) | (1<<TEN_BIT); // Enable Timer1 and START

    // Enable PIT
    PIT_MCR &= (1<<RTI_MDIS_BIT);
    PIT_MCR &= (1<<MDIS_BIT);
    PIT_MCR &= (1<<FRZ_BIT);
}

4.4. Timer Interrupt Flag 설정#

PIT_RTI_TFLG 주소: 400B00FCh (PIT_0_BASE + FCh)

PIT_TFLG1 주소: 400B011Ch (PIT_0_BASE + 11Ch)

  • Handler가 호출되면 Timer Interrupt Flag를 clear해준다.
  • flag를 clear해주지 않으면 인터럽트를 다시 발생시킬 수 없으므로, 반드시 마지막에 flag를 클리어해 주어야 한다.
#define PIT_RTI_TFLG        (*(volatile unsigned*)(PIT_0 + 0xFC)
#define PIT_TFLG1           (*(volatile unsigned*)(PIT_0 + 0x11C))

void PIT0_Handler(void) {
    if (PIT_RTI_TFLG) SIUL2_GPDO74 = SIUL2_GPDO74 ? 0 : 1;
    if (PIT_TFLG1)    SIUL2_GPDO29 = SIUL2_GPDO29 ? 0 : 1;

     // Clear interrupt flag
    PIT_RTI_TFLG |= 1;
    PIT_TFLG1    |= 1;
}
  • TFLG 레지스터를 확인하여 호출된 interrupt가 RTI때문인지 Timer1 때문인지 확인한다.

4.5. SIUL2 설정#

// SIUL2 Registers
#define SIUL2_BASE          (0x40290000UL)
#define SIUL2_MSCR_BASE     (SIUL2_BASE + 0x240)
#define SIUL2_MSCR74        (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x128))
#define SIUL2_MSCR29        (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x74))
#define SIUL2_GPDO_BASE     (SIUL2_BASE + 0x1300)
#define SIUL2_GPDO74        (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x49))
#define SIUL2_GPDO29        (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x1E))

void SIUL_init(){
   SIUL2_MSCR74 &= ~(0b1111 << SSS_BITS);
   SIUL2_MSCR74 |= (1 << OBE_BIT);
   SIUL2_MSCR29 &= ~(0b1111 << SSS_BITS);
   SIUL2_MSCR29 |= (1 << OBE_BIT);
}

5. 최종 코드#

#include <stdint.h>

// SIUL2 Registers
#define SIUL2_BASE          (0x40290000UL)
#define SIUL2_MSCR_BASE     (SIUL2_BASE + 0x240)
#define SIUL2_MSCR74        (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x128))
#define SIUL2_MSCR29        (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x74))
#define SIUL2_GPDO_BASE     (SIUL2_BASE + 0x1300)
#define SIUL2_GPDO74        (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x49))
#define SIUL2_GPDO29        (*(volatile unsigned char *)(SIUL2_GPDO_BASE + 0x1E))

// MSCR Bits
#define OBE_BIT             21
#define SSS_BITS            0

//NVIC Registers
#define NVIC_ISER_BASE      (0xE000E100UL)
#define NVIC_ISER3          *((volatile unsigned*)(NVIC_ISER_BASE + 0xC))
#define NVIC_ICPR_BASE      (0xE000E280)
#define NVIC_ICPR3          *((volatile unsigned*)(NVIC_ICPR_BASE + 0xC))
#define NVIC_IPR_BASE       (0xE000E400)
#define NVIC_IPR96          *((volatile unsigned char*)(NVIC_IPR_BASE + 0x60))

//PIT Registers
#define PIT_0               (0x400B0000UL)
#define PIT_MCR             (*(volatile unsigned *)(PIT_0))
#define PIT_RTI_LDVAL       (*(volatile unsigned*)(PIT_0 + 0xF0))
#define PIT_RTI_TCTRL       (*(volatile unsigned*)(PIT_0 + 0xF8))
#define PIT_RTI_TFLG        (*(volatile unsigned*)(PIT_0 + 0xFC))
#define PIT_LDVAL1          (*(volatile unsigned*)(PIT_0 + 0x110))
#define PIT_TCTRL1          (*(volatile unsigned*)(PIT_0 + 0x118))
#define PIT_TFLG1           (*(volatile unsigned*)(PIT_0 + 0x11C))

//MCR Bits
#define RTI_MDIS_BIT        2
#define MDIS_BIT            1
#define FRZ_BIT             0

//TCTRL Bits
#define TIE_BIT             1
#define TEN_BIT             0

//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

void SIUL_init(){
   SIUL2_MSCR74 &= ~(0b1111 << SSS_BITS);
   SIUL2_MSCR74 |= (1 << OBE_BIT);
   SIUL2_MSCR74 &= ~(0b1111 << SSS_BITS);
   SIUL2_MSCR29 |= (1 << OBE_BIT);
}

void EnablePITClock(void) {
    // 1. Enable Partition 0 (if not already enabled)
    MC_ME_PRTN0_PCONF |= 0x1;  // Enable Partition 0
    MC_ME_PRTN0_PUPD |= 0x1;   // Request update

    // 2. Enable PIT clock
    MC_ME_PRTN0_COFB1_CLKEN |= (1 << 12); // Enable clock for PIT0 (REQ44)

    // 3. Apply the changes using mode control
    MC_ME_MCTL = MC_ME_KEY | (0x4 << 16);     // RUN0 mode
    MC_ME_MCTL = MC_ME_KEY_INV | (0x4 << 16); // Confirmation key
}

void NVIC_init(void){
   NVIC_ICPR3 |= (1<<(96 % 32)); //pending state를 벗어남
   NVIC_ISER3 |= (1<<(96 % 32)); //interrupt 활성화
   NVIC_IPR96 = 10;
}

void PIT0_init(){
    PIT_MCR |= (1<<FRZ_BIT);

    // RTI
    PIT_RTI_LDVAL = (16000-1); // 0.5s
    PIT_RTI_TCTRL |= (1<<TIE_BIT) | (1<<TEN_BIT); // Enable RTI and START

    // Timer 1
    PIT_LDVAL1 = (48000000-1); // 2s
    PIT_TCTRL1 |= (1<<TIE_BIT) | (1<<TEN_BIT); // Enable Timer1 and START

    // Enable PIT
    PIT_MCR &= (1<<RTI_MDIS_BIT);
    PIT_MCR &= (1<<MDIS_BIT);
    PIT_MCR &= (1<<FRZ_BIT);
}

int main(void) {
    SIUL_init();
    EnablePITClock();
    NVIC_init();
    PIT0_init();

    while (1) {
    }

    return 0;
 }

void PIT0_Handler(void) {
    if (PIT_RTI_TFLG) SIUL2_GPDO74 = SIUL2_GPDO74 ? 0 : 1;
    if (PIT_TFLG1)    SIUL2_GPDO29 = SIUL2_GPDO29 ? 0 : 1;

     // Clear interrupt flag
    PIT_RTI_TFLG |= 1;
    PIT_TFLG1    |= 1;
}

6. 동작 확인#

RGB-LED는 0.5초마다, USERLED는 2초마다 Toggle되는 것을 확인한다.