Timer나 counter를 사용하는 가장 대표적인 세 가지 용도는 아래와 같다.
KL25Z COP
라는 WDT가 있다. Cortex-M0+를 비롯한 M-series에는 프로세서에 SysTick
이라는 periodic timer tick 모듈이 내장돼있다. 이 timer는 24-bit 길이로 돼있으며 다음과 같은 registers로 구성돼있다.
VAL
: 현재 timer의 value를 저장하고 있다. LOAD
: Timer의 초기값이 저장돼있다. Timer 값을 reload할 때 사용하는 값이다.CRTL
: Timer의 상태 정보가 저장돼있으며 timer를 제어한다.ENABLE
bit: Counter를 사용할지 여부를 의미한다. (Enable or disable)TICKINT
bit: Counter가 계속 감소하다가 0
이 되면 인터럽트를 걸지 말지 여부를 의미한다.CLKSOURCE
bit: 프로세서의 clock을 사용할지 외부에 연결된 clock을 사용할지 결정한다. SysTick Timer를 사용하는 예시 코드는 아래와 같다.
void Init_SysTick(void) {
SysTick->LOAD = (48000000L / 16); // 48MHz / 16 = 3MHz
NVIC_SetPriority(SysTick_IRQn, 3); // Set interrupt priority
SysTick->VAL = 0; // Clear current value
// Enable timer and interrupt. Use external clock source
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
}
LOAD
를 48MHz에서 16으로 나눈 3MHz를 저장하는 것이다.단순히 작은 data를 memory로부터 가져오거나 쓰는 과정은 CPU가 해도 되지만, 큰 data stream을 옮기는 과정을 CPU가 처리한다면 굉장히 큰 CPU time 낭비다. Data를 단순히 옮기는 과정은 간단한 operation이므로 굳이 CPU가 나설 필요가 없다. 이 역할을 대신 수행해주는 게 DMA다.
DMA는 n개의 채널로 구성돼있고, 위 그림은 하나의 채널이 어떤 구조를 가지고 있는지 블록 다이어그램으로 나타낸 그림이다.
위에서 소개한 DMA 작동 과정을 바탕으로, 어떤 하드웨어가 어떤 순서로 어떻게 작동하는지 알아보자.
DMA_MUX
ERQ
신호는 DMA 바깥에 있는 DMA_MUX
라는 MUX에서 들어온다.DMA_MUX
는 대용량 data stream이 필요한 여러 source를 입력으로 받는다. 예를들어 UART 통신, I2C 통신, SPI 통신, TPM 통신 등이 대표적이다. DMA_MUX_CHCFGn
이라는 control register가 DMA_MUX
를 제어한다. SOURCE
는 6-bit로 표현하니 최대 64개의 source가 DMA에게 data stream을 요청할 수 있다.TRIG
는 SW적으로 DMA를 enable 하는 기능을 허용할 것인지를 선택하는 bit다.ENBL
는 DMA를 enable할지 말지 결정하는 bit다.DMA Controller
SAR
: Source address register는 이름 그대로 data를 주는 쪽 (source)의 address를 저장한다.DAR
: Destination address register도 이름 그대로 data를 받는 쪽 (destination)의 address를 저장한다.DMA_DCRn
: DMA controller는 각 채널마다 DCR
register를 가지고 있는데, 해당 채널에서 DMA의 operation을 정의한다.DMA_DSR_BCRn
: DMA controller는 각 채널마다 DSR_BCR
register를 가지고 있는데, DMA의 상태 정보를 저장한다.SIM
register의 SCGC7
register를 조작해서 DMA에게 clock을 enable 해준다.SIM
register의 SCGC6
register를 조작해서 DMA_MUX
에게 clock을 enable 해준다.DMA_MUX
와 channel에 호응하는 DMA_MUXx_CHCFGn
register를 clear해준다.SARn
과 DARn
각각에 address를 load한다.BCRn
에 몇 byte 정보를 transfer 할 것인지 load한다. DSRn
의 DONE
flag bit를 clear해준다.DMA_MUXx_CHCFGn
register의 SOURCE
bits에 source number를 적어주고, ERQ
를 set 해서 DMA를 trigger한다.#define ARR_SIZE (256)
uint32_t s[ARR_SIZE], d[ARR_SIZE];
void Test_SW_Copy(void) {
uint32_t *ps, *pd;
ps = s;
pd = d;
for (i = 0; i < ARR_SIZE; ++i) *pd++ = *ps++;
}
위와 같이 1KB 데이터를 source에서 data로 옮기는 작업을 수행하는 단순한 C코드가 있다고 생각해보자. CPU가 아니라 DMA를 사용해서 이 transfer를 효율적으로 수행하고 싶다면 아래와 같은 DMA 설정 코드를 만들어서 활용할 수 있다.
#define ARR_SIZE (256)
uint32_t s[ARR_SIZE], d[ARR_SIZE];
void Init_DMA_To_Copy(void) {
SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;
DMA0->DMA[0].DCR = DMA_DCR_SINC_MASK | DMA_DCR_SSIZE(0) |
DMA_DCR_DINC_MASK | DMA_DCR_DSIZE(0);
}
void Copy_Longwords(uint32_t* source, uint32_t* dest, uint32_t count) {
// Load source and destination address (by pointer)
DMA0->DMA[0].SAR = DMA_SAR_SAR((uint32_t)source);
DMA0->DMA[0].DAR = DMA_DAR_DAR((uint32_t)dest);
// Data byte count
DMA0->DMA[0].DSR_BCR = DMA_DSR_BCR_BCR(4 * count);
// Verify DONE flag is cleared
DMA0->DMA[0].DSR_BCR &= ~DMA_DSR_BCR_DONE_MASK;
// Start transfer!
DMA0->DMA[0].DCR |= DMA_DCR_START_MASK;
// Wait until transfer done.
while (!(DMA0->DMA[0].DSR_BCR & DMA_DSR_BCR_DONE_MASK));
}
void Test_DMA_Copy(void) {
uint16_t i;
Init_DMA_To_Copy();
// Initializing
for (i = 0; i < ARR_SIZE; ++i) s[i] = i, d[i] = 0;
Copy_Longwords(s, d, ARR_SIZE);
}
아 끝났다