DMX-512, STM32

Как-то понадобилось изучить протокол — DMX-512.
И научится правильно «принимать» посылки.
В интернете, о протоколе DMX — информации достаточно DMX
Простите за низкое качество фото.



DMX-512 целиком произошел от стандарта RS-485.
Основное отличие DMX от RS-485 это то, что в DMX есть «Break».
В RS-485 это воспринимается как ошибка передачи данных.
В DMX скорость передачи данных строго определена и составляет 250 кб. в сек.
Передача данных осуществляется восьми битным асинхронным протоколом с одним стартовым битом (низкий активный уровень) и двумя стоповыми битами.
Один фрейм = 11 бит. Длина пакета 44 микросекунды. Передаваемая информация находится между стартовым и стповыми битами и имеет 256 уникальных состояний от 0 до 255.
Появление низкого уровня после всего пакета (512 фреймов) данных воспринимается как начало нового пакета.
Количество каналов = фреймов, считываемых прибором с линии ДМХ зависит от количества функций, заложеных производителем в прибор.
Например одноканальный диммер (в смысле на одну нагрузку) отъедает от линии DMX только один (канал = фрейм).
При изменении цифр от 0 до 255 в ДМХ пульте управления, процессор диммера считывает эту информацию и плавно изменяет выходное напряжение на нагрузке от 0 вольт до 220 вольт.
Линия ДМХ позволяет управлять пятьсот двенадцатью одноканальными приборами.
Если в приборе указать стартовый адрес ДМХ отличный от 1, например 24, то прибор считывает число только с 24 фрейма.
На остальных каналах (фреймах) могут находится другие приборы. Главное сообщить прибору с какого фрейма он начинает принимать информацию.
http://www.x-light.ru/dmx.html
Результат работы в массиве DMX_array[512]; (Keil, Debugger).

DMX тестер посылает «20» в первом канале.






Сам код
#include "stm32f10x.h"
#include "core_cm3.h"

void USART3_init(void);
void _delay_ms (int long);
void USART3_TX_data (unsigned char);
uint8_t USART2_RX_data(void);
void TIM2_enabled(uint8_t, long int arr, long int psc);
void Printf(char *data);
void EXTI3_enabled(uint8_t on);
uint8_t USART3_RX_data(void);

uint8_t timer_full;

uint8_t start_bit;

uint8_t DMX_array[512];

unsigned char temp;

int long reset;

long int i;

