Skocz do zawartości

Własny protokół dwużyłowy z jedną linią danych - Implementacja programowa.


KatzePL

Pomocna odpowiedź

Dzień dobry, to znowu ja.

Opracowuję dla własnego użytku pewne urządzenie, posiadające kilkadziesiąt elementów komunikujących się ze sobą.
Jako że nie zadowala mnie I2C, postanowiłem że zaimplementuję własny protokół przesyłania danych.


Postanowiłem że jedna ramka danych będzie przesyłana maksymalnie w ciągu 250ms.
Ramka jest złożona z czterech liczb ośmiobitowych, każda liczba jest przesyłana od najmniej znaczącego bitu.
Oznacza to śmieszną prędkość 128 bitów na sekundę.
Stan wysoki trwający przez x czasu na linii danych oznacza 1, stan wysoki trwający przez x/2 czasu oznacza 0.

Oprócz linii danych będzie również linia zajętości, która wskazuje urządzeniom odbierającym dane zakaz nadawania. 

Wszystkie urządzenia w trybie normalnym działają jako odbiorniki.
Transmisja half-duplex będzie realizowana następująco:

  1. Urządzenie przełącza się w tryb nadawania, i przełącza linię zajętości w stan wysoki.
  2. Pozostałe urządzenia odbierające sygnał zajętości, przygotowują się do transmisji, poprzez ustawienie liczników i innych elementów na żądanie.
  3. Urządzenie nadawcze wysyła ramkę danych, a urządzenia odbiorcze odbierają dane, zapisując je do bufora.
  4. Urządzenie nadawcze po zakończeniu transmisji przełącza się w tryb odbioru i przełącza linię zajętości w stan niski.

Oto obrazek ukazujący ramkę danych z jedną liczbą:
twowire.thumb.png.63033cfe54e58bec86991d90659ceca4.png

Długość przewodu transmisyjnego to maksymalnie 2 metry. Napięcie maksymalne to 3.3V  

Zasadniczym problemem jest to że nie potrafię zaprogramować AVR ani ESP32 w tryb odbioru (zliczania długości impulsu).
Tryb nadawania realizuję poprzez opóźnienie programu. _delay_ms()
Wiem że należy zastosować timery, jednakże nie mam pojęcia w jakim trybie należy go skonfigurować (potrafię jednak zapisywać do rejestrów AVR).

Potrzebuję drobnego przykładu zliczania długości impulsu dla AVR C. Ewentualnie również dla ESP32 (ESP IDF), wykluczy to potrzebę stosowania AVR przy ESP32.

Edytowano przez KatzePL
Link do komentarza
Share on other sites

Próbowałeś zrobić to na przerwaniach w trybie CHANGE używając millis()? To najprostsze rozwiązanie, jak nie da rady to idziesz w timery, chociaż prędzej bym zamienił AVR na RP2040 i po problemie 😄 (Bo masz dwa rdzenie)

Edytowano przez H1M4W4R1
  • Lubię! 2
Link do komentarza
Share on other sites

35 minut temu, KatzePL napisał:

Potrzebuję drobnego przykładu zliczania długości impulsu dla AVR C.

Rozdział 17: https://helion.pl/pobierz-przyklady/jcmikr/

Zachęcam do zakupu książki https://helion.pl/ksiazki/jezyk-c-dla-mikrokontrolerow-avr-od-podstaw-do-zaawansowanych-aplikacji-tomasz-francuz,jcmikr.htm#format/d 

Ja mam wydanie jak w linku. Jest też nowe. 

Edytowano przez pmochocki
  • Lubię! 2
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

1 godzinę temu, KatzePL napisał:

Oprócz linii danych będzie również linia zajętości, która wskazuje urządzeniom odbierającym dane zakaz nadawania. 

To akurat możesz zrobić za pomocą dodatkowego bita który będzie informował RX'a czy już może przejść w tryb TX bo ten który nadawał właśnie skończył i przeszedł w RX..oczywiście trzeba by dodać jakiś interwał że jeśli TX nie zaczął nic nadawać to RX może automatycznie nadawać (tak na wszelki wypadek jak by się coś sypnęło w transmisji)

