로봇/STM32

STM32 자동차 제어하기(3) - 모터 제어하기

with-RL 2023. 3. 20. 22:15

이번 과정은 STM32 보드와 추가로 제작된 확장보드 및 RC카 구동체를 이용해서 자동차를 제어하기 위한 간단한 프로그램을 만드는 과정의 세 번째로 확장보드의 모터 드라이버를 이용해 모터를 제어하는 과정입니다.

아래 과정은 이전 과정을 완료 후 진행하시길 추천드립니다.

모터를 제어하기 위해서는 아래 그림과 같이 12V 전원과 모터 구동체가 확장보드에 연결되어 있어야 합니다.

1. 통신규격 추가하기

  • 기존에 정의한 통신 규격에 모터제어를 위한 명령을 추가합니다.
  • 우선 아래 코드와 같이 bsp_uart.h 에 COMMAND 및 RESULT를 정의합니다. 0x03 CMD_MOTOR이 추가됐습니다.
/* BSP UART command */
#define CMD_LED		0x01
#define CMD_BUZZER	0x02
#define CMD_MOTOR	0x03

/* BSP UART error */
#define REQUEST		0x00
#define RES_OK		0x01
  • python의 Car_driver.py에도 아래와 같이 COMMAND 및 RESULT를 정의합니다.
# BSP UART command
CMD_LED = 0x01
CMD_BUZZER = 0x02
CMD_MOTOR = 0x03

# BSP UART error
REQUEST = 0x00
RES_OK = 0x01
  • 확장보드를 통해 모터를 제어를 위해서는 아래와 같이 STM32 보드에 보내면 됩니다.
     COMMAND: 0x03
      RESULT: 0x00
      DATA: [speed1 (2byte), speed2 (2byte), speed3 (2byte), speed4 (2byte) ]

2. STM32 - 메시지 수신 및 제어 기능 구현 (MOTOR)

  • 'Timer' >> 'TIM3'에서 아래와 같이 설정합니다.
    ◦ Clock Source: Internal Clock
    ◦ Channel 1: PWM Generation CH1
    Channel 2: PWM Generation CH2
    ◦ Channel 3: PWM Generation CH3
     Channel 4: PWM Generation CH4
    Prescaler: 71
    Counter Period: 999
      Pulse: 0
      PC6: TM3_CH1
      PC7: TM3_CH2
      PC8: TM3_CH3
      PC9: TM3_CH4

  • 'System Core' >> 'GPIO'에서 아래와 같이 설정합니다.
    PA11: GPIO_Output, DIR1
    PB0: GPIO_Output, DIR2
    PA8: GPIO_Output, DIR3
    PB1: GPIO_Output, DIR4

  • 툴바의 'Device Configuration Tool Code Generation' 버튼을 눌러서 코드를 생성합니다.
  • 아래와 같이 'bsp_uart.h' 파일을 수정합니다.
#ifndef BSP_UART_H_
#define BSP_UART_H_

/* BSP UART command */
#define CMD_LED		0x01
#define CMD_BUZZER	0x02
#define CMD_MOTOR	0x03

/* BSP UART error */
#define REQUEST		0x00
#define RES_OK		0x01

/* BSP UART function */
void USART2_Init(void);

#endif /* BSP_UART_H_ */
  • 다음은 아래와 같이 'bsp_uart.c' 파일을 수정합니다.
#include <string.h>
#include "main.h"
#include "bsp_uart.h"


extern UART_HandleTypeDef huart2;
uint8_t rx_flag;
uint8_t rx_data[256];
uint8_t rx_csum;
uint8_t tx_data[256];
uint8_t tx_csum;

extern TIM_HandleTypeDef htim3;


void USART2_Init(void)
{
	HAL_UART_Receive_IT(&huart2, &rx_flag, 1);
}

int Check_RxCheckSum() {
	int len = rx_data[1] + 2;
	int sum = 0;
	for(int i = 0; i < len; i++) {
		sum += rx_data[i];
	}
	sum &= 0xFF;
	return rx_csum == sum;
}

