จากบทความก่อนหน้านี้ได้ทดลองเขียนโปรแกรมเพื่อศึกษาองค์ประกอบของไฟล์ต่าง ๆ ที่ต้องใช้งานซึ่งจะพบว่ามีรายละเอียดและขั้นตอนเยอะพอสมควร แต่เป็นพื้นฐานสำคัญสำหรับผู้ต้องการศึกษาการเขียนโปรแกรมควบคุม Cortex-M0 ผ่านชิพ STM32F030F4P6 (ภาพที่ 1), Cortex-M3 ด้วย STM32F103C (ภาพที่ 32) และ Cortex-M4 ด้วย STM32F401CCU6 (ภาพที่ 27) อย่างจริงจัง และมีประโยชน์ต่อการศึกษาโครงสร้างการเขียนโปรแกรมด้วยเครื่องมือเขียนโปรแกรมอย่าง STM32CubeIDE (ภาพที่ 2) ซึ่งเป็นเครื่องมือหลักที่จะใช้ในบทความชุดนี้ เนื่องจากเป็นการรวมชุดพัฒนาสำหรับ ARM ของบริษัท ST แบบครบครันทั้ง CubeMX สำหรับออกแบบการใช้งานชิพ เครื่องมือชุดคอมไพล์เลอร์ เครื่องมือดีบักโปรแกรมผ่าน ST-Link ลงชิพ และชุดแก้ไขโค้ดอยู่ในตัวเดียว แถมรองรับการใช้งานทั้งระบบปฏิบัติการ Windows, Linux และ macOS
![](https://www.jarutex.com/wp-content/uploads/2021/07/DSF7291.jpg)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-01.png)
เริ่มต้นสร้างโครงงาน
จากภาพที่ 2 ให้เลือกรายการ “Create a New STM32 project” ดังภาพที่ 3 หรือเข้าเมนู File เลือก New และเลือกรายการย่อย STM32 Project ระบบจะทำการเชื่อมต่ออินเทอร์เน็ตเพื่อดาวน์โหลดข้อมูลสำหรับเลือกไมโครคอนโทรลเลอร์สำหรับใช้งานในโครงงานดังภาพที่ 4
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-01a.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-02-1024x772.png)
จากภาพที่ 4 ในช่อง Part Number ให้ใส่ STM32F030F4 จะแสดงรายละเอียดของชิพ ให้เลือกรายการชิพ หลังจากนั้นคลิกปุ่ม Next>> เพื่อสร้างโครงงานดังภาพที่ 5
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-03.png)
จากภาพที่ 5 ให้ตั้งชื่อโครงงาน (ทีมเราตั้งชื่อไว้เป็น helloSTM32Core0) กำหนดที่เก็บโครงงาน เลือกภาษา (เลือก C ไว้) ตั้งประเภทไบนารีปลายทางเป็น Executable และเลือกประโครงงานเป็น STM32Cube สุดท้ายคลิกที่ปุ่ม Next > จะเข้าสู่ขั้นตอนถัดไปตามภาพที่ 6
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-04.png)
จากหน้าจอในภาพที่ 6 ให้เลือกรุ่นของเฟิร์มแวร์ที่ต้องการ โดยในที่นี้เลือก V1.11.2 และบันทึกไฟล์ที่ดาวน์โหลดเอาไว้ใน Location ที่กำหนด เมื่อเรียบร้อยคลิกที่ปุ่ม Finish ระบบจะดาวน์โหลดไฟล์ที่เกี่ยวข้องตามขั้นตอนในภาพที่ 7 และ 8 หลังจากนั้นจะสร้างไฟล์ตามโครงสร้างในภาพที่ 9 หลังจากที่ผ่านขั้นตอนการกำหนดค่าความถี่สัญญาณนาฬิกาในหัวข้อถัดไป
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-05.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-06.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-12-1.png)
ปรับแต่งการใช้งานขา
เมื่อทุกอย่างเรียบร้อยจะมีกำหนดหน้าที่ขาใช้งานของชิพดังภาพที่ 10 หรือถ้าต้องการปรับแก้ให้เข้าไปที่ส่วนของ Project Explorer แล้วดับเบิลคลิกที่ไฟล์นามสกุล ioc
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-07-1024x550.png)
ขั้นตอนถัดไปนี้เป็นการกำหนดหน้าที่ให้กับขาของไมโครคอนโทรลเลอร์ โดยแบ่งเป็น 3 ขา คือ ขาสำหรับนำเข้าสัญญาณนาฬิกาภายนอกจากคริสตัล 8MHz บนบอร์ด ขาที่ 2 เป็นขาสำหรับกำหนดหน้าที่ให้กับขาส่งสัญญาณนาฬิาออกไปให้คริสตัล และขา PA4 สำหรับเป็น GPIO_Output ซึ่งเป็นขาที่เชื่อมต่อกับหลอด LED บนบอร์ด STM32F030F4P6
ในภาพที่ 11 ให้คลิกที่ขา PF0 หลังจากนั้นจะปรากฏรายการหน้าที่ของขาให้เลือก โดยให้เลือกเป็น RCC_OSC_IN หรือกรณีที่ต้องการยกเลิกให้คลิกที่รายการ Reset_State ผลลัพธ์หลังจากเลือก RCC_OSC_IN เป็นดังภาพที่ 12
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-07a.png)
จากภาพที่ 12 ให้คลิกเลือกที่ขา PF1 เพื่อกำหนดหน้าที่เป็น RCC_OSC_OUT จะได้ผลลัพธ์ดังภาพที่ 13
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-07b.png)
จากภาพที่ 13 ให้คลิกที่ขา PA4 หลังจากนั้นกำหนดหน้าที่เป็น GPIO_Output
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-07c.png)
ถ้ากำหนดหน้าที่ถูกต้องจะแสดงหน้าจอของชิพดังภาพที่ 14
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-08.png)
ตั้งค่าสัญญาณนาฬิกา
เนื่องจากบอร์ดมีคริสตัลภายนอกให้ ทำให้สามารถสร้างความถี่จากการคูณ 6 (ตัวเลือก X6) และคลิกเลือก PLLCLK เพื่อเปิดการทำงานของ css และจะได้ความถี่ในการทำงานเป็น 48MHz ดังภาพที่ 16 โดยให้คลิกที่แถบ Clock Configuration ดังภาพที่ 15
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-12-49-06-1024x626.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-12-49-39.png)
เมื่อเรียบร้อยให้บันทึกด้วยปุ่ม ดังภาพที่ 17 หรือเข้าเมนู File เลือก Save All จะมีการดาวน์โหลดไฟล์เฟิร์มแวร์และสร้างไฟล์ต้นฉบับให้ใช้งานดังภาพที่ 18 และได้ผลลัพธ์รายการไฟล์ดังภาพที่ 19
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-10.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-11.png)
ไฟล์โปรแกรม
เมื่อสร้างไฟล์เรียบร้อยจะสามารถเขียนคำสั่งการทำงานได้ในไฟล์ main.c ที่อยู่ในโฟลเดอร์ Core/Src ดังภาพที่ 19
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-13.png)
เมื่อดับเบิลคลิกที่ชื่อไฟล์ 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
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-15.png)
ในภาพที่ 21 เป็นฟังก์ชัน main() จะพบว่ามีการสร้าง comment เพื่อให้ผู้เขียนโปรกรมอ่านทำความเข้าใจและระบุตำแหน่งของการเขียนโค้ดเอาไว้ค่อนข้างละเอียด โดยใจความหลักของขั้นตอนที่ทำของ main() คือ มีการเรียก HAL_Init( ) หลังจากนั้นเรียก SystemClock_Config() แล้วกำหนดค่าของ GPIO ตามฟังก์ชัน MX_GPIO_Init() สุดท้ายมีส่วนของวนรอบไม่รู้จบด้วย while (1) { } สำหรับให้เขียนโค้ดที่ต้องการเหมือนในฟังก์ชัน loop( ) ของ Arduino
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-14.png)
คำสั่งสำหรับส่งสถานะ 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 เพื่อตกลงการตั้งค่า
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-11-47-26.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-11-47-37.png)
เมื่อตั้งค่าเป็น Release เป็นที่เรียบร้อย (ไม่เลือกเป็น Debug เพื่อลดปริมาณโค้ดและทีมงานเราไม่มีเครื่องมือสำหรับดีบักโปรแกรมด้วยเช่นกันครับ) ให้คลิกที่ไอคอนตามภาพที่ 24 แล้วรอการคอมไพล์ ถ้าไม่พิมพ์ผิดพลาดจะแสดงผลลัพธ์ในช่อง Console ดังภาพที่ 25
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-11-48-04.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-11-48-38-1024x627.png)
เมื่อคอมไพล์ผ่านสำเร็จจะปรากฏโฟลเดอร์ Release ใน Project Explorer ดังภาพที่ 26
![](https://www.jarutex.com/wp-content/uploads/2021/07/Screenshot-from-2021-07-17-11-49-16.png)
เมื่อได้ helloSTM32Core0.bin หรือ helloSTM32Core0.elf ให้ใช้โปรแกรม STM32CubeProgrammer ตามบทความก่อนหน้านี้อัพเข้าสู่ชิพ หลังจากนั้นเปลี่ยนโหมดบนบอร์ดและรีเซ็ตเพื่อรันโปรแกรมจะพบว่าหลอดแอลอีดีบทบอร์ดติดดับสลับกันไป
STM32F401CCU6
กรณีที่ใช้ Cortex-M4 ชิพ STM32F401CCU6 หรือ Black Pill ในภาพที่ 27 ซึ่งมีการต่อคริสตัลภายนอกที่ขา PC14 และ PC15 และปรับตั้งความเร็วสูงสุดของการทำงานอยู่ที่ 84MHz นั้นมีขั้นตอนการสร้างโครงงานเหมือน Cortex-M0 แต่เลือกประเภทของไมโครคอนโทรลเลอร์เป็นดังภาพที่ 28
![](https://www.jarutex.com/wp-content/uploads/2021/07/DSF7305.jpg)
![](https://www.jarutex.com/wp-content/uploads/2021/07/f401-01.png)
กำหนดหน้าที่ของขา
ให้กำหนดหน้าที่ของขา 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 ถูกเลือก
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f401-pins-1024x517.png)
กำหนดสัญญาณนาฬิกา
หลังจากกำหนดหน้าที่ของขาเสร็จให้กำหนดเรื่องของการทำงานของสัญญาณนาฬิา โดยตั้งค่าตามภาพที่ 30 ด้วยการเลือกเปิด HSE ตั้งค่า /M เป็น /25 ตัวคูณ *N เป็น X168 และหาร /P เป็น /2 พร้อมทั้งเลือก PLLCLK กำหนด AHB Prescaler เป็น /1 จะได้ HCLK เป็น 84MHz
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f401-clks.png)
main.c
โค้ดโปรแกรมใน main.c เป็นดังนี้
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© 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
![](https://www.jarutex.com/wp-content/uploads/2021/07/f401-compiled-1024x679.png)
STM32F103C8
สำหรับ Cortex-M3 ซึ่งในที่นี้ใช้ไมโครคอนโทรลเลอร์ STM32F103C8 หรือ Blue Pill ดังภาพที่ 32 มีการจัดวางโครงสร้างขาเหมือนกับบอร์ด Black Pill
![](https://www.jarutex.com/wp-content/uploads/2021/07/DSF7306.jpg)
การสร้างโครงงานให้เลือกชิพเป็น STM32F103C8 ดังภาพที่ 33 หลังจากนั้นกำหนดชื่อโครงงานตามภาพที่ 34 และติดตั้งเฟิร์มแวร์ดังภาพที่ 35 ตามลำดับ
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-01-1024x411.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-02.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-03.png)
ในขั้นตอนของการกำหนดหน้าที่ของขาให้เลือกตามภาพที่ 36 และในขั้นตอนของการกำหนดสัญญาณนาฬิกาให้เลือกตามภาพที่ 37 หลังจากนั้น Save All เพื่อสร้างโค้ดต้นแบบ
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-pins-1024x440.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-clks.png)
จากโค้ดต้นแบบให้แก้ไข main.c เป็นดังนี้ หลังจากนั้นเลือกการคอมไพล์เป็น Release และสั่งคอมไพล์จะได้รายงานผลของการใช้หน่วยความจำดังภาพที่ 36 พร้อมผลของการคอมไพล์ดังภาพที่ 37
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© 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****/
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-build-analyzer.png)
![](https://www.jarutex.com/wp-content/uploads/2021/07/ep02-f103-console.png)
เมื่ออัพโหลด ep02f103.bin เข้าชิพและเปลี่ยนโหมดเป็นโหมดทำงานจะได้ผลการทำงานเป็นดังภาพที่ 38 และ39
![](https://www.jarutex.com/wp-content/uploads/2021/07/DSF7308.jpg)
![](https://www.jarutex.com/wp-content/uploads/2021/07/DSF7309.jpg)
สรุป
จากบทความนี้จะพบว่า เครื่องมือการเขียนโปรแกรมของ STM32 นั้นมีให้ใช้งานครอบคลุมตั้งแต่การสร้างโครงงาน การกำหนดหน้าที่ของขา การตั้งค่าสัญญาณนาฬิกา การแก้ไขโค้ด การคอมไพล์ และการอัพโหลดเข้าบอร์ด ซึ่งช่วยซ่อนการเขียนส่วนต่าง ๆ จากบทความในตอนแรกได้มาก แต่อย่างไรก็ดีถ้าผู้อ่านเข้าใจบทความก่อนหน้านี้จะมองภาพรวมของการบริหารจัดการโค้ดโปรแกรมได้ดีขึ้น และสามารถเข้าไปแก้ไขส่วนของตารางอินเทอร์รัพต์หรือส่วนเริ่มต้นทำงานของโปรแกรมได้ถึงระดับล่างของการเขียนโปรแกรม ส่วนบทความในตอนที่ 2 นี้ผู้อ่านได้ผ่านขั้นตอนการใช้เครื่องมือ พร้อมทั้งได้เรียนรู้การตั้งค่า GPIO การกำหนดค่าของขา และการหน่วงเวลากันแล้ว สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ
ท่านใดต้องการพูดคุยสามารถคอมเมนท์ไว้ได้เลยครับ
(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-07-17, 2021-07-18, 2021-10-24