Sida 1 av 4

Olika metoder för att skicka stora datamängder (UART)

Postat: 9 januari 2012, 19:35:27
av Korken
Godagens!

Jag har under en tid arbetat med hur KFly ska kunna skicka kommandon och stora datamänder via UART för debug, telemetri, config och annat.
Men jag skulle vilja höra hur ni gör när ni ska skicka data över UART och kommandot inte får plats i FIFOn.

Just nu gör jag såhär:
Jag har en interrupt som säger till varje gång FIFOn på 32byte blir tom och då laddar den in 32 nya (eller färre om det inte finns 32st) bytes från en 256 bytes stor Round Robin buffer.
När jag skickar kommandon tex SendString(&str, 79) (där 79 är längden på strängen) så är det en funktion som bara lägger till de 79 bytes:en längst bak i buffern och "kickar" igång överförningen om kön är tom.

Hur brukar ni göra?
Finns det mer effektiva metoder?
Är min metod dålig ur någon synvinkel?

Jag är nyfiken på hur mer erfarna programmerare gör så var inte blyga! :)

Mvh
Emil

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 9 januari 2012, 20:32:54
av blueint
Interrupten för mottagning bör kanske trigga när bufferten nästan är full. Annars hinner bufferten kanske inte läsas av i tid.

3/4 kan vara ett riktmärke.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 00:30:09
av Andax
En sak som är viktig är att dimensionera generering av data så att snittet aldrig ligger över vad datalänken klarar och att topparna är korta nog för att tas upp av bufferns storlek. Sedan ska man försöka implementera en felhantering vad som händer om buffern är full. Ska man vänta i anropet tills det finns plats för data, eller returnera felkod? Ska man skicka ett kort "buffer full detected" så att man kan se att genereringstakten i praktiken är för stor?

För telemetri är det ju alltid bra med CRC eller annan checksumma om det inte sköts av modulen.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 08:10:58
av Icecap
Jag har lite svårt att förstå problemet. Vi pratar UART och den högsta praktiska hastigheten är då 115.200 bit/sekund (baud). Det går 10 bit på en byte (startbit + 8 databit + stoppbit), alltså är den praktiska hastighet 11.520 bytes/sekund. Med en µC med en klockhastighet på 1MHz är detta ~86 instruktioner och den ska då enkelt hinna med detta.

Men jag använder ofta en utskriftrutin som egentligen skriver ut till en minnesbuffer, denna skickas sedan till utskriftrutinen. Är det en UART datan ska skickas ut till har jag då en ringbuffer som fylls i.

Om UARTen är tom skickas första byte direkt till UART'ens sändregister, är den upptagit med att sända något läggs dessa istället in i ringbuffern och en flagga sätts. När UART'en aktiverar TX-interrupten (som i "nu är det sänd klart") kollas flaggan och det matas på om det finns, är det tomt rensas flaggan och interrupten stoppas.

Detta gör att det blir en interrupt för ung. varje byte som sänds, med "ung." menar jag att det på vissa µC finns en FIFO-funktion på själva UART'en som tar hand om lite extra bytes, den hinner att bli fyllt först innan det blir interrupt.

Såklart har jag en blockering på så att om in-pekaren och ut-pekaren är lika OCH flaggan är aktiverat kan rutinen som lägger i data i ringbuffern inte göra detta, den är ju knökfull redan. Om datan sedan ska kastas eller om hela programmet ska vänta på att det blir plats beror ju på vad som är viktigast.

På detta vis få jag ett antal snabbt utförda interrupts, något jag gillar mer än ett fåtal som tar längre tid.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 11:18:43
av jesse
Fattar jag rätt, Korken, att du menar så här:

Du vill inte att processorn blockeras när du sänder enorma mängder data?

Om du har en sändbuffert på x antal bytes så blir den snabbt full och då kommer sändrutinen stå och vänta på att få putta in nya bytes i bufferten vilket ger samma effekt som om du aldrig haft någon sändbuffert = processorn står och väntar och kan inte jobba.

Jag tycker din lösning är bra...

du slänger ut ett bestämt antal bytes åt gången (som får plats i bufferten) och sätter en flagga när bufferten är tömd.
då kan processorn arbeta med annat under tiden och så fort flaggan är satt så startar ett interrupt som skickar ut flera bytes.
Låter mer effektivt än att skicka bytes ett och ett - det blir interrupt alldeles för ofta.

