2024.03.18 - [ARM/Study] - [ARM] STM32_UART 기초
이전에 만들어두었던 UART.c 파일을 이용합니다.
1. TIMER 정리
1-1. TIMER 종류
1) General-Purpose TIMER
: 다양한 기능을 가지고 있는 범용성 있는 타이머
→ 입출력 제어(LED ON/OFF, PWM 등), Interrupt, Counter 등
2) Advanced TIMER
: General-Purpose TIMER 보다 더 많은 기능을 지원하는 타이머
→ 고급 PWM(3상 ac모터), Dead-Time Generator 등
3) Basic TIMER
: 입출력 기능이 없는 가장 단순한 타이머
→ 간단한 타이머 기능으로 사용, 기능이 적은 대신 가장 적은 HW 리소스 사용
4) Channel TIMER
: General-Purpose TIMER와 기능은 같으나 채널이 적은 타이머
1-2. TIMER register
1) Prescaler(분주기)
: 16bit register, 입력된 시스템 클럭을 레지스터 크기만큼 나누어 주파수를 감소
ex) SysCLK = 16MHz, Prescaler = 16-1이면 → 1Mhz로 주파수 감소 됨
2) CNT(Counter)
: 16bit register, Prescaler를 통해 조절된 주파수 1클럭 당 레지스터 값 Up/Down
→ TIMER 실행 중 지속적으로 값이 바뀌는 핵심 레지스터
ex) 주파수 = 1MHz이면 → 1us에 한번씩 CNT 값 Up/Down
3) ARR(Auto Reload Register)
: CNT의 TOP값, 주파수 조절 가능
a) Up Count이면 → CNT==ARR이 되면 CNT=0부터 다시 카운트(overflow)
ex) 주파수 = 1MHz, ARR = 1000-1이면 → CNT==1000일 때(1000us) overflow, 주파수 = 1kHz
b) Down Count이면 → CNT==0이 되면 CNT=ARR부터 다시 카운트(underflow)
4) CCR(Capture/Compare Register)
: CNT==CCR일 때 Capture/Compare
ex) CNT<=CCR이면 → High, CNT>CCR이면 → Low로 출력하여 duty rate 조절 가능
1-3. TIMER mode
1) Up Counting mode
: CNT register를 증가시키며 카운트 하는 모드
→ overflow Interrupt 사용 가능
2) Down Counting mode
: CNT register를 감소시키며 카운트 하는 모드
→ underflow Interrupt 사용 가능
3) Up/Down Counting mode
: CNT register가 증가, 감소를 반복하며 카운트 하는 모드
4) Output Compare mode
: CNT==CCR 일 때, Interrupt 혹은 출력을 바꾸는 모드
5) PWM mode
: PWM 출력을 발생시키는 모드
→ 주파수는 ARR, duty rate는 CCR로 설정
1-4. 타이머 관련 함수
1) Time Base 함수
: Timer/Count 동작 제어
함수명 | 기능 |
`HAL_TIM_Base_Init(TIM_HandleDef *htim)` | htim 구조체 변수를 초기화하고 TIM Base Handle 생성 Parameter : *htim(TIM Base handle 구조체 변수) Return : HAL status |
`HAL_TIM_Base_DeInit(TIM_HandleDef *htim)` | 초기화 해제 Parameter: *htim(TIM Base handle 구조체 변수) Return : HAL status |
`HAL_TIM_Base_Start(TIM_HandleDef *htim)` | TIM Base의 동작 시작 Parameter : *htim(TIM Base handle 구조체 변수) Return : HAL status |
`HAL_TIM_Base_Stop(TIM_HandleDef *htim)` | TIM Base의 동작 정지 Parameter : *htim(TIM Base handle 구조체 변수) Return : HAL status |
`HAL_TIM_Base_Start_IT(TIM_HandleDef *htim)` | TIM Base의 인터럽트 모드 동작 시작 Parameter : *htim(TIM Base handle 구조체 변수) Return : HAL status |
`HAL_TIM_Base_Stop _IT(TIM_HandleDef *htim)` | TIM Base의 인터럽트 모드 동작 종료 Parameter : *htim(TIM Base handle 구조체 변수) Return : HAL status |
2) 다른 함수들
Time Output Compare | Time PWM |
`HAL_TIM_OC_Init(TIM_HandleDef *htim)` | `HAL_TIM_PWM_Init(TIM_HandleDef * htim)` |
`HAL_TIM_OC_DeInit(TIM_HandleDef *htim)` | `HAL_TIM_PWM_DeInit(TIM_HandleDef * htim)` |
`HAL_TIM_OC_Start` `(TIM_HandleDef *htim, uint32_t, Channel)` |
`HAL_TIM_PWM_Start` `(TIM_HandleDef * htim, uint32_t Channel)` |
`HAL_TIM_OC_Stop` `(TIM_HandleDef *htim, uint32_t, Channel)` |
`HAL_TIM_PWM_Stop` `(TIM_HandleDef * htim, uint32_t CHANNEL)` |
`HAL_TIM_OC_Start_IT` `(TIM_HandleDef *htim, uint32_t Channel)` |
`HAL_TIM_PWM_Start_IT` `(TIM_HandleDef * htim, uint32_t CHANNEL)` |
`HAL_TIM_OC_Stop _IT` `(TIM_HandleDef *htim, uint32_t Channel)` |
`HAL_TIM_PWM_Stop _IT` `(TIM_HandleDef * htim, uint32_t CHANNEL)` |
⦁ uint32_t Channel : 동작시킬 채널
⦁ 그 외 기능은 1) Time Base 함수 기능과 같음
3) 주변 장치 설정 함수
함수명 | 기능 |
`HAL_TIM_OC_ConfigChannel` `(TIM_HandleDef *htim,` `TIM_OC_InitTypeDef * sConfig, uint32_t Channel)` |
TIM Output Compare Channel 설정 (채널, 모드, 기능, 주기 등) Parameter : *htim(TIM Base handle 구조체 변수) sConfig : TIM Output Compare Configuration 구조체 변수 Channel : Enable 시킬 채널, TIM_CHANNEL_x(1 ~ 4) Return : HAL status |
`HAL_TIM_PWM_ConfigChannel` `(TIM_HandleDef *htim,` `TIM_OC_InitTypeDef * sConfig, uint32_t Channel)` |
TIM PWM Channel 설정 (PWM 주기, duty rate 등) Parameter : *htim(TIM Base handle 구조체 변수) sConfig : TIM Output Compare Configuration 구조체 변수 Channel : Enable 시킬 채널, TIM_CHANNEL_x(1 ~ 4) Return : HAL status |
4) Call Back 함수
함수명 | 기능 |
`HAL_TIM_PeriodElapsedCallback` `(TIM_HandleDef *htim)` |
Overflow 발생했을 때 호출되는 함수 |
`HAL_TIM_OC_DelayElapsedCallback` `(TIM_HandleDef *htim)` |
CNT==CCR 일 때 발생하는 함수 |
`HAL_TIM_IC_CaptureCallback(TIM_HandleDef *htim)` | Input Capture 이벤트가 발생했을 때 호출되는 함수 → 사용자가 정의하는 것이 아니라 Input Capture 이벤트가 발생할 때마다 자동으로 호출 |
`HAL_TIM_PWM_PulseFinishedCallback` `(TIM_HandleDef *htim)` |
PWM pulse 생성이 완료되었을 때 호출되는 함수 |
`HAL_TIM_TriggerCallback(TIM_HandleDef * htim)` | Trigger 이벤트 발생(External Interrupt와 유사한 개념)했을 때 호출되는 함수 |
`HAL_TIM_ErrorCallback(TIM_HandleDef * htim)` | Timer 오류가 발생했을 때 실행되는 함수 |
5) 상태 함수
함수명 | 기능 |
`HAL_TIM_Base_GetState(TIM_HandleDef *htim)` | TIM Base의 상태 값을 반환하는 함수 (ERROR, READY 등) |
`HAL_TIM_OC_GetState(TIM_HandleDef *htim)` | TIM OC의 상태 값을 반환하는 함수 (ERROR, READY 등) |
`HAL_TIM_PWM_GetState(TIM_HandleDef *htim)` | TIM PWM의 상태 값을 반환하는 함수 (ERROR, READY 등) |
`HAL_TIM_IC_GetState(TIM_HandleDef *htim)` | TIM Input Capture의 상태 값을 반환하는 함수 (ERROR, READY 등) |
`HAL_TIM_OnePulse_GetState(TIM_HandleDef *htim)` | TIM One Pulse Mode의 상태 값을 반환하는 함수 (ERROR, READY 등) |
`HAL_TIM_Encoder_GetState(TIM_HandleDef *htim)` | TIM Encoder Mode의 상태 값을 반환하는 함수 (ERROR, READY 등) |
2. Data 전송 flow 구상
- STX(Start text) : 0x02
- ID : Slave ID
- CMD : Command
- DATA0
- DATA1
- DATA2
- DATA3
- CRC : 처음부터 CRC 전까지의 합
- ETX(End text) : 0x03
⇒ 9 Byte
UART.c 코드 수정
- 구상한 Data 전송 flow 구현
// UART.c
/* ㅡㅡㅡㅡㅡㅡLecture : 0315ㅡㅡㅡㅡㅡㅡ */
#define STX 0x02
#define ETX 0x03
typedef struct {
uint8_t id;
uint8_t command;
uint32_t data;
} protocol_t;
// Send Binary Data
void binaryTransmit(protocol_t inData)
{
uint8_t txBuffer[] = {STX, 0, 0, 0, 0, 0, 0, 0, ETX};
// copy data
memcpy(&txBuffer[1], &inData, 6);
// calculate CRC
for (int i = 0; i<7; i++)
{
txBuffer[7] += txBuffer[i];
// txBuffer[7] = Sum of txBuffer[0]~[6] <- CRC data
}
// send data
HAL_UART_Transmit(myHuart, txBuffer, sizeof(txBuffer), 10);
}
main.c 코드 수정
// main.c
...
while (1)
{
static int angle = 0;
protocol_t txData;
angle++;
angle %= 360; //range of angle : 0~360
uint32_t value = sin(angle * 3.14 / 180) * 10000 + 10000;
// range of value : 0 ~ 20000
txData.id = 1;
txData.command = 'S';
txData.data = value;
binaryTransmit(txData);
HAL_Delay(10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
...
}
...
→ 02 + 01 + 53 + 00 + 08 + E9 + 48 = 18F이지만 overflow되어 8F(CRC data)
2-1. Python matplot 사용해서 출력
void binaryTransmit(protocol_t inData)
{
uint8_t txBuffer[] = {STX, 0, 0, 0, 0, 0, 0, 0, ETX};
// copy data
//memcpy(&txBuffer[1], &inData, 6); // Not work...
txBuffer[1] = inData.id | 0x80;
txBuffer[2] = inData.command | 0x80;
txBuffer[3] = inData.data | 0x80; // save 32bit data to 8bit Buffer
txBuffer[4] = (inData.data >> 7) | 0x80;
txBuffer[5] = (inData.data >> 14) | 0x80;
txBuffer[6] = (inData.data >> 21) | 0x80;
// calculate CRC
for (int i = 0; i<7; i++)
{
txBuffer[7] += txBuffer[i];
// txBuffer[7] = Sum of txBuffer[0]~[6] <- CRC data
}
// send data
HAL_UART_Transmit(myHuart, txBuffer, sizeof(txBuffer), 10);
}
import serial
import struct
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from collections import deque
ser = serial.Serial("COM11", 115200)
fig, ax = plt.subplots()
data = deque(maxlen = 400) # maxlen 기본값 설정
# 함수 정의
def updateGraph(fram):
# 시리얼 데이터 확인
if ser.in_waiting > 0:
bytesData = ser.read() # 1byte Read
if bytesData == b'\x02':
value = 0
count = 1
while bytesData != b'\x03': # ETX 전까지 실행
if ser.in_waiting > 0:
mask = b"\x7f" # mask할 데이터(0111 1111)
bytesData = ser.read()
count += 1
if count == 4: # id, command 건너뛰기 위해 count == 3
bytesData = bytes([bytesData[0] & mask[0]])
# mask와 마스크 오프 -> 0x80을 제거
value = value + int.from_bytes(bytesData)
elif count == 5:
bytesData = bytes([bytesData[0] & mask[0]])
value = value + (int.from_bytes(bytesData) << 7)
elif count == 6:
bytesData = bytes([bytesData[0] & mask[0]])
value = value + (int.from_bytes(bytesData) << 14)
elif count == 7:
bytesData = bytes([bytesData[0] & mask[0]])
value = value + (int.from_bytes(bytesData) << 21)
# plot update
data.append(value)
ax.clear()
ax.plot(data)
"""if ser.in_waiting > 7: # Start 제외한 data 개수
bytesData = ser.read(7) # 7byte Read
trimData = bytesData[2:6] # 배열 2번~5번 저장
# 데이터 파싱(바이트)
value = int.from_bytes(trimData, "little")
data.append(value) # collection으로 된 data에 value 추가
ax.clear()
ax.plot(data)"""
# 애니메이션 등록
ani = animation.FuncAnimation(fig, updateGraph, interval = 10)
# updateGraph를 10ms에 한번씩 호출
# 플롯 표시
plt.show()
ser.close()
코드리뷰
⦁ 뒤에서부터 7개 bit만 전송(최상위 비트는 1로 고정)
Why? 맨 앞 bit가 1이면 STX, ETX가 아니라고 판단
data = 0x12345678 = 0b0001 0010 0011 0100 0101 0110 0111 1000
0b0001 [0010 001][1 0100 01][01 0110 0][111 1000] // 7bit씩 끊은 것
txBuffer[3] = [111 1000] | 0x80 = 1111 1000 // -> 맨 앞 bit를 1로 만들어 STX, ETX와 구분
// 가능한 범위 : 0~127 | 0x80 = 128~255
txBuffer[4] = [01 0110 0] | 0x80 = 1010 1100
txBuffer[5] = [1 0100 01] | 0x80 = 1101 0001
txBuffer[6] = [0010 001] | 0x80 = 1001 0001
+ 대신, data의 맨 앞 0001은 못 씀
: `txBuffer[]`값이 STX, ETX 값과 같아지는 때에 오류 발생 → 데이터는 최상위 비트를 1로 하여 STX, ETX와 구분
3. PWM
- Prescaler 설정 → 1/16 = 1MHz
- ARR(Auto Reload Register)
- Counter Period 조절하여 주파수 조절 (top 값)
- 1MHz → 1 count = 1us
- ARR = 999) 0 ~ 999 count하고 overflow → 1kHz
- CCR (Channel Compare Register) : Pulse 값 조절하여 duty rate 조절
- CCR = 499) 0 ~ 499 = HIGH, 500 ~ 999 = LOW
- CH Polarity 조절하여 시작을 LOW or HIGH
3-1. LED PWM 사용
- TIMER1 PWM 이용하여 주파수 : 1kHz, duty rate : 변수 대입하여 변수 값에 따라 LED 출력 변환
- sin파형을 CCR1(duty rate)에 대입하여 LED 출력 변환
/* USER CODE BEGIN 2 */
// start PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
static int angle = 0;
angle++;
angle %= 360;
int value = sin(angle * 3.14 / 180) * 499 + 499;
htim1.Instance->CCR1 = value;
HAL_Delay(5);
...
▷ 코드 문제점 : delay 함수를 사용하여 delay 동안에는 MCU를 제어할 수 없음
위 코드에서 `HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);` 함수 설명
- TIMER PWM을 시작시키는 함수
- (파라미터) `&htim` : TIMER 관련 변수가 저장되는 구조체 변수
아래 코드에서 보이는 바와 같이 `htim` 구조체 변수에서 TIMER 관련 register(CCR, ARR, CNT 등)에 접근할 수 있다.
typedef struct
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array
This array is accessed by a @ref DMA_Handle_index */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
__IO HAL_TIM_ChannelStateTypeDef ChannelState[4]; /*!< TIM channel operation state */
__IO HAL_TIM_ChannelStateTypeDef ChannelNState[4]; /*!< TIM complementary channel operation state */
__IO HAL_TIM_DMABurstStateTypeDef DMABurstState; /*!< DMA burst operation state */
/*ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ*/
typedef struct
{
__IO uint32_t CR1; /*!< TIM control register 1, Address offset: 0x00 */
__IO uint32_t CR2; /*!< TIM control register 2, Address offset: 0x04 */
__IO uint32_t SMCR; /*!< TIM slave mode control register, Address offset: 0x08 */
__IO uint32_t DIER; /*!< TIM DMA/interrupt enable register, Address offset: 0x0C */
__IO uint32_t SR; /*!< TIM status register, Address offset: 0x10 */
__IO uint32_t EGR; /*!< TIM event generation register, Address offset: 0x14 */
__IO uint32_t CCMR1; /*!< TIM capture/compare mode register 1, Address offset: 0x18 */
__IO uint32_t CCMR2; /*!< TIM capture/compare mode register 2, Address offset: 0x1C */
__IO uint32_t CCER; /*!< TIM capture/compare enable register, Address offset: 0x20 */
__IO uint32_t CNT; /*!< TIM counter register, Address offset: 0x24 */
__IO uint32_t PSC; /*!< TIM prescaler, Address offset: 0x28 */
__IO uint32_t ARR; /*!< TIM auto-reload register, Address offset: 0x2C */
__IO uint32_t RCR; /*!< TIM repetition counter register, Address offset: 0x30 */
__IO uint32_t CCR1; /*!< TIM capture/compare register 1, Address offset: 0x34 */
__IO uint32_t CCR2; /*!< TIM capture/compare register 2, Address offset: 0x38 */
__IO uint32_t CCR3; /*!< TIM capture/compare register 3, Address offset: 0x3C */
__IO uint32_t CCR4; /*!< TIM capture/compare register 4, Address offset: 0x40 */
__IO uint32_t BDTR; /*!< TIM break and dead-time register, Address offset: 0x44 */
__IO uint32_t DCR; /*!< TIM DMA control register, Address offset: 0x48 */
__IO uint32_t DMAR; /*!< TIM DMA address for full transfer, Address offset: 0x4C */
__IO uint32_t OR; /*!< TIM option register, Address offset: 0x50 */
} TIM_TypeDef;
- (파라미터) `TIM_CHANNEL_1` : enable 시킬 TIMER 채널
아래 코드에서 보이는 바와 같이 4byte의 상수로 define되어 있다.
#define TIM_CHANNEL_1 0x00000000U /*!< Capture/compare channel 1 identifier */
#define TIM_CHANNEL_2 0x00000004U /*!< Capture/compare channel 2 identifier */
#define TIM_CHANNEL_3 0x00000008U /*!< Capture/compare channel 3 identifier */
#define TIM_CHANNEL_4 0x0000000CU /*!< Capture/compare channel 4 identifier */
#define TIM_CHANNEL_ALL 0x0000003CU
3-2. LED, Buzzer PWM 사용(+Interrupt)
Delay 최소화
1) Interrupt 발생마다 void timeTickCallback() 함수 실행 (ex. 1ms마다 실행)
2) void timeTickCallback() 함수에서 count data가 0보다 크면 count data -1 (ex. 1ms마다 data -1)
3) count data = 0 이면 count data 값(delay 시간) 지정하고 내가 원하는 코드 실행
⇒ count data의 값(delay 시간)마다 원하는 동작 실행 (ex. LED ON/OFF 상태 변화)
- Core → Src → stm32f4xx_it.c(모든 Interrupt 관리)의 `SysTick_Handler()` 함수에서 `timeTickCallback()` 함수 호출
→ `SysTick_Handler()` : 시스템 timer Interrupt가 발생할 때마다 호출되는 함수
- main에서 `timeTickCallback()` 함수 정의
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
timeTickCallback(); // Interrupt every 1ms
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
<main.c>
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int countSinLED = 0;
int countPulseLED = 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 */
int main(void)
{
/* USER CODE BEGIN 2 */
// start PWM
HAL_TIM_PWM_Start(&htim1, 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 = 1000;
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) // Sounds get Higher
{
countSound = 10;
static int freq = 20;
freq++;
if(freq > 20000) freq = 20;
setSound(freq);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
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_Buzzer, Ulatrasonic, 필터링, ServoMotor (0) | 2024.03.19 |
[ARM] STM32_UART 기초 (0) | 2024.03.18 |
Let's Be Happy!
도움이 되었으면 좋겠어요 :)