Jak chodzi o avr to szukasz pod hasłem "ICP" jest na forbocie temat pod tytułem "Funkcja Input Capture w Arduino" poczytaj bo troche tam jest gotowców

Edytowano przez farmaceuta
Link do komentarza
Share on other sites

Udało mi się skomunikować dwie płytki Arduino za pomocą przerwań i timera, w trybie jednokierunkowym.
Jednakże problem występuje kiedy oba płytki mają piny w trybie wejścia, wtedy płytka odbierająca również "odbiera" jakieś dane i zapisuje do buforu.
Czy powinienem zastosować rezystor pull-down?

Link do komentarza
Share on other sites

Urządzenie odbierające:
 

#include <avr/io.h>
#include <util/delay.h>

uint8_t arr[4];
uint8_t arridx = 0;
bool opad = false;
uint8_t bitno = 0;
uint8_t number = 0;
bool enable = false;
void UART_init(unsigned int baud,bool RX,bool TX)
{
    // ustawienie prędkości
    unsigned int ubrr;
    ubrr=(((F_CPU / (baud * 16UL))) - 1);
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    // ustawienie bitów parzystości
    //UCSR0C |= 1<<UPM00;// 1 bit parzystości
    // ustawienie ilości bitów danych
    UCSR0C |= (1<<UCSZ01) | (1<<UCSZ00);
    // ustawienie bitów stopu
    // domyślnie jest 1
 
    if(RX)
    {
        UCSR0B |= 1<<RXEN0;
    }
    if(TX)
    {
        UCSR0B |= 1<<TXEN0;
    }
}
void UART_send( unsigned char data){

 while(!(UCSR0A & (1<<UDRE0)));
 UDR0 = data;
 
}

void UART_putstring(char* StringPtr){
 
while(*StringPtr != 0x00)
{
 UART_send(*StringPtr);
 StringPtr++;
 }
}

void readbit() {
  if (TCNT0 >= 125 && TCNT0 < 250) {
    arr[arridx] &= ~(1<<bitno);
  } else if (TCNT0 >= 250) {
    arr[arridx] |= (1<<bitno);
  }
  bitno = bitno + 1;
  if (bitno > 7) {
    bitno = 0;
    arridx = arridx + 1;
  }
  if (arridx > 3) {
    arridx = 0;
    for (int i = 0; i < 4; i++) {
      char str[10];
      sprintf(str,"D%u\n",arr[i]);
      UART_putstring(str);
    }
    memset(arr,0,sizeof(arr));
  }
}

ISR(INT0_vect)
{
    if (opad == false) {
      EICRA &= ~(1<<ISC00); //Wyzwolenie INT0 na zboczu spadającym
      TCCR0B |= (1<<CS00) | (1<<CS02); //Załączenie licznika
      PORTB |= (1<<PB7);
      opad = true;
    }else {
      TCCR0B = 0;  //Wyłączenie licznika
      readbit();
      TCNT0 = 0;  //Reset licznika
      EICRA |= (1<<ISC00); //Wyzwolenie INT0 na zboczu narastającym
      PORTB &= ~(1<<PB7);
      opad = false;
    }
}


int main() {
DDRB |= (1<<PB7);//LED jako sygnalizacja stanu wysokiego
EICRA |= (1<<ISC01) | (1<<ISC00); //Wyzwolenie INT0 na zboczu narastającym
EIMSK |= (1<<INT0); //załączenie wyzwalania przerwania INT0
//EIMSK |= (1<<INT1);
//OCR0A = 125;
//OCR0B = 250;
//TIMSK0 |= (1<<OCIE0A) | (1<<OCIE0B);

sei();

UART_init(9600,1,1);

  while(1) {
    
  }
  return 0;
}

Urządzenie nadające:

#include <avr/io.h>
#include <util/delay.h>
const uint8_t number1[4] = {100,1,0,12};
const uint8_t number2[4] = {100,1,100,12};

