[STM32] ADC & DAC with DMA

myblack·2024년 8월 20일
0

STM32

목록 보기
6/8
post-thumbnail

ADC

ADC (Analog to Digital Converter)
아날로그를 Digital 신호로 변경
아날로그 전압을 몇 단계로 쪼개서 디지털화
기준전압 - VREF, 쪼개는 단계 - ADC Resoultion

ADC 계산방법

  • STM32는 Reference 전압이 3.3v 임

VREF = 3.3V
4096단계 = 12bit resolution

ADC결과 1500이면

  • ADC 1당 전압 = VREF / Bit_Resolution = 3.3V / 4096 = 0.806mV
  • ADC로 측정된 전압 = ADC 1당 전압 * ADC 결과 = 0.806 x 1500 = 1.209V

프로그래밍 하는 방식

  • 기존에는 AD 변환이 완료되면 Value[0] ~ Value[3] 까지 하나씩 저장해달라고 CPU 클럭이 소모됨
  • 위와 다르게 DMA 방식으로 AD변환이 완료되면 ADC결과를 DMA 컨트롤러가 자동으로 원하는 변수에 저장

정리

  • 특정메모리의 값을 가져올 때 사용

  • 몇개데이터를 가져와서 복사할떄는 효과가 거의 없지만 1kbyte 크기일때는 for문 돌려서 복사하느라 CPU가 다른일을 하지 못하지만 DMA 이용하면 바로 복사함. CPU의 클럭 소요 없이 DMA가 복사하기 때문에 좋음.

  • Uart로 데이터가 100byte씩 들어올때 보통 1byte씩 인터럽트 걸어서 배열에 차곡차곡 쌓아놓는데 그러면 100번의 인터럽트가 걸린다. 하지만 DMA를 이용하면 100byte를 DMA가 알아서 저장하고 다 받아지면 인터럽트 한번 걸어서 수행함

ADC, DMA 설정

  • 4개의 ADC를 설정함
    ADC1 - IN9,10,12,13

  • ADC의 계산 방법 등을 설정함

  • 순차적으로 ADC의 값을 배열에 하나씩 저장 할 것이기 때문에 Circular 모드로 설정
  • 저장할 Memory는 순차적으로 증가하도록 설정
소스코드
HAL_ADC_Start_DMA(&hadc1, &adcval[0], 4);
uint16_t adcval[4];

while (1)
{
	  printf(str, "%4d  %4d",adcval[0],adcval[1]);
	  printf(str, "%4d  %4d",adcval[2],adcval[3]);
}

예제코드

#include "main.h"

// ADC와 DMA를 다루기 위한 핸들러 구조체입니다.
ADC_HandleTypeDef hadc1;          // ADC1 핸들러 구조체
DMA_HandleTypeDef hdma_adc1;      // ADC1과 연결된 DMA 핸들러 구조체

/* USER CODE BEGIN PV */
// ADC로부터 읽어온 값을 저장하는 변수입니다.
uint32_t adcBuf = 0;  // DMA를 통해 ADC 값이 이 변수에 저장됩니다.
/* USER CODE END PV */

// 시스템 클럭 설정 함수의 선언입니다.
void SystemClock_Config(void);

// GPIO 초기화 함수의 선언입니다.
static void MX_GPIO_Init(void);

// DMA 초기화 함수의 선언입니다.
static void MX_DMA_Init(void);

// ADC1 초기화 함수의 선언입니다.
static void MX_ADC1_Init(void);

// 시스템 틱(Tick) 증가 함수입니다. 시스템 시간의 카운트를 증가시킵니다.
void HAL_IncTick(void)
{
  // uwTick 전역 변수를 uwTickFreq 주기로 증가시킵니다.
  uwTick += uwTickFreq;

  // uwTick이 200의 배수가 되면 scan 플래그를 SET으로 설정합니다.
  if(uwTick % 200 == 0){
      scan = SET;
  }
}

int main(void)
{
  // HAL 라이브러리를 초기화합니다. 시스템 설정을 초기화하는 중요한 함수입니다.
  HAL_Init();

  // 시스템 클럭을 설정합니다.
  SystemClock_Config();

  // GPIO를 초기화합니다.
  MX_GPIO_Init();

  // DMA를 초기화합니다.
  MX_DMA_Init();

  // ADC1을 초기화합니다.
  MX_ADC1_Init();

  /* USER CODE BEGIN 2 */
  // DMA를 사용하여 ADC 변환을 시작합니다.
  // 변환된 값은 adcBuf에 저장되며, 1개의 데이터만 수신합니다.
  HAL_ADC_Start_DMA(&hadc1, &adcBuf, 1);
  /* USER CODE END 2 */

  while (1)
  {
    // scan 플래그가 SET이면, 즉 시스템 시간이 200틱(주기)마다 이 조건을 만족하면 실행됩니다.
    if(scan){
      // scan 플래그를 리셋하여 다음 200틱까지는 실행되지 않도록 합니다.
      scan = RESET;

      // DMA의 연속 요청이 비활성화된 경우, 다시 ADC 변환을 시작해야 합니다.
      // 위의 주석에서 이 부분을 설명하고 있으며, 필요한 경우 주석을 해제하여 사용합니다.
      //HAL_ADC_Start_DMA(&hadc1, &adcBuf, 1); 
    }

    // __NOP()는 No Operation 명령어로, CPU를 잠시 대기 상태로 만듭니다.
    // 이 함수는 주로 디버깅 목적으로 사용됩니다.
    __NOP();
  }
}

