본문 바로가기
개발이야기

[STM32+ADC+DMA] 정리 #1

by 코저씨 2024. 8. 17.
728x90

ADC 항상 나에게 어려운 부분이었다.

디지털 처리는 0,1에서 계산으로 시작하지만 아날로그 신호는 기준전압부터 해서 주파수, 레졸루션등 알아야 할 것이 많았다.

최근 진행한 프로젝트에서 STM32로 ADC를 연속적으로 처리해야 해서 DMA로 처리해야 한다는 걸 알았고

여러 가지 시행착오를 겪은 일은 정리해 보기로 했다.

 

ADC란?

 

쉽게 말하면 입력핀으로 입력되는 신호(예 : 전압, Wave 파형)를 0 또는 1로 전환하는 게 아닌

8비트 ~16비트의 값으로 전환하는 것을 말한다.

 

8비트 기준으로 볼 때 0은 0볼드, 255값은 3볼드의 값으로 바꾸는 것이다.

물론 255일 때의 값은 3V가 아니라 기준전압에 의해 바뀐다. 지금은 3V라고 알고 있자.

 

https://www.rohm.co.kr/electronics-basics/battery-charge/bcm_what1

 

배터리 전압을 ADC로 받으면 남은 배터리 용량이 얼마인지 계산할 수 있다. 또는 온도센서 나 조도센서도 이런 식으로 사용된다.

 

LPC1768: PWM - Tutorials (exploreembedded.com)

또 PWM이나 MSR F2F같이 느린? 클럭신호가 입력된다고 할 때 ADC 값을 얻어내면서 High, Low을 계산해서 몇 클럭인지 MSR 값이 뭔지 계산할 수가 있다.

File:Circle cos sin.gif - Wikipedia

하지만 이렇게 웨이브 타입의 신호(아날로그)가 입력되면 단순히 ADC 값을 무식하기 읽는 방법이나 디지털로는 처리가 불가능해진다. 이때 ADC를 올바르게 사용해서 현재 입력 값을 디지털로 변환해서 신호를 처리하면 아날로그 신호의 분석이 가능해진다.

 

샘플 레이트

 

아날로그 신호를 정확하게 복원하려면 입력된 신호의 클럭보다 최소 2배는 더 빠른 속도로 각 위치별 신호값을 전환해야 한다.

 

출처: Tony R. Kuphaldt - Lessons in Electric Circuits

 

 

위 그림처럼 입력 파형 클럭보다 느리게 또는 비슷하게 ADC 값을 얻어내면 아래 디지털신호처럼 이상한 파형으로 복원되게 된다. 사실 전압 레벨만 다른 베터리 전원 같은 걸 계산하는 경우라면 샘플 레이트는 속도가 크게 중요하지 않지만, 입력되는 주파수을 계산해야 하는 경우라면 샘플 레이트는 굉장히 중요하게 된다.

여기까지 내용은 아래의 링크에 더 자세히 설명하고 있으니 참조 바라며 아래에서는 STM32로 ADC를 DMA 연속적으로 수신 받으려면, 정확한 샘플 레이트를 계산하려면 어떻게 해야 하는지 알아보겠다.

ADC에 대해서 알아보자 :: OSHW Alchemist (tistory.com)

LPC1768: PWM - Tutorials (exploreembedded.com)

아날로그-디지털 변환기: ADC의 작동 원리는 무엇일까요? | Arrow.com

 

 

ADC로 데이터를 처리하려면 3가지 방법이 있다.

하나는 폴링 방식이고 두 번째는 ADC 전환 완료 인터럽트로 데이터를 받기이다. 앞의 2가지 경우는 한 번에 한 개의 채널만 컨버팅이 가능하기 때문에 여러 채널을 동시에 연산하기 위해서는 세 번째의 DMA 방식을 이용한다.

첫 번째와 두 번째는 예제가 흔함으로 바로 세 번째로 넘어간다.

DMA

