softwarový sér. port u AT89C51ED2

Ahoj,poradil by mě někdo jak si udělat na 51 softwarový sér port? Už jsem něco našel na atmel.com/dyn/resources/prod … 2ec329.pdf ale moc tomu nerozumím a nejradši bych to chtěl napsat v Céčku.

Eat this!

best-microcontroller-projects.com/pic-serial-port.html

koukám sice že je to na pic, ale ta teorie je na 51 určitě stejná. Koukal jsem na ten program:

void _Soft_USART_Write(unsigned short chr) {                     
unsigned short mask=1,i;                                         
unsigned int data;                                               
                                                                 
   data = chr << 1;                                              
   data &= ~0x0001; // START bit (=0).                           
                                                                 
   // 10 output bits                                             
   for (i=0;i<9;i++) { // lsb 1st  8 bits                        
      if (mask & data) {                                         
         asm {CLRW}                                              
         SU_set; // output low for logic 1                       
      } else {                                                   
         SU_res;// output high for logic 0                       
         asm {CLRW}                                              
      }                                                          
      mask <<=1;                                                 
      delay_us(395); // 2400 simulate to calibrate the loop.     
   }

   SU_set; // stop bit
   delay_us(417); // 2400
}

a myslím si že ho celkem chápu. Prostě vezmu data a masku a navzájem to 8x rotuju a pak posílám 1 nebo 0,ale není mě jasné co dělá to asm{CLRW}
a delay_us(395);
myslím že jedno bude ještě něco s tím odesláním na port a druhé nastavení rychlosti přenosu???

Omg. Kdyby to nemelo byt stejny proc bych Ti to posilal? CLRW je instrukce nulovani watchdogu myslim. A Delay Ti asi dava zpozdeni nebo delku jednoho bitu v seriovem prenosu. Jeho delkou nastavis baudrate.

Ten reset watchdogu bych z funkce vyhodil, nulovat po kazdym odeslanym bitu je docela neefektivni, resetoval bych ho nekde pred volanim funkce…

já to naprogramoval takhle, ale zatím netuším jestli to vůbec bude fungovat. Potřeboval jsem komunikaci oběma směry.

/* ========================================================================== */
/*                 ser data cteni                                             */
/* ========================================================================== */
ser_data_cteni:
maska2=0x01;      // 0000 0001
P3|=0x00;
if(P3==0x40)
        {
        cteni=1;
}
if(cteni==1)
      {
      cekej(1,5bitu)      //cekej 1,5 bitu
      for(ii=0;ii<8;ii++)
            {
            if(P3==0x40)
                  {
                  data_in |= maska2;
                  cekej(1bit);   // 1bit
                  }
            else
                {
                 data_in &=0xff;    
                }
              }
              maska2 << 1;
      cteni=0;
      cekej(0,5bitu)
}
/* ========================================================================== */
/*                 ser data zapis                                             */
/* ========================================================================== */
ser_data_zapis:                                                          
int data_out;
int maska=0x80;
int ii;                                                                                           
P3&=0xDF; // START bit =0                         
cekej(1bit);        //cekej 1bit                                                                                                            
for (ii=0;ii<9;ii++) 
                {                      
                if (maska & data_out) 
                                {                                                                                    
                                P3|=0x20; // vystup log 1                       
                                } 
                else 
                    {                                                   
                    P3&=0xDF;   // vystup log 0                                                                  
                    }                                                          
                data_out = data_out << 1;                                                 
                cekej(1bit); //cekej 1bit
                }
P3|=0x20; // vystup log 1 -> stop bit = 1
cekej(1bit); //cekej 1bit

s tím že fce cekej(cislo) je dána přesně přerušením od čítače/časovače a zbejvá nastavit na nejmenší krok a pak přesně dopočítat časy aby to komunikovalo 38400 bit/s.
P.S.: to nahoře anonimní je můj dotaz :wink:

Zdravim,

nechcem moc cerit tiche vody typu “delay_us”, ale prave taketo riesenia
dehonestuju vybavu hw periferii jednocipakov.

