한때에는 임베디드 시스템 프로그램을 RTOS를 기반으로 개발하는 것이 일반적이었던 때가 있었다. RTOS의 종류로는 VxWorks, pSOS와 같은 고가의 상용 제품과 더불어 uC/OS, FreeRTOS, RTEMS 등 소스가 공개되어 무료 또는 저가의 라이센스 비용만 지불하면 사용할 수 있는것들을 포함하여 그 수가 헤아릴수 없을 정도이다. 그러나, 최근에는 UNIX 계열 OS인 리눅스를 임베디드 시스템의 OS로 적용하는 것이 대세이기 때문에 점차 상용 RTOS는 사양길로 접어들고 있다. 서버 컴퓨터용 OS인 리눅스가 어떻게 임베디드 시스템 OS 시장을 장악하게 되었는지 그 이유를 따져본다면 다음과 같은 이유때문이 아닐까 싶다. 아마, 가장 큰 이유는 리눅스가 무료이기 때문일것이다. 게다가 소스코드까지 ..
입문 과정중에 인터럽트의 개념과 간단한 사용법, 그리고 인터럽트가 발생하는 시점부터 어떤 절차를 거쳐 인터럽트가 종료되는지 설명하였다. 이번 글에서는 Atmel Studio에서 제공하는 인터럽트 코드를 사용하지 않고, 프로그래머가 직접 인터럽트 서비스 코드를 만드는 방법에 대해서 설명하도록 하겠다. 이번 글을 위한 프로젝트는 앞에서 설명한 startup 프로젝트를 그대로 사용하도록 하겠다. 즉, startup.S 코드는 다음과 같다. #include .section .vectors .global _start _start: clr r1 out AVR_STATUS_ADDR, r1 ldi r28,lo8(__stack) ldi r29,hi8(__stack) out AVR_STACK_POINTER_HI_ADDR,..
이번 글에서는 앞에서 설명한 링커 스크립트 파일과 crt0를 응용하여 startup 코드를 직접 만들어 보는 방법에 대해서 설명하도록 하겠다. 먼저 링커 스크립트 파일부터 만들어 보도록 하겠다. OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr") OUTPUT_ARCH(avr:5) ENTRY(_start) MEMORY { flash (rx) : org = 0, len = 32K sram (rw!x) : org = 0x800100, len = 2K } SECTIONS { .text : { *(.vectors) *(.text) . = ALIGN(2); *(.text.*) . = ALIGN(2); } > flash .data : { __data_start = .; *(.d..
C 프로그램에서는 반드시 main() 함수가 하나 있어야 한다. 그 이유는 C 프로그램은 main() 함수로부터 시작되기 때문이다. 즉, 프로그램의 진입점으로 규정했기 때문에 반드시 main() 함수가 있어야 한다. 만약 main() 함수가 없으면 프로그램이 어디서부터 시작되어야할지 모르기때문에 컴파일러는 main() 함수가 없다고 에러를 발생하게 된다. 그러나 이미 앞에서 링커 스크립트를 설명할 때 main() 함수가 실행되기 이전에 처리해 두어야 하는 내용들이 있다고 하였다. 즉, ROM에 있는 .data 섹션을 RAM으로 복사해주고, .bss 섹션을 0으로 초기화 해주어야 한다. 이러한 일련의 초기화 코드를 crt0라고 통칭한다. AVR 프로그램에 들어있는 crt0 코드는 어떻게 생겼는지 분석해 보..
일반적으로 컴퓨터에서 실행되는 어플리케이션 프로그램을 C나 C++같은 언어로 개발할 때에는, 만들어진 실행파일이 물리적으로 어디에 저장되어 있는지, 실행될 때 메모리의 어느 주소에 어떤 구조로 로드되는지 몰라도 프로그램을 실행하는데 전혀 문제될 것이 없다. 하지만, 임베디드 시스템 프로그램을 개발할 때에는 얘기가 조금 달라진다. 프로그래머는 CPU에 따른 메모리 맵이 어떻게 생겼는지 알아야 하고, 이러한 메모리 맵에 맞도록 실행파일을 만들어야 한다. 이번 글에서 설명하고자 하는 링커 스크립트라는 것이 바로 CPU의 메모리 맵에 따라 실행파일을 어떻게 생성할 것인지를 알려주게 된다. 이 블로그의 입문과정 첫번째 글인 "hello, world"에서 컴파일 과정을 전체적으로 간단히 설명하였는데, 마지막 단계가..
앞의 글에서 구현했던 패킷 통신 프로젝트는 수신측에서 데이터를 수신 하든지 말든지 관계없이 송신측에서 일방적으로 데이터를 내보내는 구조를 가지고 있다. 즉, broadcast 방식인 셈이다. 이렇게 만든 통신 방식은 송신측에서는 단순하게 기능을 구현할 수 있는 장점이 있는 반면, 쓸데없는 자원을 낭비하는 것이라고 생각할 수 있다. 누군가가 데이터가 필요할 때 어떤 데이터가 필요한지 요청하고, 그 요청에 따른 결과를 되돌려주면 좀더 효율적인 통신이 이루어진다. 따라서, 이번 글에서 다룰 프로젝트는 Request/Reply 방식의 패킷 통신을 구현해 보는것이다. 컴퓨터 프로그램에서 특정 데이터를 요구하는 request message를 AVR로 보내면, AVR은 요청에 맞는 데이터를 reply 해 주는 것이다..
앞의 글에서 설명한 내용까지 문제없이 진행되었으면, 이제는 수신된 데이터 중에서 필요한 부분만 추출하여 원하는 방법으로 데이터를 처리해 주면 된다. AK8975 data sheet 문서에 나오는 레지스터 설명은 다음과 같다. 이 중에서 실제 필요한 데이터는 0x03 ~ 0x08까지의 X,Y,Z 축에 대한 데이터이다. 따라서 이 부분만 화면에 출력해 주면 될것이다. 실제로는 이들 세가지 값을 가지고 어느 방향을 가르키는지 방위각을 계산하여야 하지만 그 부분은 지금 다루고 있는 통신과는 다른 주제이므로 여기에서는 생략하기로 하겠다. X,Y,Z 레지스터 값은 16비트 값으로 되어 있고, little endian으로 제공된다. 이러한 정보를 가지고 컴퓨터 프로그램에서 X,Y,Z 값을 출력해 보도록 하겠다. #-..
통신에 있어서 데이터를 수신하는 측에서는 수신한 데이터가 오류없이 제대로 들어왔는지 궁금할 때가 있다. 전송 선로의 길이가 길거나, 아니면 아예 선이 없는 무선 통신의 경우에는 더더욱 수신된 데이터에 대한 신뢰가 떨어지게 된다. 이러한 이유로 다양한 방법을 사용해서 수신된 데이터에 오류가 발생했는지 여부를 확인할 수 있게 한다. 쉽게 사용할 수 있는 방법이 체크섬(check sum)이다. 이 방법은 8비트 혹은 16비트 단위로 모든 데이터를 더해서 그 마지막 결과값을 추가로 데이터에 덧붙여 보내주면, 수신측에서 동일한 계산을 하여 check sum값이 같은지 비교해서 수신한 데이터의 오류를 판단할 수 있게 하는것이다. 그러나 단순한 덧셈 연산이기 때문에 어떤 데이터에 1이 더한 값이 전달되고, 다른 데이..
이번 글에서는 데이터를 수신하는 측에서 패킷의 시작과 끝을 알 수 있게 하는 방법에 대해서 설명하도록 하겠다. 패킷의 시작과 끝을 알수 있게 하는 방법은 무수히 많겠지만, 이미 잘 알고 있는 방법중에서 아이디어를 빌려오도록 하겠다. 그것은 다름아닌 HDLC(High-Level Data Link Control)를 이용하는 것이다. HDLC는 IBM의 SDLC라는 프로토콜을 국제적으로 표준화한 방법인데, 기본 적인 패킷의 구조는 다음과 같다. 위의 그림에 보이는것처럼 패킷의 시작과 끝에 8비트 크기의 Flag 필드가 있다. 이 플래그는 이진수로 '01111110'의 값을 가진다. 헥사값으로 표시하면 '0x7E'이다. 나머지 필드들은 다음에 설명하기로 하고, 이번글에서는 Flag필드에만 초점을 맞추어 설명하도..
이제 UART를 통해서, 문자형 데이터가 아닌 실제 데이터, 즉, 바이너리 데이터를 송수신하는 프로그램을 만들어 보겠다. 데이터 통신을 하기 위해서는 AVR의 UART 프로그램을 다음과 같이 수정해 주어야 한다. void uart_tx(uint8_t data) { while (!(uart->ucsr_a & UDRE)); uart->udr = data; } uint8_t uart_rx(void) { while (!(uart->ucsr_a & RXC)); return uart->udr; } void uart_init(void) { uint16_t baudrate = F_CPU/UART_BAUDRATE/16; uart->ucsr_b = 0x00; uart->ucsr_a = 0x00; uart->ucsr_c =..