시리얼 통신
- 시리얼 인터페이스는 널리 사용되는 통신 인터페이스로, 비용이 저렴하고 사용이 간단하며, 두 장치 간의 상호 통신을 가능하게 합니다.
- 마이크로컨트롤러의 시리얼 포트는 마이크로컨트롤러-마이크로컨트롤러, 마이크로컨트롤러-컴퓨터, 마이크로컨트롤러-모듈 간의 통신을 가능하게 하여, 마이크로컨트롤러의 응용 범위를 확장하고 하드웨어 성능을 강화합니다.
하드웨어 회로
- 단순 양방향 시리얼 통신에는 전송선(TX)과 수신선(RX) 두 개의 통신선이 필요합니다.
- TX와 RX는 교차 연결되어야 합니다.
- 단방향 데이터 전송이 필요한 경우, 단일 통신선만 연결하면 됩니다.
- 전압 표준이 다른 경우, 전압 변환 칩을 추가해야 합니다.
장치가 TX를 통해 고저전압을 전송하고, 다른 장치가 RX를 통해 전압을 수신합니다.
STM32는 3.3V 시스템이므로, 지면 대비 3.3V는 논리 1을 나타내고, 0V는 논리 0을 나타냅니다.
시리얼 통신 매개변수 및 타이밍
- 보드레이트: 시리얼 통신 속도(비동기 통신을 사용할 경우 양측이 통신 속도를 약속해야 합니다)
- 시작비트: 데이터 프레임 시작을 표시하는 비트, 항상 낮은 전압
- 데이터비트: 데이터 프레임의 유효 부하, 1은 높은 전압, 0은 낮은 전압, 최하위 비트 우선
- 검증비트: 데이터 검증을 위한 비트, 데이터비트에 따라 계산됩니다(홀수 검증: 전송할 바이트가 0000 1111이면 마지막 비트에 1을 추가하여 총 5개의 1을 만듭니다. 전송할 바이트가 0000 0111이면 마지막 비트에 0을 추가하여 3개의 1을 만듭니다. 수신측은 1의 개수가 홀수인지 확인합니다. 짝수 검증도 동일하지만, 홀수/짝수 검증은 데이터 검증의 일정 수준만 보장합니다)
- 정지비트: 데이터 프레임 간격을 위한 비트, 항상 높은 전압
각 바이트는 데이터 프레임에 포함되어 있으며, 각 데이터 프레임은 시작비트, 데이터비트, 정지비트로 구성됩니다. 왼쪽 그림은 8비트 데이터비트를 나타내며, 오른쪽 그림은 9비트 데이터비트를 나타냅니다. 마지막 비트는 홀수/짝수 검증비트입니다.
작동 중이 아닌 상태에서는 고전압 상태이며, 작동을 시작하면 시작비트로 낮은 전압이 발생하여 수신 장치에 데이터 전송을 알립니다. 동일하게, 한 바이트 데이터 전송이 완료된 후에는 정지비트가 필요합니다.
USART
- USART(일반 동기/비동기 수신/전송기)는 STM32 내부에 통합된 하드웨어 외부 장치입니다. 데이터 레지스터의 1바이트 데이터를 자동으로 데이터 프레임 타이밍을 생성하고, TX 핀을 통해 전송할 수 있습니다. 또한 RX 핀의 데이터 프레임 타이밍을 자동으로 수신하여 1바이트 데이터로 조합하고, 데이터 레지스터에 저장합니다.
- 내장 보드레이트 생성기, 최대 4.5Mbits/s 지원
- 데이터비트 길이 설정 가능(8/9비트), 정지비트 길이 설정 가능(0.5/1/1.5/2비트)
- 선택적 검증비트(검증 없음/홀수 검증/짝수 검증)
- 동기 모드, 하드웨어 흐름 제어(하드웨어 회로에서 추가 라인을 통해 B가 수신 준비가 되었을 때 고전압으로 설정하고, 준비되면 저전압으로 설정하여 A가 데이터를 전송하는 방식), DMA, 스마트 카드, IrDA, LIN 지원
- STM32F103C8T6 USART 자원: USART1, USART2, USART3
USART 블록 다이어그램
데이터 전송 레지스터: 대기 중인 음식; 전송 이동 레지스터: 음식을 받는 중;
수신 이동 레지스터: 음식을 받았음; 수신 데이터 레지스터: 음식을 먹는 중.
USART 기본 구조
데이터가 데이터 레지스터에서 이동 레지스터로 전송될 때, TXE 플래그가 설정됩니다. 이 플래그를 확인하여 다음 데이터를 쓸 수 있는지 확인할 수 있습니다;
동일하게, 이동 레지스터에서 데이터 레지스터로 전송될 때, RXNE 플래그가 설정됩니다. 이 플래그를 확인하여 데이터 수신 여부를 확인할 수 있습니다.
데이터 프레임
정지비트는 정지 비트 수를 나타내며, 일반적으로 1비트 사용됩니다
시작비트 감지
입력 회로가 데이터 프레임의 시작비트를 감지하면, 보드레이트 주기로 데이터 프레임을 연속적으로 샘플링합니다. 시작비트부터 샘플링 위치가 정확히 중앙에 맞춰져야 하며, 첫 번째 비트가 정렬되면 나머지 비트도 정렬됩니다.
입력 부분 회로는 샘플링 클록을 분할 처리하며, 보드레이트의 16배 주파수로 샘플링합니다. 즉, 1비트 시간 동안 16회 샘플링합니다.
처음에는 공급 상태의 고전압 샘플링이 계속 1로 유지되며, 어느 위치에서 갑자기 0을 샘플링하면, 이 두 샘플링 사이에 하강 에지가 발생한 것입니다. 노이즈가 없다면 시작비트일 것입니다. 시작비트에 대해 연속 16회 샘플링합니다.
데이터 샘플링
1~16은 1비트 시간 길이입니다. 1비트 시간 동안 16개의 샘플링 클록이 있으며, 시작비트 감지가 이미 샘플링 클록을 정렬했기 때문에, 8, 9, 10회 샘플링을 시작합니다. 데이터 정확성을 보장하기 위해 3회 연속 샘플링을 수행합니다. 노이즈의 영향으로 2:1 규칙을 적용합니다. 2회가 1이면 1로, 2회가 0이면 0으로 처리합니다. 이 경우 노이즈 플래그 NE도 설정되어, 데이터를 수신했지만 노이즈가 있음을 알립니다.
보드레이트 생성기(레지스터)
- 송신기와 수신기의 보드레이트는 보드레이트 레지스터 BRR의 DIV 값에 의해 결정됩니다
- 계산식: 보드레이트 = fPCLK2/1 / (16 × DIV) (왜 16이 있는지, 1비트에 16개의 샘플링 클록이 있기 때문입니다)
- 라이브러리 함수를 사용하는 경우, 라이브러리 함수가 자동으로 BRR 값을 계산합니다
HEX 데이터 패킷
데이터 패킷의 역할: 별도의 데이터를 패키지로 묶어 다바이트 데이터 통신을 편리하게 합니다.
예: 자이로 센서가 시리얼 포트를 통해 데이터를 전송하는 STM32
고정 패킷 길이, 헤더 및 테일 포함
여기서 0xFF는 헤더, 0xFE는 테일로 정의됩니다
변동 패킷 길이, 헤더 및 테일 포함
데이터가 헤더 및 테일과 중복되는 경우, 고정 패킷 길이를 선택하여 수신 오류를 방지하는 것이 좋습니다. 데이터가 헤더 및 테일과 중복되지 않는 경우, 변동 패킷 길이를 선택할 수 있습니다.
텍스트 데이터 패킷
고정 패킷 길이, 헤더 및 테일 포함
@를 헤더로, \r\n을 테일로 사용합니다
변동 패킷 길이, 헤더 및 테일 포함
HEX 데이터 패킷 프로세스 다이어그램
상태머신을 사용할 수 있습니다. 3개의 상태를 정의합니다. 첫 번째 상태는 헤더 대기, 두 번째 상태는 데이터 수신, 세 번째 상태는 테일 대기입니다.
처음 S=0, 데이터가 중단에 들어오면, S=0 상태 프로그램으로 진입하여 데이터가 헤더 FF인지 확인합니다. 헤더 FF이면, 헤더를 수신한 것으로, S=1로 설정하고 중단을 종료합니다. 다음 중단은 S=1 상태 프로그램으로 진입합니다. 첫 번째 상태에서 수신된 데이터가 FF가 아니면, 데이터 패킷이 맞지 않음을 나타내므로 헤더는 여전히 0입니다. 다음 중단은 헤더 상태로 들어가, FF가 나타날 때까지 계속합니다. FF가 나타나면 수신 데이터 상태로 이동합니다. 이후 수신된 데이터는 배열에 저장되고, 수신된 데이터 수를 기록하는 변수가 있습니다. 4개의 데이터가 수신되지 않으면 계속 수신 상태를 유지합니다. 수신되면 S=2로 설정하고, 다음 중단 시 다음 상태로 진입합니다. 마지막으로 테일 대기, 테일 FE인지 확인하고, FE이면 S=0으로 설정하고, 다음 루프로 돌아갑니다. FE가 아니면 계속 FE를 기다립니다.
텍스트 데이터 패킷 프로세스 다이어그램
위의 데이터 패킷 프로세스 다이어그램과 유사하지만, 이 경우 변동 패킷 길이를 사용하므로, S=1 상태에서 데이터를 수신하면서 테일을 기다려야 합니다. 항상 테일 수신 여부를 감시해야 합니다.
추천
일반적으로, HEX 데이터 패킷은 다양한 센서의 개별 데이터 전송에 자주 사용되며, 예를 들어 자이로의 X, Y, Z 축 데이터, 온도 습도 데이터 등에 사용됩니다. 텍스트 데이터 패킷은 시리얼 포트를 통해 텍스트를 전송하여 특정 기능을 수행하는 데 사용됩니다.
코드 부분
시리얼 전송 및 수신
#include "Bsp_Serial.h"
uint8_t ReceivedData;
uint8_t DataAvailable;
void USART_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 1. 클럭 설정
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct; // 2. GPIO 설정
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 매뉴얼에서 추천하는 복합 푸시-풀 모드
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitTypeDef USART_InitStruct; // 3. USART 설정
USART_InitStruct.USART_BaudRate = 9600; // 보드레이트
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 하드웨어 흐름 제어
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 전송 및 수신 활성화
USART_InitStruct.USART_Parity = USART_Parity_No; // 홀수/짝수 검증
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 정지 비트
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 전송 비트 길이
USART_Init(USART1, &USART_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 시리얼 중단 활성화
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 중단 그룹 설정
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
USART_Cmd(USART1, ENABLE); // 4. USART1 활성화
}
/* 단일 바이트 전송 */
void SendByte(uint8_t Data) // 여기서 8비트만 전달하며, uint_8 타입 사용. 9비트 전달 시 uint_16 타입 사용
{
USART_SendData(USART1, Data);
while ((USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET));
}
/* 배열 전송 */
void SendArray(uint8_t *Data, uint32_t Size)
{
for (uint16_t i = 0; i < Size; i++)
{
SendByte(Data[i]);
}
}
/* 문자열 전송 */
void SendString(char *Text)
{
for (uint8_t i = 0; Text[i] != '\0'; i++)
{
SendByte(Text[i]);
}
}
/* 제곱 */
uint32_t CalculatePower(uint32_t Base, uint32_t Exponent)
{
uint32_t Result = 1;
while (Exponent --)
{
Result *= Base;
}
return Result;
}
/* 10진수 전송 */
void SendNumber(uint32_t Value, uint8_t Length)
{
for (uint8_t i = 0; i < Length; i++)
{
SendByte(Value / CalculatePower(10, Length - i - 1) % 10 + '0');
}
}
/* printf 재정의 fputc는 printf의 하위 함수, 시리얼로 재지정 */
int fputc(int ch, FILE *f)
{
SendByte(ch);
return ch;
}
/* sprintf 래핑 */
void PrintFormat(char *format, ...)
{
char Buffer[100];
va_list args; // va_list는 유형 이름, args는 변수 이름
va_start(args, format); // va_start는 format 위치부터 인수를 받습니다
vsprintf(Buffer, format, args); // Buffer에 포맷된 문자열을 출력합니다
va_end(args); // 인수를 해제합니다
SendString(Buffer);
}
/* RXNE 플래그 확인 */
/*
void CheckRXNE(uint8_t RxData)
{
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
{
RxData = USART_ReceiveData(USART1);
OLED_ShowHexNum(1, 1, RxData, 2);
}
}
*/
/* 중단 수신 확인 */
uint8_t IsDataAvailable(void)
{
if (DataAvailable == 1)
{
DataAvailable = 0;
return 1;
}
return 0;
}
/* 중단 데이터 반환 */
uint8_t GetReceivedData(void)
{
return ReceivedData;
}
/* USART1 중단 함수 */
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == 1)
{
ReceivedData = USART_ReceiveData(USART1);
DataAvailable = 1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
시리얼 HEX 데이터 패킷 전송
#include "Bsp_Serial.h"
// uint8_t ReceivedData;
uint8_t ReceivedPacket[4];
uint8_t TransmittedPacket[4];
uint8_t DataAvailable = 0;
void USART_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 1. 클럭 설정
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct; // 2. GPIO 설정
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 매뉴얼에서 추천하는 복합 푸시-풀 모드
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitTypeDef USART_InitStruct; // 3. USART 설정
USART_InitStruct.USART_BaudRate = 9600; // 보드레이트
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 하드웨어 흐름 제어
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 전송 및 수신 활성화
USART_InitStruct.USART_Parity = USART_Parity_No; // 홀수/짝수 검증
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 정지 비트
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 전송 비트 길이
USART_Init(USART1, &USART_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 시리얼 중단 활성화
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 중단 그룹 설정
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
USART_Cmd(USART1, ENABLE); // 4. USART1 활성화
}
/* 중단 수신 확인 */
uint8_t IsDataAvailable(void)
{
if (DataAvailable == 1)
{
DataAvailable = 0;
return 1;
}
return 0;
}
/* HEX 데이터 패킷 전송 */
void SendPacket(void)
{
SendByte(0xFF);
SendArray(TransmittedPacket, 4);
SendByte(0xFE);
}
/* 중단 데이터 반환 */
/*
uint8_t GetReceivedData(void)
{
return ReceivedData;
}
*/
/* USART1 중단 함수 */
void USART1_IRQHandler(void)
{
static uint8_t State = 0; // static은 전역 변수와 유사하지만, 함수 내부에서만 사용
static uint8_t PacketIndex = 0; // 현재 수신된 바이트 인덱스
if (USART_GetITStatus(USART1, USART_IT_RXNE) == 1)
{
uint8_t RxData = USART_ReceiveData(USART1);
switch (State) // 상태머신 사용
{
case 0:
{
if (RxData == 0xFF)
{
State = 1;
PacketIndex = 0;
}
}
break;
case 1:
{
ReceivedPacket[PacketIndex] = RxData;
PacketIndex ++;
if (PacketIndex >= 4)
{
State = 2;
}
}
case 2:
{
if (RxData == 0xFE)
{
State = 0;
DataAvailable = 1;
}
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
시리얼 텍스트 데이터 패킷 전송
#include "Bsp_Serial.h"
// uint8_t ReceivedData;
char ReceivedPacket_Text[100]; // 텍스트 데이터 패킷 수신 변수
uint8_t ReceivedPacket_Hex[4]; // HEX 데이터 패킷 수신 변수
uint8_t TransmittedPacket[4];
uint8_t DataAvailable = 0;
void USART_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 1. 클럭 설정
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct; // 2. GPIO 설정
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 매뉴얼에서 추천하는 복합 푸시-풀 모드
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitTypeDef USART_InitStruct; // 3. USART 설정
USART_InitStruct.USART_BaudRate = 9600; // 보드레이트
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 하드웨어 흐름 제어
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 전송 및 수신 활성화
USART_InitStruct.USART_Parity = USART_Parity_No; // 홀수/짝수 검증
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 정지 비트
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 전송 비트 길이
USART_Init(USART1, &USART_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 시리얼 중단 활성화
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 중단 그룹 설정
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
USART_Cmd(USART1, ENABLE); // 4. USART1 활성화
}
/* 텍스트 데이터 패킷 상태머신 */
void USART1_IRQHandler(void)
{
static uint8_t State = 0; // static은 전역 변수와 유사하지만, 함수 내부에서만 사용
static uint8_t PacketIndex = 0; // 현재 수신된 바이트 인덱스
if (USART_GetITStatus(USART1, USART_IT_RXNE) == 1)
{
uint8_t RxData = USART_ReceiveData(USART1);
switch (State)
{
case 0:
{
if (RxData == '@' && DataAvailable == 0) // 두 조건이 모두 충족될 때만 수신
{
State = 1;
PacketIndex = 0;
}
}
break;
case 1:
{
if (RxData == '\r')
{
State = 2;
}
else
{
ReceivedPacket_Text[PacketIndex] = RxData;
PacketIndex ++;
}
}
break;
case 2:
{
if (RxData == '\n')
{
State = 0;
ReceivedPacket_Text[PacketIndex] = '\0';
DataAvailable = 1;
}
}
break;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}