드디어 DMX512 세번째입니다.
저번 시간까지는 대략적인 특징과 하드웨어 구성(회로도)에 대해서 보았는데요 -> 2012/08/31 - [Protocol] - DMX512-2
이번 시간에는 DMX 프로토콜에 대해서 더 상세히 살펴본 후 소프트웨어 뼈대까지 나아가 봅시다
위 그림이 DMX512의 타이밍 차트입니다. 1990 이라고 씌여 있는 것을 보니 씁쓸하군요. 암튼 DMX512는 크게 보자면 3 파트로
이루어져 있습니다. Break, Start Code, 512 Frame입니다.
그 외 기타 MAB나 MTBP와 같은 것은 다음 싸이트를 참고하시면 될 것 같습니다.
<DMX 타이밍 용어 설명 : http://www.erwinrol.com/dmx512/>
1. 먼저 Break는 Frame과 Frame을 구분하기 위한 것이고 우리는 이 Break를 이용해서 Data가 시작한다는 것을 알 수
있습니다.
2. Start Code는 DMX512에서는 0x00으로 다른 값에서는 동작하면 안됩니다. RDM이라는 프로토콜은 Start Code가
0xCC을 사용하고 Text Packets은 0x17, System Information Packet은 0xCF도 쓰기 때문입니다. 따라서 기본적인
DMX512 패킷은 0 만을
받아서 동작하는 것이 좋습니다.
3. 512 Frame은 말 그대로 정보입니다. 512개의 데이터가 250000bps로 연달아 나타납니다.
음. 크게 3부분을 나눴는데 이걸 어떻게 프로그램으로 구현하면 될까요? Start Code와 512개의 data는 단순하게 8bit,
250000bps, 2 stop bit, no parity bit의 UART 통신과 동일합니다. 그렇죠? ㅎ그럼 데이터의 시작인 Break는 어떻게 구현할까요?
Tranceiver 입장에서 보면 UART 핀을 일반 I/O로 변경한 뒤, 88 us가 넘게만 OFF -> ON 시켜주면 될 것 같습니다.
타이밍 차트를 보니 Break의 허용시간은 88us ~ 1s 이니 꽤 여유가 있는 편입니다. 이 후에는 해당핀을 다시 UART로 바꾸고
Start Code 0x00을 포함한 총 513개의 데이터를 쏴주면 끝이군요!!!.
이번에는 Receiver의 입장해서 볼까요? Receiver에서 Break는 어떻게 알아챌까요? Tranceiver처럼 UART 핀을 일반 IO로
변경하고 Low인 시간을 측정해서 88us이상이면 UART로 변경하고 Data를 읽으면 될거에요. but! 더 쉬운 방법을 찾아봅시다.
바로 UART의 Error Detection 기능을 이용하는 것인데요. 대부분의 MCU에 있는 UART에는 Frame Error Detection 기능이
있습니다. 이 에러는 형식을 맞추지 않은 Data가 들어올 경우 발생합니다. Break 이외에 Frame Error가 발생할 만한 부분이
없기 때문에 우리는 이 Frame Error Detection 기능을 사용해서 Break 구간이라는 것을 간편하게 알 수 있습니다.
자! 그럼 실제로 사용하기로 한 stm32f103ze의 UART 파트 중 Error Detection 부분을 보겠습니다.
stm32f103ze의 UART 관련 register 중 Status register부분입니다. 위 표를 보시면 0번 bit부터 3번 bit까지가 Error
bit이며 각각 PE(Parity bit error), FE(Frame error), NE(Noise error), ORE(Overrun error) 입니다. 이 에러들은 대부분의
MCU에서 Detection이 가능하며 인터럽트 또한 지원하고 있습니다. 따라서 여러분들은 FE 인터럽트가 걸리면 그 때
513개의 데이터를 읽어들이면 되는 것이죠. 순서대로 다시한번 생각해보면
1. DMX data 입력.
2. FE 인터럽트 발생.
3. DMX buffer에 데이터 입력 시작.
4. Start code == 0 이면 옳바른 DMX512 데이터.
5. 나머지 512개 데이터를 주소에 맞게 활용.
6. 반복~
입니다. 간단하죠? 간단하지 않다구여? -_-+ 간단합니다. 안간단해도 간단하다고 자기최면을 거는 겁니다.
그럼 간단하게 샘플 코드를 나열하기 전에!!!! 한가지 더!! DMX512라는 프로토콜은 간단하고 강력합니다. 하지만 무식하다는
단점이 있죠. 뭐가 무식하냐구여? 생각해보시면 DMX512라는 프로토콜은 절대 쉬지 않습니다. 항상 512개의 데이터를 뿜고
있어야 하고 받을 준비를 하고 있어야 합니다. 받는 입장이라면 250kbps의 UART에 의해 인터럽트가 항상 걸리고 있다는
점입니다.
이 점 때문에 DMX512 리시버는 항상 인터럽트에 시달리는데요. 여기에 여러가지 인터럽트들을 짬뽕시키고 Flash 읽고 하다
보면 각종 버그에 시달릴 수 있습니다. 어떻게 하면 이런 문제를 회피할 수 있을까요? 저는 요세 거의 모든 MCU에 있는 DMA
라는 기능을 사용합니다. 이게 뭐냐면 Direct Memory Acces의 약어로 주변기기와 메모리의 데이터를 하드웨어적으로
연결시켜주는 기능입니다. -_-;; 참 설명 못하죠? 쉽게 말씀드리자면 자동입니다. 자동. 무이가 자동이냐? 예를 들면 UART를
통해 데이터가 들어오면 지정된 메모리의 주소를 늘려가며 자동으로 메모리에 데이터가 들어갑니다. 우리는 해당 데이터를
읽어 쓰면 그만입니다. 초기 설정 외에 소프트웨어적인 처리(인터럽트따위)가 필요하지 않기 때문에 여러분의 코드는 한결
여유로워 집니다.
따라서 항상 데이터를 읽고 있어야 하는 DMX512 라는 시스템에는 딱!인 기술이라고 생각됩니다. 코드를 살펴봅시다.
void USART2_IRQHandler(void)
{
FlagStatus flagStatus;
flagStatus = USART_GetFlagStatus(USART2,USART_IT_FE);
switch (dmxStatus){
case Idle:
if(flagStatus) //Break Error Detected!!
{
SetTimer1(1000); //타이머 1초 설정
dmxStatus = BreakError;
DMA_Cmd(DMA1_Channel6,DISABLE); //DMA 잠깐 쉬기
DMA_SetCurrDataCounter(DMA1_Channel6,bufferSize); //DMA 초기화
DMA_Cmd(DMA1_Channel6,ENABLE); //DMA 다시 시작!
USART_ITConfig(USART2,USART_IT_ERR,DISABLE); //ERR Disable
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); //RXNE Enable
}
break;
case BreakError:
dmxStatus = ReadyToReceive; //ReadyToReceive
break;
case ReadyToReceive:
dmxStatus = Idle; //Idle
USART_ITConfig(USART2,USART_IT_RXNE,DISABLE); //RXNE Disable
USART_ITConfig(USART2,USART_IT_ERR,ENABLE); //ERR Enable
break;
default:
dmxStatus = Idle;
break;
}
USART_ClearITPendingBit(USART2,USART_IT_FE|USART_IT_ORE|USART_IT_NE|USART_IT_PE); //에러 클리어
USART_ClearFlag(USART2,USART_FLAG_FE|USART_FLAG_ORE|USART_FLAG_NE); //에러 클리어
}
함수 이름이 매우 친숙하죠? 물론 아니실 분들도 있겠죠. UART 인터럽트 핸들러입니다. 인터럽트가 걸리면 호출되는 함수죠.
위를 보시면 아시겠지만 DMA 덕분에 데이터를 다루는 구문이 한곳도 없습니다. 다만 "DMA 시작합시다~"라는 구문이 있죠.
또 1초 타이머를 설정해 놓았는데요. 그 이유는 DMX의 최대 지연이 1초이기 때문입니다.
그럼 어디에 데이터가 들어가냐!! 하실 분들이 있으실텐데요. 그 DMA는 초기화 함수에 이미 초기화되어 있죠.
아래 코드 처럼요. 주변기기 주소는 USART2_DR_Address이고 메모리 주소는 Buffer 이군요. Buffer는 물론 주소를 나타냅니다.
DMA_DeInit(DMA1_Channel6); //DMA1 채널 6의
DMA_InitStructure.DMA_PeripheralBaseAddr = USART2_DR_Address; //주변기기 어드레스는 UART의 데이터 레지스터다
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Buffer; //메모리 어드레스는 Buffer다.(물론 포인터)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //방향은 주변기기 -> 메모리 이다.
DMA_InitStructure.DMA_BufferSize = bufferSize;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //주변기기 주소 증가 금지
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //메모리 주소 증가할 것.
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //1 Byte
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //1 Byte
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //노멀 모드
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA도 인터럽트 처럼 우선순위가 있습니다.
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //M2M: memory to memory disable
DMA_Init(DMA1_Channel6, &DMA_InitStructure); //설정~
Buffer는 적절하게 선언만 해 두었다면 어느 시점에라도 읽어올 수 있습니다. Buffer를 이용해서 모터를 굴리던 LED
디밍을 하던 자유롭게 할 수 있습니다. 어떠신가요?
DMX512 Receiver만 하고 Tranceiver는 왜 안하냐구요? Tranceiver는 난이도가 낮다고 생각해서 후배에게 맡겨
진행했거든요. 그래서 Tranceiver 파트는 제가 실제로 구현한 것이 한개도 없습니다. 실제로 크게 난이도가 높이 않으므로
여러분들이라면 금세 하실 수 있으실 거에요. 참고로 말씀드리자면 후배가 만든 DMX Tranceiver는 천원짜리 cortex m0로
순식간에 만들어졌습니다.
코드 전체를 올리지는 않겠습니다. 회사 것이기도 하고 제가 짰지만 지금 보니깐 코드에 의심스러운 구석이 있네요. -_-;;;
쪽팔.. 그럼 이것으로 DMX512에 대한 글은 끝마치겠습니다. 제가 글 솜씨가 부족하고 해서 이해가 안가시는 분들이 계시면
언제든 물어보셔도 됩니다. 그리고 제가 틀린 것도 분명 있을 텐데 지적해 주시면 감사하겠습니다.
언제나 힘내시구요. 아는 한 최대한 돕겠습니다. 화이팅~~~
- Dwarp -