// one-wire UART MPCM demonstrace - kód pro "slave"
#define F_CPU 3333333 // tovární frekvence
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>



void init_onewire_uart(void); 
uint8_t uart_frame_tx(uint8_t address, uint16_t data);

// proměnné pro předání přijaté zprávy z rutiny přerušení
volatile uint16_t rx_data=0; // obsah zprávy
volatile uint8_t rx_flag=0; // informace že přišla zpráva

#define MY_ADDRESS 2 // moje adresa na které poslouchám

int main(void){
 //PORTA.PIN6CTRL |= PORT_PULLUPEN_bm;
 init_onewire_uart(); // inicializace "one-wire" módu
 sei(); // globální povolení přerušení
 set_sleep_mode(SLEEP_MODE_IDLE); // když není co dělat, šetříme šťávu v "idle" :)
 sleep_enable(); // povolíme uspávání

 while (1){ 
  if(rx_flag){ // pokud přišla zpráva
   rx_flag=0; // vymaž si vlajku
   _delay_us(200); // chvíli počkej abychom zprávy oddělili na osciloskopu
   uart_frame_tx(1,rx_data+1); // ppřijatá data inkrementuj a pošli odpověď
  }
  sleep_cpu(); // není co dělat, spíme
 }
}

// příjem po UARTu
ISR(USART1_RXC_vect){
 static uint8_t phase=0; // proměnná pro "stavový automat"
 static uint16_t tmp_rxdata; // tady postupně ukládáme přijatá data
 uint8_t tmpl; // dočasné proměnné pro zpracování přijatých dat
 uint8_t tmph = USART1.RXDATAH; // přečti nejprve "horní" přijatý byte

 tmpl = USART1.RXDATAL; // a pak dolní přijatý byte
 if((tmph & USART_DATA8_bm) && !(tmph & USART_FERR_bm)){ // pokud je nastaven 8.bit (je to adresa) a pokud nemáme frame error
  if(tmpl == MY_ADDRESS){ // zkontroluj jestli je to naše adresa
   USART1.CTRLB &=~USART_MPCM_bm; // pokud ano deaktivuj MPC mód (tedy přijímej všechno)
   phase=1; // přejdi do další fáze příjmu
  }
  else{ // pokud nastal problém přenosu nebo nepřišla naše adresa, aktivuj MPC mód (všecko co není adresa ignoruj) a čekej na další zprávu
   USART1.CTRLB |= USART_MPCM_bm; // poslouchej jen adresy 
  }
 }else if(phase==1){ // pokud přišel byte a minule přišla naše adresa, přijímáme MSB datové části zprávy
  tmp_rxdata = (((uint16_t)tmpl) << 8); // uložíme si MSB do lokální proměnné
  phase++; // přejdi do další fáze
 }else if(phase==2){ // pokud přišel druhý byte datové části zprávy
  tmp_rxdata |= tmpl; // je to LSB a uložíme si ho
  USART1.CTRLB |= USART_MPCM_bm; // dál už nechci přijímat (další datové byty ignoruj, přijmi jen adresu)
  if(!rx_flag){ // pokud jsou minulá data zpracovaná
   rx_data = tmp_rxdata; // uložíme si nově přijatá data 
   rx_flag=1; // dáváme vědět zbytku programu že jsme přijali kompletní nová data
  }
 }
}
 