Jag ska ta till mig den metoden (ska snart göra ett program som skickar 1 MB via UART 38.400 BAUD, och processorn har annat att göra under tiden... (Det tar minst 4½ minuter att skicka) .

Om du har mycket långa datamängder kan det vara viktigt att dela upp dem i mindre bitar som var och en har checksumma och får bekräftelse av mottagaren. Det är lite tröttsamt om man tagit emot 1MB och sedan står det "Checksum failed" :evil: och så ska man sända om 1 MB.. Då vore det bättre om man kör t.ex. 256 bytes åt gången och sedan verifierar.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 11:44:56
av Nerre
Det går ju också att i värsta fall göra det i flera nivåer, med flera buffertar.

Applikationen skriver till buffert A som kan vara enorm, en interruptrutin plockar data från buffert B (inte enorm) till UART, om buffert B är tom fylls den med ett nytt block från buffert A.

Då kan buffert A vara typ en dynamiskt allokerad buffert som har varierande storlek.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 11:56:18
av jesse
Ofta är ju minnet ganska begränsat i en µ-controller.

Om den tillgängliga datan redan är lagrad i minnet (som t.ex. en array) så kan man ju plocka ihop en sändrutin som läser direkt ur arrayen och på så vis använder den som "buffert".

Själv tänker jag läsa in data från ett externt flash-minne med SPI. Datan komer att läsas in t.ex. 128 bytes åt gången och läggas i sändbuffert. Sedan (under tiden som UART sänder) måste processorn använda SPI-bussen till andra enheter... Det kräver ju lite planering, för man kan ju inte avbryta läsning från SPI med ett interrupt som i sin tur vill läsa SPI på en annan enhet... Enheterna måste ställas på kö för att använda SPI.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 12:28:08
av Nerre
Hade lite liknande problem i mitt exjobb. Skulle implementera ett filter på en TMS320C80, den har 5 CPUer i sig, varav 4 är DSP:er. Problemet var att läsning från externt minne tog 16 klockcykler.

DSP:erna (eller PP kallades de väl, Parallel Processors) kunde räkna kvadratiskt medelvärde på en klockcykel per byte med lite trix (förutsatte då visserligen mer än 4 bytes), så det var grymt frustrerande att behöva vänta på läsningen. Men det var ju bara att göra den "först" så var nästa värde tillgängligt efter beräkningen.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 13:57:23
av Icecap
Det är ju helt enkelt inte möjligt att sända mer data än UARTens hastighet medger! Och det är inte möjligt att sända större block än man kan buffra.

Så ska det sändas 1MB på 38.400 baud (som bäst tar det 273,1 sekund = 0:04:33) är det data som samlas ihop under den tid det tar om inte du har en buffer som har plats för dessa data.

Men låt ponera att du skriver ut 1MB till den buffer du har i hårdvaran, då blir det 1M interrupt - som alla är korta och effektiva. Processorn har massor av tid att göra annat under tiden varje byte sänds, det tar ju ändå 260µs per byte som sänds och själva interruptrutinen tar knappast någon längre tid.

Den ska:
* Rensa interrupt-bit'en (om inte hårdvaran gör det automatisk)
* Räkna upp ut-pekaren ett steg.
* Kolla om ut-pekaren är högre än bufferns storlek och i så fall nolla den.
* Ta nästa byte från ut-pekarens index i buffern.
* Lägga den byte i TX-registret.
* Jämföra in-pekaren och ut-pekaren, är de lika ska flaggan för "Upptagit" nollas och vidare interrupt stoppas.

När man kallar sänd-rutinen ska den:
* Kolla om "Upptagit"-flaggan och TX-flagga är inaktiva, om så är fallet ska byten tryckas direkt i TX-registret och saken är biff.
* Om TX redan är igång ska den:
- Lägga in byten som ska sändas i buffern på platsen in-pekaren pekar på.
- Räkna upp in-pekaren.
- Kolla om in-pekaren är högre än buffern är stor, nolla om det behövs.
- Aktivera "Upptagit"-flaggan och tillåta TX-interrupt.

Klart.

Kan jag ställer jag alltid prioriteten på TX så lågt det går, då kör den bara när det är OK så att säga.

Att vänta med att sända till en buffer är fylld till en viss grad ger ju en lägre överföringshastighet, man hinner fylla MÅNGA bytes i en buffer under tiden första byte sänds. 260µs (1 byte vis 38.400 baud) vid en processorhastighet på 16MHz är faktisk 4160 cpu-steg och med 1 instruktion per klockpuls hinner CPU'n enkelt med en massa annat under tiden.

Sedan undrar jag hur man först ska samla ihop en FIFO och sedan börja sända... Finns den FIFO i UART-hårdvaran är det väl bara att fylla på den direkt och är det en mjukvara-FIFO kommer hela det sätt att ta orimligt mycket tid på sig att ordna, då blir det mycket mer tid till resten om man kör med interruptstyrd sändning.

Och vill man skriva ut ett block när det finns plats är det enkelt: kolla "Upptagit"-flaggan! Är den aktiv är det saker på gång, är den inaktiv är det bara att kräma på...

Jag kör en realttids-styrning på detta sätt, jag kan köra full hastighet på UART'en (båda...) samtidig utan att vara sig andra interrupt eller main-loop blir lidande eller går trögt. Enkelt är oftast snabbt och stjäl mindre CPU-tid än komplicerat...

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 10 januari 2012, 14:07:52
av jesse
Och vill man skriva ut ett block när det finns plats är det enkelt: kolla "Upptagit"-flaggan! Är den aktiv är det saker på gång, är den inaktiv är det bara att kräma på...
nja... att upptaget-flaggan är inaktiv betyder ju bara att minst en byte i FIFO är ledig. Ska jag då "kräma på" ett helt block får jag ju stå där och vänta i alla fall.... Just därför kan man ju använda metoden att kolla att FIFO är tom innan man trycker in t.ex. 256 bytes till.. Då vet man att det får plats, UARTEN kan jobba på med 256 bytes i lugn och ro medan processorn kan göra annat.

Att fylla på en byte i bufferten varje gång det blir en ledig plats är ju bökigt... och då förlorar ju bufferten sitt syfte.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 11 januari 2012, 13:58:50
av Korken
Mycket bra läsning! :tumupp:

Det är ganska enkelt och MCUn behöver inte arbeta varje byte som ska sändas utan bara ett block i taget, detta gör att jag tjänar in massa exekveringstid som annars skulle gå till att "administrera" sändningen.
Först och främst vill jag påpeka att detta körs på en Cortex-M3 processor i 100MHz så under de 16 byte (FIFOn va på 16 byte, kom ihåg fel) som skickas (i 115200 baud) så hinner den med ca 140k instruktioner så det blir massor av tid till övers för det om är viktigt innan man behöver ta hand om interrupten igen.

Detta använder jag främst för att mosa ut massa sensordata och filterdata på KFly medan det flyger för att lättare hitta problem i filterkod och reglerkod.
Då för varje avläsning av sensorerna skickar jag 40 byte (inkl. checksum, kommandon och timestamp) och detta görs 200ggr/s.
Så det blir att ca 70% av min bandbredd går till detta, så det sparkar på ganska bra! :)

