***해당 실습은 RCC, GPIO, SYSCLK을 설정하였다는 가정하에 진행된다.
UART(범용 비동기화 송수신기)는 병렬 데이터의 형태를 직렬 방식으로 전환하여 데이터를 전송하는 하드웨어의 일종이다. 대표적인 RS-232 통신 표준을 사용한다. 문자 그대로 비동기 통신 방식을 사용하여 양쪽에서 사전에 Baudrate를 지정해야 한다.
UART 통신의 장점은 하드웨어적인 구성과 통신 방식이 간단하다는 것이다. 그래서 STLINK 같은 별도의 디버깅을 위한 디바이스가 없는 경우 펌웨어 업로드, 디버깅 등에 자주 활용된다. 데이터의 송수신 형태는 다음과 같다.
시작 비트를 받은 수신측은 종료 비트를 수신하기 전까지 데이터 비트 또는 패리티 비트를 읽게 된다. 데이터 비트의 길이, 패리티 비트의 유무, 짝수 또는 홀수 패리티 등 옵션들은 레지스터 값을 조정해주는 것으로 설정할 수 있다. UART 통신을 하기 위해서는 과련 레지스터들을 조정해줄 필요가 있다. HAL 라이브러리를 사용한다면 굳이 할 필요가 없지만, CMSIS 라이브러리를 사용하기 때문에 데이터시트를 참조할 필요가 있다.
UART 통신에 사용되는 CR(Controll Register)는 총 세 개이나 디폴트로 사용할 것이기 때문에 우선은 두 레지스터만 설정에 사용하기로 한다. BRR 레지스터는 Baudrate와 연관된 정보가 들어가고, CR1은 UART 통신을 활성화하는 역할을 한다.(TE, RE, UE)
BRR 레지스터에 들어가는 값을 구하는 공식은 다음과 같다.
UART 관련 코드는 다음과 같다. (코드에 앞서서 UART 통신을 할 때 핀을 잘 연결해야 한다. -> 의외로 실수가 많이 발생한다.)
uart.h : uart 관련 설정(RCC 설정 -> GPIO 핀 설정 -> UART 레지스터 설정 순으로)
#ifndef UART_H
#define UART_H
#ifndef COMMON_H
#include "common.h"
#endif
typedef struct{
uint32_t uart;
uint32_t clock;
uint32_t baudrate;
uint32_t remap;
}uart_config_t;
void uartConfig(uart_config_t* config);
void sendChar(uint32_t uart, const char c);
void sendStr(uint32_t uart, const char* str);
char readChar(uint32_t uart);
#endif
uart.c
#include "uart.h"
/*
USARTDIV = Fck / BaudRate
Mantissa : integer
Fraction : decimal
*/
void uartConfig(uart_config_t* config){
float usartdiv;
int32_t mantissa;
uint32_t fraction;
switch(config->uart){
case UART1:
MODIFY_REG(RCC->APB2ENR, RCC_APB2ENR_USART1EN, RCC_APB2ENR_USART1EN);
if(config->remap > 0){
MODIFY_REG(RCC->APB2ENR, RCC_APB2ENR_AFIOEN, RCC_APB2ENR_AFIOEN);
MODIFY_REG(AFIO->MAPR, AFIO_MAPR_USART1_REMAP, config->remap << 2);
}
usartdiv = (float)config->clock/(16*config->baudrate);
mantissa = usartdiv;
fraction =((uint32_t)((usartdiv - mantissa) * 16 + 0.5f)) & (uint32_t)0xF;
USART1->BRR = (mantissa << 4) | fraction;
MODIFY_REG(USART1->CR1, USART_CR1_TE|USART_CR1_RE, USART_CR1_TE|USART_CR1_RE);
MODIFY_REG(USART1->CR1, USART_CR1_UE, USART_CR1_UE);
break;
case UART2: //UART2 share port with debugging port which means that if you connect pa2, pa3, carefull
MODIFY_REG(RCC->APB1ENR, RCC_APB1ENR_USART2EN, RCC_APB1ENR_USART2EN);
if(config->remap > 0){
MODIFY_REG(RCC->APB2ENR, RCC_APB2ENR_AFIOEN, RCC_APB2ENR_AFIOEN);
MODIFY_REG(AFIO->MAPR, AFIO_MAPR_USART2_REMAP, config->remap << 3);
}
usartdiv = (float)config->clock/(16*config->baudrate);
mantissa = usartdiv;
fraction = ((uint32_t)((usartdiv - mantissa) * 16 + 0.5f))&(uint32_t)0xF;
USART2->BRR = (mantissa << 4) | fraction;
MODIFY_REG(USART2->CR1, USART_CR1_TE|USART_CR1_RE, USART_CR1_TE|USART_CR1_RE);
MODIFY_REG(USART2->CR1, USART_CR1_UE, USART_CR1_UE);
break;
case UART3:
MODIFY_REG(RCC->APB1ENR, RCC_APB1ENR_USART3EN, RCC_APB1ENR_USART3EN);
if(config->remap > 0){
MODIFY_REG(RCC->APB2ENR, RCC_APB2ENR_AFIOEN, RCC_APB2ENR_AFIOEN);
MODIFY_REG(AFIO->MAPR, AFIO_MAPR_USART3_REMAP, config->remap << 4);
}
usartdiv = (float)config->clock/(16*config->baudrate);
mantissa = usartdiv;
fraction = ((uint32_t)((usartdiv - mantissa) * 16 + 0.5f))&(uint32_t)0xF;
USART3->BRR = (mantissa << 4) | fraction;
MODIFY_REG(USART3->CR1, USART_CR1_TE|USART_CR1_RE, USART_CR1_TE|USART_CR1_RE);
MODIFY_REG(USART3->CR1, USART_CR1_UE, USART_CR1_UE);
break;
}
}
void sendChar(uint32_t uart, const char c){
switch(uart){
case UART1:
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = c;
break;
case UART2:
while (!(USART2->SR & USART_SR_TXE));
USART2->DR = c;
break;
case UART3:
while (!(USART3->SR & USART_SR_TXE));
USART3->DR = c;
break;
}
}
void sendStr(uint32_t uart, const char* str){
while(*str){
sendChar(uart, *str++);
}
}
char readChar(uint32_t uart){
switch(uart){
case UART1:
while (!(USART1->SR & USART_SR_RXNE));
return (char)USART1->DR;
case UART2:
while (!(USART2->SR & USART_SR_RXNE));
return (char)USART2->DR;
case UART3:
while (!(USART3->SR & USART_SR_RXNE));
return (char)USART3->DR;
}
return '\0';
}
hardware.h : 해당 헤더 파일을 config 파일처럼 사용하였다.
#ifndef HARDWARE_H
#define HARDWARE_H
#ifndef COMMON_H
#include "common.h"
#endif
#ifndef RCC_H
#include "rcc.h"
#endif
#ifndef GPIO_H
#include "gpio.h"
#endif
#ifndef TIMER_H
#include "timer.h"
#endif
#ifndef UART_H
#include "uart.h"
#endif
/*setting - add device that you need*/
/*RCC config*/
#define SYSTEM_CLOCK MEGA(72)
rcc_config_t RCC_CONFIG = {
72, false, true, true,
RCC_CFGR_PLLMULL9,
RCC_CFGR_PLLXTPRE_HSE,
RCC_CFGR_PLLSRC_HSE,
RCC_CFGR_ADCPRE_DIV2, //adc
RCC_CFGR_PPRE2_DIV1, //apb2
RCC_CFGR_PPRE1_DIV2, //apb1
RCC_CFGR_HPRE_DIV1, //ahb
RCC_CFGR_SW_PLL
};
/*GPIO config*/
int IO_COUNT = 5;
gpio_config_t GPIO_CONFIG[] = {
{
PORTC, 13,
INPUT_MODE,
PUPD_INPUT,
INTERRUPT_OFF
}, //user button
{
PORTA, 5,
OUTPUT_2MHZ,
GENERAL_PUSHPULL_OUTPUT,
INTERRUPT_OFF
}, // led
{
PORTA, 8,
OUTPUT_50MHZ,
ALTERNATE_PUSHPULL_OUTPUT,
INTERRUPT_OFF
}, //for mco debug
{
UART2_PORT, UART2_TX_PIN,
UART_TX_MODE,
UART_TX_CONFIG,
INTERRUPT_OFF
},
{
UART2_PORT, UART2_RX_PIN,
UART_RX_MODE,
UART_RX_CONFIG,
INTERRUPT_OFF
}
};
/*UART config*/
int UART_COUNT = 1;
uart_config_t UART_CONFIG[] = {
{UART2, MEGA(36), 115200, 0}
};
#endif
여기서 UART_CONFIG의 클럭은 시스템 클럭이 아니라 UART 모듈에 실재 할당되는 클럭 수이다. DIV 신경 써야함
main.c : 실제 코드 동작, UART2를 사용하였다. 주의할 점은 내가 사용한 보드(nucleo-f103rb)의 경우 UART2가 디버깅 및 펌웨어 업로드에 사용되어서 핀을 이중으로 연결할 경우 제대로 통신이 되지 않는 문제가 발생하였다. 이 문제를 해결하는 방법은 그냥 다른 UART를 사용하거나, 디버깅 포트를 그대로 이용하여 UART 통신을 하면 된다.(즉 별도로 연결이 필요없다는 소리이다.)
#include "hardware.h"
int main(){
configRCC(&RCC_CONFIG);
systickConfig(); //system clock
for(int i = 0; i < IO_COUNT; i++){
configGPIO(&GPIO_CONFIG[i]);
}
for(int i = 0; i < UART_COUNT; i++){
uartConfig(&UART_CONFIG[i]);
}
while(1){
sendStr(UART2, "Hello World\r\n");
togglePin(PORTA, 5);
msDelay(1000); //timer, usart debugging
}
return 0;
}
전체 코드를 블로그에 올리기에는 양이 많아서 별도로 github에 올리겠다. 전체적인 코드 내용은 해당 부분을 참고하기를 바란다.
https://github.com/youngSeok0413/NUCLEO-F103RB
GitHub - youngSeok0413/NUCLEO-F103RB: embedded system architecture practice using nucleo-f103rb + cmsis library
embedded system architecture practice using nucleo-f103rb + cmsis library - youngSeok0413/NUCLEO-F103RB
github.com
Reference
https://ko.wikipedia.org/wiki/UART
UART - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전. UART(범용 비동기화 송수신기: Universal asynchronous receiver/transmitter)는 병렬 데이터의 형태를 직렬 방식으로 전환하여 데이터를 전송하는 컴퓨터 하드웨어의 일종이
ko.wikipedia.org
'프로젝트 > 임베디드' 카테고리의 다른 글
[이론] SPI 통신 (0) | 2025.04.28 |
---|---|
RCC 오실로스코프로 확인하기 (0) | 2025.04.07 |
JTAG/SWD 동작 안하는 경우 봐야할 것!! (0) | 2025.02.22 |
0. PPG에 대하여 (0) | 2024.05.07 |
5. 임베디드 실습 : GPIO + Systick (1) | 2024.01.26 |