일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- conda오류
- conda
- anaconda오류
- conda: command not found
- Machine Learning
- torch
- 리눅스
- 머신러닝
- essential deep learning paper reading
- Tensor
- 일귀
- Ai
- ML
- pytorch tensor
- pyTorch
- stm32f 시리즈를 이용한 arm cortex-m3/m4 구조와 응용
- 텐서
- 리눅스 오류
- 공부
- Today
- Total
Embedded World
STM32F103RB 보드와 초음파 센서(HC-SR04)를 이용한 거리 측정기 개발 [코드 포함] 본문
서론
C언어를 활용해 MCU와 센서를 직접 제어하는 Application을 개발해보고자, ST사의 'STM32F103RB'(Nucleo F103RB) 보드와, 초음파 센서인 'HC-SR04'를 활용하여, 초음파로 최대 4m의 거리를 측정하는 시스템을 개발해 보았습니다.
개발을 위한 IDE로는 ST사의 'CubeMX'와 ARM사의 'Keil MDK'를 사용하였고, 개발 언어로는 'C'를 사용했습니다.
개발 결과, 최소 2cm부터 최대 400cm까지의 거리를 측정해 UART로 측정 결과를 송신하는 시스템을 개발할 수 있었습니다.
본론
프로젝트의 목표는 "초음파 센서를 이용해 거리를 측정한 뒤, 측정된 거리 데이터를 PC로 송신하는 것" 입니다.
1. 시스템 요구사항 선정
- Echo 이후 다음 Trig까지 최소 10mS의 간격을 두어야 한다. (HC-SR04 센서의 제약사항)
- 한번 신호를 보낸 이후 최소 60ms의 간격을 두어야 한다. (HC-SR04 센서의 제약사항)
- HC-SR04 센서의 측정 가능 범위인 2cm~400cm 의 측정이 가능해야한다.
- 통신 프로토콜을 사용하여 측정된 거리를 PC로 송신할 수 있어야 한다. ('xx cm' 형식)
2. 하드웨어 스펙 파악
2.1. 초음파센서(HC-SR04) Spec 확인
센서 사용을 위해, 가장 먼저 HC-SR04의 User Manual로 센서 Spec을 확인했습니다.
1. 센서가 탐지 가능한 거리는 2cm ~ 400cm(±3mm) 이다.
2. HC-SR04의 동작 전압은 DC 3.3V ~ 5V 내외이다.
3. Trig핀에 10uS동안 3.3Vdc~5Vdc의 Pulse를 인가해 8 Cycle Sonic Burst를 만들 수 있다.
4. 물체 사이의 거리(cm)는, trigger 신호를 보낸 시점과 echo를 받은 시점의 간격(uS)을 58로 나눈 값과 같다.
5. 물체 감지에 실패할 경우, Echo핀은 36mS의 Pulse를 발생시킨다.
6. Echo 이후 다음 Trig까지 최소 10mS의 간격을 두어야 한다.
7. 한번 신호를 보낸 이후 최소 60ms의 간격을 두어야 한다.
2.2. 마이크로컨트롤러(STM32F103RB) Spec 확인
이후, 프로젝트 목표인 센서를 이용해 측정된 거리 데이터를 PC로 송신하는 시스템 설계가 가능한지 파악하기 위해 보드의 Spec을 확인하였습니다.
1. 128KB 64bit Flash / 20KB SRAM 을 가진다.
2. VDD 동작 전압은 2~3.6V 이다.
2. STM32F103RB 보드의 HSI Clock은 최대 64MHz까지 동작 가능하다.
3. 4개의 Timer 을 가진다.
4. USART, I2C, CAN, SPI, USB 통신을 지원한다.
해당 조건에서 프로젝트 구현이 충분히 가능하다고 판단하여, 다음으로는 HW 개발을 진행하였습니다.
3. HW 개발
3.1 CubeMX CHIP 설정
CubeMX IDE를 사용해 그림4, 그림5와 같이 Pin setting과 Clock setting을 하였습니다.
1. Trig에 초음파 Pulse를 출력하기 위한 GPIO Output Pin (PA7)
2. Trig에 10uS동안 Pulse를 주기 위한 Counter TIM3
3. Echo를 통한 Input Capture용 TIM4(Input Capture Mode) (PB6)
4. 디버깅용 8개의 LED Pin (PC0~7)
5. USART (PA2, PA3)
6. Switch Pin (SW1~4, B1)
3.2 Timer 설정
(1) TIM3 : Output Compare / Counter
TIM3는 두가지 용도를 가집니다.
1) Trig에 10uS의 Pulse를 주기 위해 사용합니다.
TIM3는 APB1(64MHz)에 연결되어있으니, Prescaler을 64-1로, Auto Reload Register(Period)을 65536-1로 설정해 1uS 단위로 count 되도록 먼저 설정을 해 줍니다. 그리고 Output Compare모드로 CCR을 10-1로 설정해 10us 뒤 저절로 Interrupt가 걸려 Trig에 10uS길이의 Pulse를 만들 수 있도록 설정하겠습니다.
2) 음파가 돌아오기까지 시간을 측정합니다.
HC-SR04의 spec상, 음파의 간격을 최대 36mS까지 측정할 수 있어야 하므로, ARR을 65536-1로 설정해, 65mS까지 Count할 수 있도록 설정하여 사용하도록 하겠습니다.
(2) TIM4 : Input Capture
TIM4는 Echo핀의 Rising Edge와 Falling Edge 사이의 간격을 계산하기위해 사용하겠습니다. 이 또한 최대 36mS까지 측정이 가능해야하므로, ARR은 65536-1로 설정하고, 최소 탐지 거리가 3mm(음파가 8.82uS에 이동하는 거리)이므로 prescaler은 64-1로 설정하겠습니다.
3.3 HW 연결
이후 그림5와 같이 보드와 센서를 연결해 하드웨어를 초기화했습니다.
- 초록선 (5Vdc VDD)
- 파랑선 (Trig, D12 == PA7)
- 보라선 (Echo, D10 == PB6)
- 하양선 (GND)
4. 소프트웨어 개발
먼저 Pseudo Code입니다.
/* Pseudo Code */
int main():
HW Initialization
while (1):
Default:
Do nothing
if Switch pressed:
Make_Ultrawave_Pulse()
Count untill 36mS
duration = time between Trig & Echo
echo_width = echo_signal_width
if echo_width > 36ms: // failed
Send Failed Message
Turn LED_7
else: // success
distance = duration/58 // distance(cm)
Send distance
Turn LED_6
위와 같은 흐름이 '요구사항 1~7'을 모두 만족시킬 수 있다고 판단했습니다.
4.1. Make_Ultrawave_Pulse() 함수
Make_Ultrawave_Pulse() 는 HC-SR04 센서로 초음파를 보내도록 명령하는 함수입니다.
HC-SR04 센서로 Sonic Pulse를 만들기 위해선 Trig핀(PA7)에 10uS동안 High 신호를 인가해야 합니다.
그리고 이는 `PA7 Pin High --> 10uS delay --> PA7 Pin Low` 매커니즘을 통해 구현할 수 있다고 생각하여 다음과 같이 구현하였습니다.
void Make_Ultrawave_Pulse()
{
/* PB6에서 Echo Signal을 받기위해 TIM4를 미리 Enable! */
__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_CC1);
HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_1);
// 10uS Pulse 생성
HAL_TIM_Base_Start(&htim3);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
__HAL_TIM_SET_COUNTER(&htim3, 0);
while (__HAL_TIM_GET_COUNTER(&htim3) < 10); // delay 10uS
HAL_TIM_Base_Stop(&htim3);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
}
while (__HAL_TIM_GET_COUNTER(&htim3) < 10); 의 10은 TIM3의 10ticks 를 의미합니다.
그 이유는 TIM3는 64MHz의 Clock에 연결되어있고, Prescaler는 64-1으로 설정되있기 때문에, TIM3의 1tick에 1uS가 소요되기 때문입니다.
4.2. Distance 계산
초음파가 물체에 반사되어 돌아오기까지 걸리는 시간(time duration)은, Make_Ultrawave_Pulse()을 실행시킨 뒤, Echo핀에 High가 들어오기까지의 시간입니다.
이 time duration을 이용해 물체까지의 거리를 구하면 다음과 같습니다.
(거리를 2로 나누어준 이유는, 초음파가 왕복했기 때문이며, 음파의 속력을 340m/sec 로 반영하였습니다.)
Distance [cm] = time_duration[sec] * 34,000 [cm/sec] / 2 |
4.3. 통신 프로토콜 선정
다음으로 사용자 요구사항 3번(통신 프로토콜을 사용해 측정된 거리를 송신한다)을 만족시키기 위해, 계산된 Distance를 PC로 송신하는 코드를 구성해보았습니다.
현 프로젝트는 [거리측정 ~ 통신]까지 1Cycle에 대한 Upper Time Limit이 없어 보드에서 지원하는 UART, SPI, I2C 통신을 모두 사용할 수 있습니다. 또한 보드는 Micro-USB를 이용한 통신을 지원하기에, I2C, SPI 통신은 사용이 불가능 합니다.
따라서 UART를 활용해 PC로 데이터를 전송하는 코드로 전체 코드를 작성, 마무리하였습니다.
4.4 전체 Code
작성된 전체 main.c 코드입니다.
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "main.h"
#include <stdio.h>
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim4;
UART_HandleTypeDef huart2;
uint32_t distance = 0;
uint32_t width = 0;
uint16_t UEV_count = 0;
uint32_t start_captured = 0;
uint32_t first_captured = 0;
uint8_t first_captured_flag = 0;
uint32_t second_captured = 0;
uint8_t flag1 = 0;
uint8_t flag2 = 0;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_TIM4_Init(void);
double Get_Distance(void);
void Make_Ultrawave_Pulse(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_TIM3_Init();
MX_TIM4_Init();
flag1 = 1;
while (1)
{
if (flag1)
Make_Ultrawave_Pulse();
else if (flag2)
{
char buffer[20];
int len;
if (width >= 36000)
len = sprintf(buffer, "%lu cm\r\n", (unsigned long)0);
else
len = sprintf(buffer, "%lu cm\r\n", (unsigned long)distance);
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, len, HAL_MAX_DELAY);
// Wait till TIM3 UEV and TIM4 CCR counts over 10ms
while(__HAL_TIM_GET_COUNTER(&htim3) < 60000-1);
while(__HAL_TIM_GET_COUNTER(&htim4) < 10000-1);
__HAL_TIM_SET_COUNTER(&htim3, 0);
// HAL_TIM_Base_Stop(&htim3);
__HAL_TIM_SET_COUNTER(&htim4, 0);
// HAL_TIM_Base_Stop(&htim4);
flag1 = 1;
flag2 = 0;
}
}
}
void Make_Ultrawave_Pulse(void)
{
// Generate 10uS Pulse
HAL_TIM_Base_Start(&htim3);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
__HAL_TIM_SET_COUNTER(&htim3, 0);
while (__HAL_TIM_GET_COUNTER(&htim3) < 10); // delay 10uS
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
__HAL_TIM_SET_COUNTER(&htim3, 0);
// Start TIM4
HAL_TIM_Base_Start(&htim4);
__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_CC1);
HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_1);
flag1 = 0;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if (first_captured_flag == 0)
{
first_captured = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //CCR1 register
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
first_captured_flag = 1;
}
else
{
second_captured = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
__HAL_TIM_SET_COUNTER(htim, 0);
if (first_captured > second_captured)
width = 0xffff + second_captured - first_captured;
else
width = second_captured - first_captured;
distance = width / 58;
__HAL_TIM_DISABLE_IT(&htim4, TIM_IT_CC1);
first_captured_flag = 0;
flag2 = 1;
}
}
}
/**
* @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_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
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 TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 64-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 60000-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_OC_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_TIMING;
sConfigOC.Pulse = 10-1;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
__HAL_TIM_ENABLE_OCxPRELOAD(&htim3, TIM_CHANNEL_1);
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
}
/**
* @brief TIM4 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM4_Init(void)
{
/* USER CODE BEGIN TIM4_Init 0 */
/* USER CODE END TIM4_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
/* USER CODE BEGIN TIM4_Init 1 */
/* USER CODE END TIM4_Init 1 */
htim4.Instance = TIM4;
htim4.Init.Prescaler = 64-1;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 65536-1;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM4_Init 2 */
/*
5. HAL_TIM_IC_Start(), HAL_TIM_IC_Start_DMA(), HAL_TIM_IC_Start_IT()
6. HAL_TIM_DMABurst_WriteStart(), HAL_TIM_DMABurst_ReadStart()
*/
/* USER CODE END TIM4_Init 2 */
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, LD2_Pin|Trig_Pin_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PC0 PC1 PC2 PC3
PC4 PC5 PC6 PC7 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
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);
/*Configure GPIO pins : LD2_Pin Trig_Pin_Pin */
GPIO_InitStruct.Pin = LD2_Pin|Trig_Pin_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);
/*Configure GPIO pins : SW4_Pin SW2_Pin SW3_Pin */
GPIO_InitStruct.Pin = SW4_Pin|SW2_Pin|SW3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pin : SW1_Pin */
GPIO_InitStruct.Pin = SW1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(SW1_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI4_IRQn);
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* 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 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 CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
결과
시스템 요구사항 인수 테스트
- Echo 이후 다음 Trig까지 최소 10mS의 간격을 두어야 한다. (HC-SR04 센서의 제약사항) O
- 한번 신호를 보낸 이후 최소 60ms의 간격을 두어야 한다. (HC-SR04 센서의 제약사항) O
- HC-SR04 센서의 측정 가능 범위인 2cm~400cm 의 측정이 가능해야한다. O
- 통신 프로토콜을 사용하여 측정된 거리를 PC로 송신할 수 있어야 한다. ('xx cm' 형식) O
모두 만족하였다.
회고
• HC-SR04 센서의 데이터 시트 p6에는 "You can calculate the range through the time interval between sending trigger signal and receiving echo signal." 라고 적혀있다. 이 부분을 trigger 신호를 보낸 뒤 echo signal을 받기 까지 소요되는 시간으로 거리를 계산할 수 있다는 것으로 이해하여 구현하였으나, 실제 소요된 시간을 측정한 결과, 거리에 관계없이 일정하게 측정됨을 발견하였고, 하는 수 없이 echo pulse의 width를 통해 거리를 계산하였다.
내가 이해를 잘 못한건지 모르겠지만, trig신호를 보낸 뒤 echo를 받기까지의 시간이 데이터시트의 설명과 다르게 왜 일정하게 측정되는지는 아직 이해하지 못하였다.
• CPU 블로킹 현상을 방지하기 위해, 최대한 main loop에서 HAL_Delay()를 사용하지 않고 모든 부분들을 Interrupt로 구현하려고 하였다. 하지만 그럼에도 측정 시간이 60ms 이하인 경우 main loop에서 대기를 하게 된다. 현업에서 더 큰 프로젝트를 수행한다면, 이러한 main loop의 병목은 단점이 될 수 있는 부분이라 생각한다.
이를 해결하기 위해선 if 문을 활용해볼 수 있을 것 같다. loop를 task별로 나눈 뒤, task 실행 조건에 맞을 때만 task가 수행되도록 구현한다면 loop에서 블로킹 현상을 막을 수 있을 것 같다고 생각한다. 하지만 해당 방식은 if문이 지나치게 많아져 코드의 가독성이 안좋아질 수 있을 점이 염려된다.
아직 공부중이기에 잘 모르지만, TASK별 실행 시간을 조절할 수 있는 RTOS를 공부해서 적용해본다면 어떨지 또한 앞으로 궁금한 부분이다.
이미지 출처
https://www.st.com/en/microcontrollers-microprocessors/stm32f103rb.html
https://kr.rs-online.com/web/p/bbc-micro-bit-add-ons/2153181
참고 사이트
https://controllerstech.com/hcsr04-ultrasonic-sensor-and-stm32/