void Make_TxCheckSum() {
	int len = tx_data[1] + 2;
	int sum = 0;
	for(int i = 0; i < len; i++) {
		sum += tx_data[i];
	}
	tx_csum = sum & 0xFF;
}

void Send_Data() {
	Make_TxCheckSum();
	HAL_UART_Transmit(&huart2, tx_data, tx_data[1] + 2, 10);
	HAL_UART_Transmit(&huart2, &tx_csum, 1, 10);
}

void Set_Led() {
	if (rx_data[3] == CMD_LED) {
		HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, rx_data[5]);

		// response
		memset(tx_data, 0x00, sizeof(tx_data));
		memcpy(tx_data, rx_data, rx_data[1] + 2);
		tx_data[4] = RES_OK; // result
		Send_Data(); // send result
	}
}

void Set_Buzzer() {
	if (rx_data[3] == CMD_BUZZER) {
		HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, rx_data[5]);

		// response
		memset(tx_data, 0x00, sizeof(tx_data));
		memcpy(tx_data, rx_data, rx_data[1] + 2);
		tx_data[4] = RES_OK; // result
		Send_Data(); // send result
	}
}

void Set_Motor() {
	if (rx_data[3] == CMD_MOTOR) {
		short speed1 = (rx_data[5] << 8) | rx_data[6];
		short speed2 = (rx_data[7] << 8) | rx_data[8];
		short speed3 = (rx_data[9] << 8) | rx_data[10];
		short speed4 = (rx_data[11] << 8) | rx_data[12];

		// motor 1
		int dir1 = 1;
		if (speed1 < 0) {
			speed1 = -speed1;
			dir1 = 0;
		}
		if (speed1 < 0) speed1 = 0;
		else if (speed1 > 950) speed1 = 950;
		TIM3->CCR1 = speed1;
		HAL_GPIO_WritePin(DIR1_GPIO_Port, DIR1_Pin, dir1);

		// motor 2
		int dir2 = 1;
		if (speed2 < 0) {
			speed2 = -speed2;
			dir2 = 0;
		}
		if (speed2 < 0) speed2 = 0;
		else if (speed2 > 950) speed2 = 950;
		TIM3->CCR2 = speed2;
		HAL_GPIO_WritePin(DIR2_GPIO_Port, DIR2_Pin, dir2);

		// motor 3
		int dir3 = 1;
		if (speed3 < 0) {
			speed3 = -speed3;
			dir3 = 0;
		}
		if (speed3 < 0) speed3 = 0;
		else if (speed3 > 950) speed3 = 950;
		TIM3->CCR3 = speed3;
		HAL_GPIO_WritePin(DIR3_GPIO_Port, DIR3_Pin, dir3);

		// motor 4
		int dir4 = 1;
		if (speed4 < 0) {
			speed4 = -speed4;
			dir4 = 0;
		}
		if (speed4 < 0) speed4 = 0;
		else if (speed4 > 950) speed4 = 950;
		TIM3->CCR4 = speed4;
		HAL_GPIO_WritePin(DIR4_GPIO_Port, DIR4_Pin, dir4);

		// response
		memset(tx_data, 0x00, sizeof(tx_data));
		memcpy(tx_data, rx_data, rx_data[1] + 2);
		tx_data[4] = RES_OK; // result
		Send_Data(); // send result
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart->Instance == USART2) {
		if (rx_flag == 0xFF) {
			memset(rx_data, 0x00, sizeof(rx_data));
			rx_data[0] = rx_flag;
			HAL_StatusTypeDef uartState = HAL_UART_Receive(&huart2, &rx_data[1], 1, 10); // recv len
			if (uartState == HAL_OK) {
				uartState = HAL_UART_Receive(&huart2, &rx_data[2], rx_data[1], 10); // recv data
			}
			if (uartState == HAL_OK) {
				uartState = HAL_UART_Receive(&huart2, &rx_csum, 1, 10); // recv crc
			}

			if (uartState == HAL_OK && Check_RxCheckSum() > 0) {
				switch (rx_data[3]) {
					case CMD_LED:
						Set_Led();
						break;
					case CMD_BUZZER:
						Set_Buzzer();
						break;
					case CMD_MOTOR:
						Set_Motor();
						break;
				}
			}
		}
		HAL_UART_Receive_IT(&huart2, &rx_flag, 1);
	}
}
  • 다음은 아래 그림과 같이 툴바의 'Build Debug' 버튼을 눌러 컴파일을 진행합니다.
  • 다음은 아래 그림과 같이 툴바의 'Debug' 버튼을 눌러서 프로그램을 보드에 다운로드합니다.

