linux uart 커널 설정 및 non-blocking 모드 시리얼 통신 example c code

 본 포스트는 uart 드라이버의 linux 커널 설정 방법과 linux user mode에서 동작하는 uart 통신 예제를 싣고 있다.


1. Kernel configuration

uart serial 드라이버는 보통 사용할 수 있도록 설정되어 있다. 만약 확인이 필요하다면, 아래 리눅스 커널 menuconfig 이미지와 같이 Device Drivers의 Character devices가 활성화되어 있는지, Character devices내의 Serial drivers 내의 uart controller가 제대로 선택되었는지 보면 된다. 


uart 세부 설정을 변경하고 싶으면, linux 커널의 dts(device tree script)파일이나 칩 제조사가 제공하는 설정 방법으로 변경할 수 있다.

아래 이미지는 allwinner사의 sys_config.fex파일에서의 설정 예이다.


아래 이미지는 dtsi 파일에의 uart 설정 예이다. dtsi파일은 arm 계열의 경우 /linux/arch/arm/boot/dts 폴더에 있다. 


2. uart example code

2.1. uart 드라이버 설정 및 read/write 함수

아래 예제 코드는 non-blocking 방식으로 동작하도록 설정되어 있다. select 문을 사용해 수신된 데이터가 있는지 체크하도록 했다. 


#ifndef __uart_h__
#define __uart_h__

#ifdef __cplusplus
extern "C"{
#endif
        int uart_open(char *dev, int baud_rate);
        void uart_close(int uartd);
        int uart_readytoread(int uartd,int timeoutms);
        int uart_waitnread(int uartd, char *data, int size,int timeoutms);
        int uart_read(int uartd, char *data, int size);
        int uart_write(int uartd, char *data, int size);
#ifdef __cplusplus
}
#endif

#endif


/*=========================================
    INCLUDE
=========================================*/
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#include <string.h>
#include "uart.h"


/*=========================================
LOCAL DEFINE
>=========================================*/
typedef struct _uart_ctx_t
{
    pthread_mutex_t mutex;
    int fd;
    struct termios oldtio,newtio;
}uart_ctx_t;


/*=========================================
LOCAL FUNCTIONS
=========================================*/
int open_fd(char *dev)
{
    int fd = 0;
    if (!dev) return -1;
    
    fd = open( dev, O_RDWR | O_NOCTTY | O_NONBLOCK );
    if (fd < 0)
    {
        return -1;
    }
    return fd;
}

int uart_config(uart_ctx_t *ctx, int baud_rate)
{
    if (!ctx || ctx->fd <= 0) return -1;
    tcgetattr(ctx->fd,&ctx->oldtio); // save current port setting.
    //set new port settings
    switch(baud_rate)
    {
        case 9600:
            ctx->newtio.c_cflag = B9600;
            break;
        case 19200:
            ctx->newtio.c_cflag = B19200;
            break;
        case 38400:
            ctx->newtio.c_cflag = B38400;
            break;
        case 57600:
            ctx->newtio.c_cflag = B57600;
            break;
        case 115200:
        default:
            ctx->newtio.c_cflag = B115200;
            break;
    }

    ctx->newtio.c_cflag |= (CRTSCTS | CS8 | CLOCAL | CREAD);
    ctx->newtio.c_iflag = IGNPAR;
    ctx->newtio.c_oflag = 0;
    ctx->newtio.c_lflag = 0;//non-Canonical mode ... if you want Canonical mode, add ICANON;
    ctx->newtio.c_cc[VMIN]= 0;  // non blocking...
    ctx->newtio.c_cc[VTIME]=0;
    tcflush(ctx->fd, TCIFLUSH);
    tcsetattr(ctx->fd,TCSANOW,&ctx->newtio);
    return 0;
}

int uart_fd_select(int fd, int timeout_ms)
{
    fd_set io_fds;
    struct timeval timeout;

    if (fd <=0) return -1;
    if (timeout_ms<0) timeout_ms = 0;

    FD_ZERO(&io_fds);
    FD_SET(fd,&io_fds);

    timeout.tv_sec = 0;
    timeout.tv_usec = 1000*timeout_ms;

    if(select(fd+1,&io_fds,0,0,&timeout) == -1)      {
        return -1;
    }

    if(FD_ISSET(fd,&io_fds))
    {
        return fd;
    }

    return -1;
}


/*=========================================
GLOBAL FUNCTIONS
=========================================*/
int uart_open(char *dev, int baud_rate)
{
    uart_ctx_t *ctx = NULL;
    if (!dev) return 0;
    
    ctx = (uart_ctx_t*)malloc(sizeof(uart_ctx_t));
    if (ctx)
    {
        memset(ctx,0,sizeof(uart_ctx_t));
        pthread_mutex_init(&ctx->mutex,NULL);
        
        ctx->fd = open_fd(dev);
        if(ctx->fd <= 0)
        {
            uart_close((int)ctx);
            return 0;
        }
        
        uart_config(ctx,baud_rate);
    }
    
    return (int)ctx;
}