void OW_send(uint8_t bit) {
  if (bit == 0) {
    PORTB |= (1<<PB5);
    _delay_ms(8);
    PORTB &= ~(1<<PB5);
  }else {
    PORTB |= (1<<PB5);
    _delay_ms(16);
    PORTB &= ~(1<<PB5);
  }
  _delay_ms(20);
}

void send(uint8_t frame[]) {
  while(PINH & (1<<PH6));
  DDRH |= (1<<PH6);
  PORTH |= (1<<PH6);
  PORTB |= (1<<PB7);
  for (int i = 0; i < 4; i++) {
    uint8_t temp = frame[i];
    for (int b = 0; b < 8; b++) {
      OW_send((temp >> b) & 1);
      _delay_ms(10);
    }
  }
  PORTH &= ~(1<<PH6);
  DDRH |= (1<<PH6);
  //DDRB &= ~(1<<PB6);
  //PORTB &= ~(1<<PB5);
  //DDRB &= ~(1<<PB5);

  PORTB &= ~(1<<PB7);
}

int main() {
//PORTB |= (1<<PB6);
DDRB |= (1<<PB7);
DDRB |= (1<<PB5);
//UART_init(9600,1,1);

  while(1) {
    send(number1);
    _delay_ms(1000);
    send(number2);
    _delay_ms(2000);
  }
  return 0;
}

 

Link do komentarza
Share on other sites

(edytowany)

Udało mi się prawie skomunikować dwukierunkową transmisję z dwoma Arduino.
Kod dla nadajnika i odbiornika:

#include <avr/io.h>
#include <util/delay.h>

uint8_t arr[4];
uint8_t arridx = 0;
bool wzrost = false;
uint8_t bitno = 0;
uint8_t number = 0;
bool enable = false;
bool parsing = false;
void UART_init(unsigned int baud,bool RX,bool TX)
{
    // ustawienie prędkości
    unsigned int ubrr;
    ubrr=(((F_CPU / (baud * 16UL))) - 1);
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    // ustawienie bitów parzystości
    //UCSR0C |= 1<<UPM00;// 1 bit parzystości
    // ustawienie ilości bitów danych
    UCSR0C |= (1<<UCSZ01) | (1<<UCSZ00);
    // ustawienie bitów stopu
    // domyślnie jest 1
 
    if(RX)
    {
        UCSR0B |= 1<<RXEN0;
    }
    if(TX)
    {
        UCSR0B |= 1<<TXEN0;
    }
}
void UART_send( unsigned char data){

 while(!(UCSR0A & (1<<UDRE0)));
 UDR0 = data;
 
}

void UART_putstring(char* StringPtr){
 
while(*StringPtr != 0x00)
{
 UART_send(*StringPtr);
 StringPtr++;
 }
}

void readbit() {
  parsing = true;
  uint8_t on = 0;
  if (TCNT0 >= 125 && TCNT0 < 250) {
    arr[arridx] &= ~(1<<bitno);
  } else if (TCNT0 >= 250) {
    arr[arridx] |= (1<<bitno);
    on = 1;
  }
  //char str[10];
  //sprintf(str,"B%u=%u\n",bitno,on);
  //UART_putstring(str);
  bitno = bitno + 1;
  if (bitno >= 8) {
    bitno = 0;
    arridx = arridx + 1;
  }
  if (arridx >= 4) {
    arridx = 0;
    bitno = 0;
    for (int i = 0; i < 4; i++) {
      char str[10];
      sprintf(str,"D%u\n",arr[i]);
      UART_putstring(str);
    }
    memset(arr,0,sizeof(arr));
    parsing = false;
  }
}

ISR(INT0_vect)
{
    if (wzrost == false) {
      EICRA |= (1<<ISC00); //Wyzwolenie INT0 na zboczu narastającym
      TCCR0B |= (1<<CS00) | (1<<CS02); //Załączenie licznika
      //PORTB |= (1<<PB7);
      wzrost = true;
    }else {
      TCCR0B = 0;  //Wyłączenie licznika
      readbit();
      TCNT0 = 0;  //Reset licznika
      EICRA &= ~(1<<ISC00); //Wyzwolenie INT0 na zboczu spadającym
      //PORTB &= ~(1<<PB7);
      wzrost = false;
    }
}


