[TH] Bare Metal Cortex-M Ep.2

จากบทความก่อนหน้านี้ได้ทดลองเขียนโปรแกรมเพื่อศึกษาองค์ประกอบของไฟล์ต่าง ๆ ที่ต้องใช้งานซึ่งจะพบว่ามีรายละเอียดและขั้นตอนเยอะพอสมควร แต่เป็นพื้นฐานสำคัญสำหรับผู้ต้องการศึกษาการเขียนโปรแกรมควบคุม Cortex-M0 ผ่านชิพ STM32F030F4P6 (ภาพที่ 1), Cortex-M3 ด้วย STM32F103C (ภาพที่ 32) และ Cortex-M4 ด้วย STM32F401CCU6 (ภาพที่ 27) อย่างจริงจัง และมีประโยชน์ต่อการศึกษาโครงสร้างการเขียนโปรแกรมด้วยเครื่องมือเขียนโปรแกรมอย่าง STM32CubeIDE (ภาพที่ 2) ซึ่งเป็นเครื่องมือหลักที่จะใช้ในบทความชุดนี้ เนื่องจากเป็นการรวมชุดพัฒนาสำหรับ ARM ของบริษัท ST แบบครบครันทั้ง CubeMX สำหรับออกแบบการใช้งานชิพ เครื่องมือชุดคอมไพล์เลอร์ เครื่องมือดีบักโปรแกรมผ่าน ST-Link ลงชิพ และชุดแก้ไขโค้ดอยู่ในตัวเดียว แถมรองรับการใช้งานทั้งระบบปฏิบัติการ Windows, Linux และ macOS

ภาพที่ 1 บอร์ด STM32F030F4P6 กับ USB–RS232
ภาพที่ 2 โปรแกรม STM32CubeIDE บน Linux

เริ่มต้นสร้างโครงงาน

จากภาพที่ 2 ให้เลือกรายการ “Create a New STM32 project” ดังภาพที่ 3 หรือเข้าเมนู File เลือก New และเลือกรายการย่อย STM32 Project ระบบจะทำการเชื่อมต่ออินเทอร์เน็ตเพื่อดาวน์โหลดข้อมูลสำหรับเลือกไมโครคอนโทรลเลอร์สำหรับใช้งานในโครงงานดังภาพที่ 4

่ภาพที่ 3 รายการสร้างโครงงานแบบต่าง ๆ
ภาพที่ 4 หน้าจอเลือกไมโครคอนโทรลเลอร์

จากภาพที่ 4 ในช่อง Part Number ให้ใส่ STM32F030F4 จะแสดงรายละเอียดของชิพ ให้เลือกรายการชิพ หลังจากนั้นคลิกปุ่ม Next>> เพื่อสร้างโครงงานดังภาพที่ 5

ภาพที่ 5 หน้าจอสร้างโครงงาน

จากภาพที่ 5 ให้ตั้งชื่อโครงงาน (ทีมเราตั้งชื่อไว้เป็น helloSTM32Core0) กำหนดที่เก็บโครงงาน เลือกภาษา (เลือก C ไว้) ตั้งประเภทไบนารีปลายทางเป็น Executable และเลือกประโครงงานเป็น STM32Cube สุดท้ายคลิกที่ปุ่ม Next > จะเข้าสู่ขั้นตอนถัดไปตามภาพที่ 6

ภาพที่ 6 หน้าจอกำหนดรุ่นเฟิร์มแวร์ที่ต้องการใช้งาน

จากหน้าจอในภาพที่ 6 ให้เลือกรุ่นของเฟิร์มแวร์ที่ต้องการ โดยในที่นี้เลือก V1.11.2 และบันทึกไฟล์ที่ดาวน์โหลดเอาไว้ใน Location ที่กำหนด เมื่อเรียบร้อยคลิกที่ปุ่ม Finish ระบบจะดาวน์โหลดไฟล์ที่เกี่ยวข้องตามขั้นตอนในภาพที่ 7 และ 8 หลังจากนั้นจะสร้างไฟล์ตามโครงสร้างในภาพที่ 9 หลังจากที่ผ่านขั้นตอนการกำหนดค่าความถี่สัญญาณนาฬิกาในหัวข้อถัดไป

