본문 바로가기
DSP, MCU/STM32 (ARM Cortex-M)

STM32 ] 28BYJ-48 스텝모터 제어하기

by eteo 2022. 12. 4.

 

 

 

 

사진과 같은 스텝모터를 구입하면 보면 ULN2003 IC칩과 같이 온다. MCU가 모터에 충분한 전류를 제공하지 못하기 때문에 모터를 작동시키기 위해 이런 모터 드라이버가 필요하다.

 

 

 

 

 

스텝모터의 기본적인 구동원리는 고정자 각 극에 교대로 전류를 흘려 회전자를 회전하는 방법이다.

 

 

 

 

스펙

 

 

감속비는 1/64 이고 스텝당 각도가 5.625°/64=0.087890625° 이다.

즉, 이 모터를 한바퀴 돌리기 위해(하프스텝모드 기준) 4096(=64*64) 스텝이 필요하다.

0.087890625° * 64 * 64 = 360°

 

풀 스텝모드 기준으로는 1회전을 위해 2048 스텝이 필요하고 스텝당 각도는 0.17578125°이다.

 

 

 

 

 

 

스텝모터의 구동방식

  • Wave Drive
  • Full Drive
  • Half Drive

 

 

 

 

 

 

 

Wave Drive

이 모드에서는 한 번에 하나의 고정자 전자석만 활성화되며, 모터는 1회전을 완료하는 데 2048 STEPS가 소요된다. 그리고 이는 2048/4 = 1회전당 512시퀀스를 의미한다.

 

 

 

 

 

 

 

 

 

Full Drive

두 개의 고정자 전자석이 한 번에 활성화되고 모터는 최대 토크로 작동한다. 모터가 1회전을 완료하는 데 2048 STEPS가 소요되며, 이는 2048/4 = 1회전당 512 시퀀스 를 의미 합니다.

 

 

 

 

 

 

 

 

Half Drive

이 모드는 모터의 각도 분해능을 높이는데 사용되지만 Full Drive에 비해 토크는 감소한다. 모터는 1회전을 완료하는 데 4096 STEPS 가 소요되며 , 이는 2048/8 = 1회전당 512시퀀스 를 의미한다.

 

 

 

 

 

 

 

 

배선

 

 

 

 

 

 

 

 

소스코드 및 CubeMX 설정

 

 

 

STM32 모터드라이버
PC8 IN1
PC9 IN2
PC10 IN3
PC11 IN4

 

 

us 딜레이를 위한 타이머 설정

TIM2는 APB2에서 클락소스를 공급받고 APB2 timer clock은 180MHz이다.

Prescaler를 180-1로해서 1MHz로 설정하고 Conter Period는 16 bit 타이머 맥시멈값인 0xFFFF까지로한다.

 

 

포텐셔미터를 통한 제어를 위해 ADC 설정

DMA Circular 모드로 하고 Data Width는 기본인 Half Word로 한다.

 

DMA enable하고 나면 DMA interrupt는 자동으로 켜진다.

 

Resolution은 12bits이며 Continuous Conversion Mode 와 DMA Continous Request 모드를 Enable한다.

 

참고.

ST사의 ADC 활용 한글판 Guide :

https://www.st.com/content/dam/kms/Contents/Reflibrary/ADC_Firmware_guide_Mode_and_Feature.pdf

 

 

/* USER CODE BEGIN 0 */
// 마이크로 세컨드 딜레이 함수 Blocking 방식
void delay (uint16_t us)
{
  __HAL_TIM_SET_COUNTER(&htim1, 0);
  while (__HAL_TIM_GET_COUNTER(&htim1) < us);
}

// steps per revolution 정의
#define stepsperrev 4096

// delay를 통해 rpm을 설정하는 함수. 이 모터의 최대 rpm은 14 정도이다.
// 60초/stepsperrev/rpm설정값 만큼 us 지연한다.
void stepper_set_rpm (int rpm)  // Set rpm--> max 13, min 1,,,  went to 14 rev/min
{
  delay(60000000/stepsperrev/rpm);
}

