레이블이 C인 게시물을 표시합니다. 모든 게시물 표시
레이블이 C인 게시물을 표시합니다. 모든 게시물 표시

2018년 6월 20일 수요일

C 언어 비교문에서 == 사용 방법

C 언어에서 '='는 assignment, 즉 변수에 값을 넣어줄 때 사용하고 '=='는 compare, 즉 앞과 뒤의 값이 같은가를 비교하기 위해 사용한다.

하지만 '=='는 코딩을 하다 보면 타이핑 실수로 '='를 하나 빼 먹는 실수를 범하기 쉬운데, 별거 아닌거 같은 실수가 아주 치명적인 결과를 가져올 수가 있다.

먼저 아래 코드를 보자.

void setup() {
  Serial.begin(115200); 
  pinMode(13, OUTPUT);
}

void loop() {
  if (Serial.available()) {
    byte ch = Serial.read();
    if (ch == '1') {
      digitalWrite(13, HIGH);
    } else {
      digitalWrite(13, LOW);
    }
  }
}


시리얼 포트로 글자를 입력 받아 '1'이면 아두이노에 있는 LED를 켜고, 그 이외 글자인 경우 LED를 꺼 주는 매우 단순한 코드이다.

이 부분에서 시리얼 포트에서 입력받은 글자가 '1'인지 비교하고 있다. 그런데 저기서 실수로 '='를 하나 빼 먹으면 어떻게 될까?


이렇게 해도 컴파일을 하면 아무 문제 없이 컴파일이 완료된다.


이 코드를 아두이노에 업로드해 실행을 하면 어떻게 될까?

원래대로라면 업로드가 완료되면 시리얼 모니터에서 '1'을 보내 줘야만 불이 켜지게 된다. 하지만 위의 코드가 업로드 되면 시리얼 모니터에서 어떤 글자를 보내건 무조건 불이 켜지고 그 이후에는 어떤 글자를 보내는가 상관 없이 불이 계속 켜저 있고 끌수가 없게 된다.

왜 이런 문제가 발생하는 것일까?

답은 맨 위에서 설명했던 것 처럼 '='와 '=='의 의미 차이 때문이다.

ch == '1' 라고 하는 경우는 ch에 들어 있는 글자와 '1'라는 글자를 비교해 같으면 True, 다르면 False라는 값이 출력된다. 그래서 if 구문이 ch에 들어 있는 글자에 따라 불을 켜거나 끌 수 있게 된다.

그런데 ch = '1' 의 경우는 ch에 어떤 글자가 들어 있는가는 상관 없이 ch에 '1'이라는 글자를 새로 넣어주라는 의미가 된다. 즉 시리얼 포트에서 어떤 글자가 들어왔는가는 그냥 무시되어 버리는 것이다. 게다가 C 언어에서 '=' (assignment)의 경우 무조건 결과값으로 True가 나오게 되기 때문에

if (ch = '1') {
  digitalWrite(13, HIGH); 
} else {
  digitalWrite(13, LOW);
}

저 코드에서 시리얼 포트에서 받은 값과 상관 없이 항상 digitalWrite(13, HIGH); 만 실행되게 된다.

게다가 바둑이나 장기를 둘 때 자기는 못보지만 훈수를 두는 사람은 쉽게 묘수를 볼 수 있는것처럼 저런 실수는 코딩을 한 당사사는 찾지 못하는 경우가 많다. 실제 코드가 길어지면 심한 경우 디버깅을 한다고 몇일, 몇달동안 코드를 한줄 한줄 뒤져보면서도 저 실수가 눈에 안 들어온다.


이것이 원하는 동작을 하는 코드인데 두개를 보고 한눈에 차이점을 찾을 수 있는가?? 지금까지 구구절절히 설명을 했으니 쉽게 찾겠지만 만일 따로 설명 없이 두개의 스크린샷만 놓고 차이점을 찾으라고 한다면 쉽지 않을 것이다.

그래서 비교문을 쓸 때 순서를 바꿔 쓰라고 권장하는 것이다.

즉 if (ch == '1') 대신 if ('1' == ch) 이렇게 쓰는 습관을 들이는 것이 좋다.

그럼 단지 순서만 바뀌 쓰는 것인데 뭐가 좋다는 것일까?

만일 둘 다 '='를 하나 빼먹었다고 생각을 해 보자.

if (ch ='1') 는 문법상 틀린 부분이 없기 때문에 정상적으로 컴파일이 완료되고 실행된다. 물론 동작은 원하는것과 다르겠지만...
하지만 if ('1' = ch) 는 C언어 문법 자체가 틀렸기 때문에 컴파일을 하면


이렇게 에러가 발생하기 때문에 바로 실수를 바로잡아 줄 수가 있게 된다.

* C언어 문법에서 '='로 변수에 값을 넣어주는 경우 변수 이름은 '='의 왼쪽에, 변수에 넣어 줄 값은 '='의 오른쪽에 와야만 한다. "lvalue required as left operand of assignment" 이 문구가 그게 잘못되었다고 알려주는 것이다.

바로 잘못된 부분을 수정하고 컴파일 하면 이제 정상적으로 동작하게 된다.


프로그래밍을 할 때 컴파일러가 에러를 내 주는건 해결하기도 훨씬 쉽다. 하지만 위와 같이 실수를 했는데도 불구하고 컴파일은 정상적으로 되지만 생각하는것과 다르게 동작하는걸 찾는건 훨씬 힘들기 때문에 코딩할 때 부터 그 가능성을 없애줄 수 있는 습관을 들여 놓는 것이 좋다.

