Ten post chciałbym poświęcić na opisanie sposobu wykonania komunikacji w standardzie transmisji szeregowej RS485.
Opis
Dane będą wysyłane z mikrokontrolera przy użyciu trzech linii. Dwie z nich czyli RX oraz TX wysyłają oraz odbierają dane. Natomiast trzecia będzie odpowiadała za sterowanie.
W standardzie 485 dane muszą być wysyłane naprzemiennie w trybie half-duplex. Dużym plusem takiego rodzaju transmisji jest odporność na zakłócenia przez zastosowanie skrętki dwu przewodowej, daleki zasięg oraz możliwość podłączenia wielu urządzeń na linii.
Jeśli chodzi o hardware to można wykorzystać jakieś gotowe moduły, bądź wykonać własny konwerter np. poprzez wykorzystanie układu ST3485EB, który jest konwerterem na RS485/RS422. Pozwala on zarówno na wysyłanie jak i odbieranie danych w trybie half-duplex. Aby dane odebrać poprawnie na komputerze przez serial port to należałoby użyć konwerter z RS485 na USB.
Kod Programu:
Wybrałem domyślne porty do obsługi UARTC:
- #define RE_DE_PIN PIN4_bm
- #define RE_DE_PORT PORTC
- #define TX_PIN PIN3_bm
- #define TX_PORT PORTC
- #define RX_PIN PIN2_bm
- #define RX_PORT PORTC
Włączenie pinów wygląda następująco:
- #define setPinInput(port, pin) port.DIRCLR |= pin
- #define setPinOutput(port, pin) port.DIRSET |= pin
- #define setPinHigh(port, pin) port.OUTSET = pin
- #define setPinLow(port, pin) port.OUTCLR = pin
- setPinInput(RX_PORT, RX_PIN);
- setPinOutput(TX_PORT, TX_PIN);
- setPinHigh(TX_PORT, TX_PIN);
- setPinOutput(RE_DE_PORT, RE_DE_PIN);
Linia RE_DE jest odpowiedzialna za inicjalizacje trybu pracy, ustawienie albo dla nadawania bądź przygotowanie do odbioru danych. W programie ta linia będzie ustawiana w stan wysoki gdy dane będą miały być wysyłane. Domyślnie będzie w stanie niskim, czyli w trybie oczekiwania na odbiór danych.
- /*
- * XTAL = 11,0592 MHz = 11059200
- * BSEL: ((11059200)/((2^0)*16*9600)) - 1 = 71
- * Calculation: Fbaud = ((11059200)/((2^0) * 16 * (71+1))) = 9600
- */
Włączenie układu natomiast wygląda w ten sposób:
- cli();
- USARTC0_BAUDCTRLB = 0;
- USARTC0_BAUDCTRLA = 71;
- USARTC0_CTRLC = USART_CHSIZE_8BIT_gc;
- USARTC0_CTRLA = USART_RXCINTLVL_HI_gc;
- USARTC0_CTRLB = USART_RXEN_bm | USART_TXEN_bm;
- sei();
Na początku wyłączane są wszystkie przerwania, następuje wgranie ustawień po czym przerwania zostają ponownie włączone. Ustawione są ,idąc od początku, prędkości transmisji, format ramki, włączenie przerwać od RX oraz włączenie transmisji.
W celu przesłania pojedynczego znaku należy wykonać taką funkcję:
- static void sendChar(char c)
- {
- while(!(USARTC0.STATUS & USART_DREIF_bm)); //Wait until DATA buffer is empty
- USARTC0_DATA = c;
- }
Właściwie aby pojedynczy znak mógł być wysłany należy jeszcze wykonać odpowiednie ustawienie portów. Ja tą czynność wykonuje podczas przesyłania ciągu wiadomości. Natomiast dla pojedyncze znaku wyglądało by to tak:
- #define delayms(time) _delay_ms(time)
- #define delayus(time) _delay_us(time)
- static void sendChar2(char c)
- {
- setPinHigh(RE_DE_PORT, RE_DE_PIN);
- setPinHigh(TX_PORT, TX_PIN);
- delayms(10);
- while(!(USARTC0.STATUS & USART_DREIF_bm)); //Wait until DATA buffer is empty
- USARTC0_DATA = c;
- delayms(10);
- setPinLow(RE_DE_PORT, RE_DE_PIN);
- }
Funkcja opoźniająca jest tutaj niezwykle istotna, ponieważ bez niej porty mogą nie zostać poprawnie ustawione lub zresetowane aby cała wiadomość spokojnie została wysłana do drugiego układu.
Teraz funkcja wysyłająca ciąg znaków w oparciu o funkcję sendChar:
- void sendString(char *text)
- {
- setPinHigh(RE_DE_PORT, RE_DE_PIN);
- setPinHigh(TX_PORT, TX_PIN);
- delayms(10);
- while(*text)
- {
- sendChar(*text++);
- }
- delayms(10);
- setPinLow(RE_DE_PORT, RE_DE_PIN);
- }
Wysłanie ciągu znaków z wybranym znakiem końcowym. Można to zrobić także bezpośrednio wpisując dane do funkcji wcześniejszej.
- void sendStringWithEnd(char *text, uartEndSign_Typedef last_sign)
- {
- setPinHigh(RE_DE_PORT, RE_DE_PIN);
- setPinHigh(TX_PORT, TX_PIN);
- delayms(10);
- while(*text)
- {
- sendChar(*text++);
- }
- if(last_sign==CR_Sign){
- sendChar('\r');
- }
- else if(last_sign==LF_Sign){
- sendChar('\n');
- }
- else if(last_sign==CR_LF_Sign){
- sendChar('\r');
- sendChar('\n');
- }
- else if(last_sign==LF_CR_Sign){
- sendChar('\n');
- sendChar('\r');
- }
- else if(last_sign==NONE_Sign){}
- delayms(10);
- setPinLow(RE_DE_PORT, RE_DE_PIN);
- }
Obsługa przerwania RX może wyglądać następująco:
- #define MAX_VALUE_UART 200
- volatile uint8_t buffer_data[MAX_VALUE_UART];
- char usart_getc(void)
- {
- while (!(USARTC0_STATUS & USART_RXCIF_bm));
- return USARTC0.DATA;
- }
- //----------------------------------------------------
- ISR(USARTC0_RXC_vect)
- {
- uint8_t dat;
- uint8_t counter;
- for(counter=0;counter<MAX_VALUE_UART;counter++)
- {
- buffer_data[counter] = (uint8_t)usart_getc();
- if(buffer_data[counter] == '\n') { break; }
- }
- }
Odebrane dane zostają zapisane w zmiennej buffer_data. Odbiór danych będzie następował do momentu przepełnienia bufora bądź gdy na końcu zostanie odebrany znak nowej linii (LF - '\n'). Dane mogą być bezpośrednio przekazywane na ekran, bądź obrabiane w osobnej funkcji do której przekazany zostanie odebrany buffor.
Bibliografia:
[1] http://elektronikab2b.pl/technika/3404-czym-jest-rs-485-#.WTTxz-vyi70
[2] https://pl.wikipedia.org/wiki/EIA-485