// 하프 드라이브 함수 case 0부터 7까지 8스텝을 수행하면 한 시퀀스가 끝난다.
void stepper_half_drive (int step)
{
  switch (step){
    case 0:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET);   // IN4
        break;

    case 1:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET);   // IN4
        break;

    case 2:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET);   // IN4
        break;

    case 3:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_SET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET);   // IN4
        break;

    case 4:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_SET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET);   // IN4
        break;

    case 5:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_SET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_SET);   // IN4
        break;

    case 6:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_SET);   // IN4
        break;

    case 7:
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);   // IN1
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);   // IN2
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET);   // IN3
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_SET);   // IN4
        break;

    }
}

// 한 시퀀스당 각도는 0.703125 이다. 파라미터로 넘어온 각도만큼 이동하기 위해선 몇 시퀀스를 반복해야하는지 계산하고 반복한다.
// clockwise는 7부터 0까지 스텝을 반대로 밟는다.
void stepper_step_angle (float angle, int direction, int rpm)
{
  float anglepersequence = 0.703125;  // 360 = 512 sequences
  int numberofsequences = (int) (angle/anglepersequence);

  for (int seq=0; seq<numberofsequences; seq++)
  {
    if (direction == 0)  // for clockwise
    {
      for (int step=7; step>=0; step--)
      {
        stepper_half_drive(step);
        stepper_set_rpm(rpm);
      }

    }

    else if (direction == 1)  // for anti-clockwise
    {
      for (int step=0; step<8; step++)
      {
        stepper_half_drive(step);
        stepper_set_rpm(rpm);
      }
    }
  }
}

// ADC값을 저장할 변수는 DMA설정시 half word로 했으니 16비트이다.
uint16_t ADC_VAL;
float voltage;
float angle;
float currentAngle;

// ADC Conversion 완료후 호출되는 callback 함수에서 ADC value를 voltage로 먼저 환산한다.
// (참조전압 3.3v, 12비트 ADC)
// 가변저항은 회전할 수 있는 각도가 300도니까 다시 전압을 각도로 환산한다.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  voltage = (float)(ADC_VAL*3.3)/4095;
  angle = voltage*300/3.3;
  Stepper_rotate(angle, 10);
}

// 콜백함수에서 호출되어 파라미터로 들어온 angle에서 currentAngle을 뺀 값을 changeinangle에 저장하고
// 변화값이 양수인 경우, 음수인 경우를 구분하여 stepper_step_angle() 함수를 호출해 회전시킨 후
// currentAngle에는 다시 angle값을 저장한다.
void Stepper_rotate (int angle, int rpm)
{
  int changeinangle = 0;
  changeinangle = angle-currentAngle;
  if (changeinangle > 1)
  {
    stepper_step_angle (changeinangle,0,rpm);
    currentAngle = angle;
  }
  else if (changeinangle <-1)
  {
    changeinangle = -(changeinangle);
    stepper_step_angle (changeinangle,1,rpm);
    currentAngle = angle;
  }

}

/* USER CODE END 0 */

 

 

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_RTC_Init();
  MX_TIM1_Init();
  MX_ADC1_Init();
  
  /* USER CODE BEGIN 2 */
  // TIM1 start
  HAL_TIM_Base_Start(&htim1);
  // ADC DMA start
  HAL_ADC_Start_DMA(&hadc1, &ADC_VAL, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  
//    for(int i=0; i<512; i++)
//    {
//      for(int i=0; i<8; i++)
//      {
//        stepper_half_drive(i);
//        stepper_set_rpm(5);
//      }
//    }

//    stepper_step_angle(45, 1, 13);
//    HAL_Delay(1000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

 

 

참고로 HAL_ADC_Start_DMA 함수의 두번째 파라미터 값이 uint32_t* 형이기 때문에 uint16_t*형을 넘기면 warning이 뜨지만 그냥 빌드해도 정상적으로 작동한다. 

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)

 

 

 

Reference : https://controllerstech.com/interface-stepper-motor-with-stm32/