ADC_HandleTypeDef hadc1; 및 DMA_HandleTypeDef hdma_adc1;:

  • ADC 및 DMA 모듈을 제어하기 위해 사용되는 핸들러입니다. 이 구조체들은 HAL 라이브러리 함수에서 ADC 및 DMA 하드웨어를 제어하는 데 사용됩니다.

uint32_t adcBuf = 0;:

  • ADC 변환 결과를 저장하는 변수입니다. DMA를 통해 이 변수에 변환된 아날로그 값이 저장됩니다.

HAL_IncTick(void):

  • 이 함수는 시스템의 틱 카운터를 증가시키는 역할을 합니다. 여기서 uwTick은 주기적으로 증가하며, 200틱마다 scan 플래그가 SET됩니다. 이 플래그는 이후의 작업에서 신호로 사용됩니다.

main() 함수:

  • HAL_Init()를 통해 HAL 라이브러리와 하드웨어를 초기화한 후, 시스템 클럭, GPIO, DMA, ADC를 각각 초기화합니다.
  • 초기화 후, HAL_ADC_Start_DMA(&hadc1, &adcBuf, 1);를 호출하여 DMA를 사용해 ADC 변환을 시작합니다. 변환된 값은 adcBuf에 저장됩니다.
  • 메인 루프에서는 200틱마다 scan 플래그를 체크하여 추가 작업이 필요할 경우 해당 작업을 수행합니다.

DAC

DAC

DAC는 ADC의 반대로, Digital값을 Analog 값으로 변환하는 모듈입니다.

STM32F103에는 12bit DAC 모듈을 가지고 있습니다.
0부터 Vref+ 에 인가된 전압을 4096(2^12)개로 쪼개어 출력으로 보냅니다.

DAC 출력 공식!

(출처: Reference Manual)

DACoutput: DAC 출력
VREF: VREF+ 핀에 인가된 전압
DOR: Data Output Register로 여기에 0~4095값이 쓰여짐

Vref+ 가 3.3V일경우
3.3*1 / 4095 = 0.000806 이 됩니다.
즉 Digital Value 1당 0.000806V가 출력으로 나가게 됩니다.

HAL

HAL 드라이버에서 초기화를 제외한 DAC에 필요한 함수는 아래와 같습니다.

HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);
// DAC Output Enable 함수
// hdac: dac 인스턴스
// Channel: DAC_CHANNEL_1 or DAC_CHANNEL_2

HAL_StatusTypeDef HAL_DAC_Stop(DAC_HandleTypeDef *hdac, uint32_t Channel);
// DAC Output Disable 함수
// hadc: dac 인스턴스
// Channel: DAC_CHANNEL_1 or DAC_CHANNEL_2

HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data);
// DAC Value 설정 함수
// hadc: dac 인스턴스
// Channel: DAC_CHANNEL_1 or DAC_CHANNEL_2
// Alignment: DAC_ALIGN_12B_R or DAC_ALIGN_12B_L or DAC_ALIGN_8B_R
// data: 0 ~ 4095 값

사용법
목표: DAC 채널 1의 출력을 2V로 설정하고 싶다.
DOR = 2V * 4095 / 3.3V = 2481.82 ≒ 2482

HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2482);
HAL_DAC_Start(&hdac1, DAC_CHANNEL_1);

소스코드

HAL_DAC_Start(&hdac, DAC_CHANNEL_1);

while (1)
{
	//HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2047);
    
    //삼각파 : 4095까지 증가 후 0으로 감소.
    HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacval);
	dacval++;
	if(dacval > 4095)	dacval = 0;
    
}

사인파

#include <math.h>

MX_TIM7_Init(void)
{
  htim7.Instance = TIM7;
  htim7.Init.Prescaler = 9;
  htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim7.Init.Period = 8399;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	static unsigned char cnt =0;
	if(htim->Instance == TIM7)
	{
		HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, (sinf(2*3.1415926535f*50*cnt / 1000.f)+1)*2047);
		cnt++;
		if(cnt>999) cnt=0;
	}
}

Ref
https://louie0724.tistory.com/362
https://www.youtube.com/watch?v=o6hZEyv6s88&list=PLUaCOzp6U-RqMo-QEJQOkVOl1Us8BNgXk&index=13
https://wikidocs.net/168981
https://superbaik.tistory.com/entry/STM32F-ADC-with-DMA

profile
기록과 분석, 이해

0개의 댓글