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가 출력으로 나가게 됩니다.

 

 

CubeMX 설정

CubeMX에서 DAC 설정화면

 

여기서 Output Buffer는 DAC 출력핀의 버퍼를 두어 출력 임피던스를 줄이는 역할을 합니다.

 

* DAC Output Buffer를 Enable로 했을 경우, 200mV 이하로 내려가지 않는 이슈가 있습니다.

community.st.com/s/question/0D50X00009XkXzE/minimum-dac-output-voltage-on-stm32f30x

 

PA4와 PA5핀에 DAC 기능이 활성화된 화면

 

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 값

 

Alignment 설정에 따라 데이터가 정렬되는 모습

 

사용법

 

목표: 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);

 

반응형

사용 MCU: STM32F103RETx

컴파일러: Truestudio 9.3.0

 

 

목표: Timer8의 4번 채널을 사용해서 3KHz의 클럭을 생성

 

 

저는 Output Compare기능을 클럭 신호와 같이 일정 시간마다 출력을 토글시켜주어야 할 때 사용합니다.

 

아래는 클럭 설정입니다. 최대 클럭 72MHz로 설정합니다.

MCU 클럭 설정

 

타이머를 설정합니다.

 

CubeMX에 Timers 카테고리에 TIM8을 선택 해 줍니다.

 

 

 

Clock Source를 Internal Clock으로 설정해서 내부 클럭에 의해 타이머가 카운트 되도록 설정하고

채널 4번에 클럭을 생성한다고 했으니 Channel4에 Output Compare CH4를 선택해줍니다.

그러면 다음과 같이 MCU 핀에 TMI8_CH4 라벨이 붙었습니다.

 

 

Output Compare Channel 4의 Mode를 Toggle on match를 선택하여 OC가 될 때 GPIO가 토글되도록 합니다.

 

Timer 주기를 선택해 줍니다.

3KHz의 주파수를 만들기 위해서 

 

72MHz / 1000 / 24 = 3000 가 나옵니다. 즉 숫자를 3000번 세면 Output Compare 인터럽트가 발생하여 토글 시키는데 그러면 GPIO 출력으로 나오는 클럭이 1500Hz가 되기 때문에  2를 더 나누어서 설정합니다

 

72MHz / 1000 / 24 / 2 = 1500 라서 

 

Prescaler 에 12 - 1,

Counter Period에 1000 - 1을 넣습니다

 

 

 

코드는 while에 들어가기전에 HAL_TIM_OC_Start(타이머 번호, 채널 번호) 값을 넣어 호출해주면 PC9번 핀에 클럭이 나오는 것을 볼 수 있습니다.

int main()
{
 ...
 MX_TIM8_Init();

 ...
 
 // Timer8 Output Compare 시작
 HAL_TIM_OC_Start(&htim8, TIM_CHANNEL_4);
 
 
 while(1)
 {
 
 
 }
}
반응형

'MCU > STM32:HAL' 카테고리의 다른 글

[STM32F]FSMC와 LCD  (0) 2021.02.28
[STM32F]FSMC로 LCD(SSD1963) 제어하기  (2) 2021.02.07
[STM32F][HAL] USART Interrupt  (0) 2020.12.23
[STM32F][HAL] DAC (Digital to Analog Converter)  (0) 2020.12.22
[STM32F][HAL] USB CDC (Virtual Port Com) 사용하기  (1) 2020.05.28
[STM32F][HAL] HAL_Delay  (0) 2020.05.21
[STM32F][HAL] ADC - ADC Calibration  (4) 2020.05.17
[STM32F][HAL]GPIO  (0) 2020.05.10

사용 MCU: STM32F103VCT

 

안녕하세요.

 

STM32F103에 있는 USB IP중 CDC(Communication Device Class)를 CubeMX로 적용하는 방법에 대해 알아봅시다.

CDC를 사용하면 Uart 핀에 Uart To Serial 모듈을 따로 쓰지 않고 바로 USB를 통해 컴퓨터와 통신 할 수 있다는 장점이 있습니다.

 

 

제가 가지고 있는 보드의 USB 회로는 아래와 같습니다.

USB 회로

컴퓨터에서 USB를 인식하려면 PA1 핀이 High 상태로 있어야 합니다.

 