Myslim, ze vacsinou sa od procesora ocakava, ze ak komunikuje, to automaticky neznamena, ze nema robit nic ineho (AD prevody, filtracia signalu, zobrazovanie na LCD, interakcia s tlacitkami, vypocty regulacnych sluciek, atd, atd). Obzvlast, ak caka na prijem nejakej spravy.

SW uart moze byt napisany tak ako bolo uvedene vyssie, ale ak je to mozne, za systemovejsie riesenie povazujem vyuzit HW vybavu procesora.
Konkretne na x51 (ale aj na ine typy) je dobre pouzit interupt od casovaca.
Predpokladam, ze MCU najprv caka na nejaku spravu od PC a potom na nu odpoveda, komunikacia typu 8N1.

Prijem:
Vstupny pin casovaca T vyuzijem na RX. 8b casovac nastavim do modu pocitania vstupnych impulzov a do pocitadla mu vlozim hodnotu 0xff. Prichodom dobeznej hrany na pine casovac pretecie a vyhodi prerusenie. Nie je teda potrebne obetovat na RX nejaky iny pin s funkciou INT.
V tomto preruseni zmenim mod citaca/casovaca na casovac a prednastavim cas na 1/2 Bd rychlosti. Samozrejme s prerusenim.
Po druhom preruseni otestujem, ci je vstupny signal v log.0. Ak ano, udalost prehlasim za platny start bit. Do predvolby nastavim cas 1 Bd rychlosti.
Postupne pride 8 preruseni. V kazdom z nich otestujem hodnotu pinu procesora (vstup pre T) a ulozim do nejakeho bajtu ako platne bity.
Pockam na deviate prerusenie. Ak je hodnota pinu 1, jedna sa o platny stop bit, prijaty bajt mozem ulozit do komunikacneho bufera - akoby mi prislo prerusenie od UARTu a celu cinnost opakujem.

Vysielanie:
Ten isty citac / casovac, ktory som pouzival pri prijme vyuzijem na odpocet vyslania bajtu. Predpokladam komunikaciu typu poloduplex.
Ak komunikujem cez budic RS485, prepnem smer na vysielanie (iny pin ako RX a TX.)
Pin procesora (iny ako som pouzil na RX, ale v zasade lubovolny) nastavim na log.0 (start bit), do predvolby vlozim cas 1 Bd rychlosti. Postupne pride 8 preruseni. V kazdom z nich nastavim na vystup prislusny bit z vysielaneho bajtu. Po deviatom preruseni nastavim pin na log.1 - stop bit. Ak komunikujem cez RS 232, cinnost ukoncim/pokracujem v odvysielani dalsieho bajtu, ak cez RS485, pockam na desiate prerusenie a otocim smer budica RS485 na prijem, ak uz nie je potrebne odvysielat dalsi bajt.

Ak komunikujem rychlostou 38400/9600Bd, potom cas medzi jednotlivymi bitmy je cca 26/104us.Osobne robim s ATmega (14.7456MHz) a tie za tu dobu urobia 383/1533 instrukcii. A to je naozaj dost dlha doba, aby sa procesor len tak flakal v nejakej zapraskanej casovej slucke. Okrem toho C prekladac program optimalizuje a bezduche casove slucky (ak nie su asm volatile) optimalizuje, pripadne cele vyhadzuje. s tym maju hlavne zaciatocnici psychicke problemy typu - ved som to tam napisal, tak preco to ten “blby” prekladac vyhadzuje a upravuje. No preto, lebo to nie je ASM a od prekladaca primarne chceme, aby nase programatorske “blaboly” optimalizoval do hustejsieho a rychlejsieho kodu. A pri rozne zapnutych optimalizaciach budu prirodzene casove/velkostne vysledky rozne.
Preto celkovo v C doporucujem sa funkciam typu delay_ms/us vyhybat (aj ked principialne sa pouzit daju, akurat brzdia cely procesor) a naucit sa vyuzivat ine prostriedky, ktore su na casove funkcie v jednocipoch urcene.

Horeuvedeny postup je uplne nezavisly, ci programujem v ASM, alebo v C.

Takže abych vše upřesnil. Zapojení vypadá takto:

mám tam připojený krystal 12MHz. Ale popravdě moc nevím jak to zapracovat do toho čítače časovače.Něco jsem napsal,ale jestli je to ok to nevím,zkusím to vyzkoušet.[code]
/* ========================================================================== /
/
ser data cteni /
/
========================================================================== /
void cteni ()
{
mikros=0;
while(mikros==39){ //dokud neubehne 1,5 bitu vrat (39 mikrosekund)
//nop(); // nic nedelej
}
for(ii=0;ii<8;ii++)
{
mikros=0;
if(P3==0x40)
{
data_in |= maska2;
}
else
{
data_in &=0xff;
}
while(mikros==26){ //dokud neubehne 1 bit vrat (26 mikrosekund)
//nop();
}
maska2 << 1;
}
while(mikros==13){ //dokud neubehne 0,5 bitu vrat (13 mikrosekund)
//nop(); // nic nedelej
}
}
/
========================================================================== /
/
ser data zapis /
/
========================================================================== /
void zapis ()
{
maska=0x80;
P3&=0xDF; // START bit =0
mikros=0;
while(mikros==26){ //dokud neubehne 1 bit vrat (26 mikrosekund)
//nop(); // nic nedelej
}
for (ii=0;ii<9;ii++)
{
if (maska & data_out)
{
P3|=0x20; // vystup log 1
}
else
{
P3&=0xDF; // vystup log 0
}
data_out = data_out << 1;
while(mikros==26){ //dokud neubehne 1 bit vrat (26 mikrosekund)
//nop(); // nic nedelej
}
mikros=0;
}
P3|=0x20; // vystup log 1 → stop bit = 1
while(mikros==26){ //dokud neubehne 1 bit vrat (26 mikrosekund)
//nop(); // nic nedelej
}
}
/
========================================================================== /
/
funkce preruseni /
/
========================================================================== */
void timer0(void) interrupt 1
{
static int i;
if(i<1000){ //casova smycka 1ms
i++;
}
else
{
ms++;
i=0;
}
mikros++; // pocita mikro sekundy >> jeden krok citace/casovace
ser_data_cteni:
maska2=0x01; // 0000 0001
P3|=0x00;
if(P3==0x40)
{
cteni();
}
ser_data_zapis:
if(data_out!=0){
zapis();
}
return;
}

/* ========================================================================== /
/
HLAVNI FUNKCE /
/
========================================================================== /
void main()
{
/
========================================================================== /
/
nastaveni citace /
/
========================================================================== */

TMOD = 0x02;
TH0 = 256-1;
TR0 = 1;
ET0 = 1;
EA = 1;

while(1)
{

}[/code]

:arrow_right: administrator: příspěvek byl upraven
Externí soubor byl uploadnut na server.

Asi ani neskusaj :slight_smile:
Pod prerusenim mas zase nejake casove slucky, ktore Ti este moze prekladac zoptimalizovat a pripadne aj z programu vyhodit (dovody som pisal vyssie).

Okrem toho, ako vidim na RX do MCU (teda TX zo Zigbee modulu) mas privedeny /wr a nie vstup od citaca casovaca.
Musis si byt 110% isty, co oznacenie TX a RX na Zigbee znamena. Normalne by to malo znamenat, ze do RX na module ide signal TX z MCU a signal z TX na module ma ist do RX na MCU.

Nerobim s x51, tak ti neviem napisat program s presnym oznacenim registorom v Tvojom MCU, ale princip je rovnaky.

  1. TX z modulu pripoj napr na vstup T0.
    potom si prepis nasledovny algoritmus. Je to pre ATmega. Netestujem extra start bit, preto je tam na zaciatku 1.5 nasobok Bd

SIGNAL (COM5_SIG_TEST_START_BITU) - funkcia, ktora sa zavola po preteceni casovaca, ked je nastaveny v mode pocitadla vst impulzov a ma nastavenu predvolbu na 0xff

toto su vsetko makra, ktore si musis ty napisat podla svojho, ich vyznam je jasny z nazvov
COM5_ZAKAZ_PRERUSENIA_OVERFLOW_TIMER;
COM5_SET_TIMER_RX_TX_BIT; // zapni prijem od compare match
COM5_POVOLENIE_PRERUSENIA_COMAPRE_MATCH;

SIGNAL (COM5_SIG_TIMER_RX_TX_BIT) - funkcia, ktora sa zavola, ked pride prerusenie od compare match


