■ LookUPTable
- 결과값을 미리 저장하고 있는 배열
- LUT(LookUPTable) 배열의 인덱스는 입력, 배열의 값은 출력
/* USER CODE BEGIN 0 */
int soundLUT[] = {131, 147, 165, 175, 196, 220, 247, 262}; // LookUPTable
...
int main(void)
{
...
while (1)
{
if (countSound == 0)
{
countSound = 500;
static int num = 0;
setSound(soundLUT[num]);
num++;
num %= 8;
}
...
1. Buzzer(UART 이용 음계 출력)
⦁ UART로 특정 문자 입력시 Buzzer 음계 출력
- 계이름별 주파수를 LookUPTable로 저장
- UART로 특정 문자 입력 → Interrupt 발생 → 입력 받은 문자 변수 저장 → LookUPTable 활용 Buzzer 출력
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "UART.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim3;
UART_HandleTypeDef huart2;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART2_UART_Init(void);
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int soundLUT[] = {131, 147, 165, 175, 196, 220, 247, 262};
int countSinLED = 0;
int countPulseLED = 0;
int countSound = 0;
// Call this function if Interrupt occurs(every 1ms)
void timeTickCallback()
{
if(countSinLED > 0) countSinLED--;
if(countPulseLED > 0) countPulseLED--;
if(countSound > 0) countSound--;
}
void setSound(int freq)
{
htim3.Instance->ARR = 1000000 / freq - 1;
htim3.Instance->CCR1 = htim3.Instance->ARR / 2;
}
void stopSound()
{
htim3.Instance->CCR1 = 0;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* 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();
MX_TIM1_Init();
MX_TIM3_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
initUart(&huart2);
// start PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(countSinLED == 0)
{
countSinLED = 5; // if count=0, count init(=5)
// Run every 5ms
static int angle = 0;
angle++;
angle %= 360;
int value = sin(angle * 3.14 / 180) * 499 + 499;
htim1.Instance->CCR1 = value;
}
if(countPulseLED == 0)
{
countPulseLED = 100;
HAL_GPIO_TogglePin(Pulse_GPIO_Port, Pulse_Pin);
/*
* read the current state and run reverse state
* if cur = 0 -> 1
* if cur = 1 -> 0
*/
}
if (countSound == 0)
{
countSound = 500;
stopSound();
// UART receive area
char ch = getChar();
switch(ch)
{
case 'c':
setSound(soundLUT[0]);
break;
case 'd':
setSound(soundLUT[1]);
break;
case 'e':
setSound(soundLUT[2]);
break;
case 'f':
setSound(soundLUT[3]);
break;
case 'g':
setSound(soundLUT[4]);
break;
case 'a':
setSound(soundLUT[5]);
break;
case 'b':
setSound(soundLUT[6]);
break;
case 'C':
setSound(soundLUT[7]);
break;
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
...
2. Ultrasonic(HC-SR04)
: 초음파 송수신 센서
1. 최소 10us 이상의 high level signal(Trigger)
2. 8개의 40kHz 초음파를 전송하고 펄스 신호가 돌아오는걸 감지
- 가청 주파수 : 20Hz ~ 20kHz → Ultrasonic은 사람이 들을 수 없음
3. 신호가 high level로 돌아오면 초음파의 송신부터 수신까지의 시간으로 거리 계산 가능
- 음속 : 340m/s → 음속과 초음파 전달 시간으로 거리를 구할 수 있음(거리 = 속도 * 시간)
2-1. Ultrasonic 활용 거리 측정 코드 구현
- Trigger 구현(us dely는 TIM10 활용하여 구현)
- Echo pin에서 Rising edge 발생하면 Interrupt 발생(TIM1 활용) → CNT 값 저장
- Echo pin에서 Falling edge 발생하면 Interrupt 발생 → CNT 값 저장
- Rising edge CNT값과 Falling edge CNT값의 차이를 통해 시간 측정
- 측정한 시간으로 거리 계산
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "UART.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim10;
UART_HandleTypeDef huart2;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM10_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// delay Function implement using TIMER10(for us delay)
void delayUS(uint16_t time)
{
htim10.Instance->CNT = 0;
while(htim10.Instance->CNT < time);
}
uint32_t IC_Val1 = 0;
uint32_t IC_Val2 = 0;
uint32_t Difference = 0;
uint8_t IsFirstCaptured = 0;
uint32_t Distance = 0;
// ISR function
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
// If Rising edge occurs, execute the code
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
// Rising edge Interrupt
if(IsFirstCaptured == 0)
{
// Read Timer CNT
IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
IsFirstCaptured = 1;
// Next Interrupt -> Falling edge set
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_FALLING);
}
// Falling edge Interrupt
else if(IsFirstCaptured == 1)
{
// Read Timer CNT
IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
htim->Instance->CNT = 0;
if(IC_Val2 > IC_Val1) Difference = IC_Val2 - IC_Val1;
// (IC_Val2 - IC_Val1) mean Ultrasonic transmit and receive time difference
else if(IC_Val1 > IC_Val2) Difference = (0xffff - IC_Val1) + IC_Val2;
// (IC_Val1 > IC_Val2) mean CNT value overflow
// calculate distance
/*
* CLK = 1Mhz -> 1clk = 1us
* distance = velocity * time = 340[m] * (CNT/2)[us]) = 0.034 * CNT / 2
*/
Distance = Difference * 0.034 / 2;
// Next Interrupt -> Rising edge set
IsFirstCaptured = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_RISING);
// disable Interrupt
__HAL_TIM_DISABLE_IT(htim, TIM_IT_CC1);
}
}
}
uint32_t getDistance()
{
// Trigger
HAL_GPIO_WritePin(Trigger_GPIO_Port, Trigger_Pin, 1);
delayUS(10);
HAL_GPIO_WritePin(Trigger_GPIO_Port, Trigger_Pin, 0);
// enable Interrupt -> can only occur Interrupt when I want to
__HAL_TIM_ENABLE_IT(&htim1, TIM_IT_CC1);
return Distance;
}
/* USER CODE END 0 */
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();
MX_TIM1_Init();
MX_TIM10_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
initUart(&huart2);
HAL_TIM_Base_Start(&htim10); // TIMER10 basic mode
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); // TIMER1 InputCapture Interrupt mode
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
printf("%d\n", getDistance());
HAL_Delay(50);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
코드 리뷰
// delay Function implement using TIMER10(for us delay)
void delayUS(uint16_t time)
{
htim10.Instance->CNT = 0;
while(htim10.Instance->CNT < time);
}
`HAL_DELAY()` 함수는 ms 단위로만 delay 가능하기 때문에 Timer/Counter 기능을 이용하여 us 단위로 사용 가능한 delay함수 구현
- TIM10의 CNT값을 0으로 초기화시키고 CNT 값이 매개변수 값이 될 때까지 while문 동작하여 delay 구현
- TIM10 Prescaler를 16-1로 설정하여 주파수가 1Mhz이기 때문에 CNT +1에 걸리는 시간은 1us
// ISR function
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
TIM의 InputCapture Interrupt가 발생했을 때 자동으로 실행되는 함수(외부 인터럽트와 거의 유사)
// If Rising edge occurs, execute the code
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
// Rising edge Interrupt
if(IsFirstCaptured == 0)
{
// Read Timer CNT
IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
IsFirstCaptured = 1;
// Next Interrupt -> Falling edge set
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_FALLING);
}
첫 번째 if문 : TIM의 Channel1이 활성화 되었을 때만 실행
두 번째 if문 : Flag 변수 값이 0이면 실행(0일 때 Risingedge Interrupt 코드 실행, 1일 때 Falling edge Interrupt 코드 실행)
- Rising edge Interrupt 발생 했을 때의 CNT값을 변수에 저장
- Flag 변수 1로 변경(이후 Falling edge Interrupt 코드 실행시키기 위해)
- TIM의 InputCapture Interrupt 모드를 Falling edge가 발생했을 때 실행되도록 설정
// Falling edge Interrupt
else if(IsFirstCaptured == 1)
{
// Read Timer CNT
IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
htim->Instance->CNT = 0;
if(IC_Val2 > IC_Val1) Difference = IC_Val2 - IC_Val1;
// (IC_Val2 - IC_Val1) mean Ultrasonic transmit and receive time difference
else if(IC_Val1 > IC_Val2) Difference = (0xffff - IC_Val1) + IC_Val2;
// (IC_Val1 > IC_Val2) mean CNT value overflow
// calculate distance
/*
* CLK = 1Mhz -> 1clk = 1us
* distance = velocity * time = 340[m] * (CNT/2)[us]) = 0.034 * CNT / 2
*/
Distance = Difference * 0.034 / 2;
// Next Interrupt -> Rising edge set
IsFirstCaptured = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_RISING);
// disable Interrupt
__HAL_TIM_DISABLE_IT(htim, TIM_IT_CC1);
}
Flag 변수 값이 1이면 실행
- Falling edge Interrupt 발생 했을 때의 CNT값을 변수에 저장
- Rising edge Interrupt CNT값과 Falling edge Interrupt CNT값의 차이 계산(==시간)
- else if문은 CNT register가 overflow 발생했을 때에도 정상적으로 시간을 측정하기 위해 만든 부분
- 아래 그림 참조
- TIM의 InputCapture Interrupt 모드를 Rising edge가 발생했을 때 실행되도록 설정(다음 거리 측정 위해)
uint32_t getDistance()
{
// Trigger
HAL_GPIO_WritePin(Trigger_GPIO_Port, Trigger_Pin, 1);
delayUS(10);
HAL_GPIO_WritePin(Trigger_GPIO_Port, Trigger_Pin, 0);
// enable Interrupt -> can only occur Interrupt when I want to
__HAL_TIM_ENABLE_IT(&htim1, TIM_IT_CC1);
return Distance;
}
Trigger 구현, Interrupt Enable 설정
2-2. 필터링(이동평균필터, 칼만 필터)
- 이동평균필터
- 100개 data의 평균을 계산하는 필터
- 칼만 필터
- 노이즈가 있는 과거 데이터(측정 데이터)를 통해 미래 데이터를 예측하는 재귀 필터
- 선형적이며 가우시안 분포를 따르는 그래프에 적합
- 시스템의 상태는 평균과 공분산으로 나타낸다.
#include "filter.h"
#define maxValue 100
uint16_t movingAvgFilter(uint16_t inData)
{
/*
* 1. 합계에서 현재 위치의 버퍼값 빼기
* 2. 현재 위치의 버퍼값을 갱신
* 3. 합계에 현재 위치의 버퍼값 더하기
* 4. 현재 위치 증가
* 5. 현재 위치 리셋
*/
static uint16_t filterBuffer[maxValue];
static uint32_t sumValue = 0;
static uint16_t bufPos = 0; // current Position
static _Bool isFirst = 0;
// 맨 처음 딱 한번만 실행 -> 첫 100개의 데이터가 모일때까지 반응이 늦는걸 보완
// 첫 데이터를 버퍼 모든 인덱스에 저장
if(isFirst == 0)
{
isFirst = 1;
for(int i = 0; i < maxValue; i++)
filterBuffer[i] = inData;
}
// 1. 합계에서 현재 위치의 버퍼값 빼기
sumValue -= filterBuffer[bufPos];
// 2. 현재 위치의 버퍼값 갱신
filterBuffer[bufPos] = inData;
// 3. 합계에 현재 위치의 버퍼값 더하기
sumValue += filterBuffer[bufPos];
// 4. 현재 위치 증가
bufPos++;
// 5. 현재 위치 리셋
bufPos %= 100;
return sumValue / 100;
}
double Kalman(double measurement)
{
// Kalman filter setup
static double P = 1.0; // 추정 오차의 공분산
static double varP = 0.0001; // 프로세스 변동성
static double R = 0.25; // 측정치 오차의 공분산
static double K = 1.0; // 칼만 이득
static double X = 20.0; // 현재 추정된 상태
// Kalman Simple Filter
P = P + varP;
K = P / (P + R);
X = (K * measurement) + (1 - K) * X; P = (1 - K) * P;
return X;
}
두 필터를 사용하여 위 Ultrasonic 센서 값을 필터링 한다.
<main.c>
...
/* USER CODE BEGIN WHILE */
while (1)
{
uint16_t value = getDistance(); // raw data
printf("%d\t%d\t%d\n", value, movingAvgFilter(value), (int)Kalman((double)value));
HAL_Delay(50);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
...
3. Servo Motor
3-1. -90도 ~ +90도 동작 코드 구현
- TIM3 PWM을 50Hz로 설정하고 CCR 값을 조절하여 동작
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_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
for (int i = 600; i < 2400; i++)
{
htim3.Instance->CCR1 = i;
HAL_Delay(1);
}
for (int i = 0; i < 1800; i++)
{
htim3.Instance->CCR1 = 2400 - i;
HAL_Delay(1);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3-2. 가변저항 이용하여 모터 제어
- 가변 저항을 조절하면 신호가 아날로그적으로 바뀜
- 아날로그 신호를 디지털 신호로 변환하여(ADC) 서보 모터의 PWM 제어
- 최종적으로 가변 저항의 저항값을 바꿀 때마다 서보 모터 동작
⦁ 하나의 비교기로 여러번 비교하여 결과 출력
1) 1클록 : 최상위 비트 MSB를 1로 만들고(나머지는 0) VIN과 VREF/2를 비교
→ VIN > VREF/2 이면, 최상위 비트 1 확정
2) 2클록 : 2번째 상위 비트를 1로 만들고 VIN과 (VREF/2+VREF/4)를 비교
…(LSB까지 bit 수만큼 진행)
→ VIN < VREF/2 이면, 최상위 비트 0 확정
2) 2클록 : 2번째 상위 비트를 1로 만들고 VIN과 VREF/4를 비교
…(LSB까지 bit 수만큼 진행)
⦁ ADC SAR 방식 특징
1. bit가 클수록 더 정확도 향상
2. Sigma-Delta ADC에 비해 고속이지만 정확도는 떨어짐
3. 비교기를 하나만 사용하기때문에 저전력, 저가
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "UART.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim3;
UART_HandleTypeDef huart2;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);
static void MX_ADC1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* 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();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
MX_ADC1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1)
initUart(&huart2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// ADC conversion Start
HAL_ADC_Start(&hadc1);
// wait for Analog convert to Digital
HAL_ADC_PollForConversion(&hadc1, 10);
// read data
uint16_t result = HAL_ADC_GetValue(&hadc1);
// ADC conversion End
HAL_ADC_Stop(&hadc1);
// 0~4095(12bit) to 600~2400 range conversion
uint16_t servoValue = map(result, 0, 4095, 600, 2400);
htim3.Instance->CCR1 = servoValue;
printf("%d\n", servoValue);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
코드 리뷰
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
⦁ 현재 데이터의 범위를 바꿔주는(mapping) 함수
`x` : mapping 할 data
`in_min` : 현재 data 범위의 최소값
`in_max` : 현재 data 범위의 최대값
`out_min` : mapping 할 범위의 최소값
`out_max` : mapping 할 범위의 최대값
// ADC conversion Start
HAL_ADC_Start(&hadc1);
// wait for Analog convert to Digital
HAL_ADC_PollForConversion(&hadc1, 10);
// read data
uint16_t result = HAL_ADC_GetValue(&hadc1);
// ADC conversion End
HAL_ADC_Stop(&hadc1);
⦁ ADC를 사용하기 위한 코드
`HAL_ADC_Start(&hadc1)` : ADC를 시작하기 위한 함수
`HAL_ADC_PollForConversion(&hadc1, 10)` : ADC가 완료될 때까지 대기해는 함수 (매개변수 10 : ms단위 대기 시간)
`uint16_t result = HAL_ADC_GetValue(&hadc1)` : ADC 완료된 값을 읽어 변수에 저장
`HAL_ADC_Stop(&hadc1)` : ADC 변환 완료 후 종료하기 위한 함수
동작 Flow
1. 3.3V DC가 가변 저항을 거치면서 Analog signal로 바뀜
2. Analog signal을 MCU에 입력으로 넣고 Digital signal로 변환
3. 변환된 Digital signal의 값을 TIM3 CCR값에 대입
4. CCR 값에 따라 Pulse width가 바뀌어 Servo Motor 동작
UART 출력 결과를 보면 가변저항을 돌릴때마다 ADC 된 값이 출력된다.
Made By Minseok KIM
'ARM > 1_Study' 카테고리의 다른 글
[ARM] STM32_RTC, Switch (0) | 2024.04.02 |
---|---|
[ARM] STM32_LCD I2C, ADC(polling 방식, DMA 방식), CDS (0) | 2024.03.29 |
[ARM] STM32_Radar (0) | 2024.03.24 |
[ARM] STM32_TIMER정리, data 전송 flow 구상, PWM(LED, Buzzer) (0) | 2024.03.18 |
[ARM] STM32_UART 기초 (0) | 2024.03.18 |
Let's Be Happy!
도움이 되었으면 좋겠어요 :)