Jag funderade ofta på om buffern skulle bli full, men under config samt telemetridata så brukar toppen ligga på ca 100byte.
Så maxet på 256 byte används inte, men det är bra att ha extra utifall att något händer.
Jag har lite enkel error-hantering som säger till om strängen man försöker skicka inte kommer få plats i buffern, men den har aldrig än behövts. :)


Jag gör liknande när jag tar emot data. RX har också en FIFO på 16 byte, men den kräver lite mer "administration".
En interrupt går av när det finns 14 bytes i buffern, då laddas det av till en "kommando tolk" (en buffer egentligen) som väntar på att hela kommandot ska komma.
Problemet är om man skickar och FIFOn bara får säg 7 bytes i sig. Då kommer ingen interrupt. :humm:
Där försöker jag hitta en bra lösning, men just nu har jag en funktion som kollar var 20e ms om buffern är tom och om den inte är tom så laddar den ur de få bytes som finns till kommando tolken.
Detta är inte direkt optimalt, men det finns ingen hårdvaru time out som man kan sätta, så det blir lite extra jobb.

Ni kanske har något bra tipps på hur man kan lösa detta?

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 13 januari 2012, 08:45:18
av benpalm
Jag brukar göra mitt "grund-API" för kommunikation och tidbas så här:

När interruptet för att UART:en är tom kommer så kollas om det finns
något i transmit-FIFOt. Finns inget att sända så sätts en flagga, "tx_ready".

Sen finns ett timeravbrott på säg 500 us. Där kollas om tx_ready är satt
och i så fall så hämtas data från FIFOt och UART:en laddas. Dessutom har
jag en räknare (variabel) där som slår om vid t.ex. var 1/10 sek. Då räknas
en ULONG variabel upp (eller ner). Den utgör sen tidbas för "längre" tidshantering,
t.ex. realtidsklocka eller timeouter:

ULONG timeout;
TimebaseGet(&timeout);
if(TimebaseDiff(timeout) > 1000) ) { // 100 sek timeout
....

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 13 januari 2012, 16:00:54
av Hedis
Har du kollat på att använda DMA till detta?
Väldigt praktiskt. Då behöver du bara ange vartifrån den skall skicka (pekare till minnesområde) samt storlek så sker det utan åtgärd i bakgrunden sen.
Var några år sedan jag använde DMA i 1768:an, men smidigt var det nästan alltid, förutom när det behövde debugas..... :)

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 13 januari 2012, 16:17:15
av Korken
DMA är utan tvekan det bästa, men jag slogs med det i ett par månader innan jag gav upp på det. Hur jag än höll på vart det hard faults och även när jag körde exempel som bara skulle kompileras och köras så vart det hard faults.
En vacker dag kanske jag försöker igen på det, men då ska det nog krisa. :)
Eller om jag hittar någon som är riktigt bra på det. ;) Sen så har vi bytit MCU i KFly nu till en STM32F4. Så det blir att börja om om jag vill få igång DMAn på den.

Re: Olika metoder för att skicka stora datamängder (UART)

Postat: 16 januari 2012, 21:07:05
av Korken
Jag måste ställa en till fråga när vi är på ämnet. Hur brukar ni göra en Time Out funktion? :humm:

För stunden ser mitt protokoll ut såhär: CMD | SIZE | DATA | CHKSUM
Och säg att den förväntar sig 50 bytes men dataströmmen upphör innan den har kommit till slutet.
Hur brukar ni göra error-hantering och främst detektera detta på ett bra sätt?
Att polla och kolla buffern känns som en väldigt ineffektiv metod (det jag gör nu) så jag undrar om ni har en bättre metod för detta?