int main(void) {  
	
	RCC -> APB2ENR |= RCC_APB2ENR_IOPBEN;
        RCC -> APB1ENR |= RCC_APB1ENR_USART2EN;
	RCC -> APB1ENR |= RCC_APB1ENR_USART3EN;
	RCC -> APB2ENR |= RCC_APB2ENR_IOPAEN;
	RCC -> APB2ENR |= RCC_APB2ENR_IOPDEN;
        RCC->  APB2ENR   |= RCC_APB2ENR_AFIOEN;
	RCC->  APB1ENR |= RCC_APB1ENR_TIM2EN;

        GPIOB->CRH|=GPIO_CRH_MODE10;	    GPIOB->CRH |= GPIO_CRH_CNF10;  GPIOB->CRH &=~ GPIO_CRH_CNF10_0;  // PORTB 10 - out
	GPIOB->CRH &=~ GPIO_CRH_MODE11;	    GPIOB->CRH |= GPIO_CRH_CNF11; GPIOB->CRH &=~ GPIO_CRH_CNF11_0;   // PORTA 3 - input DMX
	GPIOD->CRH |=  GPIO_CRH_MODE11;	    GPIOD->CRH &=~ GPIO_CRH_CNF11;                                   // PORTD 9 - out USART_debuger
	GPIOB->CRH &=~ GPIO_CRH_MODE9;	    GPIOB->CRH |= GPIO_CRH_CNF9;  GPIOB->CRH &=~ GPIO_CRH_CNF9_0;
	RCC -> CFGR = RCC_CFGR_SW_1; // PLL-on
	 
	USART3_init();
	
	while (1)
      {
							
	//  Основной цикл			
				
     if(!(GPIOB->IDR & GPIO_IDR_IDR11)) // Начало, start_bit, timer_full =0
	{
           if(start_bit==0) 
		 {
		    EXTI3_enabled(1); // Включить внешнее прерывание на DMX_input
		    TIM2_enabled(1, 80, 100); // На 115 Мкс
		    start_bit=1;
                 } 
         }  // Выходим, крутимся пока не сработает таймер.
	    // Пока он считает до 115 Мкс - внешних прерываний быть не должно. 
	    // Если будут - reset++, и будет больше 0, значит это не стартовый бит
        
        if(timer_full==1) // первое прерывания срабатывает сразу после включения внешнего прерывания
	   {
             if(reset<=1) // Не больше 1
		 { 
		   while(!(GPIOB->IDR & GPIO_IDR_IDR11)){}; // Подождем когда закончится низкий уровень стартового бита, 
                                                            // сюда надо добавить сторожа на зацикливание!!
	           EXTI3_enabled(0); // Внешние прерывания выключить
				 	 
		   // Определение MAB
		   timer_full=0;
		   TIM2_enabled(1, 5, 100); // На 9,500 Мкс
		   reset=0;
		   EXTI3_enabled(1); // Внешние прерывания включить
								 
		   while(timer_full==0){}; // Ждем прерывания таймера через 9,500 Мкс
									 
		   if(reset==0) // Перепадов быть не должно
		        {									 
			 temp = USART3_RX_data();
			 TIM2_enabled(0, 0, 0); // Выключить
			 reset=0;
			 EXTI3_enabled(0); // Выключить
			 // Первый бит
			 start_bit = USART3_RX_data();
                             if(start_bit==0){  // Должен быть 0
           // Старт DMX распознан правильно, попадаем к данным				 
	   // Цикл чтения 512 байтов
          
            for(i=0;i<512;i++)
		   {
                       DMX_array[i]= USART3_RX_data();
										   
                   }
                 
		 i=0;          // Все обнулить
 		 timer_full=0;
		 start_bit=0;
		 TIM2_enabled(0,0,0);
		 reset=0; 	 
               }}
							 
		 // Не стартовый бит, ошибка
		 timer_full=0;
		 start_bit=0;
		 reset=0;
		 TIM2_enabled(0,0,0);
               }
           }       
       } 
   }
}


void EXTI3_enabled(uint8_t on)
   {
   if(on==1){
     AFIO->EXTICR [0] |= AFIO_EXTICR1_EXTI3_PA; // Прерывание на PIND 3	 
     NVIC_EnableIRQ (EXTI3_IRQn);// Разрешить прерывание
     EXTI->IMR |= EXTI_IMR_MR3;  // Прерывание
     EXTI->RTSR |= EXTI_IMR_MR3; // Спаду
     EXTI->FTSR |= EXTI_IMR_MR3; // Подьему
   } else
	 {
   AFIO->EXTICR [0] &=~ AFIO_EXTICR1_EXTI3_PA; // Прерывание на PIND 3	 
   NVIC_EnableIRQ (EXTI3_IRQn); // Разрешить прерывание
   EXTI->IMR &=~ EXTI_IMR_MR3;
   EXTI->EMR &=~ EXTI_IMR_MR3;
   EXTI->RTSR &=~ EXTI_IMR_MR3;
   EXTI->FTSR &=~ EXTI_IMR_MR3;
   }
}

void EXTI3_IRQHandler (void)
{
     if (EXTI->PR & (1<<3))
      {
       EXTI->PR |= (1<<3); // Сбросить флаг EXTI3
       reset++; //  Если по выходу переменная будет > 0, значит это не стартовый бит
      }
}

 void TIM2_IRQHandler (void) // TIM2 INTERRUPT
{
      TIM2->SR &= ~TIM_SR_UIF;        
      timer_full++;	
      start_bit=0;
      TIM2_enabled(0,0,0);
}