ภาพที่ 7 หน้าต่างยืนยันการเปิดไฟล์
ภาพที่ 8 หน้าจอดาวน์โหลดเฟิร์มแวร์
ภาพที่ 9 รายการไฟล์ที่ถูกสร้างขึ้น

ปรับแต่งการใช้งานขา

เมื่อทุกอย่างเรียบร้อยจะมีกำหนดหน้าที่ขาใช้งานของชิพดังภาพที่ 10 หรือถ้าต้องการปรับแก้ให้เข้าไปที่ส่วนของ Project Explorer แล้วดับเบิลคลิกที่ไฟล์นามสกุล ioc

ภาพที่ 10 หน้าต่างกำหนดคุณสมบัติของชิพ

ขั้นตอนถัดไปนี้เป็นการกำหนดหน้าที่ให้กับขาของไมโครคอนโทรลเลอร์ โดยแบ่งเป็น 3 ขา คือ ขาสำหรับนำเข้าสัญญาณนาฬิกาภายนอกจากคริสตัล 8MHz บนบอร์ด ขาที่ 2 เป็นขาสำหรับกำหนดหน้าที่ให้กับขาส่งสัญญาณนาฬิาออกไปให้คริสตัล และขา PA4 สำหรับเป็น GPIO_Output ซึ่งเป็นขาที่เชื่อมต่อกับหลอด LED บนบอร์ด STM32F030F4P6

ในภาพที่ 11 ให้คลิกที่ขา PF0 หลังจากนั้นจะปรากฏรายการหน้าที่ของขาให้เลือก โดยให้เลือกเป็น RCC_OSC_IN หรือกรณีที่ต้องการยกเลิกให้คลิกที่รายการ Reset_State ผลลัพธ์หลังจากเลือก RCC_OSC_IN เป็นดังภาพที่ 12

ภาพที่ 11 กำหนดหน้าที่ขา RCC_OSC_IN

จากภาพที่ 12 ให้คลิกเลือกที่ขา PF1 เพื่อกำหนดหน้าที่เป็น RCC_OSC_OUT จะได้ผลลัพธ์ดังภาพที่ 13

ภาพที่ 12 กำหนดหน้าที่ของขา RCC_OSC_OUT

จากภาพที่ 13 ให้คลิกที่ขา PA4 หลังจากนั้นกำหนดหน้าที่เป็น GPIO_Output

ภาพที่ 13 กำหนดหน้าที่ของขา PA4

ถ้ากำหนดหน้าที่ถูกต้องจะแสดงหน้าจอของชิพดังภาพที่ 14

ภาพที่ 14 ภาพของชิพเมื่อกำหนดหน้าที่ของขาเสร็จแล้ว

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

เนื่องจากบอร์ดมีคริสตัลภายนอกให้ ทำให้สามารถสร้างความถี่จากการคูณ 6 (ตัวเลือก X6) และคลิกเลือก PLLCLK เพื่อเปิดการทำงานของ css และจะได้ความถี่ในการทำงานเป็น 48MHz ดังภาพที่ 16 โดยให้คลิกที่แถบ Clock Configuration ดังภาพที่ 15

ภาพที่ 15 หน้าจอของ Clock Configuration
ภาพที่ 16 การตั้งค่า PLLMul เป็น X6 เพื่อสร้างสัญญาณนาฬิกา 48MHz

เมื่อเรียบร้อยให้บันทึกด้วยปุ่ม ดังภาพที่ 17 หรือเข้าเมนู File เลือก Save All จะมีการดาวน์โหลดไฟล์เฟิร์มแวร์และสร้างไฟล์ต้นฉบับให้ใช้งานดังภาพที่ 18 และได้ผลลัพธ์รายการไฟล์ดังภาพที่ 19

ภาพที่ 17 ปุ่ม Save All
ภาพที่ 18 หน้าจอขั้นตอนการสร้างโค้ด

ไฟล์โปรแกรม

เมื่อสร้างไฟล์เรียบร้อยจะสามารถเขียนคำสั่งการทำงานได้ในไฟล์ main.c ที่อยู่ในโฟลเดอร์ Core/Src ดังภาพที่ 19

ภาพที่ 19 ไฟล์ main.c