USB in a NutShell - Chapter 2 - Hardware (beyondlogic.org)

 

USB in a NutShell - Chapter 2 - Hardware

Connectors All devices have an upstream connection to the host and all hosts have a downstream connection to the device. Upstream and downstream connectors are not mechanically interchangeable, thus eliminating illegal loopback connections at hubs such as

www.beyondlogic.org

 

PA11과 PA12에 연결된 저항은 22옴이 사용되었습니다.

 

 

CubeMX를 실행 시켜

 

Connectivity > USB 메뉴에 Device(FS)를 체크해줍니다.

USB 기능 ON

 

 

그러면 아래 처럼 USB_DP 핀과 USB_DM핀이 활성화 될 것입니다.

 

USB 핀이 설정된 화면

 

Middleware > USB_DEVICE 메뉴로 가서 Class For FS IP를 Communication Device Class (Virtual Port Com)을 선택해주고 GENERATE CODE를 해줍니다.

 

CDC기능 ON

 

Device Descriptor 탭으로 가면 아래 화면처럼 설정되어 있습니다.

 

VID는 Vendor ID의 약자로 제조사 아이디를 뜻합니다.

 

디폴트 설정으로 1155로 설정되어있는데

모든 VID는 usb.org에서 관리되어 있습니다. 

https://usb.org/sites/default/files/vendor_ids051920_0.pdf에서 확인해 보면

1155는 STMicroelectonics라는 것을 확인 할 수 있습니다.

 

VID를 위 리스트에 없는 숫자로 해도 정상적으로 동작합니다. (15768)

 

PID는 Product ID 로 제품 ID를 설정해 주는 파라미터입니다.

 

PA1핀도 Output으로 설정해주고 Generate Code를 해줍니다.

 

USB 송수신에 관련된 함수는 usbd_cdc_if.c 파일에 있습니다.

// usbd_cdc_if.c


// USB로부터 수신한 데이터가 있을경우 호출되는 함수 
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
	...
}


// USB로부터 데이터를 송신하기 위한 함수
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
	...
}

 

 

제가 가지고 있는 보드는 PA1핀을 High로 만들어줘야지 컴퓨터가 USB를 인식한다고 했습니다.

따라서 프로그램이 시작되면 PA1핀을 High로 만들어 주고 printf가 USB를 통해 출력되도록 코드를 수정해줍니다.

 

/*
  1초마다 USB로 "Hello CDC Test\r\n" 출력하기 예제

*/

// USB로 printf를 출력시키기 위한 설정 함수
int _write(int file, char *ptr, int len){
    //HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 100);
    // USB로 ptr를 출력합니다.
    CDC_Transmit_FS(ptr, len);
    return (len);
}


int main(void)
{
  static uint32_t time = 0;
  ...
  MX_GPIO_Init();
 
  MX_USB_DEVICE_Init();
  ...
  
  // PA1 Pin High
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);

  while (1)
  {
    if (HAL_GetTick() - time > 1000) {
      time = HAL_GetTick();
      printf("Hello CDC Test\r\n"); 
    }
  
  }

}

 

프로그램 다운로드를 완료한 후 장치관리자에 보면 아래와 같이 USB 직렬 장치라고 인식이 됩니다.

USB를 인식한 화면

 

가지고 있는 시리얼 통신 프로그램으로 COM15에 연결하면 1초마다 Hello CDC Test 라는 메세지가 나타날 것입니다.

반응형

'MCU > STM32:HAL' 카테고리의 다른 글

[STM32F]FSMC와 LCD  (0) 2021.02.28
[STM32F]FSMC로 LCD(SSD1963) 제어하기  (2) 2021.02.07
[STM32F][HAL] USART Interrupt  (0) 2020.12.23
[STM32F][HAL] DAC (Digital to Analog Converter)  (0) 2020.12.22
[STM32F][HAL] OUTPUT COMPARE 사용하기  (0) 2020.06.01
[STM32F][HAL] HAL_Delay  (0) 2020.05.21
[STM32F][HAL] ADC - ADC Calibration  (4) 2020.05.17
[STM32F][HAL]GPIO  (0) 2020.05.10

HAL드라이버 내에 일정시간동안 지연시켜주는 HAL_Delay 함수가 있습니다.