3. Python - STM32 보드 제어기능 구현 (MOTOR)

  • 아래와 같이 보드를 제어하기 위한 'Car_driver.py' 코드를 작성합니다.
import itertools
import threading

import serial

# BSP UART command
CMD_LED = 0x01
CMD_BUZZER = 0x02
CMD_MOTOR = 0x03

# BSP UART error
REQUEST = 0x00
RES_OK = 0x01


class CarDriver:
    def __init__(self, baudrate=115200, port="COM1"):
        # unique seq counter
        self.seq = itertools.count()
        # serial communication driver
        self.ser = serial.Serial()
        self.ser.baudrate = baudrate
        self.ser.port = port
        self.ser.open()
        # serial recive thread run
        self.run_receive_thread()

    def run_receive_thread(self):
        task_receive = threading.Thread(target=self.__recv_data, name="serial_recv_task")
        task_receive.setDaemon(True)
        task_receive.start()

    def __recv_data(self):
        while True:
            data = bytearray(self.ser.read())[0]
            print(data)

    def set_led(self, state):
        self.__send_data(CMD_LED, [1 if state else 0])

    def set_buzzer(self, state):
        self.__send_data(CMD_BUZZER, [1 if state else 0])

    def set_motor(self, speed1, speed2, speed3, speed4):
        data = []
        data.extend(speed1.to_bytes(2, "big", signed=True))
        data.extend(speed2.to_bytes(2, "big", signed=True))
        data.extend(speed3.to_bytes(2, "big", signed=True))
        data.extend(speed4.to_bytes(2, "big", signed=True))
        self.__send_data(CMD_MOTOR, data)

    def __send_data(self, command, data):
        seq = next(self.seq) % 0xFE + 1
        cmd = [0xFF, 0, seq, command, REQUEST]
        cmd.extend(data)
        cmd[1] = len(cmd) - 2
        checksum = sum(cmd) & 0xFF
        cmd.append(checksum)
        print(cmd)
        self.ser.write(cmd)
  • 다음은 보드를 제어하기 위한 'Car_driver_test.ipynb' 파일의 내용을 아래와 같이 바꿉니다.
from ipywidgets import interact
import ipywidgets as widgets

from Car_driver import CarDriver

driver = CarDriver(baudrate=115200, port="COM5")

driver.set_led(True)

driver.set_led(False)

driver.set_buzzer(True)

driver.set_buzzer(False)

driver.set_motor(0, 0, 0, 0)

def run_motor(M1, M2, M3, M4):
    driver.set_motor(M1, M2, M3, M4)
    return M1, M2, M3, M4
    

interact(run_motor, \
         M1=widgets.IntSlider(min=-950,max=950,step=1,value=0), \
         M2=widgets.IntSlider(min=-950,max=950,step=1,value=0), \
         M3=widgets.IntSlider(min=-950,max=950,step=1,value=0), \
         M4=widgets.IntSlider(min=-950,max=950,step=1,value=0))
  • 아래 그림과 같은 실행화면에서 M1, M2, M3, M4 슬라이드를 조절하면 모터를 제어할 수 있습니다.