[TH] Bare Metal Cortex-M Ep.6

หลังจากได้ใช้คำสั่ง HAL สำหรับสั่งงาน Cortex-M0/M3/M4 ไปกันพอสมควร ครั้งนี้มาประยุกต์เพื่อขับเคลื่อนหุ่นยนต์รถ 2 ล้อให้เดินหน้า ถอยหลัง หันซ้าย หันขวา และหยุดกันบ้าง ดังนั้น ในบทความนี้กล่าวถึงการประยุกต์ใช้ GPIO เพื่อส่งสัญญาณ 0 หรือ 1 ไปยังพอร์ตที่ต่อกับภาคขับมอเตอร์ดังภาพที่ 1 เพื่อให้ตัวหุ่นนั้นเคลื่อนที่

ภาพที่ 1 หุ่นยนต์เคลิื่อนที่ด้วยล้อ

อุปกรณ์และการเชื่อมต่อ

ในการทดลองครั้งนี้ใช้อุปกรณ์ดังต่อไปนี้

  1. ตัวโครงสร้างหุ่น (ในภาพที่ 1 เป็นแบบที่พิมพ์จากเครื่องพิมพ์ 3 มิติ)
  2. บอร์ด STM32F030F4P6 หรือบอร์ดที่เป็น Cortex-M3/M4 โดยให้กำหนดขาให้ถูกต้องโดยศึกษาได้จากบทความก่อนหน้านี้
  3. ชุดโมดูลขับมอเตอร์ (ดังภาพที่ 2)
  4. มอเตอร์ไฟฟ้ากระแสตรง 48:1 หรือรุ่นอื่น ๆ ตามความเหมาะสม
  5. ล้อสำหรับต่อเข้ากับตัวมอเตอร์
  6. ล้อพยุง

ตัวบอร์ดหรือโมดูลสำหรับขับมอเตอร์นั้นมีหลายรุ่นหลายแบบ ทางทีมงานเลือกใช้ตัวดังภาพที่ 2 เนื่องจากราคาย่อมเยา ขนาดเล็กและสามารถใช้กับแรงดัน 3V3 ของ GPIO ได้โดยตรง

ภาพที่ 2 ชุดขับมอเตอร์

จากภาพที่ 2 ได้เชื่อมต่อกับขาของไมโครคอนโทรลเลอร์ STM32F030F4P6 ดังนี้

  1. INT1 ต่อกับ MOTOR_R1
  2. INT2 ต่อกับ MOTOR_R2
  3. INT3 ต่อกับ MOTOR_L1
  4. INT4 ต่อกับ MOTOR_L2

และทางฝั่ง MOTOR-A ต่อเข้ากับมอเตอร์ทางด้านขวา และ MOTOR-B ต่อกับมอเตอร์ทางด้านซ้าย ดังภาพที่ 1

ตั้งค่าบอร์ดไมโครคอนโทรลเลอร์

ก่อนอื่นต้องกำหนดหน้าที่ขาของบอร์ดไมโครคอนโทรลเลอร์ STM32F030F4P6 ให้รองรับการเชื่อมต่อ UART ผ่านทางขา PA9 และ PA10 สำหรับแสดงผลการทำงาน ส่วนขาสำหรับเชื่อมต่อกับโมดูลขับมอเตอร์เป็น PA0, PA1, PA2 และ PA3 ดังภาพที่ 2

ภาพที่ 2 การกำหนด GPIO

ส่วนของสัญญาณนาฬิกากำหนดเหมือนที่ผ่านมา (ภาพที่ 3)

การตั้งค่าสัญญาณนาฬิกา

ในตัวอย่างโปรแกรม ep06m0Car มีการสร้างฟังก์ชันดังนี้

  1. moveStop() สำหรับส่งสถานะ 0 ไปยังสายไฟควบคุมมอเตอร์ทั้ง 4 เส้น ทำให้ไม่มีไฟเลี้ยวมอเตอร์จึงหนุดหมุน
  2. moveForward() สำหรับให้มอเตอร์หมุนเพื่อเคลื่อนที่ไปด้านหน้า
  3. moveBackward() สำหรับให้มอเตอร์หมุนเพื่อเคลื่อนที่มาฝั่งตรงข้ามกับ moveForward()
  4. turnLeft() สำหรับให้เกิดการหมุนซ้าย
  5. turnRight() สำหรับให้เกิดการหมุนขวา