DMA는 쉽게 말하면 디렉트 메모리 엑세스로 소스코드가 메모리가 접근하지 않고 CPU가 PWM이나 타이머같이 알아서 데이터, 메모리에 접근하는 것을 말한다. 예를 들어 100바이트 배열을 복사하려면 memcpy 등으로 복사를 진행하는 데 이때 동안은 CPU는 복사하는 일을 하게 된다. 근데 DMA로 복사 명령을 내리면 복사가 되는 도중에 소스코드의 명령을 실행할 수가 있게 된다. 좋아 보이는 기능이긴 하지만 DMA로 여러 번 머리가 깨질 뻔해서 DMA에 대한 이야기는 다음에 하기로...

 

STM32 ADC/DMA 관련된 예제는 아래 링크만 한 자료를 찾지 못했다.

 

https://www.youtube.com/watch?v=MBn6LdFENE8

 

처음에 이해가 가지 않았던 내용에 대해 알아보겠다.

  1. HAL_ADC_Start_DMA 함수

이 함수는 DMA와 ADC를 동시에 시작하라는 함수이다.

ADC 채널이 4개이고 각 채널별로 20개씩 데이터를 dmaAdcBuffer 버퍼에 저장하고 싶으면, HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &dmaAdcBuffer, 80); 와 같이 호출하면 된다.

그러면 채널 1, 채널 2, 채널 3, 채널 4, 채널 1, 채널 2, 채널 3, 채널 4, 채널 1, 채널 2, 채널 3, 채널 4, ... 와 같이 저장된다.

2. ADC 전환 속도, 샘플링 사이클

사실 아래 3번 내용이 2번이었는데 적다 보니 전환 속도에 대해 먼저 설명해야 할 것 같아 내용을 추가했다.

ADC 전환시간은 샘플링 시간과 전환시간으로 나누어진다. 샘플링 시간은 레지스터로 지정이 가능하며, 전환시간은 ADC resolution 의해 바뀐다(가변). STM32H인 경우 N/2 + 0.5 cycle , 14비트이면 14/2 + 0.5 = 7.5cycle이 나온다(고정). cycle의 주기는 ADC Freq(fadc)인데 지정 가능한 Max 테이블은 아래와 같다.

내 경우 12비트 해상도에 29Mhz ADC 클럭이 나왔다. 최소 샘플링 타임과 ADC 채널은 4개를 하는 케이스였기 때문에 계산한 공식은 다음과 같다.

3. DMA Start를 했다 해서 바로 저장되지 않고 HAL_ADC_ConvCpltCallback 인터럽트와 HAL_ADC_ConvHalfCpltCallback 인터럽트 함수에서 처리해야 한다. 보통 전자의 인터럽트 함수만 사용하는데 HAL_ADC_ConvHalfCpltCallback는 요청한 전환 데이터가 절반이 완료되었다는 의미이다. 굳이 사용 안 해도 될 것 같지만 굉장히 중요한 인터럽트이다. 그냥 전환 완료 인터럽트에서 dmaAdcBuffer 버퍼의 값을 복사해 가면 되지 않나요?라고 할 수 있는데 ADC가 좀 뭐 같은 게 뭐냐면 컨버팅한 값을 저장하는 버퍼가 DR 레지스터 하나뿐이라는 것이다. DR 레지스터에 새로운 ADC 값이 들어가면 DMA는 지정한 버퍼로 이를 가져간다. 연속 변환이 되어 있는 경우 HAL_ADC_ConvCpltCallback가 실행되어도 ADC 전환은 계속되고 있다.

2.에서 설명한 샘플링 타임 1.5 cycle 안에 값은 안 가져가면 DR 레지스터의 값이 오버런이 발생해 버린다. 어쩔 수 없이 샘플링 타임을 늘려줘야 하는데 이는 특별난 방법이 없음으로 한 단계씩 늘려주면서 프로젝트 상황(OS 유무, 인터럽트 우선순위 등)에 맞춰 적절한 값을 찾아야 한다. 나는 16.5 Cycle 적절해서 사용했다.

0.5us 증가라는 매우 적은 시간 같아 보이지만 메모리 복사시간을 얻기에는 충분했다.

(계산상 시스템 클럭 90 Mhz의 0.5us는 45사이클이니 ..)

728x90