프로그래밍 초보 탈출

EFM32 예제로 배워보기

[장치.05] ADC - Scan Mode

째즈토끼 2022. 5. 7. 21:39

지난 시간 Single Mode에 이어 Scan Mode에 대해 공부해보자. ADC로 측정할 데이터가 하나뿐이라면 Single Mode 만으로 충분하지만 2개 이상이면 Scan Mode가 필연적이다. 물론 Single Mode와 Scan Mode를 같이 사용해도 된다.

지난 시간에는 PB3, PB4의 신호를 차동(Differential Input) 신호로 해석했는데, 이번 시간에는 두 신호를 각각의 Single Ended Input으로 취급해 Scan Mode로 측정한다.

#define	ADC		ADC0
#define cmuClock_ADC	cmuClock_ADC0
#define	ADC_IRQn	ADC0_IRQ
#define	ADC_IRQHandler	ADC0_IRQHandler

#define	ADC_FREQ		1000000UL	// Max	16000000UL

#define	ADC_SCAN_DVL		(2 - 1)
#define ADC_SCAN_ACQ_TIME	adcAcqTime256

#define	PB3_ADC_POS		adcPosSelAPORT2XCH19
#define	PB4_ADC_POS		adcPosSelAPORT1XCH20

bool8_t drv_init_adc(void)
{
	CMU_ClockEnable(cmuClock_ADC, true);
	
	ADC_Init_TypeDef adc_init = ADC_INIT_DEFAULT;
	adc_init.prescale = ADC_PrescaleCalc(ADC_FREQ, 0);
	ADC_Init(ADC, &adc_init);

	__adc_init_scan();
	
	ADC_IntEnable(ADC, ADC_IEN_SCAN);
	NVIC_SetPriority(ADC_IRQn, NVIC_PRIORITY_ADC);
	NVIC_ClearPendingIRQ(ADC_IRQn);
	NVIC_EnableIRQ(ADC_IRQn);

	return true;
}
static void __adc_init_scan(void)
{
	ADC_InitScan_TypeDef adc_initScan = ADC_INITSCAN_DEFAULT;
	adc_initScan.diff		= false;        	// single ended
	adc_initScan.reference		= adcRef2V5;		// adc reference
	adc_initScan.resolution		= adcRes12Bit;  	// 12-bit resolution
	adc_initScan.acqTime		= ADC_SCAN_ACQ_TIME;
	adc_initScan.fifoOverwrite	= true;			// FIFO overflow overwrites
	adc_initScan.prsEnable		= true;
	adc_initScan.prsSel		= PRS_CH_PERIODIC_ADC_SCAN;	// adcPRSSELCh0
	g_adc_scan_id[0] = ADC_ScanSingleEndedInputAdd(&adc_initScan, adcScanInputGroup0, PB3_ADC_POS);
	g_adc_scan_id[1] = ADC_ScanSingleEndedInputAdd(&adc_initScan, adcScanInputGroup0, PB4_ADC_POS);
	ADC_InitScan(ADC, &adc_initScan);

	ADC->SCANCTRLX &= ~_ADC_SCANCTRLX_DVL_MASK;
	ADC->SCANCTRLX |= ADC_SCAN_DVL << _ADC_SCANCTRLX_DVL_SHIFT;	// Set scan data valid level (DVL)
	ADC->SCANFIFOCLEAR = ADC_SCANFIFOCLEAR_SCANFIFOCLEAR;		// Clear ADC Scan fifo
}

이번에는 PRS를 통해 Start 하도록 지정되었는데, PRS에 대한 자세한 사항은 지난 강좌를 참고하면 되겠다.

https://jazz16.tistory.com/8

 

[장치.02] Periodic Timer와 PRS

지금까지 진행했던 수많은 프로젝트들 중에 타이머를 사용하지 않는 프로젝트는 하나도 없었다. 타이머가 모자라서 애먹은 프로젝트는 있었지만. 타이머라고 해서 시간만 재는 거라고 생각하

jazz16.tistory.com

예제에서 눈여겨볼 부분은 ADC_ScanSingleEndedInputAdd() 함수다. 측정하고자 하는 채널을 등록하는 것으로 32개의 채널을 측정한다면 32번 호출해야 한다. 여기서는 PB3와 PB4, 두 채널을 측정하려 하니 두 번 호출했다. 리턴 값으로 Scan ID라는 번호가 돌아오는데, 이걸 저장해 두어야 한다. Scan ID는 0 ~ 31까지 5비트로 되어 있는데, 측정 결괏값 리포팅 시 스캔 ID가 같이 돌아온다. 현재 데이터가 어느 채널의 데이터인지 구별할 수 있는데, 타 MCU에서 순서로 구별하는 것과는 상당히 다른 방식이다. TG11의 ADC FIFO는 사이즈가 4밖에 안되기 때문에, 채널의 수가 많아지면 순서로 구별이 안되기 때문에 사용하는 구별법이다.