// Definicia LOCALnych funkcii

// identifikacia start bitu, prisla dobezna hrana signalu
SIGNAL (COM5_SIG_TEST_START_BITU)
{
	// nastav hodnotu compare match na 1.5 nasobok bitovej rychlosti a prerusenie od compare match
	SET(sw_status,ROZPRACOVANY_PRIJEM);
	COM5_PRAC_POCITADLO = 0x00;// prednastavenie hodnoty TCNT tak, aby pri najblizsiemu compare match doslo k preruseniu
	COM5_COMPAR_REGISTER = sw_predvolba_pre_1_5nasobok_bitu;
	sw_udr_prac = 0;
	sw_pocitadlo_bitov = 0;
	COM5_ZAKAZ_PRERUSENIA_OVERFLOW_TIMER;
	COM5_SET_TIMER_RX_TX_BIT;  // zapni prijem od compare match
	COM5_POVOLENIE_PRERUSENIA_COMAPRE_MATCH;

	return;
}


SIGNAL (COM5_SIG_TIMER_RX_TX_BIT)
{
	uint8_t prac;
	
	COM5_COMPAR_REGISTER = sw_predvolba_pre_1nasobok_bitu;


	sw_pocitadlo_bitov++;

	// bezi prijem
	if TST(sw_status,ROZPRACOVANY_PRIJEM) {

		// test stop bitu
		if (sw_pocitadlo_bitov > 8) {
			if (GET_RX5) {
				sw_udr = sw_udr_prac;

// ****************** zrusenie prerusenia po prijati bajtu ****************
				RES(sw_status,ROZPRACOVANY_PRIJEM);
				COM5_ZAKAZ_PRERUSENIA_COMAPRE_MATCH;
				COM5_PRAC_POCITADLO = COM5_MAX_HODNOTA_PRAC_POCITADLA;// prednastavenie hodnoty TCNT tak, aby pri najblizsej dobeznej hrane prislo k preruseniu		
				COM5_SET_TEST_START_BITU;  // zapni prijem
				COM5_RES_INT_FLAG;
				COM5_POVOLENIE_PRERUSENIA_OVERFLOW_TIMER;
				POVOLENIE_PRERUSENIA;
// ************************************************************************

				// inicializacia funkcie PAUSE, lebo bolo nieco prijate
				timer_com5.timer = COM5_TIMEOUT_RX;
				prg_tb_timer(TB_5ms, TIMER_INIT, (TIMER_STRUCT * ) &timer_com5);

#if (COM5_ECHO == TRUE)

				sprava_prijata = TRUE;	
				mem[cnt_mem_max] = sw_udr;
				if (cnt_mem_max < (MAX_MEM - 1)) {
					cnt_mem_max++;
				}

				RES(sw_status,ROZPRACOVANY_PRIJEM);
				RES(sw_status,ROZPRACOVANE_VYSIELANIE);
				COM5_ZAKAZ_PRERUSENIA_COMAPRE_MATCH;
				COM5_PRAC_POCITADLO = COM5_MAX_HODNOTA_PRAC_POCITADLA;// prednastavenie hodnoty TCNT tak, aby pri najblizsej dobeznej hrane prislo k preruseniu		
				COM5_SET_TEST_START_BITU;  // zapni prijem
				COM5_POVOLENIE_PRERUSENIA_OVERFLOW_TIMER;
				return;
				
				// priprav nacitanie dalsieho bajtu
#else
				com5_struct.pracovny_bajt = sw_udr;
				prac = prg_prijaty_bajt((COM_STRUCT *)&com5_struct);

				if (prac == COM_VYPNI_PRIJEM) {

				// ak bol pri novej sprave este stale zablokovany box, iniciuj ho
					SET(com5_struct.status,SPRACUJ_PROTOKOL);
					RES(sw_status,ROZPRACOVANY_PRIJEM);
					COM5_ZAKAZ_PRERUSENIA_COMAPRE_MATCH;
					return;
				}
				RES(sw_status,ROZPRACOVANY_PRIJEM);
				RES(sw_status,ROZPRACOVANE_VYSIELANIE);
				COM5_ZAKAZ_PRERUSENIA_COMAPRE_MATCH;
				COM5_PRAC_POCITADLO = COM5_MAX_HODNOTA_PRAC_POCITADLA;// prednastavenie hodnoty TCNT tak, aby pri najblizsej dobeznej hrane prislo k preruseniu		
				COM5_SET_TEST_START_BITU;  // zapni prijem
				COM5_POVOLENIE_PRERUSENIA_OVERFLOW_TIMER;
				return;
#endif		
			}
			// nebol stop bit
			else prg_com5(COM_ZAPNI_PRIJEM, 0);
		}
		else {
			sw_udr_prac >>= 1; // rotacia do prava
			if (GET_RX5) {
				sw_udr_prac |= 0x80;
			}
			// skratenie casu testu stop bitu
			if (sw_pocitadlo_bitov == 8) COM5_COMPAR_REGISTER = sw_predvolba_pre_0_8nasobok_bitu;

		}
		return;
	}

	if TST(sw_status,ROZPRACOVANE_VYSIELANIE) {
		// generovanie start bitu

		if (sw_pocitadlo_bitov == 1) {
#if (COM5_ECHO == TRUE)

			if (cnt_mem == cnt_mem_max) { // ukonci vysielanie
				prg_com5(COM_ZAPNI_PRIJEM, 0);
				return;
			}
			else {
				if (cnt_mem == 0) sw_udr = 's';
				else sw_udr = mem[cnt_mem];

				if (cnt_mem < cnt_mem_max) cnt_mem++;
			}
#else			
			sw_udr = prg_vyslany_bajt((COM_STRUCT *)&com5_struct);
#endif
			if TST(com5_struct.setup,PREPINAJ_SMER) { // je rezim RS485
				COM5_SMER_RS485_RX;
			}

			RES_TX5;
		}
		else {
			if (sw_pocitadlo_bitov < 10) {
				if (sw_udr & 0x01) SET_TX5;
				else RES_TX5;
				sw_udr >>= 1;
			}
				// generuj 3x stop bit
			if (sw_pocitadlo_bitov == 10) {
				SET_TX5;
			}
			// otestuj, ci treba este vysielat
			if (sw_pocitadlo_bitov == 13) {
				if TST(com5_struct.setup,PREPINAJ_SMER) { // je rezim RS485
					COM5_SMER_RS485_RX;
				}
				sw_pocitadlo_bitov = 0;
#if (!(COM5_ECHO == TRUE))
				// ak sa uz nema nic odvysielat, potom prepni UART na prijem
				if (!(TST(com5_struct.status,JE_VYSIELANIE))) {
					prg_com5(COM_ZAPNI_PRIJEM, 0);
				}
#endif
			}
		}
		
		return;
	}
}