void uart_close(int uartd)
{
    uart_ctx_t *ctx = (uart_ctx_t *)uartd;
    if(ctx)
    {
        if(ctx->fd > 0)
        {
            tcsetattr(ctx->fd,TCSANOW,&ctx->oldtio);
            close(ctx->fd);
            ctx->fd = 0;
        }

        free(ctx);
    }
}

int uart_readytoread(int uartd,int timeoutms)
{
    uart_ctx_t *ctx = (uart_ctx_t *)uartd;

    if(!ctx) return -1;

    pthread_mutex_lock(&ctx->mutex);
    if(uart_fd_select(ctx->fd,timeoutms) == ctx->fd) 
    {
        pthread_mutex_unlock(&ctx->mutex);
        return 1;
    }
    pthread_mutex_unlock(&ctx->mutex);

    return 0;
}

int uart_read(int uartd, char *data, int size)
{
    int readlen = 0;
    uart_ctx_t *ctx = (uart_ctx_t *)uartd;

    if(!ctx || ctx->fd <= 0 || !data || size <= 0) return 0;

    pthread_mutex_lock(&ctx->mutex);
    readlen = read(ctx->fd,data,size*sizeof(char));
    pthread_mutex_unlock(&ctx->mutex);

    return readlen;
}

int uart_waitnread(int uartd, char *data, int size,int timeoutms)
{
    int readlen = 0;
    uart_ctx_t *ctx = (uart_ctx_t *)uartd;

    if(!ctx || ctx->fd <= 0 || !data || size <= 0) return 0;

    pthread_mutex_lock(&ctx->mutex);
    if(uart_fd_select(ctx->fd,timeoutms) == ctx->fd)
    {
        readlen = read(ctx->fd,data,size*sizeof(char));
    }
    pthread_mutex_unlock(&ctx->mutex);

    return readlen;
}

int uart_write(int uartd, char *data, int size)
{
    int wrotelen = 0;
    int failcnt = 0;
    uart_ctx_t *ctx = (uart_ctx_t *)uartd;

    if(!ctx || ctx->fd <= 0 || !data || size <= 0) return 0;

    pthread_mutex_lock(&ctx->mutex);

    while(wrotelen < size && failcnt < 10)
    {  
        int ret = 0;
        int towritelen = size - wrotelen;
        char *ptr = data + wrotelen;

        ret = write(ctx->fd,ptr,towritelen*sizeof(char));

        if (ret > 0)
        {
            wrotelen += ret;
        }
        else
        {
            failcnt ++;
        }
    }
    pthread_mutex_unlock(&ctx->mutex);

    return wrotelen;
}
/*EOF*/


2.2. uart 드라이버 사용 예제 코드

위 2.1.의 uart 데이터 read/write 코드를 사용한 예제 코드는 아래와 같다. 예제는 esp8266 wifi 모듈의 초기 부팅 메시지를 읽고, AT 커맨드를 보내 응답을 읽는 코드로 uart 통신이 정상적으로 이루어지는지를 테스트했다.

uart 수신은 별도의 rx thread에서 수신 데이터 체크 후 데이터를 읽어오도록 했다.


int g_run = 0;
void rx_thread(void *param)
{
#define reset_buffer() memset(rx,0,sizeof(char)*1024)
    char *rx = (char*)malloc(sizeof(char)*1024);
    int uartd = (int)param;

    while(g_run)
    {
        if(uart_readytoread(uartd,10)>0)
        {
            int len = 0;
            reset_buffer();
            len = uart_read(uartd, rx, 1024);
            if(len >0) 
            {
                printf("rx(%d) : %s\n",len,rx);
            }
        }
        th_delay(50);
    }

    free(rx);
}


int main(int argc, char *argv[])
{
    int uartd = 0;
    pthread_t rx_th;
    int rx_th_id;

    uartd = uart_open("/dev/ttyS3", 115200);
    if (uartd <= 0)
        return 0;

    esp8266_pow(1);

    g_run = 1;
    rx_th_id = pthread_create(&rx_th, NULL, rx_thread, (void*)uartd);

    th_delay(1000*10);
    printf("tx : AT\n");
    uart_write(uartd, "AT\r\n", 4);
    th_delay(1000*5);

    esp8266_pow(0);
    g_run = 0;
    if(rx_th_id > 0)
    {
        int status;
        pthread_join(rx_th, (void **)&status);
        rx_th = 0;
        rx_th_id = 0;
    }

    uart_close(uartd);
    return 0;
}

위 코드의 실행 결과는 아래와 같다. 통신이 정상적으로 이루어 지고 있다. 



댓글

  1. th_delay는 어디에 정의된 api인가요?

    답글삭제
    답글
    1. 만든 코드입니다. usleep같은 함수입니다.

      삭제

댓글 쓰기

이 블로그의 인기 게시물

간단한 cfar 알고리즘에 대해

windows에서 간단하게 크롬캐스트(Chromecast)를 통해 윈도우 화면 미러링 방법

바로 프로젝트 적용 가능한 FIR Filter (low/high/band pass filter )를 c나 python으로 만들기

base64 인코딩 디코딩 예제 c 소스

python ctypes LoadLibrary로 windows dll 로드 및 함수 호출 예제