임베디드시스템에서 주로 사용된는 시리얼 통신은 UART, I2C, SPI 등이 있다.
UART는 두 디바이스 사이의 통신이며 TxD는 RxD와 연결하고, RxD는 TxD와 연결한다. GND로 디바이스의 전압기준을 맞춰야 통신이 가능하다. 두 디바이스의 속도를 맞춰야 통신이 가능한데, 너무 속도가 빠르면 싱크를 맞추기 어렵기 때문에 속도의 한계가 존재한다. 표준속도 9600이다.
I2C는 master의 SCL과 SDA가 slave의 SCL과 SDA로 연결된다. Open Drain형태이므로 SCL과 SDA line에는 풀업저장 필요하다. 하나의 master에 여러 slave를 연결할 수 있으며 slave사이는 주소로 구분한다.
느린 통신속도를 가진다. 표준속도는 100kbps, fast는 400kbps, 최근에는 I3C도 등장했다.
SPI는 master의 clk, MISO, MOSI를 각각의 slave에 병렬로 연결, chip select핀을 각각 slave에 할당하여 연결, slave사이의 CS(chip select)로 구분한다. 가장빠른(Mbps)속도를 가지며, 그래픽LCD에서 사용된다.
Chip Level에서는 I2C와 SPI가 주로 사용 : 확장성 용이
UART통신은 baudrate, data bit, stop bit, parity bit를 두 디바이스가 서로 같게 설정해야한다.
9600bps로 설정한다면 송신 디바이스에서 9.6kHz의 내부clk으로 wire에 data를 보내고, 수신 디바이스에서 9.6kHz의 내부clk으로 wire를 체크해 data를 확인하여 버퍼에 저장한다.
따라서 두 디바이스의 통신속도(+data bit, stip bit, parity)가 맞지 않으면 data가 완전히 달라지게 된다.
start bit는 IDLE상태인 High에서 Low로 떨어지면 시작한다.
parity bit는 even과 odd가 있고, even이면 1의 갯수가 짝수가 되도록 parity bit를 만들고, odd면 1의 갯수가 홀수가 되도록 parity bit를 만든다. 송신단과 수신단의 parity를 비교해 같으면 통과한다. -> 그러나 최근에는 통신기술이 좋아 거의 사용하지 않는다.
stop bit는 high를 보낸다. 1개,2개가 있지만 주로 1개를 사용한다.
HAL_UART_Receive(&huart2, &rcvData, 1, 0xffffffff);
blocking code : 1byte 데이터가 수신되지 않으면 timeout만큼 계속 기다린다. 데이터가 수신되면 rcvData에 저장된다.
non-blocking방식으로 만들기 위해 수신데이터를 interrupt로 받아야한다.
HAL_UART_Receive_IT(&huart2, &rcvData, 1); // uart2가 1byte를 받으면 rcvData에 저장하고, interrupt로 점프
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
HAL_UART_Transmit(&huart2, &rcvData, 1, 1000);
HAL_UART_Receive_IT(&huart2, &rcvData, 1); // 콜백함수 안에 한번 더 써준 이유는 계속 인터럽트를 사용할 수 있기 때문, 없다면 인터럽트 해제됨
}
}
여러byte의 데이터를 놓치지 않고 저장하기 위해 저장공간(버퍼)이 필요하다. FIFO형식의 Queue를 활용한 Circular Buffer를 이용해 데이터를 관리할 수 있다.
Queue | Stack |
---|---|
FIFO | LIFO |
Queue 구현
배열에서 tail인덱스를 이용해 write(enQueue)할 때마다 인덱스를 올리고, head인덱스를 이용해 read(deQueue)할 때마다 인덱스를 올린다.
queCounter변수를 만들고, enQue와 deQue에서 더하고 빼면서 구현, queCounter는 읽어야되는 data의 개수를 의미하기도 함
#include "queue.h"
#include <stdint.h>
#define BUF_SIZE 100
typedef struct {
uint8_t queBuff[BUF_SIZE];
int tail;
int head;
int queCounter; // 현재 Queue에 있는 data 수
int cmpltFlag;
}que_t;
void que_init(que_t *que)
{
que->head = 0;
que->tail = 0;
que->queCounter = 0;
que->cmpltFlag = 0;
}
void setQueFlag(que_t *que, int flagState)
{
que->cmpltFlag = flagState;
}
int getQueFlag(que_t *que)
{
return que->cmpltFlag;
}
int queFull(que_t *que)
{
//if (head == ((tail+1)%BUF_SIZE)) // queCounter변수가 없을 때
if (que->queCounter == BUF_SIZE) // 버퍼크기만큼 data가 가득하다
return 1; // Full
else
return 0;
}
int queEmpty(que_t *que)
{
//if (head == tail) // queCounter변수가 없을 때
if (que->queCounter == 0) // Queue에 data가 전혀없다
return 1; // Empty
else
return 0;
}
void enQue(que_t *que, uint8_t data) // push
{
if(queFull(que)) return;
que->queBuff[que->tail] = data;
que->tail = (que->tail+1)%BUF_SIZE; // enQue시 인덱스를 하나 올린다. 인덱스는 buffer size로 제한
que->queCounter++; // 데이터가 Queue에 들어왔으므로 count up
}
uint8_t deQue(que_t *que) // pop
{
if(queEmpty(que)) return -1;
uint8_t temp = que->queBuff[que->head];
que->head = (que->head+1)%BUF_SIZE; // enQue시 인덱스를 하나 올린다. 인덱스는 buffer size로 제한
que->queCounter--; // 데이터가 Queue에서 나갔으므로 count down
return temp;
}