uint8_t uart_frame_tx(uint8_t address, uint16_t data){
 uint8_t err=0; // příznak chyby
 uint8_t tmp; // pracovní proměnná
 USART1.CTRLA &=~USART_RXCIE_bm; // deaktivovat přerušení od Rx (budeme přijímat vlastní data)
 
 // posíláme adresu
 USART1.TXDATAH = 0b1; // nastavíme 8.bit - posíláme adresu
 USART1.TXDATAL = address;
 while(!(USART1.STATUS & USART_TXCIF_bm)); // počkat na dokončení vysílání
 USART1.STATUS = USART_TXCIF_bm; // vyčistit vlajku 
 if(USART1.STATUS & USART_RXCIF_bm){ // pokud jsme přijali svůj vlastní byte (což bychom měli)
  tmp = USART1.RXDATAH; // přečteme si MSB a vlajky
  if(tmp & USART_FERR_bm || !(tmp & USART_DATA8_bm)){err=0xff;} // Frame error => kolize na sběrnici, Není nataven 8.bit ? => kolize na sběrnici  
  if(USART1.RXDATAL != address){err=0xff;} // neodpovídají přijatá data odvysílaným ? => kolize na sběrnici
 }else{err=0xff;} // pokud jsme vůbec nepřijali svůj vlastní byte=> (nemožná) kolize na sběrnici 
 if(err){// pokud nastal Error, ukončíme vysílání
  USART1.CTRLA |= USART_RXCIE_bm; // zpět povolit přerušení od Rx
  return err; 
 }
 
 // vypneme MPC mód - chceme poslouchat svá vlastní data - abychom případně zachytili kolizi na sběrnici
 USART1.CTRLB &=~USART_MPCM_bm; // poslouchej všechno
 // posíláme "payload" MSB
 USART1.TXDATAH = 0b0; 
 USART1.TXDATAL = (uint8_t)(data>>8);
 while(!(USART1.STATUS & USART_TXCIF_bm)); // počkat na dokončení vysílání
 USART1.STATUS = USART_TXCIF_bm; // vyčistit vlajku
 if(USART1.STATUS & USART_RXCIF_bm){ // pokud jsme přijali svůj vlastní byte (což bychom měli)
  tmp = USART1.RXDATAH;
  if(tmp & USART_FERR_bm || (tmp & USART_DATA8_bm)){err=0xff;} // Frame error => kolize na sběrnici, Není vynulován 8.bit ? => kolize na sběrnici
  if(USART1.RXDATAL != (uint8_t)(data>>8)){err=0xff;} // neodpovídají přijatá data osvysílaným ? => kolize na sběrnici
  }else{err=0xff;} // pokud jsme vůbec nepřijali svůj vlastní byte=> (nemožná) kolize na sběrnici 
 if(err){// pokud nastal Error, ukončíme vysílání
  USART1.CTRLA |= USART_RXCIE_bm; // zpět povolit přerušení od Rx
  return err;
 }
 
 // posíláme "payload" LSB
 USART1.TXDATAH = 0b0;
 USART1.TXDATAL = (uint8_t)data;
 while(!(USART1.STATUS & USART_TXCIF_bm)); // počkat na dokončení vysílání
 USART1.STATUS = USART_TXCIF_bm; // vyčistit vlajku
 if(USART1.STATUS & USART_RXCIF_bm){ // pokud jsme přijali svůj vlastní byte (což bychom měli)
  tmp = USART1.RXDATAH;
  if(tmp & USART_FERR_bm || (tmp & USART_DATA8_bm)){err=0xff;} // Frame error => kolize na sběrnici, Není vynulován 8.bit ? => kolize na sběrnici
  if(USART1.RXDATAL != (uint8_t)data){err=0xff;} // neodpovídají přijatá data osvysílaným ? => kolize na sběrnici
  }
 else{err=0xff;} // pokud jsme vůbec nepřijali svůj vlastní byte=> (nemožná) kolize na sběrnici   
  
 
 USART1.CTRLB |= USART_MPCM_bm; // už neposlouchej všechno, poslouchej jen adresy
 USART1.CTRLA |= USART_RXCIE_bm; // zpět povolit přerušení od Rx
 return err;
}

void init_onewire_uart(void){
 PORTC_PIN0CTRL |= PORT_PULLUPEN_bm; // aktivovat pullup na Tx(Rx) pinu
 USART1.BAUD = 0x0074;  // baudrate (115200 při 3.33M)
 USART1.CTRLA = USART_LBME_bm | USART_RXCIE_bm; // režim "loopback" (propojí Tx a Rx a uvolní Rx pin), povolení přerušení od RX
 USART1.CTRLC = USART_CHSIZE_9BITH_gc; // 9bit transfer
 USART1.DBGCTRL = USART_DBGRUN_bm; // UART má běžet i během debugu (kdy je MCU zastavené)
 USART1.CTRLB = USART_RXEN_bm | USART_TXEN_bm | USART_ODME_bm; // spustit Rx+Tx a zvolit "open drain" mód
}

