Need english version ? it’s here
Introduction
Lorsque je modifie les fonctions d’un projecteur, j’utilise le bus DMX pour le piloter. Cet article vous présente le code en C que j’utilise pour décoder les données. Il est utilisé sur des microcontrolleur PIC de chez Microchip mais il est adaptable à d’autre hardware.
Présentation du bus
Avant de commencer, je vous encourage à lire l’article Wikipedia. Il est bien documenté et vous aidera à comprendre le protocole. Mais, voici quelques explications:
A l’arrêt, la ligne est à un niveau haut. Pour indiquer un début de trame il y a un « break ». Ce break dure entre 88µs et 100ms. Ensuite, on a le Mark After Break qui dure au moins 8µs.
La première données qui est envoyée c’est le « Start Code » comme l’indique la norme. En général le start code sera 0x00. Ce code permet de sélectionner tous les dimmers. D’autres codes existent et ils sont décrit par l’ESTA. Ensuite, on a les 511 octets correspondant aux 511 adresses (ou 512 si on compte à partir de 1). Les données sont toujours entourées d’un START et de deux STOP.
C’est un protocole relativement simple.
Comment récupérer les données?
Pour récupérer les données, on utilise l’UART du PIC. Pour ce faire, on va faire une machine à états.
- L’état IDLE : C’est l’état par défaut.
- Lorsque l’UART remonte un FRAME ERROR c’est que la ligne est restée trop longtemps à 0. On est donc dans le BREAK. Pendant cette période, on doit lire des 0x00 sur la ligne jusqu’à ce qu’un nouveau FRAME ERROR soit indiqué par l’UART. Cela correspond au MAB
- On entre dans l’état START. Tant que on détecte des FRAME ERROR on est dans le MAB du bus DMX. Dès que la data vaut 0x00, c’est le signal du START CODE. On peut donc passer dans l’état DATA.
- L’état DATA, c’est tout simplement la réception des 512 canaux. On n’enregistrera que les données qui nous intéressent. Ensuite quand le compteur sera à 513, on lève un FLAG et on attend la nouvelle trame.
Le code
Configuration de l’UART
L’UART doit être configuré en réception à la bonne vitesse de 250kbps. Personnellement, je fais un renommage des registres avec les #define.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#define DMX_ADR_RCSTA RC1STA #define DMX_REG_RCSTA 0b10000100 /* |||||||!- RX9D disabled ||||||!-- OERR Overrun Error bit disabled |||||!--- FERR Framing error enable ||||!---- ADDEN Adress detect disable |||!----- CREN Disabled <== Has to be switch on to operate ||!------ SREN Don't care in slave mode |!------- RX9 8bits !-------- SPEN enabled */ // #define DMX_RX_ENABLE RC1STAbits.CREN //used for RX switch ON/OFF #define DMX_ADR_BAUDCN BAUD1CON #define DMX_REG_BAUDCN 0b00000000 /* |||||||!- ABDEN disabled ||||||!-- WUE disabled |||||!--- Not used ||||!---- BRG16 8bit baud generator |||!----- SCKP non inverted ||!------ Not used |!------- RCIDL cleared !-------- ABDOVF disabled */ //Baud Rate = 250kbps (@FOSC=16MHz) #define DMX_ADR_SPBRGL SP1BRGL #define DMX_REG_SPBRGL 0 #define DMX_ADR_SPBRGH SP1BRGH #define DMX_REG_SPBRGH 0 #define DMX_ADR_RCREG RC1REG #define DMX_INT_RXIF RC1IF #define DMX_INT_FERR RC1STAbits.FERR #define DMX_INT_OERR RC1STAbits.OERR #define DMX_BIT_RXEN RC1IE |
Ensuite, dans la libraire DMX, il y a la fonction d’initialisation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void DMX_UART_Init(void) { /* Function : DMX_UART_Init * Argument : None * Returns : None * * Description : * Configure the UART and TIMER for the DMX reception * The UART RX has to be switched on before using it. */ //configuration UART DMX_ADR_TXSTA = DMX_REG_TXSTA; DMX_ADR_RCSTA = DMX_REG_RCSTA; DMX_ADR_BAUDCN = DMX_REG_BAUDCN; DMX_ADR_SPBRGL = DMX_REG_SPBRGL; DMX_ADR_SPBRGH = DMX_REG_SPBRGH; DMX_ADR_RCREG = 0; //TIMER configuration for timeout detection DMX_BIT_WDGIE = 0; DMX_REG_WDG = 0; DMX_INT_WDG = 0; //Initialisation of DMX status DMXState = DMX_BUS_IDLE; DMXRx.NEW_DATA = 0; } |
Quelques fonctions pour activer ou désactiver l’UART
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void DMX_UART_Start() { char dummy = DMX_ADR_RCREG; DMX_RX_Switch(1); DMX_RX_INT_Switch(1); DMX_Timeout_Switch(1); } void DMX_UART_Stop() { DMX_Timeout_Switch(0); DMX_RX_Switch(0); DMX_RX_INT_Switch(0); char dummy = DMX_ADR_RCREG; } |
Les interruptions
Pour faire fonctionner la machine à états, il nous faut des événements. Pour cela, on va utiliser les IT de l’UART.
1 2 3 4 |
if(DMX_INT_RXIF){ DMX_UART_IT(); } //DMX_INT_RXIF is UART IT Flag (RC1IF for ex) |
La fonction de réception
N’oubliez pas de mettre dans le .h de la librairie la déclaration de l’énumérateur.
1 2 3 4 5 6 |
typedef enum { DMX_BUS_IDLE, DMX_BUS_BREAK, DMX_BUS_START, DMX_BUS_DATA, }DMX_BUS_STATE; |
Et voici la fonction de réception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
/*----------------------------------------------------------------------------*/ /* VARIABLES GLOBALES */ /*----------------------------------------------------------------------------*/ DMX_BUS_STATE DMXState; unsigned int count; /***********************************************************************/ /* STATE MACHINE */ /***********************************************************************/ void DMX_UART_IT() { /* Function : DMX_UART_IT * Argument : None * Returns : None * * Description : * State machine for the DMX protocol */ //Variable declaration char dummy; //is used to get UART Rx register char i; //d ut de la fonction switch(DMXState) { case DMX_BUS_IDLE: //DMX is in IDLE state. The RX starts to get low. If a framing //error IT occurs, that means that the bus is in START if(DMX_INT_FERR) { //Framing error was detected ==> BREAK condition DMXState = DMX_BUS_BREAK; DMX_REG_WDG = 0; } dummy = DMX_ADR_RCREG; DMX_REG_WDG = 0; break; case DMX_BUS_BREAK: //DMX is in BREAK. The UART will receive continously null data //the data will be discared until a new framing error is detected if(DMX_INT_FERR) { //Framing error was detected and means MAB on the DMX bus DMXState = DMX_BUS_START; DMX_REG_WDG = 0; } dummy = DMX_ADR_RCREG; break; case DMX_BUS_START: //DMX is in MAB and continue to send FERR sequence //when RCREG will get a 0 value without FERR it will be the START //mark. if(DMX_INT_FERR) { //still in MAB dummy = DMX_ADR_RCREG; DMX_REG_WDG = 0; } else { //get value dummy = DMX_ADR_RCREG; if(dummy == 0) { //it's the START code got to DATA DMXState = DMX_BUS_DATA; count = 1; DMX_REG_WDG = 0; } else { //wrong start code. Go to IDLE, waiting new sequence DMXState = DMX_BUS_IDLE; } } break; case DMX_BUS_DATA: //The bus is sending data. waiting for the selected address dummy = DMX_ADR_RCREG; DMX_REG_WDG = 0; //check if in the address, the DMX bus load the new value. if((count >= DMXRx.DMX_ADDRESS) && (count <= (DMXRx.DMX_ADDRESS + CHANNEL_SIZE))){ i = count - DMXRx.DMX_ADDRESS; //loading value: DMXRx.DMX_DATA[i] = dummy; } count++; //When the last data is received, the New DATA flag is set and //the state machine returns to IDLE if(count == 513) { DMXRx.NEW_DATA = 1; DMXState = DMX_BUS_IDLE; } break; } } |
La structure des données
Vous avez certainement remarqué dans le code la présence de la variable DMXRx. C’est une structure qui contient l’adresse de départ, un tableau de la taille de nombre de canaux et d’un flag de nouvelle données.
1 2 3 4 5 6 7 8 9 10 11 |
//DMX Message structure typedef struct{ unsigned int DMX_ADDRESS; unsigned char DMX_DATA[CHANNEL_SIZE]; unsigned NEW_DATA : 1; }DMX_STRUCT; /*----------------------------------------------------------------------------*/ /* GLOBALES VARIABLES */ /*----------------------------------------------------------------------------*/ DMX_STRUCT DMXRx; |
Le timeout
Vous pouvez faire fonctionner le code sans timeout. Mais, s’il vous reste un compteur je vous conseille de le laisser. Le timeout est un simple compteur qui, quand il dépasse 100ms génère une IT qui reset toute la machine et l’UART.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void DMX_timeout_IT() { /* Function : DMX_timeout_IT * Argument : None * Returns : None * * Description : * When the timer overflows, the interruption reset the state machine in * IDLE mode. * IT is also cleared. */ //when the timer overflows, the timeout on the DMX is set. DMXState = DMX_BUS_IDLE; DMX_INT_WDG = 0; RC1STAbits.OERR = 0; RC1STAbits.FERR = 0; RC1REG = 0; } |
Et après?
Et après… dans votre fonction main() vous pouvez traiter les données reçues pour générer une PWM, une valeur analogique, une sortie binaire etc etc. Dans mes applications, j’utilise le flag DMWRx.NEW_DATA pour détecter une nouvelle donnée et mettre à jour la sortie.
That’s all.