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:
- UART RX, per pertraukima ir net DMA. Tačiau nežinom kiek baitų gausim, tai visas mūsų DMA/IRQ tvarko tik vieną baitą.
- UART TX, siuntimas blokuojant procesoriaus darbą. Mūsų programa nieko protingo nedaro, tai galima blokuoti.
- USB RX, tikriausiai per pertraukimą, naudojam HAL biblioteką.
- 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.