void TIM2_enabled(uint8_t condition, long int arr, long int psc){
     
   if(condition==1)
	{
      RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
      NVIC_EnableIRQ(TIM2_IRQn);
      TIM2->ARR = arr;
      TIM2->PSC = psc;
      TIM2->CR1 |= TIM_CR1_CEN;
      TIM2->DIER |= TIM_DIER_UIE;
      TIM2->CNT = 0x00; 
    }
   if(condition==0)
       {
      RCC->APB1ENR &=~ RCC_APB1ENR_TIM2EN;
      NVIC_DisableIRQ(TIM2_IRQn);  
      TIM2->ARR = arr;
      TIM2->PSC = psc;
      TIM2->CR1 &=~ TIM_CR1_CEN;
      TIM2->DIER &=~ TIM_DIER_UIE;
      TIM2->CNT = 0x00; 			
    }		    
}

void  USART3_init(void){
      USART3->CR1 |= (USART_CR1_TE)|(USART_CR1_RE);
      USART3->BRR= 0x00000120; // 250000 kb/s  (72 000 000 + 250 000 / 2) / 250 000
      USART3->CR1 |=USART_CR1_UE; 
      USART3->CR2 |= USART_CR2_STOP_1;	
}  
     
void  USART3_TX_data(uint8_t data){
    while (!(USART3->SR & USART_SR_TXE)) {} 
      USART3->DR=data; 
    while (!(USART3->SR & USART_SR_TXE)) {}
      USART3->SR &=~ USART_SR_TXE;
}  

void  Printf(char *data){ 
    while(*data)
	{
	  while (!(USART3->SR & USART_SR_TXE)) {} 
              USART3->DR= *data++;
		 while (!(USART3->SR & USART_SR_TXE)) {}
      } 
}

uint8_t USART3_RX_data(void){
    uint8_t a;
	   	
    while (!(USART3->SR & USART_SR_RXNE)){ };
     a = USART3->DR;
     return a;
}

void _delay_ms (int long delay){
 int long a;
 for (a=0; a<delay; a++){}}


Отлаживалось на демоплате STM_Eval-Board-STM32 (STM32F103VB)

По личным вопросам, как обычно можно в vk.com/id100603673
  • +1
  • 03 июня 2013, 23:48
  • khomin

Комментарии (29)

RSS свернуть / развернуть
Как то так )
0
Как-то суховато…
0
Я тебе кат поставил, в следующий раз сам :)
0
Извиняюсь )
Каждый раз забываю
0
Немного подправил форматирование. А то все «разъехалось» )
Надеюсь ничего не потерялось
0
Основное отличие RS-485 от DMX — это то, что это стандарт физического уровня, который, в частности, используется и в DMX-512. Там много чего есть и кроме этого (см. стандарт E1.11).

В качестве интерфейса передачи используется асинхронный интерфейс (UART) с заданными параметрами: 250 кбит/сек, 2 стоповых бита, без контроля чётности. В качестве маркера начала пакета используется уже BREAK, сброс линии, причём, длиной более 2 байт: от 92 мкс.

Про DMX написано в целом (то же, что и по ссылке), а самое главное — логика приёма/передачи на STM не расписана никак. Код непонятен и чересчур усложнён и запутан.

Тем временем, логика приёма проста до безобразия:
0. Имеем указатель текущего байта и буфер:
static uint32_t ByteCounter;
static uint8_t Buffer[513]; // вместе со стартовым кодом

1. Ждём возникновения ошибки кадра (FE, Frame Error), которая идентифицирует BREAK, в этот момент сбрасываем счётчики байт и состояние приёма;

void onBreak(void)
{
    ByteCounter = 0;
}

2. Последовательно принимаем байты в буфер, контролируя его границы;
void onReceive(uint8_t Data)
{
    if(ByteCounter < 513)
    {
        Buffer[ByteCounter++] = Data;
    }
}

3. Данные в обработчик передаются как угодно: по приходу 512 байта, по таймауту после пакета или по приходу BREAK, как удобнее и логичнее для данного прибора. Можно просто читать буфер по ходу дела. Для малоканальных приборов можно принимать только нужный участок потока, как угодно.