เมื่อดับเบิลคลิกที่ชื่อไฟล์ main.c ในภาพที่ 19 จะปรากฏหน้าต่างสำหรับแก่ไขโค้ดดังภาพที่ 20 และ 21 โดยในภาพที่ 20 แสดงให้เห็นการกำหนดหน้าที่ของขาจากหน้าจอกำหนดหน้าที่ของขาจากหัวข้อก่อนหน้านี้ มีการเปิดการทำงานของ GPIOF (สำหรับเชื่อมต่อคริสตัลภายนอก) และ GPIOA ซึ่งขา PA4 เป็นส่วนหนึ่งของ GPIO กลุ่ม A และได้กำหนดให้ขา 4 หรือ GPIO_PIN_4 มีค่าสถานะของของเป็น GPIO_PIN_RESET หรือค่า 0 ถ้าต้องการส่ง 1 ให้ใส่ตัวเลข 1 หรือใช้ GPIO_PIN_SET

ส่วนคำสั่ง GPIO_InitStruct เป็นการกำหนดการทำงานของขา ซึ่งจะเห็นว่ากำหนดให้ GPIO_PIN_4 เป็น GPIO_MODE_OUTPUT_PP ไม่ใช้ Pull Up และความเร็วเป็น GPIO_SPEED_FREQ_LOW หลังจากนั้นทำการกำหนดค่าที่ตั้งไว้ให้ไมโครคอนโทรลเลอร์ทำตามที่กำหนดด้วยคำสั่ง HAL_GPIO_Init( ) ดังภาพที่ 19

ภาพที่ 20 โค้ดใน MX_FPIO_Init()

ในภาพที่ 21 เป็นฟังก์ชัน main() จะพบว่ามีการสร้าง comment เพื่อให้ผู้เขียนโปรกรมอ่านทำความเข้าใจและระบุตำแหน่งของการเขียนโค้ดเอาไว้ค่อนข้างละเอียด โดยใจความหลักของขั้นตอนที่ทำของ main() คือ มีการเรียก HAL_Init( ) หลังจากนั้นเรียก SystemClock_Config() แล้วกำหนดค่าของ GPIO ตามฟังก์ชัน MX_GPIO_Init() สุดท้ายมีส่วนของวนรอบไม่รู้จบด้วย while (1) { } สำหรับให้เขียนโค้ดที่ต้องการเหมือนในฟังก์ชัน loop( ) ของ Arduino

ภาพที่ 21 โค้ดในส่วนของ main()

คำสั่งสำหรับส่งสถานะ 0 หรือ 1 ให้ขา PA4 ซึ่งมีชื่อเรียกว่า GPIO_PIN_4 ซึ่งเชื่อมต่อกับวงจรหลอด LED บนบอร์ดมีรูปแบบการใช้งานดังนี้ โดยสถานะเป็น 1 หรือ 0 หรือใช้ค่าคงที่ GPIO_PIN_SET และ GPIO_PIN_RESET แทนตัวเลข ส่วนพอร์ตเป็นชื่อเรียกของกลุ่มขาที่ใช้งาน จึงใช้ชื่อพอร์ต GPIOA

HAL_GPIO_WritePin( พอร์ต, ชื่อขา, สถานะ )

กรณีที่สั่งพร้อมกันหลายขาสามารถใช้เครื่องหมาย | เช่น ต้องการส่งบิต 1 ให้กับ PA2, PA3 และ PA4 เขียนดังนี้

HAL_GPIO_WritePin( GPIOA, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4, GPIO_PIN_SET );

คำสั่งสำหรับหน่วงเวลาในหลักมิลลิวินาทีคือ

HAL_Delay( จำนวนมิลลิวินาทีที่ต้องการหน่วงเวลา )

โค้ดโปรแกรมของ main.c เป็นดังนี้

#include "main.h"

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
    /* USER CODE BEGIN WHILE */
  while (1)
  {
	  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, 0);
	  HAL_Delay( 500 );
	  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, 1);
	  HAL_Delay( 500 );
  }
}

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

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  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();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  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();
  }
}

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

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);

  /*Configure GPIO pin : PA4 */
  GPIO_InitStruct.Pin = GPIO_PIN_4;
  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)  {  }
}

คอมไพล์

เมื่อเขียนโค้ดใน main.c เสร็จให้เลือกประเภทของโครงงานที่ต้องการสร้างเป็น Release ด้วยการคลิกที่ปุ่มดังภาพที่ 22 และจะพบหน้าต่างดังภาพที่ 23 ให้เลือก Configuration เป็น Release แล้วคลิกปุ่ม Set Active ค่า Status ที่ชื่อ Active จะเปลี่ยนจากที่อยู่หลัง Debug จะมาอยู่ที่แถวของ Release สุดท้ายคลิกที่ OK เพื่อตกลงการตั้งค่า