/**
  * @brief This function provides minimum delay (in milliseconds) based
  *        on variable incremented.
  * @note In the default implementation , SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals where uwTick
  *       is incremented.
  * @note This function is declared as __weak to be overwritten in case of other
  *       implementations in user file.
  * @param Delay specifies the delay time length, in milliseconds.
  * @retval None
  */
__weak void HAL_Delay(uint32_t Delay)

지연 시간은 (Delay + 1)ms입니다.

 

즉 HAL_Delay(0);을 호출하면 1ms의 딜레이가 발생됩니다. 

 

함수 내부를 보면

__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

 함수가 호출된 시점부터 wait동안 while에 묶여있도록 코드가 작성되어있습니다.

 

default로 HAL 드라이버는 1ms마다 1Tick씩 증가하도록 코딩되어 있습니다.

 

또 mininum wait로 1ms(uwTickFreq)가 추가되도록 되어있어 Delay에 0을 넣으면 1ms의 딜레이가 발생되는 것 입니다.

 

HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;  /* 1KHz */

typedef enum
{
  HAL_TICK_FREQ_10HZ         = 100U,
  HAL_TICK_FREQ_100HZ        = 10U,
  HAL_TICK_FREQ_1KHZ         = 1U,
  HAL_TICK_FREQ_DEFAULT      = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;

 

1넣었을 때 1ms 딜레이가 걸리게 하고 싶다!

1000 넣었을 때 1초 딜레이가 걸리게 하고 싶다!

 

하시는 분은 HAL_Delay 함수를 아래처럼 따로 작성하시면 됩니다.

 

/* main.c */
void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  while ((HAL_GetTick() - tickstart) < Delay)
  {
  }
}
​

 

하지만 HAL_Delay 함수를 사용하게 되면

그 위치에서 멈춰있기 때문에 저는 잘 사용하지 않습니다.

반응형

사용 MCU: STM32F103VCT

사용 IDE: TrueSTUDIO 9.3.0

 

STM32F HAL드라이브에서 HAL_ADCEx_Calibration_Start라는 함수가 있습니다.

/**
  * @brief  Perform an ADC automatic self-calibration
  *         Calibration prerequisite: ADC must be disabled (execute this
  *         function before HAL_ADC_Start() or after HAL_ADC_Stop() ).
  *         During calibration process, ADC is enabled. ADC is let enabled at
  *         the completion of this function.
  * @param  hadc: ADC handle
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc);

 

이 함수를 꼭 써야하는가 ? 의문이 들어서 Calibration을 하고 안 하고의 차이점을 확인하기 위해 직접 실험 해 보았습니다.

 

STM32F103VCT6가 달려있는 보드를 사용했고 ADC한 회로는 아래와 같이 가변저항으로 실험 했고 확인하기 좋게

한쪽으로 끝까지 돌려 PC5_SBT에 3.3V가 들어가도록 하였습니다. 

 

이론적으로 ADC결과는 4095(12bit ADC)가 나와야합니다.

가변저항 회로도

 

실험한 코드는 아래와 같습니다.

 

/* 100ms마다 ADC결과 전송 */
void adc_task()
{
    static uint32_t time = 0;
    uint16_t value;
    
    if (HAL_GetTick() - time < 100) {
    	return;
    }
    
    time = HAL_GetTick();
    
    /* ADC 시작 */
    HAL_ADC_Start(&hadc1);
    
    /* ADC가 완료될 때까지 대기 */
    while(HAL_ADC_PollForConversion(&hadc1, 10) != HAL_OK);
    
    /* ADC 값 가져오기 */
    value = HAL_ADC_GetValue(&hadc1);
    
    /* ADC 중지 */
    HAL_ADC_Stop(&hadc1);

    printf("%d\r\n", value);
}


int main(void)
{
  ...
  
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  
  ...
  
  /* ADC Calibration ? 실험 포인트 */
  while(HAL_ADCEx_Calibration_Start(&hadc1) != HAL_OK);
  
  while(1)
  {
  	adc_task();
  
  }
}

 

결과 확인

결과는 제가 확인하기 편하게 Electron으로 GUI툴을 작성하여 Serial값을 수신하여 값과 차트로 표현하도록 하였습니다.

 

1. while(HAL_ADCEx_Calibration_Start(&hadc1) != HAL_OK); 를 주석처리 하고 ADC 결과 값 확인