Ale z toho asi velmi mudry nebudes :slight_smile:, to bolo iba na ukazku ako fragment z realnej sw komunikacnej rutiny. Nie je tam uplne vsetko a o tom kode ani nemusime polemizovat. Uviedol som to iba ako dokaz, ze viem o com pisem.

Takze este raz mnemotechnicky. Neviem, ci ma x51 compare match, budem teda pisat algoritmus ako by bolo prerusenie iba od pretecenia casovaca. Predpokladam, ze sprava je ukoncena znakom 0x0d


#define FALSE 0x00
#define TRUE 0xff
#define KONCOVY_ZNAK 0x0d
#define MAX_POCET_BAJTOV 100

uint8_t prijaty_bajt, cnt_pom, bufer[MAX_POCET_BAJTOV], aktual_index, sprava_ukoncena = FALSE; 

int main(void)
{
  fn_inicializacia_prijmu_bajtu();
  aktual_index = 0;
  // hlavna slucka, tusi rob co chces
  while(1) {

    if (sprava_ukoncena == TRUE) {
      fn_spracuj_protokol();
    }
    // ak neprisla sprava, kludne si volaj do kolecka svoje dalsie funkcie, napr na spracovanie displaya a tak podobne. Len sa cas od casu pozri, ci uz nahodou nebola prijata cela sprava.
  }
}