ส่วนโปรแกรมหลักเป็นการวนรอบเพื่อเดินหน้า เลี้ยวซ้าย ถอยหลัง และเลี้ยวขา โดยในแต่ละการเคลื่อนที่ได้เรียกใช้การสั่งหยุดคั่นไว้เล็กน้อย

/* ep06m0Car -------------------------------------------------------------------
 * (C) 2021, JarutEx
 * https://www.jarutex.com
 * -----------------------------------------------------------------------------*/
#include "main.h"
#include <string.h>
#include <stdio.h>

UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

void moveStop() {
	HAL_UART_Transmit(&huart1, (uint8_t*)"Stop\n",5,100);
	HAL_GPIO_WritePin(GPIOA, MOTOR_R1_Pin|MOTOR_R2_Pin|MOTOR_L1_Pin|MOTOR_L2_Pin, GPIO_PIN_SET);
}

void moveForward() {
	HAL_UART_Transmit(&huart1, (uint8_t*)"Forward\n",8,100);
	// Right
	HAL_GPIO_WritePin(GPIOA, MOTOR_R1_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_R2_Pin, GPIO_PIN_RESET);

	// Left
	HAL_GPIO_WritePin(GPIOA, MOTOR_L1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_L2_Pin, GPIO_PIN_SET);
}

void moveBackward() {
	HAL_UART_Transmit(&huart1, (uint8_t*)"Backward\n",9,100);
	// Right
	HAL_GPIO_WritePin(GPIOA, MOTOR_R1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_R2_Pin, GPIO_PIN_SET);

	// Left
	HAL_GPIO_WritePin(GPIOA, MOTOR_L1_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_L2_Pin, GPIO_PIN_RESET);
}

void turnLeft() {
	HAL_UART_Transmit(&huart1, (uint8_t*)"Turn Left\n",10,100);
	// Right
	HAL_GPIO_WritePin(GPIOA, MOTOR_R1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_R2_Pin, GPIO_PIN_SET);

	// Left
	HAL_GPIO_WritePin(GPIOA, MOTOR_L1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_L2_Pin, GPIO_PIN_SET);
}

void turnRight() {
	HAL_UART_Transmit(&huart1, (uint8_t*)"Turn Right\n",11,100);
	// Right
	HAL_GPIO_WritePin(GPIOA, MOTOR_R1_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_R2_Pin, GPIO_PIN_RESET);

	// Left
	HAL_GPIO_WritePin(GPIOA, MOTOR_L1_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOA, MOTOR_L2_Pin, GPIO_PIN_RESET);
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  while (1)
  {
	  moveStop();
	  moveForward();
	  HAL_Delay(2000);
	  moveStop();
	  turnLeft();
	  HAL_Delay(2000);
	  moveStop();
	  moveBackward();
	  HAL_Delay(2000);
	  moveStop();
	  turnRight();
	  HAL_Delay(2000);
  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6;
  RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 38400;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_MultiProcessor_Init(&huart1, 0, UART_WAKEUPMETHOD_IDLELINE) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  HAL_GPIO_WritePin(GPIOA, MOTOR_R1_Pin|MOTOR_R2_Pin|MOTOR_L1_Pin|MOTOR_L2_Pin, GPIO_PIN_RESET);

  GPIO_InitStruct.Pin = MOTOR_R1_Pin|MOTOR_R2_Pin|MOTOR_L1_Pin|MOTOR_L2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}

สรุป

จากบทความนี้จะพบว่า การสั่งงานต่าง ๆ นั้นมีคำสั่งสั่งงานด้วยคำสั่งส่งข้อมูลออก เพื่อให้สถานะของขาที่เชื่อมต่อกับโมดูลมอเตอร์นั้นมีทิศทางหรือหยุดหมุนตามความต้องการโดยไม่ได้มีคำสั่งพิเศษใดเพิ่มเติม ดังนั้น การเขียนโปรแกรมถ้าผู้เขียนเข้าใจหลักการทำงานของอุปกรณ์ที่ใช้งานจะสามารถสั่งงานควบคุมหรือแก้ไข/ปรับปรุงการทำงานได้อย่างถูกต้อง สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-07-26