그럼 Scan ID는 ADC_ScanSingleEndedInputAdd() 호출 순서에 따라 순서대로 부여될까? 그런데 그것도 아니다. 모든 채널은 특정 Scan ID를 가지도록 결정되어 있다.

같은 버스의 채널들은 0~7, 8~15,... 등 8개씩 묶어 하나의 그룹을 만들 수 있다. 이때 X, Y의 구분은 없다. 즉 1X와 1Y는 같은 그룹이 될 수 있다. 그러니 1X와 1Y의 채널 0 ~ 7는 모두 하나의 그룹으로 묶을 수 있다. 물론 다른 그룹으로 만들 수도 있지만, 그룹은 최대 4개만 만들 수 있기 때문에, ADC 채널을 많이 필요로 하는 경우는 핀 배치를 잘해야 한다. 1X와 2X는 같은 그룹이 될 수 없다. 채널 7과 채널 8도 같은 그룹이 될 수 없다. 이론상 8개 채널이 한 그룹이 되고, 4개의 그룹을 만들 수 있으니 총 32개의 채널을 측정할 수 있지만, 핀맵 테이블을 보면 비어있는 채널들이 있기 때문에, 중복으로 등록하지 않는 이상 32개는 등록할 수가 없다.

채널들의 그룹 구성 방식이 어렵다면 대충 구성해서 돌려보면 된다. 구성할 수 없는 그룹이면 ASSERTION 에러가 나오므로, 해당 채널을 다른 그룹으로 옮기면 된다. 그룹과 채널의 조합으로 Scan ID가 결정된다.

예제에서는 2 채널을 같은 그룹으로 두었지만, 다른 그룹으로 두어도 된다. 또, FIFO에 데이터가 2개 쌓이면 인터럽트가 걸리도록 해 두었으니(ADC_SCAN_DVL=1), 인터럽트에서 SCANDATA를 순차적으로 두 번 읽으면 먼저 등록한 PB3과 나중에 등록한 PB4의 ADC 값이 순차적으로 들어올 거라고 생각하기 쉽다. 실제로 다른 대부분의 MCU들은 그렇게 동작한다. 그러나 TG11은 아니다. 등록한 순서에 상관없이 Scan ID의 순서로 들어온다. 아니, 순서를 계산할 필요 없다. 순서는 언제든 꼬일 수 있다. 데이터와 Scan ID가 같이 들어오니 Scan ID로 참조하면 된다.

static uint16_t g_adc_value[2];

void ADC_IRQHandler(void)
{
	ADC_IntClear(ADC, ADC->IF);

	while (ADC->SCANFIFOCOUNT)
	{
		uint32_t scan_id;
		uint16_t adc_value = ADC_DataIdScanGet(ADC, &scan_id);
		/**/ if (scan_id == g_adc_scan_id[0])
		{
			g_adc_value[0] = adc_value;
		}
		else if (scan_id == g_adc_scan_id[1])
		{
			g_adc_value[1] = adc_value;
		}
	}
}

uint16_t adc_get_adc_value(uint8_t adc_id)
{
	return g_adc_value[adc_id];
}

채널이 많아지고 초당 샘플링 수가 많다면 인터럽트 내에서 비교문을 많이 돌리는 것이 부담이 될 수도 있다. 그럴 땐 아래와 같은 방법으로 처리할 수도 있다. Scan ID는 0 ~ 31 값을 가지기 때문에.

static uint16_t g_adc_value[32];

void ADC_IRQHandler(void)
{
	ADC_IntClear(ADC, ADC->IF);

	while (ADC->SCANFIFOCOUNT)
	{
		uint32_t scan_id;
		uint16_t adc_value = ADC_DataIdScanGet(ADC, &scan_id);
		g_adc_value[scan_id] = adc_value;
	}
}

uint16_t adc_get_adc_value(uint8_t adc_id)
{
	return g_adc_value[g_adc_scan_id[adc_id]];
}

컴파일해 보지 않고 여기서 바로 만든 코드라 잘못된 부분이 있을 수도 있지만, 대충 요점은 이해하셨으리나 본다.

Scan Mode에서 차동 측정은 Single Mode처럼 자유롭지 않다. 짝이 될 수 있는 채널이 제한적이기 때문에 회로 설계 단계에서 잘 조합해서 설계해야 한다. 자세한 내용은 Reference Manual을 참고하자.