บทความนี้กล่าวถึงการใช้งานพอร์ตสื่อสารอนุกรม UART ซึ่งนิยมใช้มานาน และสะดวกต่อการใช้งาน โดยใช้ขา PA9 และ PA10 ต่อเข้าเป็นขา Tx และ Rx ของไมโครคอนโทรลเลอร์ทั้ง cortex-M0/M3/M4 เชื่อมต่อกับขา Rx/Tx ของตัวแปลงระดับแรงดันสัญญาณสำหรับสื่อสารผ่านทางพอร์ต USB ซึ่งตัวอย่างโปรแกรมเป็นการประมวลผลในไมโครคอนโทรลเลอร์และนำออกผลลัพธ์ผ่านทางพอร์ตสื่อสารอนุกรมที่ใช้งานโปรแกรม moserial เป็นซอฟต์แวร์สื่อสารข้อมูลดังภาพที่ 1
การสื่อสารผ่านพอร์ตอนุกรม
การสื่อสารผ่านพอร์ตอนุกรมต้องกำหนดอัตราความเร็วในการสื่อสารที่ตรงกันทั้ง 2 ฝั่ง ซึ่ง STM32F030F4P6 เลือกใช้ความเร็ว 38400 bps (bits-per-second) ส่วน STM32F103C8 และ STM32F401cc เลือกใช้ 115200 bps พร้อมทั้งกำหนดจำนวนบิตของข้อมูลให้มีขนาด 8 บิต มีบิตสำหรับใช้เป็นบิตระบุการสิ้นสุด 1 บิต (stop bit) และไม่ใช้พาริตีในการตรวจสอบข้อมูลระหว่างการสื่อสาร โดยบทความนี้กล่าวถึงเฉพาะการสื่อสารด้วยวิธีโพลิง (Polling) ซึ่งในการทำงานจริงสามารถทำงานได้ 3 แบบ คือ
- Polling
- Interrupt
- DMA (Direct Memory Access)
การตั้งค่าพอร์ตอนุกรม
ขั้นตอนการตั้งค่าการสื่อสารโดยปกติจะถูกโปรแกรม STM32CubeMX สร้างรหัสโปรแกรมการตั้งค่าให้โดยอัติโนมัติ ซึ่งอยู่ในฟังก์ชัน MX_USART1_UART_Init( void ) และมีตัวแปรภายนอกประเภท UART_HandleTypeDef ชื่อ huart1 (หรือชื่ออ่านตามที่เลือกตั้งค่า) ดังนี้ ซึ่งในบทความเลือกการทำงานของพอร์ตสื่อสารอนุกรมเป็นแบบ MultiProcessor เนื่องจากใช้สายสื่อสารเพียง 2 เส้นสำหรับการนำออกข้อมูล หรือขา Tx และนำเข้าข้อมูลผ่านทางขา Rx
UART_HandleTypeDef huart1;
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
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;
if (HAL_MultiProcessor_Init(&huart1, 0, UART_WAKEUPMETHOD_IDLELINE) != HAL_OK)
{
Error_Handler();
}
}
ขั้นตอนของการตั้งค่าประกอบด้วย 2 ส่วน คือ
- กำหนดการตั้งค่าในตัวแปร โดยตั้งค่าต่อไปนี้
- Instance สำหรับระบุหมายเลขพอร์ตสื่อสาร โดยในตัวอย่างเลือกใช้ USART1 เนื่องจากเป็นพอร์ตเดียวที่ STM32F030F4P6 มีให้ใช้งาน และใน Core-M3/M4 ล้วนรองรับพอร์ตนี้เช่นกัน
- Init.BaudRate สำหรับกำหนดความเร็วในการสื่อสาร โดยในตัวอย่างกำหนดอัตราการสื่อสารเป็น 115200
- Init.WordLength กำหนดจำนวนบิตข้อมูล ในที่นี้กำหนดเป็น UART_WORDLENGTH_8B
- Init.StopBits กำหนดจำนวนบิตของบิตระบุการสิ้นสุด ซึ่งกำหนดเป็น 1 บิต หรือ UART_STOPBOTS_1
- Init.Parity กำหนดเป็นไม่ใช้พาริตีบิต หรือ UART_PARITY_NONE
- Init.Mode เลือกใช้โหมดการทำงานแบบส่งและรับ UART_MODE_TX_RX
- Init.HwFlowCtl สำหรับกำหนดให้มีการควบคุมการนำออกหรือนำเข้าข้อมูลด้วยฮาร์ดแวร์ ซึ่งเราไม่ต้องการใช้เพราะใช้สายสื่อสารเพียง 2 เส้น จึงกำหนดเป็น UART_HWCONTROL_NONE
- Init.OverSampling กำหนดจำนวนบิตการเกิด over sampling เป็น 16 บิต หรือ UART_OVERSAMPLING_16
- ทำการเริ่มใช้งานโดยใช้คำสั่งรูปแบบต่อไปนี้ และถ้าผลลัพธ์เป็น HAL_OK หมายถึงการตั้งค่าสำเร็จ แต่ถ้าไม่ใช้หมายถึงเกิดความผิดพลาดในการเริ่มต้นทำงานตามการตั้งค่าที่ระบุ
ผลของการตั้งค่า = HAL_MultiProcessor_Init( &ตัวแปรเก็บการตั้งค่า, 0, UART_WAKEUPMETHOD_IDLELINE)
คำสั่งสำหรับส่งข้อมูลออก
รูปแบบของคำสั่งนำออกข้อมูลผ่านพอร์ตสื่อสารอนุกรมมีรูปแบบการสั่งงานดังนี้
HAL_UART_Transmit( &พอร์ตสื่อสาร, บัฟเฟอร์, ขนาดของบัฟเฟอร์, ค่าหน่วงเวลาในหน่วยมิลลิวินาที)
คำสั่งสำหรับรับข้อมูลจากพอร์ตสื่อสารอนุกรม
รูปแบบของคำสั่งนำเข้าข้อมูลจากพอร์ตสื่อสารอนุกรมเป็นดังนี้
HAL_UART_Receive( &พอร์ตสื่อสาร, บัฟเฟอร์, ขนาดของบัฟเฟอร์, ค่าหน่วงเวลา )
คำสั่งสำหรับการรับข้อมูลแบบการขัดจังหวะ
การรับข้อมูลจากพอร์ตสื่อสารอนุกรมแบบใช้การขัดจังหวะเป็นส่วนของโปรแกรมทำงานเมื่อมีการรับ/ส่งข้อมูลผ่านทางพอร์ตสื่อสารจะต้องเตรียมการดังนี้
- ตัวแปรสำหรับเก็บข้อมูล
- ฟังก์ชันสำหรับทำงานเบื้องหลัง
จากขั้นตอนทั้ง 2 สามารถเขียนเป็นได้ดังตัวอย่างดังนี้
uint8_t ตัวแปรบัฟเฟอร์[ขนาดของบัฟเฟอร์] = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// สิ่งที่ทำเมื่อเกิดข้อมูลเข้ามาในพอร์ตสื่อสาร
}
void main() {
//...
HAL_UART_Receive_IT (&พอร์ตสื่อสาร, ตัวแปรบัฟเฟอร์, ขนาดของบัฟเฟอร์);
//...
}
รูปแบบของคำสั่งเป็นดังนี้
HAL_UART_Receive_IT( ตำแหน่งพอร์ตสื่อสาร, ตัวแปรบัฟเฟอร์, ขนาดของบัฟเฟอร์ )
ตัวอย่างโปรแกรม
ตัวอย่างโปรแกรมในบทความนี้เป็นนำออกข้อความไปยังพอร์ตสื่อสารอนุกรม โดยตัวอย่างมีการแปลงข้อมูลประเภทตัวเลขให้เป็นข้อความด้วยคำสั่ง sprintf( ) ทั้งนี้เนื่องจากไม่สามารถใช้คำสั่ง printf( ) ได้เหมือนการสั่ง print()/println() ใน Arduino
Cortex-M0
เริ่มต้นด้วยการสร้างโครงงานดังภาพที่ 2 และกำหนดขา PA10 และ PA9 เป็นขาสื่อสารอนุกรมโดยใช้โหมดสื่อสารเป็น Multiprocessor Communication ดังภาพที่ 3
หลังจากนั้นปรับค่าสัญญาณนาฬิการดังภาพที่ 4 และเขียนโปรแกรมใน main.c ดังภาพที่ 5 เพื่อนำออกค่าที่เก็บในตัวแปร value หลังจากนั้นคอมไพล์จะพบว่าการใช้คำสั่งเกี่ยวกับการสื่อสารอนุกรมร่วมกับการแปลงสตริงทำให้หน่วยความจำของไมโครคอนโทรลเลอร์ถูกใช้ไปกว่า 45% ดังภาพที่ 6
ผลของการทำงานเป็นดังภาพที่ 7
ให้ปรับเปลี่ยนโค้ดโปรแกรมใน main.c เป็นดังนี้เพื่อทำการคำนวณค่า prime number และรายงานผลจำนวนตัวเลขที่พบพร้อมกับค่าเวลาที่เสียไป ดังภาพที่ 8
/* 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 */
#include <math.h>
#include <stdio.h>
#include <string.h>
/* 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 ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int counter = 0;
uint32_t t0, t1;
uint16_t isPrimeNumber(uint16_t x) {
uint16_t i;
for (i = 2; i < x; i++) {
if (x % i == 0) {
return 0;
}
}
if (i == x)
return 1;
return 0;
}
void testPrimeNumber(uint32_t maxN) {
counter = 0;
t0 = HAL_GetTick();
for (uint32_t n = 2; n < maxN; n++) {
if (isPrimeNumber(n)) {
counter++;
}
}
t1 = HAL_GetTick();
}
/* 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();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
testPrimeNumber(2000);
char msg[32];
int msgLen=0;
sprintf(msg,"Found %d.\n",counter);
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
sprintf(msg,"Done in %ld ms.\n",(t1-t0));
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* 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};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {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();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
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();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
/* 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****/
Cortex-M3
เมื่อนำตัวอย่าง Prime Number มาใช้กับชิพ STM32F103C8 ให้เริ่มสร้างโครงงานใหม่ดังภาพที่ 9 และตั้งค่าขาตามภาพที่ 10 พร้อมทั้งกำหนดการทำงานของสัญญาณนาฬิกาตามภาพที่ 11
เมื่อคอมไพล์โปรแกรมจะพบว่าการใช้หน่วยความจำน้อยกว่าทั้งนี้เพราะ STM32F103C8 มีปริมาณรอมและแรมมากกว่าดังภาพที่ 12
รหัสของโปรแกรมเป็นดังนี้
/* 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 */
#include <math.h>
#include <stdio.h>
#include <string.h>
/* 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 ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int counter = 0;
uint32_t t0, t1;
uint16_t isPrimeNumber(uint16_t x) {
uint16_t i;
for (i = 2; i < x; i++) {
if (x % i == 0) {
return 0;
}
}
if (i == x)
return 1;
return 0;
}
void testPrimeNumber(uint32_t maxN) {
counter = 0;
t0 = HAL_GetTick();
for (uint32_t n = 2; n < maxN; n++) {
if (isPrimeNumber(n)) {
counter++;
}
}
t1 = HAL_GetTick();
}
/* 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();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
testPrimeNumber(2000);
char msg[32];
int msgLen=0;
sprintf(msg,"Found %d.\n",counter);
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
sprintf(msg,"Done in %ld ms.\n",(t1-t0));
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* 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 USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
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;
if (HAL_MultiProcessor_Init(&huart1, 0, UART_WAKEUPMETHOD_IDLELINE) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
/* 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****/
ตัวอย่างผลลัพธ์ของการทำงานเป็นดังภาพที่ 1 ซึ่งจะพบว่าความเร็วในการทำงานของ STM32F103C8 นั้นสูงกว่า STM32F030F4P6 มาก และราคาสูงกว่าด้วยเช่นกัน
ตัวอย่างการรับค่า
ส่วนหลักของตัวอย่างการรับค่าเป็นดังนี้
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();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
char msg[32];
int msgLen=0;
sprintf(msg,"...Say something...\n");
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
strcpy(msg,"");
HAL_UART_Receive( &huart1, (uint8_t*)msg, 32, 5000 );
for (int i=0; i<32; i++) {
if (msg[i] == '\n') {
msg[i] = 0;
break;
}
if (msg[i] == '\r') {
msg[i] = 0;
break;
}
}
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
HAL_UART_Transmit( &huart1, (uint8_t*)"\nEnd of program.\n", 17, 100);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
}
}
จากตัวอย่างด้านบนเมื่อคอมไพล์และอัพเข้าชิพเมื่อทดสอบโปรแกรมจะเป็นดังภาพที่ 13
จากภาพที่ 13 จะพบว่า ในรอบแรกที่โปรแกรมทำงานได้รอเป็นเวลา 5 วินาที เพราะกำหนดค่า 5000 ไว้ในคำสั่ง HAL_UART_Receive( ) แต่ผู้ใช้ไม่ได้นำเข้าข้อมูลการแสดงผลจึงเป็นบรรทัดว่างและรายงาน End of program ส่วนการทำงานในรอบที่ 2 ได้กรอก Hello หลังจากนั้นเมื่อหมดเวลา 5 วินาทีจะรายงานคำว่า Hello ให้เห็นและจบโปรแกรม
ตัวอย่างโปรแกรมการรับข้อมูลจากการขัดจังหวะ
ตัวอย่างโปรแกรมการรับข้อมูลแบบใช้การขัดจังหวะช่วยการนำเข้าข้อมูลมีข้อดีคือสามารถรับข้อมูลได้โดยไม่ต้องรอให้หมดช่วงเวลาที่ระบุไว้หเหมือนในการทำโพลิง แต่การออกแบบโปรแกรมจะซับซ้อนขึ้นเนื่องจากมีส่วนของโปรแกรมทำงานเป็นเบื้องหลัง ซึ่งโค้ดของโปรแกรมเป็นดังนี้ (ตัวอย่างนี้ใช้ STM32F103C8) ซึ่งฟังก์ชัน HAL_UART_RxCpltCallback( ) จะเริ่มต้นเมื่อมีการเรียก HAL_UART_Receive_IT( ) ในฟังก์ชัน main( ) หลังจากนั้นแม้เข้าสู่การวนรอบไม่รู้จบใน while(1) โปรแกรมยังทำงานตอบสนองจากการรับค่าจากพอร์ตสื่อสารตลอดไป
#include "main.h"
UART_HandleTypeDef huart1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
uint8_t uartBuffer[32] = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(&huart1, uartBuffer, 32, 100);
HAL_UART_Receive_IT(&huart1, uartBuffer, 32);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT (&huart1, uartBuffer, 32);
while (1)
{
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
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();
}
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();
}
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
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;
if (HAL_MultiProcessor_Init(&huart1, 0, UART_WAKEUPMETHOD_IDLELINE) != HAL_OK)
{
Error_Handler();
}
}
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();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
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)
{
}
}
จากตัวอย่างโปรแกรมจะพบว่าเมื่อรันจะไม่มีข้อมูลใดแสดงเพราะในฟังก์ชันตอนสนองการขัดจังหวะที่เขียนนั้นเหมือนการทำ echo คือ เมื่อมีข้อมูลเข้าให้ส่งออกและรอรับรอบถัดไป จึงได้ผลลัพธ์ดังภาพที่ 14 เมื่อส่ง https://www.jarutex.com จะแสดงข้อความ https://www.jarutex.com และรอรับข้อมูลถัดไป พร้อมทั้งวนทำซ้ำอย่างนี้จนกว่าจะตัดการเชื่อมต่อ
กรณีที่ต้องการทำความเข้าใจให้มากขึ้นให้ผู้อ่านลองตัดบรรทัดเรียกใช้ HAL_UART_Receive_IT() ในฟังก์ชัน HAL_UART_RxCpltCallback( ) แล้วเปรียบผลการทำงานระหว่างมีกับไม่มีการเรียกใช้คำสั่ง HAL_UART_Receive_IT()
การขัดจังหวะกับ STM32F030F4P6
กรณีที่ใช้กับ STM32F030F4P6 โดยกำหนดการใช้งานขาดังภาพที่ 15 พร่อมทั้งเลือกโหมดเป็น Async. ดังภาพที่ 16 หลังจากนั้นเลือก NVIC ให้ทำงานดังภาพที่ 17
โค้ดตัวอย่างการรับค่าตัวอักษร 1 ตัวเป็นดังนี้
#include "main.h"
#include <string.h>
#include <stdio.h>
UART_HandleTypeDef huart1;
uint8_t uartBuffer[32] = {0};
char msg[32];
int msgLen=0;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(&huart1, uartBuffer, strlen(( char *)uartBuffer), 100);
HAL_UART_Receive_IT(&huart1, uartBuffer, 1);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
sprintf(msg,"...Say something...\n");
msgLen = strlen( msg );
HAL_UART_Transmit( &huart1, (uint8_t*)msg, msgLen, 100);
HAL_UART_Receive_IT (&huart1, uartBuffer, 1);
while (1)
{
}
}
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_UART_Init(&huart1) != 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, GPIO_PIN_5, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_5;
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)
{
}
}
สรุป
จากบทความนี้จะพบว่าการนำออกข้อมูลเพื่อสื่อสารกับอุปกรณ์อื่นผ่านทางพอร์ตอนุกรมเป็นวิธีการที่ไม่ยุ่งยากแต่ผู้ออกแบบระบบต้องเพิ่มวงจรแปลงแรงดันการสื่อสารเพิ่มเข้ามา โดยในตัวอย่างนี้ใช้การสื่อสารกับคอมพิวเตอร์จึงต้องอาศัยวงจร USB-to-RS232 เพื่อสื่อสารกับคอมพิวเตอร์แบบ RS232 ผ่านทางพอร์ต USB และวิธีการเขียนโปรแกรมเป็นแบบ Polling เมื่อพิจารณาจากตัวอย่างโปรแกรมรับค่าจะพบว่า ต้องรอให้ครบตามเวลาที่กำหนดไว้จึงจะแสดงข้อมูลที่นำเข้าไป ซึ่งไม่สะดวกนัก แต่เมื่อใช้การรับข้อมูลแบบการขัดจังหวะ ผู้เขียนโปรแกรมสามารถอ่านบัฟเฟอร์ได้ตลอดเวลาโดยไม่ต้องขอการรับค่าในแบบโพลิง สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ
(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-07-24, 2021-07-25, 2021-08-13