В общем-то и всё. Зачем там внешние прерывания и таймеры, когда и для приёма, и передачи достаточно только самого модуля UART — не знаю.
+1
А, да, и самое главное — если первый байт после BREAK не нулевой, весь пакет должен быть проигнорирован. Опять же, это делается как угодно программисту. В линии могут встречаться самые разные пакеты и кроме DMX, не стоит об этом забывать.
0
Вот этого и не хватает в посте. А то протокол упомянут, фотки борды есть, а вот код шмякнут кучей.
+1
Добавил
start_bit = USART3_RX_data();
     if(start_bit==0){  // Должен быть 0
0
Он стартовый код, байт, не бит)
0
)) Переменная назвалась битом, так как предназначается для стартового бита DMX (Break).
Там Байт :)
0
Не стоит переиспользовать переменные для других целей. Всегда лучше завести новую с правильным и корректным названием)
+3
Основное отличие RS-485 от DMX — это то, что это стандарт физического уровня, который, в частности, используется и в DMX-512.
То ли я плохо смотрел, то ли, все-таки, по физическому слою отличий между ними практически нет, во всяком случае та же витуха, и те же 485 микросхемы на выходах/входах.
Вот такую штуку в свое время собрал на 6 каналов. Работает. :)
0
Так я и говорю, что он и используется в DMX =) Как и много где ещё.

Просто тут больше ограничений задано и переведено в ранг обязательных: способ заземления приборов, гальваническая изоляция линии связи от устройства, нагрузочная способность драйвера/линии, терминация и т.д.
0
Потому странно их сравнивать между собой. Это как сравнивать шасси автомобиля и автомобиль.
0
Идея обнаружения стартового бита только по Break-у, мне показалась… не совсем надежной.
Таймером же получается четко по стандарту. Сказано — Break не менее 80 мкс, я отмеряю период в 115 мкс, враг не пройдет.
Тем более на такую важную функцию, как управление светом можно пожертвовать один таймер, ибо их… 16 кажетсо )
0
Почему же нет? Если всё смонтировано нормально и источник гонит корректный DMX, то левых FE не будет, только ответственные за BREAK.

И ещё, хочу заметить, что пакеты могут быть и не 513 байт длиной =) Жёсткое вычитывание, пока не закончится 512 байт после стартового, грозит ататой и потерей одного или нескольких пакетов на первом же не-DMX или неполном DMX пакете. Игнорирование стартового кода тоже может проявиться весьма неприятно.
0
Отличный подход. А те кто «тоже сделал по стандарту», но 90 мкс — идут лесом, ведь они «не умеют читать стандарт».
+1
Так и есть :)
Рекомендовано 120-150 мкс, чтобы наверняка.
0
Рекомендовано кем и для кого? Такие рекомендации подошли бы мастеру, но никак не слейву.
0
Ага, мастер должен выдерживать достаточно долгий BREAK, чтоб он надёжно зафиксировался всеми устройствами. При этом длина его на самом деле не имеет значения, не надо фиксировать минимальную длину BREAK. В стандарте обязывают устройства при передаче выдерживать задержки BREAK, MAB и т.д. не менее заданной величины, про приёмники я ничего такого не припоминаю.
0
Для приёмников есть минимально допустимые значения тишины до BREAK, длинны самого BREAK, и высокого уровня до первого байта. При их не соблюдении приёмник должен игнорировать всю посылку.
0
Ткните пальцем. Пожалуйста.
Такого найти не могу
0
0
Неа, скорее там обязательства приёмников распознавать некоторую минимальную длину, типа: «All receivers shall recognize an 8 microsecond MARK AFTER BREAK.», — а контролировать её они не должны.
0
Не совсем понял вашу фразу :(
0
Ну да, обязательства: приёмник должен успевать обрабатывать события/определять состояния, минимальная длина которых перечислена в таблице 7. Если длина события меньше — приёмник не обязан работать корректно, проблема на передающей стороне. Если длина больше, то всё ок, всем пофиг.

То есть, если устройство может работать только тогда, когда МАВ не менее 20 мкс, а если он вдруг меньше — косячит, то данное устройство не соответствует стандарту. Вполне логично.
0
DMX-512 целиком произошел от стандарта RS-485.
только физика. Протокол там обычный RS232 с фреймами синхронизации.
0
Так RS485 и RS232 — одного поля ягоды, стандарты физического уровня для UART =D
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.