หลังจากได้ใช้คำสั่ง HAL สำหรับสั่งงาน Cortex-M0/M3/M4 ไปกันพอสมควร ครั้งนี้มาประยุกต์เพื่อขับเคลื่อนหุ่นยนต์รถ 2 ล้อให้เดินหน้า ถอยหลัง หันซ้าย หันขวา และหยุดกันบ้าง ดังนั้น ในบทความนี้กล่าวถึงการประยุกต์ใช้ GPIO เพื่อส่งสัญญาณ 0 หรือ 1 ไปยังพอร์ตที่ต่อกับภาคขับมอเตอร์ดังภาพที่ 1 เพื่อให้ตัวหุ่นนั้นเคลื่อนที่
อุปกรณ์และการเชื่อมต่อ
ในการทดลองครั้งนี้ใช้อุปกรณ์ดังต่อไปนี้
- ตัวโครงสร้างหุ่น (ในภาพที่ 1 เป็นแบบที่พิมพ์จากเครื่องพิมพ์ 3 มิติ)
- บอร์ด STM32F030F4P6 หรือบอร์ดที่เป็น Cortex-M3/M4 โดยให้กำหนดขาให้ถูกต้องโดยศึกษาได้จากบทความก่อนหน้านี้
- ชุดโมดูลขับมอเตอร์ (ดังภาพที่ 2)
- มอเตอร์ไฟฟ้ากระแสตรง 48:1 หรือรุ่นอื่น ๆ ตามความเหมาะสม
- ล้อสำหรับต่อเข้ากับตัวมอเตอร์
- ล้อพยุง
ตัวบอร์ดหรือโมดูลสำหรับขับมอเตอร์นั้นมีหลายรุ่นหลายแบบ ทางทีมงานเลือกใช้ตัวดังภาพที่ 2 เนื่องจากราคาย่อมเยา ขนาดเล็กและสามารถใช้กับแรงดัน 3V3 ของ GPIO ได้โดยตรง
จากภาพที่ 2 ได้เชื่อมต่อกับขาของไมโครคอนโทรลเลอร์ STM32F030F4P6 ดังนี้
- INT1 ต่อกับ MOTOR_R1
- INT2 ต่อกับ MOTOR_R2
- INT3 ต่อกับ MOTOR_L1
- INT4 ต่อกับ MOTOR_L2
และทางฝั่ง MOTOR-A ต่อเข้ากับมอเตอร์ทางด้านขวา และ MOTOR-B ต่อกับมอเตอร์ทางด้านซ้าย ดังภาพที่ 1
ตั้งค่าบอร์ดไมโครคอนโทรลเลอร์
ก่อนอื่นต้องกำหนดหน้าที่ขาของบอร์ดไมโครคอนโทรลเลอร์ STM32F030F4P6 ให้รองรับการเชื่อมต่อ UART ผ่านทางขา PA9 และ PA10 สำหรับแสดงผลการทำงาน ส่วนขาสำหรับเชื่อมต่อกับโมดูลขับมอเตอร์เป็น PA0, PA1, PA2 และ PA3 ดังภาพที่ 2
ส่วนของสัญญาณนาฬิกากำหนดเหมือนที่ผ่านมา (ภาพที่ 3)
ในตัวอย่างโปรแกรม ep06m0Car มีการสร้างฟังก์ชันดังนี้
- moveStop() สำหรับส่งสถานะ 0 ไปยังสายไฟควบคุมมอเตอร์ทั้ง 4 เส้น ทำให้ไม่มีไฟเลี้ยวมอเตอร์จึงหนุดหมุน
- moveForward() สำหรับให้มอเตอร์หมุนเพื่อเคลื่อนที่ไปด้านหน้า
- moveBackward() สำหรับให้มอเตอร์หมุนเพื่อเคลื่อนที่มาฝั่งตรงข้ามกับ moveForward()
- turnLeft() สำหรับให้เกิดการหมุนซ้าย
- 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