ISR(INT1_vect)
{
    arridx = 0;
    bitno = 0;
    memset(arr,0,sizeof(arr));
    parsing = false;
}


void OW_send(uint8_t bit) {
  if (bit == 0) {
    PORTD &= ~(1<<PD0);
    _delay_ms(8);
    PORTD |= (1<<PD0);
  }else {
    PORTD &= ~(1<<PD0);
    _delay_ms(16);
    PORTD |= (1<<PD0);
  }
  _delay_ms(20);
}

void send(uint8_t frame[]) {
  while((PIND & (1<<PD1)) == 0 && parsing == true);
  cli();
  DDRD |= (1<<PD0);
  DDRD |= (1<<PD1);
  PORTD &= ~(1<<PD1);
  PORTB |= (1<<PB7);
  
  _delay_ms(20);
  for (int i = 0; i < 4; i++) {
    uint8_t temp = frame[i];
    for (int b = 0; b < 8; b++) {
      OW_send((temp >> b) & 1);
      _delay_ms(10);
    }
  }
  PORTD |= (1<<PD1);
  
  DDRD &= ~(1<<PD0);
  DDRD &= ~(1<<PD1);

  PORTB &= ~(1<<PB7);
  sei();
}

const uint8_t number1[4] = {120,2,1,12};
int main() {
PORTD |= (1<<PD0);
PORTD |= (1<<PD1);
DDRB |= (1<<PB7);//LED jako sygnalizacja stanu wysokiego
EICRA |= (1<<ISC01); //Wyzwolenie INT0 na zboczu spadającym
EIMSK |= (1<<INT0); //załączenie wyzwalania przerwania INT0
EICRA |= (1<<ISC11) | (1<<ISC10); //Wyzwolenie INT1 na zboczu narastającym
EIMSK |= (1<<INT1);
//OCR0A = 125;
//OCR0B = 250;
//TIMSK0 |= (1<<OCIE0A) | (1<<OCIE0B);

sei();

UART_init(9600,1,1);

  while(1) {
    send(number1);
    _delay_ms(1000);
  }
  return 0;
}

Jedyny problem jest z jednym z Arduino. Otrzymuje ono złe dane, jednakże wysyła prawidłowe.
Problem z zakłóceniami rozwiązałem poprzez ustawienie Pull-up pinów na obydwóch mikrokontrolerach. Więc zero oznacza stan wysoki.

Edycja:
Po wyłączeniu odbioru na sprawnym Arduino oraz wyłączeniu nadawania na trefnym Arduino stwierdziłem że jedno jest niesprawne (uszkodzenie kwarcu a w rezultacie desynchronizacja Timera z stanem pinów?).
Kidy odwrócę role na obydwóch Arduino wszystko działa poprawnie.

Edytowano przez KatzePL
Link do komentarza
Share on other sites

Dnia 26.12.2022 o 20:21, pmochocki napisał:

@KatzePLw czym to rozwiązanie jest lepsze od użycia UARTa z dodatkową linią zajętości? 

UART to komunikacja Point to Point, w dodatku jest krosowany, a więc wyklucza możliwość łączenia więcej niż dwóch urządzeń ze sobą.

Link do komentarza
Share on other sites

12 minut temu, KatzePL napisał:

UART to komunikacja Point to Point, w dodatku jest krosowany, a więc wyklucza możliwość łączenia więcej niż dwóch urządzeń ze sobą.

No ale jak podłączysz więcej urządzeń to co się stanie? No ale jak dodasz dodatkową linie to wiesz kiedy nadajesz a kiedy odbierasz. Więc jak zewrzesz TX i RX to chyba jedyne co musisz zrobić to zignorować odbiór jak nadajesz. Może mi coś umyka... Jeśli tak to przepraszam. 

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.