진짜 S32K312#
Timer Example (일정 주기마다 LED toggle)#
- 새로운 예제를 위한 프로젝트를 생성한다.
- 원하는 동작을 위해 레지스터와 메모리에 직접 접근해서 값을 써야 한다.
- 해당 보드가 여러 Timer 모듈을 포함하고 있기 때문에 각 Timer 모듈에 대한 정보를 파악한다.
- 여러 Timer 모듈중 Timer Interrupt를 사용하기에 적합한 Timer 모듈을 선택한다.
- 사용할 Timer 모듈의 동작 원리를 파악하고 메모리맵을 분석한다.
- 분석 결과를 활용해 임베디드 프로그래밍을 한다.
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 모듈 설정#
- System Clock 및 분기 Clock을 설정한다(Clock 설정).
- PIT 모듈의 Interrupt를 사용하기 위한 NVIC 설정을 한다(NVIC 설정).
- PIT 모듈에서 사용할 Timer 및 RTI를 설정한다(PIT 초기화).
- 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 설정#
-
Partition 0 Process Configuration Register (PRTN0_PCONF)
MC_ME_PRTN0_PCONF 주소: 402DC100h (MC_ME_BASE + 100h)
- PCE bit를 Set 하여 Partition clock enable한다.
-
Partition 0 Process Update Register (PRTN0_PUPD)
MC_ME_PRTN0_PUPD 주소: 402DC104h (MC_ME_BASE + 104h)
- PCUD bit를 set하여 hardware process를 trigger 시킨다.
- PRTN0_PCONF와 PRTN0_PUPD 레지스터는 CTL_KEY 레지스터에 유효한 키 조합이 작성될 때 하드웨어 프로세스를 실행하도록 설정된다.
-
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);
0x4
는 RUN0 모드를 의미한다.<< 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)
- NVIC Interrupt Clear-Pending Registers (ICPR)의 96번째 비트를 Set하여 Pending 된 Interrupt를 제거한다.
NVIC_ICPR3 Register 주소: E000E280Ch (NVIC_ICPR_BASE + Ch)
- 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되는 것을 확인한다.