ARM37: zuikis ir vėžliukas

Kas nesidomi mikroprocesorių programavimų, gali ir neskaityti. Bus nelabai įdomu… :)

Klasikinė bėda- yra du procesai, kurių veikimo greičiai labai skiriasi. Šiam variante: USB ir UART. Kaip suderinti jų bendrą veikimą? Štai imat kokį source code iš interneto platybėse publikuotos pamokėlės, viskas kaip ir veikia kol spaudžiojat duomenis iš klaviatūros, o jei pabandot “copy-paste" ir staiga tekstas pradeda nebepersiduoti, raidės prapuola ir procesoriukas pastringa. Kodėl? Todėl, kad reikia suderinti skirtingo greičio ir veikimo principo procesus:
Pirmas procesas, USB, tai paketinis duomenų perdavimas su galimybe pristabdyti duomenis ar net paprašyti juos pakartoti. Viskas vyksta gana greitai.
Antras procesas, UART, tai nuoseklus, lėtas duomenų perdavimas. Ir pats perdavimas pririštas prie laiko. Jei nenaudojam kontrolinių linijų, procesą negalime sustabdyti. Kai duomenų per daug, jie prarandami.

Taip atrodo iš žmogaus pusės, iš kontrolerio pusės tai jau keturi procesai: USB TX, USB RX, UART TX, UART RX.

Kad suvaldyti šį chaosą, reikia procesus paleisti nepriklausomai, per pertraukimus. Arba, kai siunčiam, tai galima ir pastabdyti pagrindinį ciklą (bent jau šiame eksperimente). Gaunais taip:

  1. UART RX, per pertraukima ir net DMA. Tačiau nežinom kiek baitų gausim, tai visas mūsų DMA/IRQ tvarko tik vieną baitą.
  2. UART TX, siuntimas blokuojant procesoriaus darbą. Mūsų programa nieko protingo nedaro, tai galima blokuoti.
  3. USB RX, tikriausiai per pertraukimą, naudojam HAL biblioteką.
  4. USB TX, blokuojamas ar tai IRQ, naudojam HAL biblioteką*.

Visi procesai per pertraukimą daro tik vieną darbą- jei gaunam duomenis, įrašom duomenis į buferį (jei yra vietos). Buferis vadinasi “circle" nes tai kaip ir cirkuliarinis buferis**, tik aš jo neperpildau, prarandu duomenis jei buffer overflow. O pats smagumas vyksta pagrindiniam, amžinam cikle:

 while(1)
	{
	HAL_IWDG_Refresh(&hiwdg); //watchdogas
	
	while(circle_available(&cc)>0) //ar yra duomenu gautu is UART?
		{
		i=circle_available(&cc);
		for(j=0;j<i;j++)
			{
			tmp[j]=circle_pull(&cc); //viska persikopijuojam ir issiunciam
			}
		CDC_Transmit_FS(tmp, i); //siuntimas per USB (blocking?. gal ne)
		}
	
	while(circle_available(&cu)>0) //ar yra duomenu gautu is USB?
		{
		i=circle_available(&cu);
		for(j=0;j<i;j++)
			{
			tmp[j]=circle_pull(&cu); //viska persikopijuojam ir issiunciam
			}
		send_uart((char *) tmp,i); //siunciam per UART (blocking)
		}
	}
}

Tikriausiai senas Wordpresas nenusiaubė programos teksto.

Iš principo programa veikia taip: yra ką perduoti? perduodam! ir vėl iš naujo.

Tačiau galima dar labiau viską užkomplikuoti- TX padaryti su atskirais buferiais ir siuntimo procesą irgi padaryti asinchroninį. Tada procesoriuje išsilaisvintu dar šiek tiek resursų kokiai nors pagrindinei programai.
Dar viena bėda- nenumatytas atvejis, kai trumpuose momentuose, kol ištraukiami duomenys į buferį, kas nors įrašytu naujus duomenis. Teoriškai circle buferis apsaugo nuo tokių nemalonumų… Praktiškai, buitiniams reikalams viskas veikia.

Šis straipsniukas skirtas man pačiam prisiminti, nes reikėjo ir neatsiminiau. Teko kiek parašinėti.

*) HAL USB biblioteka netikrina ar duomenys išėjo. Jei reikalingi TIKRAI gerai daryti, reikia tikrinti USB būklę. Tada jau geriau nagrinėtis ATARI disko emuliatoriaus kodą (rodos ten padariau viską)
**) circular buffer, cirkuliarinis buferis leidžia rašyti ir skaityti duomenis iš buferio. T.y duomenys kaip gyvatėlė Uroboras, nauji duomenys prisideda prie galvos, o seni nusiima prie uodegos. Viskas gerai, kol galva nepasiekia uodegos ir gyvatėlė neįsikanda. Tada prarandam duomenis.
P.S. source kodas neoptimizuotas dėl aiškesnio vaizdavimo.

4 Responses to “ARM37: zuikis ir vėžliukas”

  1. justas Says:

    Jei jau yra circular buffer, tai greiciausiai bus dar 3 informacijos vienetai: last used, next available ir generation. Tuomet visa sita galima suserti i scatter/gather DMA ir atlaisvinti procesoriuka nuo baitu tampymo is/i tmp masyva.

  2. Administrator Says:

    Kadangi pas buferi tail/head neapibrėžti ir gali net būti vienas už kito, tai nesugebu dma paaiškinti kaip kopijuoti- keli baitai masyvo gale ir keli pradžioje. Gal net du dma siuntimai. O kopijavimas iš buferio į tmp tai greitas darbas procikui. O poto tmp gali išleisti per dma.
    Beto ištraukimo iš buferio metu kitas procesas gali pridėti baitus prie head. Todėl pats circular buferis yra labai neapibrėžtas dydis.

  3. Romas Says:

    Man toks gal net ne į temą žalias klausimas - o tokiose situacijose nereikia naudoti atskirų buferių pvz USB priėmimui ir UART priėmimui? T.y. ar visada kitas IRQ leis užbaigti išsiųsti visus priimtus duomenis?

  4. Administrator Says:

    Taip, buferiai skirtingi, tik labai panašiai pavadinti: &cu ir &cc - jie naudojami priėmimui. O tmp buferis (siuntimui) neliečiamas kol nesunaudojamas. Gal kiek neaišku, nes neparodytas priėmimas.

    UART:

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
              if(huart == &huart2)
              {
    			if(circle_free(&cc)) circle_push(&cc , rxx); // push received byte to circular buffer
    			HAL_UART_Receive_DMA(&huart2,(uint8_t *)&rxx,1);
              }
    }

    USB:

    static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
    {
      user_usb_rx(Buf,Len); // čia reikia ne taip primityviai, bet tikrinti būklę.
      USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
      USBD_CDC_ReceivePacket(&hUsbDeviceFS);
      return (USBD_OK);
    }

    ir

    void user_usb_rx(uint8_t* Buf,uint32_t *Len)
    {
    uint32_t a;
    a=Len[0]; //užmiršau tas & ir * komandas... :)
    if (a >  circle_free(&cu)) {a= circle_free(&cu);} //cia reiketu ne tupai nupjauti, bet stabdyti ir laukti buferio ištuštėjimo
    circle_push_buf(&cu, Buf, a);
    }

Leave a Reply

Bot-Check (Jei ne skaičiai spauskit refresh. Tik oranžinius naudoti.)

Unhappy Tikbalang