2015년 3월 31일 화요일

라즈베리파이 시리얼포트 C 프로그래밍 (Raspberry Pi Serial port programming in C)

Raspberry Pi에서는 기본적으로 UART를 시리얼 콘솔로 사용하게 된다. 하지만 UART를 어플리케이션이 다른 용도로 사용하고자 하는 경우에는 콘솔로 동작하지 못하도록 해 줘야 한다.

먼저 이전 포스트(Raspberry Pi에서 UART를 console이 사용하지 않도록 설정하기)를 참조해 설정을 변경해준다.

C 프로그램에서 UART를 사용하기 위해서는 다음의 헤더 파일을 include 해 준다.

#include <stdio.h>
#include <unistd.h> //Used for UART
#include <fcntl.h> //Used for UART

#include <termios.h> //Used for UART

아래는 UART 설정 코드이다.

// -------------------------
// ----- SETUP USART 0 -----
// -------------------------
// At bootup, pins 8 and 10 are already set to UART0_TXD, UART0_RXD 
// (ie the alt0 function) respectively
int uart0_filestream = -1;

// OPEN THE UART
// The flags (defined in fcntl.h):
// Access modes (use 1 of these):
//   O_RDONLY - Open for reading only.
//   O_RDWR - Open for reading and writing.
//   O_WRONLY - Open for writing only.
//
// O_NDELAY / O_NONBLOCK (same function) - Enables nonblocking mode. 
//    When set read requests on the file can return immediately with a failure status
//    if there is no input immediately available (instead of blocking). 
//    Likewise, write requests can also return immediately with a failure status 
//    if the output can't be written immediately.
//
// O_NOCTTY - When set and path identifies a terminal device, 
//    open() shall not cause the terminal device to become 
//    the controlling terminal for the process.
uart0_filestream = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode
if (uart0_filestream == -1) {     // ERROR - CAN'T OPEN SERIAL PORT
  printf("Error - Unable to open UART. Ensure it is not in use by another application\n");
}

// CONFIGURE THE UART
// The flags (defined in /usr/include/termios.h 
//     - see http://pubs.opengroup.org/onlinepubs/007908799/xsh/termios.h.html):
// Baud rate:- B1200, B2400, B4800, B9600, B19200, B38400, B57600, 
//             B115200, B230400, B460800, B500000, B576000, B921600, 
//             B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, 
//             B3500000, B4000000
// CSIZE:- CS5, CS6, CS7, CS8
// CLOCAL - Ignore modem status lines
// CREAD - Enable receiver
// IGNPAR = Ignore characters with parity errors
// ICRNL - Map CR to NL on input 
//         (Use for ASCII comms where you want to auto correct 
//         end of line characters - don't use for bianry comms!)
// PARENB - Parity enable
// PARODD - Odd parity (else even)
struct termios options;
tcgetattr(uart0_filestream, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; //<Set baud rate
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(uart0_filestream, TCIFLUSH);

tcsetattr(uart0_filestream, TCSANOW, &options);

코드가 좀 길기는 해도 코멘트를 참고하면 이해하는데 문제는 없을 것이다. 위의 코드는 시리얼 포트를 9600bps, 8 bit data, no parity, non-blocking 모드로 오픈해준다.

시리얼 포트가 오픈되었으면 아래와 같이 데이터를 송수신 할 수 있다.

데이터 전송

//----- TX BYTES -----
unsigned char tx_buffer[20];
unsigned char *p_tx_buffer;

p_tx_buffer = &tx_buffer[0];
*p_tx_buffer++ = 'H';
*p_tx_buffer++ = 'e';
*p_tx_buffer++ = 'l';
*p_tx_buffer++ = 'l';
*p_tx_buffer++ = 'o';

if (uart0_filestream != -1) {
  int count = write(uart0_filestream, &tx_buffer[0], (p_tx_buffer - &tx_buffer[0])); //Filestream, bytes to write, number of bytes to write
  if (count < 0) {
    printf("UART TX error\n");
  }


}

데이터 수신

//----- CHECK FOR ANY RX BYTES -----
if (uart0_filestream != -1) {
  // Read up to 255 characters from the port if they are there
  unsigned char rx_buffer[256];
  int rx_length = read(uart0_filestream, (void*)rx_buffer, 255); //Filestream, buffer to store in, number of bytes to read (max)
  if (rx_length < 0) {
    //An error occured (will occur if there are no bytes)
  } else if (rx_length == 0) {
    //No data waiting
  } else {
    //Bytes received
    rx_buffer[rx_length] = '\0';
    printf("%i bytes read : %s\n", rx_length, rx_buffer);
  }

}

일반적인 경우 수신된 데이터가 없을 때 read()를 호출하면 데이터가 수신될 때 까지 실행이 멈추게 되지만 여기서는 시리얼포트를  O_NDELAY 플래그를 사용해 non-blocking 모드로 오픈했기 때문에 read()를 호출했을 때 수신된 데이터가 없어도 바로 리턴하기 때문에 코드 내에서 데이터가 수신되기를 기다려야 한다면 while 루프등을 사용해야 한다.

시리얼 포트를 다 사용했다면 포트를 close하면 된다.

// —— CLOSE THE UART —— 
close(uart0_filestream);

시리얼 포트가 동작하는지 확인할 수 있는 가장 쉬운 방법은 Tx와 Rx핀을 서로 연결해 준 다음 프로그램에서 송신한 데이터가 그대로 수신되는지 확인해 보면 된다. (Loopback Test)