void fn_prerusenie_od_pretecenia(void) 
{
  // ak bola sprava ukoncena a je zle osetrene vypnutie prerusenia, tak z funkcie hned vyskoc
  if (sprava_ukoncena == TRUE) return;


  // normalna cinnost rutiny
  if (cnt_pom == 0) {
      - nastav T0 ako casovac
      - nastav do T0 predvolbu tak, aby sa po uplynuti casu 0.5Bd vyvolalo prerusenie
  }
  // ak cakas start bit
  if (cnt_pom == 1)  {
    //  a na pine T0 je naozaj nula, potom pokracuj
    if (!(PORT_P3 & (1<<pin_T0))) {
      - nastav do T0 predvolbu tak, aby sa po uplynuti casu 1 Bd vyvolalo prerusenie
    }
    // nejednalo sa o start bit, ale asi o nejaky sum
    else {
       fn_inicializacia_prijmu_bajtu();
       return;
    }
 }
 if (cnt_pom > 1) && (cnt_pom <= 9) {
    prijaty_bajt = prijaty_bajt<<1;
    if (PORT_P3 & (1<<pin_T0))) {
      prijaty_bajt = prijaty_bajt + 0x01;
 }
 // otestuj stop bit
 if (cnt_pom == 10) {
    if (PORT_P3 & (1<<pin_T0))) {
        bufer[aktual_index] = prijaty_bajt;
        if (prijaty_bajt !=KONCOVY_ZNAK) {
           aktual_index++;
           // ochrana proti preteceniu bufera
           if (aktual_index >=MAX_POCET_BAJTOV)  aktual_index = 0;
       }
       else {
           // v premennej aktual_index mas pocet priajtych bajtov
           sprava_ukoncena = TRUE;
           - zakaz prerusenia od T0
           return;
       }
    }
    // neprisiel stop bit
    else {
       fn_inicializacia_prijmu_bajtu();
       return;
    }
  }
  cnt_pom++;
}

void  fn_inicializacia_prijmu_bajtu(void)
{
        - nastav pin T0 ako vstupny
        - nastav do predvolby T0 0xff
        - nastav prerusenie od pretecenia T0
        prijaty_bajt = 0;
        cnt_pom = 0;
}

void  fn_spracuj_protokol(void)
{

  // co len chces
  // ....
  // koniec co len chces

  sprava_ukoncena = FALSE;
  fn_inicializacia_prijmu_bajtu();
  aktual_index = 0;
}

Dufam, ze som v druhom kode nenarobil nejake principialne chyby, treba to skontrolovat. Hlavne si vsimni, ze nikde ziadne cakacie slucky :slight_smile:
Obdobne naprogramujes i vysielanie. Na zaciatok prerusovacej rutiny si este hodis podmienku ci sa sprava prijima, alebo vysiela. Pri vysielani vyuzivas iba prerusenie od casovaca v periode casu 1Bd.
Prajem vela uspechov a daj vediet ze co a ako.

Moc pěkný postup, děkuji. Ale já myslel že bych tyto výstupy využíval normálně jako např bránu P2. a k tomu používal normální 8 bit čítač/čas co udělá vnitřní přerušení.Popravdě vůbec nevím jak udělat přerušení na T0.

poradil by mě někdo jak nastavit 16 bit čitač/časovač?aby např. každých 150 mikrosekund vytvořil přerušení.Krystal je 12MHz.
mám nastavený 8bit takto a funguje super, takže bych potřeboval podle stejného postupu i ten druhý,ale nějak se v návodu strácím:

void timer0(void) interrupt 1
{
zde je obsluha preruseni
}
void main()
{
TMOD = 0x02;
TH0 = 256-100; 
TR0 = 1;
ET0 = 1;
EA = 1; 
}

V tom Ti neporadim - robim s inym mcu. Na to su tu povolanejsi :slight_smile:

Mělo by to být nějak takto:


TMOD = 0x22;    //č/č  0 i 1 v modu 8bit RELOAD
TH0 = 256-100; 
TL0 = 256-100;
TR0 = 1;
ET0 = 1;
TH1 = 256-150; 
TL1 = 256-100;
TR1 = 1;
ET1 = 1;
EA = 1; 

Jinak pokud máš problémy se čtením cizojazyčných návodů,
tak si kup knihu “Práce s mikrokontroléry ATMEL …” z nakladatelství BEN.
Mají jich tam několik, stačí si vybrat.