ภาพที่ 22 ไอคอนตั้งค่าปรถเภทของโครงงานปลายทาง
ภาพที่ 23 หน้าต่างเลือกประเภทของการคอมไพล์

เมื่อตั้งค่าเป็น Release เป็นที่เรียบร้อย (ไม่เลือกเป็น Debug เพื่อลดปริมาณโค้ดและทีมงานเราไม่มีเครื่องมือสำหรับดีบักโปรแกรมด้วยเช่นกันครับ) ให้คลิกที่ไอคอนตามภาพที่ 24 แล้วรอการคอมไพล์ ถ้าไม่พิมพ์ผิดพลาดจะแสดงผลลัพธ์ในช่อง Console ดังภาพที่ 25

ภาพที่ 24 ไอคอน Build
ภาพที่ 25 ผลลัพธ์จากการคอมไพล์

เมื่อคอมไพล์ผ่านสำเร็จจะปรากฏโฟลเดอร์ Release ใน Project Explorer ดังภาพที่ 26

ภาพที่ 26 โฟลเดอร์ Release ที่ถูกสร้างหลังการคอมไพล์

เมื่อได้ helloSTM32Core0.bin หรือ helloSTM32Core0.elf ให้ใช้โปรแกรม STM32CubeProgrammer ตามบทความก่อนหน้านี้อัพเข้าสู่ชิพ หลังจากนั้นเปลี่ยนโหมดบนบอร์ดและรีเซ็ตเพื่อรันโปรแกรมจะพบว่าหลอดแอลอีดีบทบอร์ดติดดับสลับกันไป

STM32F401CCU6

กรณีที่ใช้ Cortex-M4 ชิพ STM32F401CCU6 หรือ Black Pill ในภาพที่ 27 ซึ่งมีการต่อคริสตัลภายนอกที่ขา PC14 และ PC15 และปรับตั้งความเร็วสูงสุดของการทำงานอยู่ที่ 84MHz นั้นมีขั้นตอนการสร้างโครงงานเหมือน Cortex-M0 แต่เลือกประเภทของไมโครคอนโทรลเลอร์เป็นดังภาพที่ 28

ภาพที่ 27 บอร์ด WeAct STM32F401CCU6
ภาพที่ 28 ข้อมูลชิพ STM32F401CC ที่เลือกในขั้นตอนสร้างโครงงาน

กำหนดหน้าที่ของขา

ให้กำหนดหน้าที่ของขา PC13, PC14 และ pc15 เป็น GPIO_Output, RCC_OSC32_ON และ RCC_OSC32_OUT ตามลำดับ เหมือนดังในภาพที่ 29 ซึ่งวิธีการเลือกส่วนของขา PC14 และ PC15 ให้เข้าไปส่วนของ System Core และคลิกช่อง RCC และตั้งค่า High Speed Clock (HSE) และ Low Speed Clock (LSE) เป็น Crystal/Ceramic Resonator เมื่อกำหนด 2 ค่านี้ทำให้ขา PH0, PH1, PC14 และ PC15 ถูกเลือก

ภาพที่ 29 หน้าที่ของขาของ STM32F40CCU6

กำหนดสัญญาณนาฬิกา

หลังจากกำหนดหน้าที่ของขาเสร็จให้กำหนดเรื่องของการทำงานของสัญญาณนาฬิา โดยตั้งค่าตามภาพที่ 30 ด้วยการเลือกเปิด HSE ตั้งค่า /M เป็น /25 ตัวคูณ *N เป็น X168 และหาร /P เป็น /2 พร้อมทั้งเลือก PLLCLK กำหนด AHB Prescaler เป็น /1 จะได้ HCLK เป็น 84MHz

ภาพที่ 30 การตั้งค่าสัญญาณนาฬิกา

main.c