Calibration 없이 3.3V ADC 결과

이론적이라면 ADC값이 최대값인 4095가 나와야하는데 4030~40대가 나오네요.

 

 

2. while(HAL_ADCEx_Calibration_Start(&hadc1) != HAL_OK); 호출한 후 ADC 결과 값 확인

Calibration 후 3.3V ADC 결과

 

중간에 하나씩 튀는 값이 있지만 이론값대로 4095가 나오는 것을 볼 수 있습니다.

 

 

아래는 위 두 실험을 한 차트에 표현했을 때 차트입니다. 차이가 있는것을 볼 수 있습니다.

(Calibration 후 ADC가 flat하니 보기 좋은데 최대 전압이라 그렇지 전압을 조금 내리면 노이즈 정도는 비슷합니다.)

 

반응형

HAL Driver 중에 GPIO에 대해서 소개하겠습니다.

 

 

stm32f1xx_hal_gpio.c

 

 

  • void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)

      CubeMX로 생성한 코드는 아래와 같이 GPIOx 페리페럴을 초기화한다.

 

      GPIO Mode

          GPIO_MODE_INPUT: 입력모드

          GPIO_MODE_OUTPUT_PP: 출력 (Push Pull) 모드

          GPIO_MODE_OUTPUT_OD: 출력 (Open Drain) 모드

          GPIO_MODE_AF_PP: 대체 기능(Alternate Function), (Push Pull)모드

          GPIO_MODE_AF_OD: 대체 기능(Alternate Function), (Open Drain)모드

 

          GPIO_MODE_ANALOG: 아날로그 모드

 

          GPIO_MODE_IT_RISING: 외부 인터럽트(External Interrupt)모드, Rising Edge

          GPIO_MODE_IT_FALLING: 외부 인터럽트(External Interrupt)모드, Falling Edge

          GPIO_MODE_IT_RISING_FALLING: 외부 인터럽트(External Interrupt)모드, Rising & Falling Edge

          

          GPIO_MODE_EVT_RISING: 외부 이벤트(External Event)모드, Rising Edge

          GPIO_MODE_EVT_FALLING: 외부 이벤트(External Event)모드, Falling Edge

          GPIO_MODE_EVT_RISING_FALLING: 외부 이벤트(External Event)모드, Rising & Falling Edge

            * Event모드는 인터럽트 발생시키지 않고 저전력모드에서 Wake-up하는데 사용된다.

          

      GPIO Pull

          GPIO_NOPULL

          GPIO_PULLUP

          GPIO_PULLDOWN

 

      GPIO Speed

          GPIO_SPEED_FREQ_LOW

          GPIO_SPEED_FREQ_MEDIUM

          GPIO_SPEED_FREQ_HIGH

/* GPIO 초기화 */
void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIOD APB2 Clock ENABLE */
    __HAL_RCC_GPIOD_CLK_ENABLE();
    
    /* Configure GPIO pins : PD13 PDPin */
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    
    /* 외부 인터럽트(EXTI) 설정 */
    HAL_NVIC_SetPriority(EXTI9_5_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}

 

  • void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)

      GPIO 설정을 초기화 한다

 

  • void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

      GPIOx의 Pin의 상태를 설정한다.

      GPIO_PinState에는 GPIO_PIN_RESET(0) 과 GPIO_PIN_SET(1) 이 있다.     

/* HAL_GPIO_WritePin 예시 */
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET); // GPIO OUTPUT LOW
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);   // GPIO OUTPUT HIGH

HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, 0); // GPIO OUTPUT LOW
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, 1); // GPIO OUTPUT HIGH

 

  • GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

      GPIOx의 GPIO_Pin의 상태를 가져온다. 이때 GPIO Pin의 mode는 GPIO_MODE_INPUT 이어야 한다.

GPIO_PinState state;

state = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5); // GPIOE5 의 상태를 읽는다

 

  • void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

      GPIO의 상태를 반전(Toggle)시킨다.

HAL_GPIO_TogglePin(GPIOE, GPIO_Pin_5); // GPIOE5 핀의 출력 상태를 반전시킨다.

 

  • HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

      GPIO Pin의 설정을 고정시킨다.

반응형

+ Recent posts