โค้ดโปรแกรมใน main.c เป็นดังนี้

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
	  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);
	  HAL_Delay( 500 );
	  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
	  HAL_Delay( 500 );
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  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.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  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(GPIOC, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


คอมไพล์โปรแกรม

จากโค้ดเมื่อทำการคอมไพล์โปรแกรมโดยไม่พบข้อผิดจะเป็นดังภาพที่ 31 หลังจากได้ไฟล์ให้ดำเนินการอัพโหลดลงชิพด้วยโปรแกรม STM32CubeProgrammer เพื่อทดสอบการทำงาน ถ้าไม่มีความผิดพลาดจะพบว่าหลอด LED ที่ต่อกับขา PC13 นั้นติดดับสลับกันทุก 250msec

ภาพที่ 31 ผลของการคอมไพล์โปรแกรม

STM32F103C8

สำหรับ Cortex-M3 ซึ่งในที่นี้ใช้ไมโครคอนโทรลเลอร์ STM32F103C8 หรือ Blue Pill ดังภาพที่ 32 มีการจัดวางโครงสร้างขาเหมือนกับบอร์ด Black Pill

ภาพที่ 32 บอร์ด Blue-Pill

การสร้างโครงงานให้เลือกชิพเป็น STM32F103C8 ดังภาพที่ 33 หลังจากนั้นกำหนดชื่อโครงงานตามภาพที่ 34 และติดตั้งเฟิร์มแวร์ดังภาพที่ 35 ตามลำดับ

ภาพที่ 33 เลือกชิพเป็น STM32F103C8
ภาพที่ 34 ตั้งค่าโครงงานสำหรับ Cortex-M3
ภาพที่ 35 เลือกเฟิร์มแวร์สำหรับ STM32F103C8

ในขั้นตอนของการกำหนดหน้าที่ของขาให้เลือกตามภาพที่ 36 และในขั้นตอนของการกำหนดสัญญาณนาฬิกาให้เลือกตามภาพที่ 37 หลังจากนั้น Save All เพื่อสร้างโค้ดต้นแบบ

ภาพที่ 36 การตั้งค่าขาของ STM32F103C8
ภาพที่ 37 การตั้งค่าสัญญาณนาฬิกา

จากโค้ดต้นแบบให้แก้ไข main.c เป็นดังนี้ หลังจากนั้นเลือกการคอมไพล์เป็น Release และสั่งคอมไพล์จะได้รายงานผลของการใช้หน่วยความจำดังภาพที่ 36 พร้อมผลของการคอมไพล์ดังภาพที่ 37

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
	  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);
	  HAL_Delay( 500 );
	  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
	  HAL_Delay( 500 );
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  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(GPIOC, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
ภาพที่ 36 หน้าจอ Build Analyzer ของชิพ STM32F103C8
ภาพที่ 37 รายงานผลการคอมไพล์ตัวอย่างสำหรับ STM32F103C8

เมื่ออัพโหลด ep02f103.bin เข้าชิพและเปลี่ยนโหมดเป็นโหมดทำงานจะได้ผลการทำงานเป็นดังภาพที่ 38 และ39

ภาพที่ 38 ตัวอย่างผลลัพธ์จากบทความนี้ 1/2
ภาพที่ 39 ตัวอย่างผลลัพธ์จากบทความนี้ 2/2

สรุป

จากบทความนี้จะพบว่า เครื่องมือการเขียนโปรแกรมของ STM32 นั้นมีให้ใช้งานครอบคลุมตั้งแต่การสร้างโครงงาน การกำหนดหน้าที่ของขา การตั้งค่าสัญญาณนาฬิกา การแก้ไขโค้ด การคอมไพล์ และการอัพโหลดเข้าบอร์ด ซึ่งช่วยซ่อนการเขียนส่วนต่าง ๆ จากบทความในตอนแรกได้มาก แต่อย่างไรก็ดีถ้าผู้อ่านเข้าใจบทความก่อนหน้านี้จะมองภาพรวมของการบริหารจัดการโค้ดโปรแกรมได้ดีขึ้น และสามารถเข้าไปแก้ไขส่วนของตารางอินเทอร์รัพต์หรือส่วนเริ่มต้นทำงานของโปรแกรมได้ถึงระดับล่างของการเขียนโปรแกรม ส่วนบทความในตอนที่ 2 นี้ผู้อ่านได้ผ่านขั้นตอนการใช้เครื่องมือ พร้อมทั้งได้เรียนรู้การตั้งค่า GPIO การกำหนดค่าของขา และการหน่วงเวลากันแล้ว สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ

ท่านใดต้องการพูดคุยสามารถคอมเมนท์ไว้ได้เลยครับ

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