Ce este modul de stocare în masă USB. Implementarea funcției de înregistrare. Aștept activ ca cardul să fie gata

În ceea ce privește modul USB Mass Storage, nu se știe unde a „dispărut” din Android Ice Sandwich cu cremă, există o mulțime de pasiuni care năvălesc pe internet în aceste zile. Toți au vorbit, atât cei care știu, dar rareori vorbesc, cât și cei care nu știu, dar au întotdeauna o părere. Pasiunile sunt atât de serioase încât a trebuit chiar să mă „întorc” de la pensionarea de blogging, să merg la LiveJournal și să scriu câteva rânduri.

Pentru a fi rapid și la obiect:
- Mod USB Mass Storage - acesta este modul pentru conectarea unui card SD la telefon, atunci când telefonul se transformă în cititor de carduri pentru card SD.
- Modul de stocare în masă USB necesită un card SD separat în telefon, înlocuibil sau încorporat. Adică depinde de configurația specifică a hardware-ului din telefon.
- Suportul pentru stocarea în masă USB nu a dispărut de la Android 4.x ICS - trebuie doar să instalați ICS pe Nexus S și veți vedea că există și funcționează grozav.
- USB Mass Storage nu este acceptat pe Galaxy Nexus, din nou din cauza configurației hardware a acestuia din urmă. Cineva pur și simplu a confundat hardware-ul cu software-ul, iar oamenii l-au luat.
- Pentru cei îngrijorați de telefoanele viitoare: nu vă faceți griji. Producătorii de telefoane sunt foarte conștienți de faptul că, în multe piețe, a avea un card SD într-un telefon este important pentru cumpărători, iar multe modele vor include suport pentru un card SD extern înlocuibil, adesea împreună cu un card SD „intern” încorporat.


Să începem de departe. Cu configurație de hard disk în timpul asamblarii computer de acasă toți tociștii s-au întâlnit. Cineva cumpără unul mare și îl împarte în mai multe partiții, cineva cumpără un SSD mic pentru a instala sistemul pe el și un SATA intern mare pentru stocarea fișierelor, cineva cumpără și un USB extern pentru stocarea/transferul de filme, fotografii și muzică. Trebuie să vă dați seama ce dimensiune de discuri să cumpărați, ce viteză, ce interfață de conectare, ce companie le-a făcut etc. Situație comună? Deci, producătorii de telefoane se confruntă cu o situație similară atunci când își proiectează și își produc următorul model.

Ce ar trebui să păstrați pe telefon? Da, aproape la fel:
1. Fișiere de sistem(OS)
2. Programele instalate și fișierele acestora
3. Fișiere utilizator

Să presupunem că trebuie să vă asigurați că diferite tipuri de date sunt stocate pe unități diferite: C, D și, respectiv, E. Vorbesc despre această defecțiune și avantajele ei. Permiteți-mi să vă reamintesc că o astfel de diviziune oferă o anumită „flexibilitate”, inclusiv pe desktop. De exemplu, trebuie să reinstalăm sistemul - formatăm C, dar datele noastre pe D și E rămân. Atunci vorbeam în principal despre C și D. Acum să vorbim despre E.

E este locul în care dvs. și programele dvs. stocați datele, mai ales dacă acele date ocupă mult spațiu. De obicei puneți acolo conținutul media (filme, muzică, fotografii), camera scrie acolo fotografii și videoclipuri noi, navigatorii GPS își încarcă acolo hărțile de gigabyte etc. În Android, acesta este cardul tău SD. Din punct de vedere istoric, memoria flash „internă” pentru C și D era costisitoare și, dacă trebuia să stocați ceva mare, trebuia făcută pe un microSD extern ieftin, adică pe unitatea E. O bucurie suplimentară a fost că utilizatorul a putut să ajusteze dimensiunea „unității E”, precum și să folosească microSD pentru a face schimb de date cu alte dispozitive, scop în care a fost formatat sub sistemul de fișiere VFAT, pe înțelesul tuturor. Ceva de genul dacă ai avea unitatea E ca hard disk extern USB și l-ai conecta la un alt computer pentru a „descărca filme”.

Continuând analogia microSD cu un hard disk extern USB, să presupunem că procesul de conectare la un alt dispozitiv (computer, media player etc.) a necesitat deconectarea completă a acestuia de la smartphone. Nu puteți conecta un hard disk USB la două dispozitive simultan. Așadar, când modul de stocare în masă USB a fost activat, controlerul de citire/scriere a cardului SD a trecut complet pe USB. Ca urmare, telefonul nu a mai avut acces la cardul SD, iar controlerul său a acționat ca un cititor USB. Ca și în cazul oricărui cititor, computerul a lucrat direct cu cardul SD. Adică, dacă ar fi formatat pentru un sistem de fișiere necunoscut pentru sistemul de operare care rulează pe computer, sistemul de operare ți-ar spune pur și simplu că integritatea cardului SD a fost compromisă și trebuie formatat. Așa funcționează orice cititor, nu înțelege structura fișierului de pe unitate, pur și simplu dă acces la „blocuri” (clustere) de pe acesta, restul trebuie să fie „înțeles” de sistemul de operare.

Producătorii experimentează mult. În unele telefoane pun un C mic rapid, scump, un D mai puțin rapid, mai ieftin și mai mare, și lasă utilizatorul să aleagă E (Galaxy S), în unele C și D „trăiesc” pe același „hard disk fizic” , iar E încă trăiește pe microSD. Și în unele (tablete, Galaxy Nexus) - totul trăiește ca partiții logice pe un singur cip mare. Și se întâmplă că producătorii instalează un E logic intern și oferă utilizatorului posibilitatea de a introduce o unitate F - un microSD extern (Samsung Vibrant). Alegerea configurației este determinată de mulți factori. Factorii variază în funcție de companie. De exemplu, în dispozitivele de la Apple, totul va trăi pe un cip intern scump. Pentru alții, configurația va fi determinată în principal de prețul componentelor, de dorințele operatorilor și de piața de vânzare. Android (mulțumită utilizării nucleului Linux) acceptă cu ușurință orice configurație.

Acum să trecem la terminologia Linux, unde toate dispozitivele (discurile logice, unitățile de dischetă, CD-ROM-urile etc.) sunt directoare și să numim un spade un spade. C va deveni /system, D - /data, E - /sdcard. Pe tablete (apropo, de ce nu s-au făcut atunci toată agitația? Unde căutau criticii Android?) și pe Galaxy Nexus /sdcard este doar directorul /data/media, montat prin FUSE. (Oh, FUSE este un lucru minunat, despre asta altădată, pentru tocilari complet fără speranță). În limbajul normal, putem spune că /sdcard a devenit o legătură atât de complicată către /data/media. Adică, unitatea E a devenit o legătură către un director de pe unitatea D. Astfel, programele utilizatorului și conținutul media de utilizator împart spațiul comun de pe disc. Și nu se dovedește că a mai rămas mult spațiu pentru muzică, dar aveți nevoie de spațiu pentru următorul program, dar acum nu a mai rămas niciunul. Exact ca pe un iPhone.

De ce nu poate fi activat USB Mass Storage Mode pentru această configurație? Și anume pentru că trebuie să conectați întreaga „unitate logică” simultan. Dar a ta nu este reală, deoarece este special făcută astfel încât să fie de mărime variabilă. Și pentru a vă putea conecta, trebuie să știți ce bucată a cipului este alocată pentru unitatea logică dorită, adică să fixați dimensiunea acesteia, ceea ce nu doriți să faceți. „De ce nu atunci întregul cip”, mă ierți, „conectează întregul cip și îmi voi da seama singur.” Bine, hai să conectăm întregul cip. Și nu aveți doar /data și /system acolo, dar este, de asemenea, destul de probabil ca boot, recovery, bootloader să fie un sistem destul de complex de mai multe partiții (unități logice), dintre care câteva sunt în sistemul de fișiere ext4 puțin înțeles. Și dacă conectați toată chestia asta la computer ca pe o unitate USB externă, orice computer vă va oferi să formatați această mizerie. Cred că nu este nevoie să explici ce se întâmplă atunci când unitatea C de pe computer se dovedește brusc a fi formatată.

Ca înlocuitor, ni se oferă MTP (Media Transfer Protocol), atunci când este utilizat, computerul dvs. vede telefonul ca pe un player MP3. Pe Windows, acest protocol este înțeles de Windows Media Player(și Windows însuși înțelege, începând cu Vista), permițându-vă să transferați conținut media din biblioteca dvs. media (de pe același Media Player) pe telefon. Soluția este mai puțin democratică decât USB Mass Storage, dar mai democratică decât iTunes. Decizia este controversată și nu va plăcea tuturor, dar să luăm în considerare următoarele:

Galaxy Nexus, deși este un dispozitiv emblematic pentru Android Ice Cream Sandwich, a fost creat pentru a demonstra nu toate capacitățile sistemului de operare, ci doar o parte dintre ele. Și în niciun caz capabilitățile sistemului de operare nu sunt limitate de capacitățile acestui telefon.

Dispozitivele Nexus sunt un fel de „loc de joacă” pentru Google, o platformă pentru testarea de noi funcții și idei. Este probabil ca unii dintre ei să meargă undeva, iar alții nu. Dar ar trebui să le încerci oricum.

Dispozitivele Nexus nu sunt disponibile altor producători. Google nu impune restricții privind configurarea telefonului. Chiar dacă Android nu acceptă un anumit hardware, producătorul nu costă nimic să adauge el însuși suport - codul sursă este deschis și acest lucru a fost făcut de mai multe ori. Deci, chiar dacă suportul pentru stocarea în masă USB ar dispărea brusc din ICS la ordinul Google, ar fi adăugat rapid înapoi (nu este dificil, crede-mă).

Vor exista telefoane pe ICS cu card SD detașabil și stocare în masă USB. Pentru piețele în care conexiunile 3G sunt lente sau au o acoperire slabă (ex. India), a avea un card SD în telefon este unul dintre principalii factori de cumpărare. Și, credeți-mă, producătorii de telefoane știu foarte bine acest lucru. Și știu că acest lucru este solicitat nu numai în India.

Dacă ești un adevărat tocilar, vei găsi întotdeauna o modalitate de a transfera orice fișier (nu doar media) pe telefon. Sugestie: fișier push adb /sdcard/

P.S. Pentru îndrăgostiți teorii ale conspirației: Ce părere aveți despre ideea că Google a ales această configurație pentru a evita utilizarea VFAT (obligatoriu atunci când se folosește un card SD), temându-se de un proces de la Microsoft bazat pe un brevet vechi de 17 ani pentru utilizarea numelor lungi de fișiere în sistemul de fișiere FAT ?

23 noiembrie 2011 la 02:17

Despre suportul „USB Mass Storage” în Ice Cream Sandwich

  • Dezvoltare Android

Din recenzii timpurii dispozitive (și anume Galaxy Nexus) pornite versiune noua Android 4.0 (aka ICS, alias „sandviș cu înghețată”) s-a dovedit că nu acceptă o funcție atât de minunată precum USB Mass Storage, de exemplu. folosind telefonul ca pe o unitate flash, fără trucuri suplimentare. Utilizatorii de dispozitive Android până la versiunea 3.0 „Honeycomb” (și, după cum s-a dovedit, au avut loc modificări în această versiune) știu că pentru a transfera fișiere pe sau de pe telefon, a fost suficient să-l conectați pur și simplu la computer, fără a ține cont pe ce sistem de operare este instalat sistemul sau software-ul. Este logic că știrile despre dispariția acestei opțiuni în versiunile noi nu au stârnit entuziasm în rândul utilizatorilor de Android și chiar i-a făcut pe mulți să se gândească la prezența unui fel de problemă sau defect. Din fericire, unul dintre inginerii Google Dan Morill(Dan Morrill) în comentariile postării furioase de pe reddit, a clarificat situația, explicând în detaliu ce s-a întâmplat de fapt și de ce. În opinia mea, acest lucru este foarte interesant, așa că traduc mai jos traducerea comentariilor sale.

ICS însuși acceptă USB Mass Storage (UMS). Dar telefonul Galaxy Nexus nu este. Este aceeași poveste ca și cu Honeycomb: HC acceptă UMS, dar tableta Xoom nu. Dacă un dispozitiv are un card SD extern, atunci accesul la acesta prin UMS este acceptat. Dacă aveți doar memorie încorporată (cum ar fi Xoom și Galaxy Nexus), atunci accesul la memoria dispozitivului este acceptat numai prin MTP *(Protocolul de transfer media)și PTP *(Protocol de transfer de imagini). Este imposibil din punct de vedere fizic să suportați UMS pe dispozitivele care nu au o partiție dedicată pentru stocarea informațiilor (cum ar fi un card SD extern sau o partiție separată, ca în Nexus S). Motivul este că USM este un protocol de bloc de nivel scăzut care oferă mașinii gazdă acces direct la blocurile fizice ale media, ceea ce nu permite montarea acestuia simultan în Android. Cu noul model de stocare unificat pe care l-am introdus în Honeycomb, toți cei 32 GB (sau toți 16 GB, sau toți N...) sunt complet partajați între aplicații și fișiere media. Nu mai trebuie să vă uitați cu tristețe la cei 5 GB gratuiti de pe Nexus S în timp ce partiția internă a aplicației este plină - acum este o partiție mare și fericită. Din păcate, prețul care a venit cu acest lucru este că Android nu își mai permite să ofere PC-ului acces direct și să îi permită accesul pe medii de stocare USB cu impunitate. În schimb, folosim MTP. Pe Windows (pe care au majoritatea utilizatorilor), există suport MTP încorporat în Explorer, iar dispozitivul arată exact la fel ca disc obișnuit. Pe Linux și Mac, din păcate, totul nu este atât de simplu, dar sunt sigur că situația se va îmbunătăți în curând. În general, acest lucru ar trebui să facă utilizarea telefonului mult mai convenabilă.

Când a fost întrebat dacă Nexus S are doar memorie internă, cum pot funcționa programe precum managerii de fișiere fără root, Dan a explicat:

Magie. ;)

În primul rând, selectăm un director din memoria internă care va fi „cardul SD”. Apoi luăm sistemul de fișiere FUSE, care nu face nimic decât să remonteze acest director ca /sdcard cu verificarea accesului dezactivată. În afară de accesări, FUSE este pur și simplu un shell end-to-end care transferă scrierea și citirea direct către/dinspre un director. Cu alte cuvinte, folosim sistemul de fișiere fals FUSE pentru a remonta un anumit director care se preface ca un card SD. Acest lucru este complet transparent pentru aplicațiile care nu știu că nu accesează direct discul.

Da. De fapt, prin definiție, folderul /sdcard care rulează sub FAT32 (sau, așa cum este numit în API - „dosarul media de stocare externă”) nu acceptă acces restricționat, ceea ce este normal, deoarece acesta este un dump comun de fișiere deschis tuturor , unde o aplicație poate călca pe fișiere pe alta. A fost conceput inițial pentru lucruri precum muzică și fotografii, și nu pentru date private care „trăiesc” în stocarea personală a aplicațiilor, situată în memoria internă cu acces partajat.
Pe dispozitivele fără card SD, singurul sistem de fișiere fizic este stocarea datelor personale ale aplicației. Așa că selectăm un director, îl declarăm un dump de fișiere, îl montăm ca un sistem de fișiere FUSE separat, care ignoră permisiunile, așa cum sunt ignorate în FAT32.

Dan a explicat motivele tuturor acestor schimbări:

(...) Nu am făcut-o pentru că am vrut să trecem la ext3, deși am câștigat-o ca prin efect. Am făcut asta pentru că am vrut să combinăm spațiul de stocare public (de exemplu, muzică și fotografii) cu stocarea internă pentru aplicații. Ne-am săturat să vedem că producătorii de telefoane includ gigaocteți de spațiu de stocare pentru muzică, dar utilizatorii rămân fără spațiu pentru aplicații și informații. Această abordare vă permite să combinați totul într-o singură secțiune, ceea ce este mult mai bine.

O altă persoană a întrebat de ce este imposibil să folosiți ambele abordări, deoarece slotul de memorie ocupă destul de mult spațiu. La care s-a spus puțin despre ideologia Android:

Din punct de vedere tehnic, nu există nicio problemă în hardware pentru a instala ambele. Problema este că nu o poți face o interfață ușor de utilizat.
Unul dintre principiile de bază ale Android este că utilizatorul nu are nevoie de un manager de fișiere. Nu. Am vrut să evităm sindromul când fiecare strănut face să apară un dialog de selectare a fișierelor, așa cum se întâmplă adesea în alte sisteme de operare. Informațiile interne cu care pot lucra aplicațiile ar trebui pur și simplu să fie disponibile „prin magie” sau stocate în cloud. Nu puteți forța utilizatorul să se angajeze în speologie în timp ce caută fișiere pe cardul SD.

Problema este că acceptând atât memoria internă, cât și un card SD extern, acest principiu devine brusc mult mai dificil de urmat. Pentru o anumită fotografie, camera ar trebui să o salveze pe 16 GB intern sau pe un card SD? Aplicații din Market – ar trebui să fie instalate pe memoria internă sau pe SD? Și așa mai departe. Da, putem rezolva acest lucru forțând utilizatorul să aleagă sau să seteze setările. Dar acesta este în cele din urmă acel dialog de selecție a fișierelor, sau ceva similar cu acesta, atât de mult încât nici nu ne place.

În plus, vor exista consecințe pentru API - dacă bagi într-un card SD cu fotografii, furnizorul de conținut media de sistem ar trebui să le indexeze?.. Dacă da, atunci aplicațiile vor avea de suferit, deoarece nu au fost concepute pentru a ține cont de faptul că fotografiile pot apărea și dispărea brusc.
La un moment dat, probabil vom adăuga conceptul de import/export din mediile conectate. Apoi, camera va salva întotdeauna fotografiile pe 16 GB intern, iar când introduceți un card SD (sau conectați Flash Drive USB), veți putea să începeți migrarea sau să obțineți o fereastră de import/export. Dar până atunci, majoritatea dispozitivelor vor avea fie un card SD, fie o memorie internă mare, dar nu ambele. Înțeleg perfect că multor oameni le plac cardurile SD și mie îmi lipsește stocarea în masă USB, dar de aceea este atât de tare că avem atât de multe dispozitive din care să alegem :)
În general, desigur, aceasta este o încurcătură de probleme. Ne gândim deja la compromisuri pentru versiunile viitoare.

Sper că acest lucru v-a ajutat să clarificați puțin situația și a fost interesant să aruncați o privire asupra funcționării interioare a dezvoltatorilor Android :)

Pentru a desemna stocarea în masă „în viața de zi cu zi” sunt folosite două abrevieri - MSC și UMS. MSC (Mass Storage Class) este oficială, iar UMS (opțiuni posibile de decodare: USB/Universal Mass Storage) este „folk”. Ele nu se contrazic, ci se completează reciproc.

MSC raportează că protocolul este una dintre „clasele de dispozitive” standard aprobate în cadrul specificației USB și, prin urmare, este un standard industrial de jure. UMS vorbește despre universalitatea protocolului, care astăzi este susținut de majoritatea sistemelor de operare și de nenumărate dispozitive finale, făcându-l un standard de facto. Opțiunea de decodare UMS ca USB Mass Storage completează aceste informații, clarificând faptul că linie fizică folosit interfata USB. Literele MS (Mass Storage), comune tuturor abrevierilor, indică faptul că acesta este un protocol conceput pentru a funcționa cu dispozitive de stocare pentru cantități mari de date. A fost conceput pentru ei acest standard.

Clasa de dispozitive de stocare în masă USB include dispozitive care transferă fișiere în una sau două direcții. Reprezentanți tipici ai acestei clase de dispozitive: hard disk-uri, CD-uri, DVD-uri și unități flash. Sistemul de fișiere permite utilizatorului să copieze, să mute și să șteargă fișiere de pe dispozitiv.

Aproape toate dispozitivele de stocare în masă USB utilizează protocolul de transport numai în vrac (BOT, numit și BBB). (Excepție fac unele unități de dischetă cu viteză maximă, care utilizează mai multe tipuri de transferuri de date: transferuri de control, în bloc și întrerupere (acest protocol se numește CBI). Dispozitivele de stocare în masă USB utilizează, de asemenea, comenzi SCSI, definite de diverse standarde SCSI (Small Computer System Interface).

Protocolul de transfer în bloc definește modul în care o gazdă USB poate trimite comenzi și primi răspunsuri folosind transferul în bloc, așa cum este definit în specificația USB. Într-un protocol de numai date, fiecare schimb de informații necesită 2 sau 3 transferuri de date USB. În prima transmisie, gazda trimite o comandă într-o structură numită CBW (Command Block Wrapper). Setul de CBW este urmat de o transmisie care conține date trimise de gazdă sau dispozitiv. În ultima transmisie, dispozitivul returnează starea într-o structură numită CSW (Command Status Wrapper).

Avantajele protocolului. Principalul lucru este simplitatea: toate operațiunile sunt efectuate prin shell-uri de fișiere standard, inclusiv. Windows Explorer (Explorer), nu sunt necesare cunoștințe sau pregătire suplimentară pentru a lucra cu acesta.

Prevalența - deja Windows Eu și 2000 aveam suport de bază pentru protocol, Windows XP îl suporta pe deplin. Multe alte sisteme de operare - MacOS, Linux etc. - compatibil cu stocarea în masă.

De asemenea, este important să existe o specificație de gazdă USB (în mișcare) care vă permite să conectați dispozitive de stocare în masă la alte dispozitive portabile (și neportabile).

Specificații:

221 Kb Engl USB Mass Storage Class – UFI Command Specification
103 Kb Engl Clasa de stocare în masă USB – Transport numai în vrac
103 Kb engleză

grafalex 5 septembrie 2017 la 18:01

Actualizarea dispozitivului de stocare în masă USB pe STM32F103 utilizând FreeRTOS și DMA

  • programare microcontroler,
  • Programarea sistemului

Recent, sau în rusă - ca o unitate flash. Se pare că totul este relativ simplu: în configuratorul grafic STM32CubeMX, am generat codul în câteva clicuri, am adăugat driverul cardului SD și voila - totul funcționează. Numai foarte lent - 200 kb/s în ciuda lățimii de bandă Autobuz USBîn modul Full Speed ​​este mult mai mare - 12 Mbit/s (aproximativ 1,2 MB/s). Mai mult, timpul de pornire a unității mele flash în sistemul de operare este de aproximativ 50 de secunde, ceea ce este pur și simplu incomod de utilizat. Din moment ce m-am scufundat în această zonă, de ce să nu reparăm viteza de transmisie.

De fapt, mi-am scris deja propriul driver pentru un card SD (mai precis, un driver SPI), care a funcționat prin DMA și a oferit viteze de până la 500 kb/s. Din păcate, acest driver nu a funcționat în contextul USB. Motivul pentru aceasta este modelul de comunicare USB în sine - totul se face pe întreruperi, în timp ce driverul meu a fost proiectat să funcționeze într-un fir obișnuit. Mai mult, este pudrat cu primitive de sincronizare FreeRTOS.

În acest articol, am făcut câteva trucuri care mi-au permis să profit la maximum de o combinație de carduri USB și SD conectate la microcontrolerul STM32F103 prin SPI. Vor fi, de asemenea, informații despre FreeRTOS, obiecte de sincronizare și abordări generale ale transferului de date prin DMA. Deci, cred că articolul va fi util pentru cei care înțeleg doar controlerele STM32 și instrumente precum DMA și abordările atunci când lucrează cu FreeRTOS. Codul este construit pe baza bibliotecilor HAL și USB Middleware din pachetul STM32Cube, precum și SdFat pentru lucrul cu un card SD.

Privire de ansamblu asupra arhitecturii

Dacă nu intrați în detalii despre componentele individuale, atunci implementarea unui dispozitiv de stocare în masă (alias Mass Storage Class - MSC) pe partea microcontrolerului este un lucru relativ simplu.

Pe de o parte este biblioteca USB Core. Comunică cu gazda, asigură înregistrarea dispozitivului și implementează tot felul de lucruri USB de nivel scăzut.

Driverul de stocare în masă (folosind nucleul USB) poate primi și trimite date către gazdă. Aproape ca un port COM, numai datele sunt transferate în blocuri. Ceea ce este important aici este conținutul semantic al acestor date: comenzile SCSI și datele către acestea sunt transmise. În plus, există doar câteva tipuri de comenzi: citiți date, scrieți date, aflați dimensiunea dispozitivului de stocare, aflați pregătirea dispozitivului.

Sarcina driverului MSC este să interpreteze comenzile SCSI și să redirecționeze apelurile către driverul dispozitivului de stocare. Acesta poate fi orice dispozitiv de stocare cu acces blocat (disc RAM, unitate flash, stocare în rețea, CD etc.). În cazul meu, dispozitivul de stocare este un card MicroSD conectat prin SPI. Setul de funcții care sunt necesare de la driver este aproximativ același: citiți, scrieți, dați dimensiunea și starea de pregătire.

Și aici apare o nuanță importantă, din cauza căreia se naște de fapt toată agitația. Faptul este că protocolul USB este orientat pe gazdă. Doar gazda poate începe tranzacții, poate trimite sau primi date. Din punctul de vedere al microcontrolerului, aceasta înseamnă că toată activitatea legată de USB va avea loc în contextul unei întreruperi. În acest caz, handlerul corespunzător va fi apelat din driverul MSC.

Cât despre trimiterea datelor de la microcontroler la gazdă. Microcontrolerul nu poate iniția singur transferul de date. Cel mai mult pe care microcontrolerul poate face este să semnaleze miezului USB că există date pe care gazda le poate prelua.

Nici cu cardul SD în sine nu este atât de simplu. Cert este că cardul este un dispozitiv complex (se pare că are propriul microcontroler), iar protocolul de comunicare este foarte nebanal. Acestea. nu a trimis/primi doar date la o anumită adresă (cum este cazul unor module I2C EEPROM). Protocolul de comunicare cu cardul oferă un set întreg de comenzi și confirmări diferite, verificări ale sumelor de control și respectarea tot felul de timeout-uri.

Folosesc biblioteca SdFat. Implementează lucrul cu un card SD la nivel de sistem de fișiere FAT, pe care îl folosesc în mod activ pe dispozitivul meu. În cazul unei conexiuni USB, tot ce are legătură cu sistemul de fișiere este dezactivat (acest rol trece gazdei). Dar ceea ce este important este că biblioteca oferă separat un driver de card cu o interfață, aproape aceeași pe care o dorește driverul MSC - citiți, scrieți, aflați dimensiunea.


Driverul cardului implementează un protocol pentru comunicarea cu cardul prin SPI. El știe exact ce comenzi să trimită pe card, în ce secvență și la ce răspunsuri să se aștepte. Dar driverul în sine nu comunică cu hardware-ul. În acest scop, este furnizat un alt nivel de abstractizare - driverul SPI, care traduce cererile de citire/scriere ale blocurilor individuale în transferul efectiv de date prin magistrala SPI. În acest loc am reușit să organizez transferul de date prin DMA, ceea ce a crescut viteza de transfer a datelor în modul normal, dar a rupt totul în Carcasa USB(DMA a trebuit în cele din urmă să fie dezactivat)

Dar mai întâi lucrurile.

Ce problema rezolvam?

Colegul meu pune deseori această întrebare, care îi nedumerește foarte mult pe interlocutorii în timpul discuțiilor tehnice.

Există 2 probleme cu toată această bucătărie:

  • Viteză scăzută a liniei atunci când lucrați de pe USB. În principal datorită utilizării operațiilor de citire/scriere sincrone
  • Încărcare mare a procesorului (până la 100%) - dispozitivul devine imposibil de utilizat. Motivul este că DMA este dezactivat și necesitatea de a conduce date folosind procesorul.
Dar acest lucru este din partea controlerului și există și aspecte ale protocolului USB Mass Storage. Am instalat sniffer-ul USB Wireshark și m-am uitat exact ce pachete rulau pe autobuz și am văzut încă cel puțin 3 motive pentru viteza redusă
  • Gazda trimite prea multe tranzacții
  • Tranzacțiile sunt prelungite în timp
  • Operațiunile de citire/scriere în sine au loc sincron, așteptând finalizarea
Problema numărului de tranzacții este destul de simplu de rezolvat. S-a dovedit că atunci când îmi conectez dispozitivul, sistemul de operare citește întregul tabel FAT și face multe alte citiri mici ale directorului și MBR. Am o unitate flash de 8 giga, formatată în FAT32 cu o dimensiune a clusterului de 4 kb. Se pare că tabelul FAT ocupă aproximativ 8 MB. Cu o rată de transfer liniară de 200 kb/s, se dovedește a fi aproape 40 de secunde.

Cel mai simplu mod de a reduce numărul de operații de citire la conectarea unui dispozitiv este reducerea tabelului FAT. Este suficient să reformatați pur și simplu unitatea flash și să creșteți dimensiunea clusterului (reducând astfel numărul și dimensiunea tabelului). Am formatat cardul cu dimensiunea clusterului setată la 16KB - dimensiunea tabelului FAT a devenit puțin sub 2 MB, iar timpul de inițializare a fost redus la 20 de secunde.

Mai mare nu este întotdeauna mai bun

Mi-am dat seama că o unitate flash de 8 giga este prea mult pentru dispozitivul meu, nu am nevoie de atât de mult. 1 gig, sau chiar 512 megaocteți, este suficient. Pur și simplu nu am încă o astfel de unitate flash la îndemână. Mai mult, nici acum nu sunt la vânzare. Va trebui să răzuiți partea de jos a butoiului. De îndată ce îl voi găsi, îl voi încerca.


În orice caz, reformatarea unei unități flash nu rezolvă problema vitezei liniare (viteza cu care fișierele mari sunt citite secvențial). Rămâne în continuare la nivelul de 200kb/s și încarcă procesorul cel mai rău. Să vedem ce putem face în privința asta.

Ce este în neregulă cu DMA de la USB?

Să trecem în sfârșit la cod și să vedem cum citesc/scriu pe un card flash (driver SPI)

Folosesc FreeRTOS în proiectul meu. Acesta este doar un instrument minunat care mi-a permis să procesez fiecare funcție a dispozitivului meu într-un fir (sarcină) separat. Am reușit să arunc mașini de stat uriașe pentru toate ocaziile, iar codul a devenit mult mai simplu și mai clar. Toate sarcinile funcționează simultan, dându-se loc una altuia și sincronizându-se dacă este necesar. Ei bine, dacă toate firele au adormit în așteptarea unui eveniment, atunci puteți utiliza modurile de economisire a energiei ale microcontrolerului.

Codul care funcționează cu cardul SD rulează și într-un fir separat. Acest lucru ne-a permis să scriem funcții de citire/scriere într-un mod foarte elegant.

Driver SPI pentru citirea/scrierea datelor pe un card SD folosind DMA

uint8_t SdFatSPIDriver::receive(uint8_t* buf, size_t n) ( // Începe transferul de date memset(buf, 0xff, n); HAL_SPI_TransmitReceive_DMA(&spiHandle, buf, buf, n); // Așteptați până când transferul este finalizat xSemaphoreTake(x 100); return 0; // Ok status ) void SdFatSPIDriver::send(const uint8_t* buf, size_t n) ( // Începe transferul de date HAL_SPI_Transmit_DMA(&spiHandle, (uint8_t*)buf, n); // Așteptați finalizat xSemaphoreTake(xSema, 100 void SdFatSPIDriver::dmaTransferCompletedCB() ( // Reluați firul SD xSemaphoreGiveFromISR(xSema, NULL); )


Frumusețea aici este că atunci când trebuie să citim sau să scriem un bloc mare de date, acest cod nu așteaptă finalizarea. În schimb, transferul de date prin DMA este pornit, iar firul în sine intră în stare de repaus. În acest caz, procesorul își poate desfășura activitatea, iar transferul de control trece la alte fire. Când transferul este finalizat, va fi apelată o întrerupere DMA și va activa firul de execuție care aștepta transferul datelor.

Problema este că această abordare este dificil de aplicat modelului USB în care toată logica de operare are loc în întreruperi, și nu în firul de execuție normal. Acestea. Rezultă că vom primi o cerere de citire/scriere într-o întrerupere, iar finalizarea transferului de date va trebui să aștepte și în aceeași întrerupere.

Desigur, putem organiza redirecționarea prin DMA în contextul unei întreruperi, dar acest lucru va fi de puțin folos. DMA funcționează bine acolo unde puteți începe un transfer și puteți comuta procesorul la alte lucrări utile până când transferul de date se termină. Dar, după ce a început transferul de la o întrerupere, nu vom putea întrerupe întrerupere (scuze pentru tautologie) și să ne continuăm treburile. Va trebui să stai acolo așteptând sfârșitul transmisiei. Acestea. functionarea va fi sincrona si timpul total va fi acelasi ca in cazul fara DMA.

Aici ar fi mult mai interesant să începeți transferul de date prin DMA la cererea gazdei și să ieșiți din întrerupere. Și apoi, la următoarea întrerupere, raportați despre munca depusă.

Dar asta nu este întreaga imagine. Dacă citirea de pe card ar consta doar în trimiterea unui bloc de date, atunci o astfel de abordare nu ar fi dificil de implementat. Dar transmisia prin SPI este, desigur, cea mai importantă parte, dar nu singura. Dacă te uiți la citirea/scrierea unui bloc de date la nivelul driverului de card, procesul arată cam așa.

  • Trimiteți o comandă către card, așteptați și verificați răspunsul
  • Așteptați până când cardul este gata
  • Trimiteți date (cu aceeași funcție pe care am dat-o mai sus)
  • calculati suma de controlși comparați-l cu o vizualizare a hărții
  • Transfer complet
Având în vedere că acest algoritm aparent liniar este implementat ca o serie de apeluri de funcții imbricate, atunci tăierea lui la mijloc nu ar fi foarte rezonabilă. Va trebui să intrăm în panică completă întreaga bibliotecă. Și dacă luăm în considerare că în unele cazuri transferul poate fi efectuat nu dintr-o singură bucată, ci într-un ciclu într-o serie de blocuri mici, atunci sarcina devine complet imposibilă.

Dar nu este chiar atât de rău. Dacă priviți și mai sus - la nivel de driver MSC - atunci nu prea îi pasă cum va avea loc exact transferul de date - într-un singur bloc sau mai multe, cu sau fără DMA. Principalul lucru este să transferați datele și să raportați starea.

Locul ideal pentru experimente ar fi stratul dintre driverul MSC și driverul cardului. Înainte de toate batjocura, această componentă părea foarte banală - în esență este un adaptor între interfața pe care driverul MSC vrea să o vadă și ceea ce scoate driverul cardului.

Implementarea originală a adaptorului

int8_t SD_MSC_Read (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) ( (void)lun; // Nu este folosit if(!card.readBlocks(blk_addr, buf, blk_len)) return USBD_BD8_); return) (void)lun; SD_MSC_Write (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) ( (void)lun; // Nu este folosit if(!card.writeBlocks(blk_addr, buf, blk_len)) return USBD_FAIL; return (USBD_OK); return (USBD_OK);


După cum am spus deja, driverul cardului nu funcționează dacă este sunat dintr-o întrerupere. Dar funcționează bine într-un fir normal. Deci, să lansăm un thread separat pentru el.

Acest thread va primi cereri de citire și scriere printr-o coadă. Fiecare cerere include informații despre tipul de operație (citire/scriere), numărul de bloc care trebuie citit sau scris, numărul de blocuri și un pointer către bufferul de date. De asemenea, am creat un pointer către contextul operației - vom avea nevoie de el puțin mai târziu.

Coada de citire/scriere

enumerare IOOperation(IO_Read, IO_Write); struct IOMsg ( IOOperation op; uint32_t lba; uint8_t * buf; uint16_t len; void * context; ); // O coadă de comenzi IO de executat într-un fir separat QueueHandle_t sdCmdQueue = NULL; // Inițializați firul responsabil pentru comunicarea cu cardul SD bool initSDIOThread() ( // Inițializați sincronizarea sdCmdQueue = xQueueCreate(1, sizeof(IOMsg)); bool res = card.begin(&spiDriver, PA4, SPI_FULL_SPEED); return res; )


Firul în sine dorește, așteptând comenzi. Dacă sosește o comandă, atunci operația necesară este efectuată și sincron. La sfârșitul operației, apelăm un callback, care, în funcție de implementare, va face ceea ce este necesar după finalizarea operației de citire/scriere.

Întreținerea firului de citire/scriere pe card

extern "C" void cardReadCompletedCB(uint8_t res, void * context); extern "C" void cardWriteCompletedCB(uint8_t res, void * context); void xSDIOThread(void *pvParameters) ( while(true) ( ​​​​IOMsg msg; if(xQueueReceive(sdCmdQueue, &msg, portMAX_DELAY)) ( switch(msg.op) (case IO_Read: ( bool res = card.readBlocks(msg. lba, msg.buf, msg.len); cardReadCompletedCB(res ? 0: 0xff, msg.context); 0: 0xff, msg.context); break ) implicit: break;


Deoarece toate acestea se fac într-un fir normal, driverul cardului poate utiliza DMA și sincronizarea FreeRTOS în interior.

Funcțiile MSC au devenit puțin mai complexe, dar nu cu mult. Acum, în loc să citească sau să scrie direct, acest cod trimite cererea către firul corespunzător.

Trimiterea cererilor de citire/scriere

int8_t SD_MSC_Read (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len, void * context) ( // Trimite comanda de citire la firul de executare IO IOMsg msg; msg.op = IO_Read; msg.lba = blk.lba = blk._addr;_ blk. msg.buf = buf; msg.context = context if(xQueueSendFromISR(sdCmdQueue, &msg, NULL) != pdPASS) return (USBD_OK, void * context) msg; (USBD_OK);


Există punct important- s-a schimbat semantica acestor funcții. Acum sunt asincrone, adică. nu așteptați finalizarea efectivă a operației. Deci, va trebui totuși să modificăm codul care îi apelează, dar vom ajunge la asta puțin mai târziu.

Între timp, pentru a testa aceste funcții vom face încă un flux de testare. Acesta va emula un nucleu USB și va trimite cereri de citire.

Firul de testare

uint8_t io_buf; static TaskHandle_t xTestTask = NULL; void cardReadCompletedCB(bool res, void * context) ( xTaskNotifyGive(xTestTask); ) void cardWriteCompletedCB(bool res, void * context) ( xTaskNotifyGive(xTestTask); ) void xSDTestThread(void *pvTestTask)(xTestTask); uint32_t prev = HAL_GetTick(); uint32_t opsPer1s = 0 uint32_t cardSize = card.cardSize(); 1000) ( prev = HAL_GetTick(); usbDebugWrite("Viteza de citire: %d kbytes/s\r\n", opsPer1s); opsPer1s = 0; ) ) while(true) ; )


Acest cod citește întregul card de la început până la sfârșit în blocuri de 1KB și măsoară viteza de citire. Fiecare operație de citire trimite o solicitare către fluxul cardului SD. Acolo, citirea are loc sincron și raportează finalizarea printr-un apel invers. Am înlocuit propria implementare a acestui callback, care pur și simplu semnalează firului de testare că poate continua (firul de testare dorm tot acest timp în funcția ulTaskNotifyTake()).

Dar cel mai important este că viteza de citire în această versiune este de aproximativ 450 kb/s, iar procesorul este încărcat doar 3-4%. Nu e rau dupa parerea mea.

Actualizarea driverului MSC

Deci, am învins driverul cardului activând DMA. Dar semantica de citire/scriere s-a schimbat de la sincron la asincron. Acum trebuie să modificăm implementarea MSC și să o învățăm să funcționeze cu apeluri asincrone. Acestea. Trebuie să începem să transferăm prin DMA la prima solicitare de la gazdă și să răspundem cumva la toate cele ulterioare, spunând „operațiunea anterioară nu s-a terminat încă, verificați mai târziu”.

De fapt, protocolul USB oferă un astfel de mecanism imediat din cutie. Partea de primire confirmă transferul de date cu o anumită stare. Dacă datele sunt primite și procesate cu succes, destinatarul confirmă tranzacția cu un statut ACK. Dacă dispozitivul nu poate procesa tranzacția (nu este inițializat, este într-o stare de eroare sau nu funcționează din orice alt motiv), atunci răspunsul va fi STALL.

Dar dacă dispozitivul a recunoscut tranzacția și este în stare de funcționare, dar datele nu sunt încă gata, atunci dispozitivul poate răspunde NAK. În acest caz, gazda este obligată să contacteze dispozitivul cu exact aceeași solicitare puțin mai târziu. Am putea folosi această stare pentru citire/scriere întârziată - la primul apel către gazdă începem să transferăm date prin DMA, dar răspundem la tranzacția NAK. Când gazda vine cu o tranzacție repetată și transferul prin DMA sa încheiat deja, răspundem cu ACK.

Din păcate, nu am găsit o modalitate bună de a trimite un semnal NAK în biblioteca USB de la ST. Codurile de returnare a funcției fie nu sunt verificate, fie pot gestiona doar 2 stări - totul este în regulă sau o eroare. În al doilea caz, toate punctele finale sunt închise, iar starea STALL este setată peste tot.

Bănuiesc că la cel mai de jos nivel driver USB iar confirmarea NAK este folosită destul de activ, dar nu mi-am dat seama cum să mă conectez corect cu NAK la nivel de șofer de clasă.

Aparent, creatorii bibliotecilor ST au oferit o interfață mai umană în loc de diverse confirmări. Dacă dispozitivul are ceva de trimis gazdei, apelează funcția USBD_LL_Transmit() - gazda însăși va prelua datele furnizate. Și dacă funcția nu a fost apelată, dispozitivul va răspunde automat cu răspunsuri NAK. Situația este aproximativ aceeași cu recepția de date. Dacă dispozitivul este gata să primească, apelează funcția USBD_LL_PrepareReceive(). În caz contrar, dispozitivul va răspunde cu un NAK dacă gazda încearcă să transmită date. Să folosim aceste cunoștințe pentru a implementa driverul nostru MSC.

Să vedem ce tranzacții rulează pe magistrala USB (analiza a fost efectuată înainte de modificări în driverul cardului).

Ceea ce este interesant aici nu sunt nici măcar tranzacțiile în sine, ci marcajele de timp ale acestora. Am ales tranzacțiile „ușoare” din această imagine - cele care nu necesită procesare. Microcontrolerul răspunde la astfel de solicitări cu răspunsuri codificate, fără prea multă gândire. Lucrul important aici este că gazda nu trimite un flux continuu de tranzacții. Tranzacțiile au loc nu mai mult de o dată la fiecare 1 ms. Chiar dacă răspunsul este gata imediat, gazda îl va prelua numai la următoarea tranzacție după 1 ms.

Și așa arată citirea unui bloc de date în ceea ce privește tranzacțiile pe magistrala USB.

Mai întâi, gazda trimite o comandă de citire SCSI, apoi citește datele (a doua linie) și starea (a treia linie) în tranzacții separate. Prima tranzacție este cea mai lungă. În timpul procesării acestei tranzacții, microcontrolerul este angajat în citirea cardului. Și, din nou, gazda întrerupe 1 ms între tranzacții.

Apropo.

În terminologia USB, direcția de la gazdă la dispozitiv se numește OUT, deși pentru controler este o recepție. Dimpotrivă, direcția de la dispozitiv la gazdă se numește IN, deși pentru noi înseamnă trimiterea de date.


Algoritmul driverului MSC de pe partea microcontrolerului arată cam așa
    • Gazda trimite o comandă de citire. Din partea microcontrolerului, este apelată funcția MSC_BOT_DataOut().
    • Comanda este procesată prin lanțul de funcții MSC_BOT_DataOut() -> MSC_BOT_CBW_Decode() -> SCSI_ProcessCmd() -> SCSI_Read10()
    • Deoarece driverul se află în starea hmsc->bot_state == USBD_BOT_IDLE, este pregătită o procedură de citire: parametrii comenzii sunt verificați, câte blocuri trebuie citite sunt amintite, după care controlul este transferat la funcția SCSI_ProcessRead() cu un solicitarea citirii primului bloc
    • Funcția SCSI_ProcessRead() citește datele în sincron modul. Aici microcontrolerul este ocupat de cele mai multe ori.
    • Când datele sunt primite, acestea sunt transferate (folosind funcția USBD_LL_Transmit()) în tamponul de ieșire punctul final MSC_IN, astfel încât gazda să le poată ridica
    • Driverul intră în starea hmsc->bot_state = USBD_BOT_DATA_IN
  • Tranzacție SCSI: Intrare de date
    • Gazda preia datele din tamponul de ieșire al microcontrolerului în pachete de 64 de octeți (dimensiunea maximă recomandată a pachetului pentru dispozitivele USB Full Speed). Toate acestea se întâmplă la cel mai scăzut nivel din nucleul USB, driverul MSC nu este implicat în acest lucru
    • Când gazda a colectat toate datele, are loc evenimentul Data In. Controlul este transmis funcției MSC_BOT_DataIn(). Aș dori să vă atrag atenția asupra faptului că această funcție este apelată după ce datele sunt efectiv trimise.
    • Driverul este în starea hmsc->bot_state == USBD_BOT_DATA_IN, ceea ce înseamnă că suntem încă în modul de citire a datelor.
    • Dacă nu au fost citite încă toate blocurile comandate - începem să citim piesa următoare și așteptăm finalizarea, transferați-l în tamponul de ieșire și așteptați ca gazda să preia datele. Algoritmul se repetă
    • Dacă toate blocurile au fost citite, driverul trece la starea USBD_BOT_LAST_DATA_IN pentru a trimite starea finală a comenzii
  • Tranzacție SCSI: Răspuns
    • driverul primește doar o notificare despre acest lucru și intră în starea USBD_BOT_IDLE
Cea mai lungă operație din această schemă este citirea efectivă de pe card. Conform măsurătorilor mele, citirea durează aproximativ 2-3 ms în modul sincron. Mai mult, transferul are loc folosind procesorul și toate acestea se întâmplă în întreruperea USB. Pentru comparație, citirea unui bloc de lungime 512 prin DMA durează puțin peste 1 ms.

Nu am reușit să accelerez semnificativ (să zicem, până la 1 Mb/s) citirea datelor - se pare că acesta este debitul cardului conectat prin SPI. Dar putem încerca să adăugăm la serviciul nostru pauze de 1 ms între tranzacții.

O vad asa (putin simplificata)

  • Tranzacție SCSI: Citire(10) LUN: 0x00 (LBA: 0x00000000, Len: 1)
    • Microcontrolerul primește o comandă de citire, verifică toți parametrii, își amintește numărul de blocuri care trebuie citite
    • Microcontrolerul începe să citească primul bloc în modul asincron
    • Ieșim din întrerupere fără a aștepta sfârșitul lecturii
  • Când citirea este terminată, se apelează înapoi
    • Datele citite sunt trimise în buffer-ul de ieșire
    • Gazda le citește fără participarea șoferului MSC
  • Tranzacție SCSI: Intrare de date
    • Este apelată funcția de apel invers DataIn(), care semnalează că gazda a colectat datele și următoarea citire poate fi făcută
    • Începem să citim următorul bloc. Algoritmul se repetă începând cu apelul de finalizare a citirii
    • Dacă toate blocurile au fost citite, trimitem un pachet de stare
  • Tranzacție SCSI: Răspuns
    • În acest moment, aceste colete au fost deja trimise.
    • Ne pregătim pentru următoarea tranzacție
Să încercăm să implementăm această abordare, deoarece funcția SCSI_ProcessRead() este ușor împărțită în „înainte” și „după”. Adică, codul care începe să citească va fi executat în contextul de întrerupere, iar codul rămas se va muta la apel invers. Scopul acestui apel invers este de a împinge datele citite în buffer-ul de ieșire (gazda va prelua apoi cumva aceste date cu cereri adecvate)

Funcția SCSI_ProcessRead() adaptată pentru citirea asincronă

/** * @brief SCSI_ProcessRead * Handle Read Process * @param lun: Numărul unității logice * @retval status */ static int8_t SCSI_ProcessRead (USBD_HandleTypeDef *pdev, uint8_t lun) ( USBD_MSC_BOT_HandleTypeTypedef *uint8_t_dmsc =C *devdms; ; len = Min (hmsc-> scsi_blk_len, msc_media_packet);< 0) { SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR); return -1; } hmsc->stare_bot = USBD_BOT_DATA_IN; returnează 0; ) void cardReadCompletedCB(uint8_t res, void * context) ( USBD_HandleTypeDef * pdev = (USBD_HandleTypeDef *)context; USBD_MSC_BOT_HandleTypeDef *hmsc = pdev->pClassDataMSC l->unc2_tbtc len = MIN(hmsc- >scsi_ blk_len , MSC_MEDIA_PACKET if(res != 0) ( SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR); return; ) USBD_LL_Transmit (pdev, MSC_IN_EP, hmsc->bot_data, lsi); * cazul 6: Hi = Di */ hmsc->csw.dDataResidue -= len if (hmsc->scsi_blk_len == 0) ( hmsc->bot_state = USBD_BOT_LAST_DATA_IN; ) )


În apelarea inversă, trebuie să accesați mai multe variabile care au fost definite în funcția SCSI_ProcessRead() - un pointer către mânerul USB, lungimea blocului transferat, LUN. Aici este util parametrul de context. Adevărat, nu am transferat totul, ci doar pdev și orice altceva poate fi extras din el. În ceea ce mă privește, această abordare este mai simplă decât tragerea unei întregi structuri cu câmpurile necesare. Și, în orice caz, acest lucru este mai bine decât a avea mai multe variabile globale.

Adăugați un tampon dublu

Abordarea, în general, a funcționat, dar viteza a fost încă puțin mai mare de 200 kb/s (deși încărcarea procesorului a fost fixă ​​și a devenit aproximativ 2-3%). Să ne dăm seama ce te împiedică să lucrezi mai repede.

Urmând sfaturile din comentariile unuia dintre articolele mele, am achiziționat în sfârșit un osciloscop (deși unul ieftin). S-a dovedit a fi foarte util pentru a înțelege ce se întâmplă acolo. Am luat un pin nefolosit și l-am setat la unu înainte de a citi și la zero după ce citirea a fost finalizată. Pe osciloscop procesul de citire arăta astfel.

Acestea. Citirea a 512 octeți durează puțin mai mult de 1 ms. Când citirea de pe card se termină, datele sunt transferate în tamponul de ieșire, de unde gazda le preia în următorii 1 ms. Acestea. aici are loc fie citirea de pe card, fie transmisia prin magistrala USB, dar nu în același timp.

Această situație este de obicei rezolvată folosind tamponarea dublă. Mai mult decât atât, perifericele USB ale microcontrolerelor STM32F103 oferă deja mecanisme de tamponare dublă. Dar nu ne vor potrivi din două motive:

  1. Pentru a utiliza tamponarea dublă pe care o oferă microcontrolerul însuși, poate fi necesar să reproiectați nucleul USB și implementarea MSC
  2. Dimensiunea tamponului este de numai 64 de octeți, în timp ce cardul SD nu poate funcționa în blocuri mai mici de 512 de octeți.
Așa că va trebui să ne inventăm propria implementare. Cu toate acestea, nu ar trebui să fie dificil. Mai întâi, să rezervăm spațiu pentru al doilea buffer. Nu am creat o variabilă separată pentru aceasta, ci pur și simplu am crescut tamponul existent de 2 ori. De asemenea, a trebuit să creăm o variabilă bot_data_idx, care va indica ce jumătate din acest buffer dublu este în uz curent: 0 - prima jumătate, 1 - a doua.

Tampon dublu

typedef struct _USBD_MSC_BOT_HandleTypeDef ( ... USBD_MSC_BOT_CBWTypeDef cbw; USBD_MSC_BOT_CSWTypeDef csw; uint16_t bot_data_length; uint8_t bot_data; uint8_t bot_data; uint8_t_dx; ...bot_data_BOT_Dx; ...bot_data)_BOT_Dx; ...


Apropo, structurile cbw și csw sunt foarte sensibile la aliniere. Unele valori au fost scrise sau citite incorect din câmpurile acestor structuri. Prin urmare, a trebuit să le mutăm mai sus decât tampoanele de date.

Implementarea originală a funcționat pe întreruperea DataIn - un semnal că datele au fost trimise. Acestea. la comanda de la gazdă, citirea a fost începută, după care datele au fost transferate în buffer-ul de ieșire. Citirea următoarei porțiuni de date a fost „reîncărcată” de întreruperea DataIn. Această opțiune nu ne convine. Vom începe să citim imediat după ce s-a terminat lectura anterioară.

Reîncărcăm lectura imediat după ce cea anterioară s-a terminat

void cardReadCompletedCB(uint8_t res, void * context) ( USBD_HandleTypeDef * pdev = (USBD_HandleTypeDef *)context; USBD_MSC_BOT_HandleTypeDef *hmsc = pdev->pClassDataMSC; =>uint.lbt.lbtl;​​ = MIN(hmsc-> scsi_blk _len, MSC_MEDIA_PACKET; if(res != 0) ( SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR); return; ) // Sincronizare pentru a evita mai multe transmisii simultan // Acesta trebuie să fie localizat aici, deoarece așteaptă terminarea precedentă Transfer USB / / în timp ce codul de mai jos pregătește următorul pdev->pClassSpecificInterfaceMSC->OnFinishOp( // Salvați aceste valori pentru transmiterea datelor uint8_t * txBuf = hmsc->bot_data + hmsc->bot_data_idx * MSC_MEDIA_PACKET; starea corectă // Notă: suntem în contextul firului SD, Nu USB întrerupere // Deci, valorile trebuie să fie corecte când apare întreruperea DataIn hmsc->scsi_blk_addr += len; hmsc->scsi_blk_len -= len; /* cazul 6: Hi = Di */ hmsc->csw.dDataResidue -= len; if (hmsc->scsi_blk_len == 0) ( hmsc->bot_state = USBD_BOT_LAST_DATA_IN; ) else ( hmsc->bot_data_idx ^= 1; hmsc->bot_data_length = MSC_MEDIA_PACKET; Verificarea codurilor SC, SCSI_p/-not; SCSI_ProcessRead() intră deja în stare de eroare în cazul eșecului de citire ) // Acum putem transmite datele citite de pe SD USBD_LL_Transmit (pdev, MSC_IN_EP, txBuf, txSize); )


Această funcție a schimbat puțin structura. În primul rând, aici este implementat suportul pentru tamponarea dublă. Deoarece această funcție este apelată când citirea de pe card este terminată, putem începe imediat următoarea citire apelând SCSI_ProcessRead(). Pentru a preveni o nouă citire să suprascrie datele citite, se folosește un al doilea buffer. Variabila bot_data_idx este responsabilă pentru comutarea bufferelor.

Dar asta nu este tot. În al doilea rând, succesiunea acțiunilor s-a schimbat. Acum citirea următorului bloc de date este încărcată mai întâi și abia apoi este apelată USBD_LL_Transmit(). Acest lucru se face deoarece funcția cardReadCompletedCB() este apelată în contextul unui fir obișnuit. Dacă apelați mai întâi USBD_LL_Transmit() și apoi modificați valorile câmpurilor hmsc, atunci potențial în acest moment poate fi apelată o întrerupere de la USB, care dorește, de asemenea, să modifice aceste câmpuri.

În al treilea rând, a trebuit să adăugăm o sincronizare suplimentară. Faptul este că, de obicei, citirea de pe un card durează puțin mai mult decât transferul prin USB. Dar uneori se întâmplă invers și apoi apelul către USBD_LL_Transmit() pentru următorul bloc are loc înainte ca blocul anterior să fie complet trimis. Nucleul USB o ia razna de la o asemenea obrăznicie și datele sunt trimise incorect.


Trimiterea datelor (Transmit) este confirmată de evenimentul de date, dar uneori au loc mai multe transmisii la rând. Pentru astfel de cazuri, este necesară sincronizarea.

Acest lucru poate fi rezolvat foarte simplu adăugând puțină sincronizare. Am adăugat câteva funcții la interfața USBD_StorageTypeDef cu o implementare destul de simplă (deși poate că numele nu sunt foarte bune). Implementarea folosește . OnFinishOp(), care este apelat în callback cardReadCompletedCB(), va dormi și va aștepta până când pachetul de date anterior este trimis.

Faptul de a trimite este confirmat de evenimentul DataIn, care este procesat de funcția SCSI_Read10(), care va apela OnStartOp(), care va debloca OnFinishOp(), care va trimite următorul pachet de date către casa pe care a construit-o Jack. Chiar dacă funcțiile sunt apelate în ordine inversă (și exact asta se va întâmpla în timpul primei citiri - mai întâi SCSI_Read10(), apoi cardReadCompletedCB()), atunci totul va funcționa bine (proprietatea semaforului în modul de așteptare a semnalului) .

Implementarea functiilor de sincronizare

void SD_MSC_OnStartOp() ( xSemaphoreGiveFromISR(usbTransmitSema, NULL); ) void SD_MSC_OnFinishOp() ( xSemaphoreTake(usbTransmitSema, portMAX_DELAY); )


Cu o astfel de sincronizare, imaginea capătă următoarea formă.


Săgețile roșii indică sincronizarea. Ultima transmisie așteaptă intrarea anterioară a datelor

Ultima piesă a puzzle-ului este funcția SCSI_Read10().

Funcția SCSI_Read10(), care este apelată în evenimentul Data In

/** * @brief SCSI_Read10 * Comanda Process Read10 * @param lun: Numărul unității logice * @param params: Parametrii comenzii * @retval status */ static int8_t SCSI_Read10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t lun , uint8_t USBD *Dems_Ty) hmsc = pdev->pClassDataMSC // Sincronizare pentru a evita mai multe transmisii simultan pdev->pClassSpecificInterfaceMSC->OnStartOp( if(hmsc->bot_state == USBD_BOT_IDLE) /* Idle */ ( // Verificare); hmsc- >scsi_blk_addr = ... hmsc->scsi_blk_len = ... hmsc->bot_state = USBD_BOT_DATA_IN ... hmsc->bot_data_idx = 0 hmsc->bot_data_length = MSC_MEDIA_PACKET;


În implementarea originală a SCSI_Read10(), primul apel la funcție a verificat parametrii și a început procesul de citire a primului bloc. Aceeași funcție este apelată mai târziu de întreruperea DataIn când pachetul anterior a fost deja trimis și trebuie începută citirea următorului. Ambele ramuri au început să citească folosind funcția SCSI_ProcessRead().

În noua implementare, apelul SCSI_ProcessRead() s-a mutat în interiorul if și este apelat doar pentru a citi primul bloc (bot_state == USBD_BOT_IDLE), în timp ce citirea blocurilor ulterioare este declanșată de la cardReadCompletedCB().

Să vedem ce a ieșit din asta. Am adăugat în mod deliberat mici întârzieri între blocurile de citire, astfel încât să pot vedea astfel de crestături pe osciloscop. De fapt, este atât de puțin timp între citiri încât osciloscopul meu nu îl vede.

După cum puteți vedea din această poză, ideea a fost un succes. Operațiune nouă citirea începe imediat după ce cea anterioară s-a încheiat. Pauzele dintre citiri sunt destul de mici și sunt dictate în principal de gazdă (aceeași întârziere de 1 ms între tranzacții). Viteza medie de citire pentru fișiere mari ajunge la 400-440 kb/s, ceea ce este destul de bun. Și, în sfârșit, utilizarea procesorului este de aproximativ 2%.

Dar înregistrarea?

Până acum am evitat cu tact subiectul scrisului pe card. Dar acum, cu cunoștințele acumulate și înțelegerea driverului MSC, implementarea funcției de înregistrare nu ar trebui să fie dificilă.

Implementarea originală funcționează cam așa.

  • Tranzacție de scriere SCSI
    • Comanda este procesată prin lanțul de funcții MSC_BOT_DataOut() -> MSC_BOT_CBW_Decode() -> SCSI_ProcessCmd() -> SCSI_Write10()
    • Deoarece driverul se află în starea hmsc->bot_state == USBD_BOT_IDLE, procedura de scriere este pregătită: parametrii comenzii sunt verificați și câte blocuri vor trebui scrise sunt amintite
    • Este apelată funcția USBD_LL_PrepareReceive(), care pregătește perifericul USB pentru a primi un bloc de date.
    • Driverul intră în starea hmsc->bot_state = USBD_BOT_DATA_OUT
  • Tranzacție SCSI: Ieșire de date
    • Dispozitivul primește date în pachete de 64 de octeți și stochează datele în memoria tampon furnizată. Toate acestea se întâmplă la cel mai scăzut nivel din nucleul USB, driverul MSC nu este implicat în acest lucru
    • Când datele sunt primite, apare evenimentul Data Out și funcția SCSI_Write10() este apelată din nou
    • Deoarece driverul se află în starea hmsc->bot_state == USBD_BOT_DATA_OUT, controlul trece la funcția SCSI_ProcessWrite()
    • Aici este scris cardul. modul sincron
    • Dacă nu au fost încă primite toate datele, atunci recepția este „reîncărcată” apelând USBD_LL_PrepareReceive()
    • Dacă toate blocurile sunt scrise, atunci funcția MSC_BOT_SendCSW() este apelată care trimite o confirmare către gazdă (Control Status Word - CSW), iar driverul trece la starea USBD_BOT_IDLE
  • Tranzacție SCSI: Răspuns
    • În acest moment, pachetul de stare a fost deja trimis. Nici o actiune necesara
Mai întâi, să adaptăm implementarea originală la asincronia funcției Write(). Trebuie doar să împărțiți funcția SCSI_ProcessWrite() și să apelați a doua jumătate într-un apel invers.

Implementarea funcției de înregistrare

/** * @brief SCSI_ProcessWrite * Handle Write Process * @param lun: Numărul unității logice * @retval status */ static int8_t SCSI_ProcessWrite (USBD_HandleTypeDef *pdev, uint8_t lun) ( uint32_t len; USBD_MSC_Type_dsc_dsc_dsc> C; len = Min (hmsc-> scsi_blk_len, msc_media_packet);< 0) { SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, WRITE_FAULT); return -1; } return 0; } return 0; } void cardWriteCompletedCB(uint8_t res, void * context) { USBD_HandleTypeDef * pdev = (USBD_HandleTypeDef *)context; USBD_MSC_BOT_HandleTypeDef *hmsc = pdev->pClassDataMSC; uint8_t lun = hmsc->cbw.bLUN; uint32_t len ​​​​= MIN(hmsc->scsi_blk_len , MSC_MEDIA_PACKET); // Verificați mai întâi codul de eroare if(res != 0) ( SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, WRITE_FAULT); return; ) hmsc->scsi_blk_addr += len; hmsc->scsi_blk_len -= len; /* cazul 12: Ho = Do */ hmsc->csw.dDataResidue -= len; if (hmsc->scsi_blk_len == 0) ( MSC_BOT_SendCSW (pdev, USBD_CSW_CMD_PASSED); ) else ( /* Pregătiți EP pentru a primi pachetul următor */ USBD_LL_PrepareReceive (pdev, MSC_OUT_EP, hmsc,->_MINSc,__scsc, MEDIA_PACKET ));


La fel ca și în cazul citirii, trebuie să livrați cumva unele variabile de la prima funcție la a doua. Și pentru asta folosesc parametrul context și trec mânerul dispozitivului USB (puteți obține toate datele necesare de la acesta).

Viteza de înregistrare în acest mod este de aproximativ 90 kb/s și este limitată în principal de viteza de scriere pe card. Acest lucru este confirmat de oscilogramă - fiecare vârf este o înregistrare a unui bloc. Judecând după imagine, scrierea a 512 octeți durează de la 3 la 6 ms (diferiți de fiecare dată).

Mai mult decât atât, înregistrarea poate rămâne uneori de la 100ms la 0.5s - se pare că undeva în card este nevoie de diverse activități interne - remaparea blocurilor, ștergerea paginilor sau ceva de genul.

Pe baza acestui fapt, terminarea tamponului dublu este puțin probabil să îmbunătățească radical situația. Cu toate acestea, vom încerca în continuare să facem acest lucru doar din interes sportiv.

Deci, esența exercițiului este de a accepta următorul bloc de la gazdă în timp ce cel anterior este scris pe card. Opțiunea care îmi vine imediat în minte este să începeți să scrieți și să primiți simultan următorul bloc undeva în funcția SCSI_Write10(), adică. la evenimentul DataOut (următorul bloc a fost primit). Nimic nu va funcționa. deoarece recepția este mult mai rapidă decât scrierea și pot fi primite mai multe date decât poate scrie cardul. Acestea. următoarele date vor suprascrie datele primite anterior, dar neprocesate încă.


În această schemă, mai multe pachete pot fi primite la rând, dar nu toate vor avea timp să fie scrise pe cardul SD. Cel mai probabil, unele dintre date vor fi tăiate de următorul bloc.

Trebuie să faci sincronizare. Dar unde? În cazul unei operații de citire, am organizat dubla tamponare și sincronizare în punctul în care se termină citirea de pe card și datele sunt transferate pe USB. Acest loc a fost funcția cardReadCompletedCB(). În cazul unei operații de scriere, funcția SCSI_Write10() va fi un loc atât de central - aici ne vom găsi când vom primi următorul bloc de date și aici vom începe să scriem pe card.

Dar există o diferență fundamentală între funcțiile cardReadCompletedCB() și SCSI_Write10() - prima funcționează în fluxul cardului SD, iar a doua în întreruperea USB. Un fir normal poate fi suspendat în timp ce se așteaptă un eveniment sau un obiect de sincronizare. Acest truc nu va funcționa cu o întrerupere - toate funcțiile FreeRTOS cu sufixul FromISR sunt neblocante. Fie funcționează conform așteptărilor (se folosește resursa dacă este liberă, trimit/primește mesaje prin coadă dacă există spațiu sau un mesaj necesar), fie aceste funcții returnează o eroare. Dar ei nu așteaptă niciodată.

Dar dacă este imposibil să organizați o așteptare într-o întrerupere, atunci puteți încerca să vă asigurați că întreruperea nu este apelată din nou deloc. Mai exact, chiar și așa: astfel încât întreruperea să se producă exact de câte ori și în acele momente când avem nevoie de ea.

Să ne uităm la câteva cazuri care pot apărea în timpul procesului de admitere/înscriere.

Cazul nr. 1: primirea primului bloc. Imediat ce primul bloc este primit, puteți începe înregistrarea acestui bloc. În același timp, puteți începe să luați al doilea bloc. Acest lucru va elimina pauza atunci când nu acceptăm următorul bloc în timp ce cel anterior este scris pe card.

Cazul #2: Primirea unui bloc în mijlocul unei tranzacții. Cel mai probabil, ambele buffer-uri vor fi deja pline. Undeva în fluxul cardului SD, se scrie un bloc de date din primul bloc, în timp ce tocmai am primit al doilea bloc de la gazdă. În principiu, nimic nu vă împiedică să încărcați înregistrarea celui de-al doilea bloc - există o coadă la intrare (vezi mai sus funcția SD_MSC_Read()), care reglează cererile de intrare și va scrie blocuri pe rând. Trebuie doar să vă asigurați că există loc în această coadă pentru 2 cereri.

Dar cum să reglezi recepția? Avem doar 2 tampon de primire. Dacă începeți imediat să primiți următorul bloc după ce ați primit al doilea bloc, aceasta va suprascrie datele din primul buffer, unde cardul este în prezent scris. În acest caz, ar fi mai corect să începeți să primiți următorul bloc de date când tamponul devine liber - când se termină înregistrarea (adică în apelarea funcției de înregistrare).

În sfârșit, cazul nr. 3: trebuie să puteți finaliza corect procedura de admitere/înregistrare. Totul este clar cu ultimul bloc - în loc să primiți următorul bloc, trebuie să trimiteți gazdei CSW că datele au fost acceptate și tranzacția poate fi închisă. Dar trebuie să ne amintim că la începutul tranzacției am organizat deja un bloc suplimentar, deci penultimul bloc nu ar trebui să ordone primirea unui bloc suplimentar.

Iată o imagine care descrie aceste cazuri.


Cazul 1: la primul DataOut începem imediat să primim al doilea bloc. Cazul 2: începem să primim următorul bloc numai după ce înregistrarea este finalizată și buffer-ul este liber. Cazul 3: nu începem să primim pe penultima înregistrare, ci pe ultima trimitem CSW

O observație interesantă: dacă scrierea pe card vine din primul buffer, atunci la finalizarea scrierii, următorul bloc va fi primit în același primul buffer. Exact la fel cu al doilea buffer. Aș dori să profit de acest fapt în implementarea mea.

Să încercăm să ne punem în aplicare planurile. Pentru a implementa primul caz (primirea unui bloc suplimentar), avem nevoie de o stare specială

Stare nouă pentru primirea primului bloc

#define USBD_BOT_DATA_OUT_1ST 6 /* Starea de ieșire a datelor pentru primul bloc de recepție */


Și prelucrarea ei

/** * @brief MSC_BOT_DataOut * Procesează datele MSC OUT * @param pdev: instanța dispozitivului * @param epnum: indexul punctului final * @retval Niciunul */ void MSC_BOT_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum) (USBD_BOT_DataOut) (USBD_HandleTypeDef *pdev, uint8_t epnum) = USBOTDpe_f_Cdeh_BOTD_v_cdeh pClassDataMSC; comutator (hmsc->bot_state) (case USBD_BOT_IDLE: MSC_BOT_CBW_Decode(pdev); break; case USBD_BOT_DATA_OUT: case USBD_BOT_DATA_OUT_1ST: if(SCSI_ProcessCmd(pdev,cbhbs-UN,cbh)< 0) { MSC_BOT_SendCSW (pdev, USBD_CSW_CMD_FAILED); } break; default: break; } }


Pentru a implementa cel de-al doilea caz (primirea unui bloc la finalizarea înregistrării), trebuie să transmiteți cumva o anumită cantitate de informații către apel invers. Pentru a face acest lucru, am creat o structură cu un context de înregistrare și am declarat 2 instanțe ale acestei structuri în mânerul USB.

Contextul de înregistrare

typedef struct (uint32_t next_write_len; uint8_t * buf; USBD_HandleTypeDef * pdev; ) USBD_WriteBlockContext; typedef struct _USBD_MSC_BOT_HandleTypeDef ( ... USBD_WriteBlockContext write_ctxt; ... ) USBD_MSC_BOT_HandleTypeDef;


Trebuie să vă amintiți să modificați dimensiunea cozii de înregistrare în fluxul cardului SD

Se inițializează coada

// Inițializați firul responsabil pentru comunicarea cu cardul SD bool initSDIOThread() ( // Inițializați sincronizarea sdCmdQueue = xQueueCreate(2, sizeof(IOMsg)); … )


Funcția SCSI_Write10() s-a schimbat puțin, au fost adăugate doar inițializarea indexului tampon dublu și trecerea la starea USBD_BOT_DATA_OUT_1ST

Funcția SCSI_Write10().

/** * @brief SCSI_Write10 * Comandă de procesare Write10 * @param lun: Numărul unității logice * @param params: Parametrii comenzii * @retval status */ static int8_t SCSI_Write10 (USBD_HandleTypeDef *pdev, uint8_t lun , uint8_BOT *Def_MSC_T) hmsc = pdev->pClassDataMSC if (hmsc->bot_state == USBD_BOT_IDLE) /* Idle */ ( // Verificarea parametrilor ... hmsc->scsi_blk_addr = ... hmsc->scsi_blk_len = ... /* Pregătiți EP pentru a primi primul pachet de date */ hmsc->bot_state = USBD_BOT_DATA_OUT_1ST; */ ( returnează SCSI_ProcessWrite(pdev, lun); ) returnează 0;


Toată logica cea mai interesantă va fi concentrată în funcția SCSI_ProcessWrite() - aici vor fi distribuite tampoanele și va fi construit întreg lanțul de citiri și scrieri.

Funcția SCSI_ProcessWrite().

/** * @brief SCSI_ProcessWrite * Handle Write Process * @param lun: Numărul unității logice * @retval status */ static int8_t SCSI_ProcessWrite (USBD_HandleTypeDef *pdev, uint8_t lun) ( USBD_MSC_BOT_HandleTypeType_t_csp ro = MIN (hmsc->scsi_blk_len , MSC_MEDIA_PACKET * ctxt = hmsc->write_ctxt + hmsc->bot_data_idx // Aflați ce să faceți după ce scrieți blocul if(hmsc->scsi_>-)_ next_write_len = 0xffffffff ) else if(hmsc->scsi_blk_len == len + MSC_MEDIA_PACKET) ( ctxt->next_write_len = 0; ) else ( ctxt->next_write_len = MIN(hmKET___) MEDIA_PACKET ) / / Pregătiți alte câmpuri din context ctxt->buf = hmsc->bot_data + hmsc->bot_data_idx * MSC_MEDIA_PACKET ctxt->pdev = pdev // Nu permiteți mai multe recepții simultan if(hmsc->bot_state != USBD_BOT; ) PDEV-> PCLASSSPECIFICINTRAȚIA ctxt)< 0) { SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, WRITE_FAULT); return -1; } // Switching blocks hmsc->bot_data_idx ^= 1; hmsc->scsi_blk_addr += len; hmsc->scsi_blk_len -= len; /* cazul 12: Ho = Do */ hmsc->csw.dDataResidue -= len; // Efectuarea unei primiri suplimentare pentru prima dată pentru a rula operațiuni de primire și scriere în paralel if(hmsc->bot_state == USBD_BOT_DATA_OUT_1ST && hmsc->scsi_blk_len != 0) ( hmsc->bot_state = USBD_BOT_DATA_OUT; USBD_OUT; MSC_OUT_EP, hmsc->bot_data + hmsc->bot_data_idx * MSC_MEDIA_PACKET, // Al doilea buffer MIN (hmsc->scsi_blk_len, MSC_MEDIA_PACKET) ) returnează 0; )


În primul rând, contextul de înregistrare este pregătit aici - informații care vor fi transmise apelului invers. În special, aici decidem ce vom face când se termină înregistrarea acestui bloc:
  • în cazul obișnuit, vom începe să primim următorul bloc în același buffer (cazul nr. 2 descris mai sus)
  • În cazul penultimului bloc nu vom face nimic (cazul nr. 3)
  • În cazul ultimului bloc, vom trimite un Control Status Word (CSW) - un raport către gazdă cu privire la starea operațiunii
După ce un bloc de date este trimis în coada de scriere de pe card, indexul bufferului (bot_data_idx) este comutat la unul alternativ. Acestea. următorul pachet va fi primit într-un alt buffer.

În sfârșit, un caz special (cazul nr. 1) - organizăm recepția de date suplimentare în cazul primului bloc (starea USBD_BOT_DATA_OUT_1ST)

Partea de răspuns a acestui cod este un apel invers despre finalizarea scrierii pe card. În funcție de blocul înregistrat, fie se primește următorul bloc, se trimite un CSW, fie nu se întâmplă nimic.

Funcția de înregistrare apel invers

void cardWriteCompletedCB(uint8_t res, void * context) ( USBD_WriteBlockContext * ctxt = (USBD_WriteBlockContext*)context; USBD_HandleTypeDef * pdev = ctxt->pdev; USBD_MSC_BOT_HandleTypeType>Cdevm =CdevmscDef = hmsc->cbw.bL UN; / Verificați mai întâi codul de eroare if(res != 0) ( SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, WRITE_FAULT); return; ) if (ctxt->next_write_len == 0xffffffff) ( MSC_BOT_SendCSW (pdev, USBD_CSW_de); CMD- else (pdev, USBD_CSW_de); >pClassSpecificInterfaceMSC->OnFinishOp(); if(ctxt->next_write_len != 0) ( /* Pregătește EP pentru a primi următorul pachet */ USBD_LL_PrepareReceive (pdev, MSC_OUT_EP, ctxt->buf, ctxt)->_next);


Coarda finală este sincronizarea, a cărei esență este mai ușor de arătat în imagine.

Foarte rar, dar uneori apare o situație când scrierea pe card se termină înainte de primirea următorului pachet. Drept urmare, codul (dacă nu ar exista sincronizare) ar putea solicita primirea unui alt pachet, deși cel actual nu a fost încă primit în totalitate. Pentru a preveni acest lucru, a trebuit să adăugăm sincronizarea. Acum, înainte de a solicita primirea următorului bloc, codul va aștepta până când cel anterior a terminat de primit. Instrumentele de sincronizare care au fost folosite în timpul citirii (OnStartOp()/OnFinishOp()) sunt destul de potrivite.

Condițiile în care trebuie să sincronizezi sunt destul de complicate. Prin primirea unui bloc suplimentar la începutul tranzacției, sincronizarea are loc cu o schimbare de un bloc. Prin urmare, apelul invers pentru scrierea celui de-al N-lea bloc așteaptă să fie primite N+1 blocuri. Acest lucru înseamnă, la rândul său, că recepția primului bloc (are loc în contextul unei întreruperi USB) și scrierea ultimului (are loc în contextul unui flux de card SD) nu necesită sincronizare.

Poate părea că săgeata roșie o dublează pe cea neagră, care începe să înregistreze următorul bloc. Dar dacă te uiți la cod, poți vedea că nu este cazul. Roșu (sincronizare) sincronizează codul din driverul MSC ( pătrat albastru), în timp ce coada este procesată în driverul cardului (unde se află bucla principală a firului cardului SD). Nu am vrut cu adevărat să interferez cu codul diferitelor componente.

Am configurat câteva înregistrări de depanare, înregistrarea a 4 kb de date arată cam așa

Bloc de 4 kb intrare jurnal de depanare

Se începe operația de scriere pentru LBA=0041C600, len=4096
Primirea primului bloc în buf=1
Bloc de scriere de date pentru LBA=0041C600, len=512, buf=0
Acesta va fi un bloc obișnuit
Primirea unui bloc suplimentar în buf=1
Bloc de scriere de date pentru LBA=0041C800, len=512, buf=1
Acesta va fi un bloc obișnuit


Bloc de scriere de date pentru LBA=0041CA00, len=512, buf=0
Acesta va fi un bloc obișnuit


Bloc de scriere de date pentru LBA=0041CC00, len=512, buf=1
Acesta va fi un bloc obișnuit
Scrieți apel invers finalizat cu starea 0 (buf=0)
Se pregătește următoarea recepție în buf=0
Bloc de scriere de date pentru LBA=0041CE00, len=512, buf=0
Acesta va fi un bloc obișnuit
Scrieți apel invers finalizat cu starea 0 (buf=1)
Se pregătește următoarea primire în buf=1
Bloc de scriere de date pentru LBA=0041D000, len=512, buf=1
Acesta va fi un bloc obișnuit
Scrieți apel invers finalizat cu starea 0 (buf=0)
Se pregătește următoarea recepție în buf=0
Bloc de scriere de date pentru LBA=0041D200, len=512, buf=0
Acesta va fi unul înainte de ultimul bloc
Scrieți apel invers finalizat cu starea 0 (buf=1)
Se pregătește următoarea primire în buf=1
Bloc de scriere de date pentru LBA=0041D400, len=512, buf=1
Acesta va fi ultimul bloc
Scrieți apel invers finalizat cu starea 0 (buf=0)
Scrieți apel invers finalizat cu starea 0 (buf=1)
Scrieți terminat. Se trimite CSW


După cum era de așteptat, acest lucru nu a adăugat o creștere semnificativă a vitezei. După modificare, viteza a fost de 95-100 kb/s. Dar, după cum am spus, totul a fost făcut din interes sportiv.

Este posibil și mai repede?

Sa incercam. Undeva în mijlocul lucrării, am observat din greșeală că citirea unui bloc și citirea unei secvențe de blocuri sunt comenzi diferite pentru cardul SD. Sunt chiar prezentate metode diferite drivere de card - readBlock() și readBlocks(). În același mod, comenzile pentru scrierea unui singur bloc și scrierea unei serii de blocuri diferă.

Deoarece driverul MSC este proiectat să funcționeze implicit cu un bloc pe unitate de timp, era logic să înlocuiți readBlocks() cu readBlock(). Spre surprinderea mea, viteza de citire a crescut chiar și a ajuns la 480-500kb/s! Din păcate, un truc similar cu funcțiile de înregistrare nu a dat nicio creștere a vitezei.

Dar de la bun început am fost chinuit de o singură întrebare. Să aruncăm o altă privire la imaginea de lectură. Între crestături (citirea unui bloc) - aproximativ 2 ms.

Ceasul meu SPI este setat la 18 MHz (folosind un divizor de frecvență de bază de 72 MHz cu 4). Teoretic, un transfer de 512 octeți ar trebui să ia 512 octeți * 8 biți / 18 MHz = 228 µs. Da, va exista o anumită suprasarcină pentru sincronizarea mai multor fire, coadă și alte lucruri, dar asta nu explică în niciun caz diferența de aproape 10 ori!

Folosind un osciloscop, am măsurat cât timp au durat de fapt diferitele părți ale operației de citire.

Spre surprinderea mea, s-a dovedit că cea mai lungă operație nu este citirea de date, ci intervalul dintre comanda de citire și confirmarea de pe card că cardul este gata și datele pot fi citite. Mai mult, acest interval variază foarte mult în funcție de diverși parametri - frecvența solicitărilor, dimensiunea datelor citite, precum și adresa blocului citit. Ultimul punct este foarte interesant - cu cât blocul care trebuie citit este mai departe de începutul hărții, cu atât este citit mai repede (cel puțin acesta a fost cazul pentru harta mea experimentală)

O imagine similară (dar mai tristă) este observată la înregistrarea pe un card. Nu am reușit să măsoare suficient de bine toate timpii, pentru că... au înotat pe o gamă destul de largă, dar arată cam așa.

Toate acestea sunt agravate de o încărcare destul de mare a procesorului - aproximativ 75%. Scrierea în sine ar trebui, teoretic, să dureze aceleași 228 de microsecunde ca și citirea - sunt tactate la aceeași frecvență de 18 MHz. Numai în acest caz apare în continuare sincronizarea firului FreeRTOS. Aparent, din cauza încărcării mari a procesorului și a necesității de a trece la alte fire de execuție (cu prioritate mai mare), timpul total este mult mai lung.

Dar cea mai mare tristețe este așteptarea ca cardul să fie gata. Este de multe ori mai mare decât în ​​cazul lecturii. Mai mult, aici cardul se poate bloca timp de 100 sau chiar 500 ms. În plus, în driverul cardului această parte este implementată prin așteptare activă, ceea ce duce la acea încărcare foarte mare a procesorului

Aștept activ ca cardul să fie gata

// așteptați ca cardul să nu fie ocupat bool SdSpiCard::waitNotBusy(uint16_t timeoutMS) ( uint16_t t0 = curTimeMS(); while (spiReceive() != 0XFF) ( if (isTimedOut(t0, timeoutMS)) ( returnează fals; ) ) returnează adevărat;


Există ramuri în cod care vor adăuga un apel la SysCall::yield() în interiorul buclei, dar mă tem că acest lucru nu va rezolva situația. Acest apel pur și simplu recomandă ca programatorul de activități să comute la un alt fir. Dar, din moment ce celelalte fire ale mele sunt în mare parte adormite, acest lucru nu va îmbunătăți dramatic situația - cardul nu va înceta să fie prost.

Un alt moment amuzant. În FreeRTOS, contextele sunt schimbate folosind întreruperea SysTick, care este setată implicit la 1 ms. Din acest motiv, multe operații pe osciloscop sunt aliniate cu grijă pe grilă cu un pas multiplu de 1 ms. Dacă cartela nu este lentă și citirea unui bloc împreună cu așteptarea durează mai puțin de 1 ms, atunci incluzând toate firele, sincronizările și cozile, vă puteți întoarce într-o singură bifare. Prin urmare, viteza maximă teoretică de citire într-un astfel de model este exact de 500 kb/s (0,5 kb la 1 ms). Vestea bună este că se realizează!


Dar chestia asta poate fi ocolită. Nivelarea de 1 ms are loc din următorul motiv. O întrerupere de la USB sau DMA nu este legată de nimic și poate apărea undeva în mijlocul unei căpușe. Dacă întreruperea a schimbat starea obiectului de sincronizare (de exemplu, a deblocat un semafor sau a adăugat un mesaj în coadă), atunci FreeRTOS nu va ști imediat despre asta. Când întreruperea își face treaba, controlul este transferat firului care funcționa înainte de întrerupere. Când bifa se termină, planificatorul este apelat și, în funcție de starea obiectului de sincronizare, acesta poate comuta la firul corespunzător.

Dar doar pentru astfel de cazuri, FreeRTOS are un mecanism pentru a forța apelarea planificatorului. După cum am spus mai devreme, nu poți întrerupe o întrerupere. Dar puteți indica necesitatea apelării programatorului (subliniez: nu sunați planificatorul, ci indicați necesitatea apelului). Acesta este exact ceea ce face funcția portYIELD_FROM_ISR().

Cereți programatorului să schimbe firele imediat după întrerupere

void SdFatSPIDriver::dmaTransferCompletedCB() ( // Reluați firul SD BaseType_t xHigherPriorityTaskWoken; xSemaphoreGiveFromISR(xSema, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xTaskPriority)Woken);


Acum, când procesarea întreruperii (să zicem, de la DMA) se termină, întreruperea PendSV va fi apelată automat, în al cărui handler este apelat planificatorul. Aceasta, la rândul său, va schimba forțat contextul și va transfera controlul către firul care aștepta semaforul. Acea. Timpul de răspuns la o întrerupere poate fi redus semnificativ și, ca rezultat, acest truc vă permite să overclockați citirea pe cardul de testare până la 600 kb/s!

Dar asta dacă nu este o așteptare lungă pentru ca cardul să fie gata. Din păcate, dacă cardul se gândește mult timp, atunci citirea se întinde cu 2 bifături (și scrierea cu 4-6) și viteza se dovedește a fi semnificativ mai mică. Mai mult, dacă codul de așteptare activ este în mod constant introdus în card, iar cardul nu răspunde mult timp, atunci poate trece o bifă întreagă. În acest caz, programatorul OS poate decide că acest fir rulează de prea mult timp și, în general, poate comuta controlul la alte fire. Acest lucru poate provoca o întârziere suplimentară.

Apropo, am testat toate acestea pe un card de 8GB Class 6, am încercat și alte câteva carduri pe care le aveam la îndemână. Un alt card, tot de 8GB, dar clasa 10 din anumite motive a dat doar 300-350 kb/s pentru citire, dar 120 kb/s pentru scriere. Chiar mi-am riscat să instalez cel mai mare și mai rapid card pe care îl aveam - 32GB. Cu ea am reușit să realizăm viteze maxime- 650 kb/s pentru citire și 120 kb/s pentru scriere. Apropo, vitezele pe care le dau sunt medii. Nu aveam ce măsura viteza instantanee.

Ce concluzii se pot trage din această analiză?

  • În primul rând, SPI nu este în mod clar o interfață nativă pentru cardurile SD. Chiar și cărțile cool sunt plictisitoare în cele mai comune operațiuni. Aici are sens să te uiți spre SDIO (am luat deja un pachet cu STM32F103RCT6 la oficiul poștal - are suport SDIO din cutie)
  • În al doilea rând, harta este diferită. Va trebui să-l cauți pe acela. Deși atunci când este conectat prin SDIO, acest lucru nu va fi atât de critic
  • În al treilea rând, dacă aveți suficientă memorie, puteți trece la citirea în blocuri mai mari (să zicem 4k). Apoi, întârzierea mare de la începutul citirii/scrierii va fi compensată de viteza mare de transfer. Până acum, cei 20 kb de memorie ai controlerului meu (STM32F103C8T6) sunt aproape umpluți la capacitate maximă și am avut dificultăți în a găsi chiar și 512 octeți pentru tamponarea dublă

Concluzie

În acest articol am povestit cum am reușit să fac upgrade la implementarea USB MSC de la STMicroelectronics. Spre deosebire de alte serii de microcontrolere STM32, seria F103 nu are suport DMA încorporat pentru USB. Dar cu ajutorul FreeRTOS am reușit să activez citirea/scrierea cardului SD prin DMA. Ei bine, pentru a-l folosi cât mai eficient debitului Am reușit să instalez dublu buffering pe magistrala USB.

Rezultatul a depășit așteptările mele. Inițial, țintesam la o viteză de aproximativ 400 kb/s, dar am reușit să stoarce până la 650 kb/s. Dar pentru mine, nici măcar indicatorii de viteză absoluti sunt importanți, ci faptul că această viteză este atinsă cu o intervenție minimă a procesorului. Deci datele sunt transferate folosind periferice DMA și USB, iar procesorul este conectat doar pentru a încărca următoarea operație.

Cu înregistrare, însă, nu s-a putut obține super viteze - doar 100-120 kb/s. Motivul pentru acest lucru este timpul uriaș de expirare a cardului SD în sine. Ei bine, din moment ce cardul este conectat prin SPI, se pare că nu există altă modalitate de a afla dacă cardul este gata (în afară de sondajul constant). Din acest motiv, există o sarcină destul de mare a procesorului în operațiunile de scriere. Am o speranță secretă că prin conectarea cardului prin SDIO puteți obține viteze mult mai mari.

Am încercat nu numai să ofer codul, ci și să spun cum funcționează și de ce funcționează în acest fel. Poate că acest lucru vă va ajuta să faceți ceva similar pentru alte controlere sau biblioteci. Nu l-am alocat unei biblioteci separate, pentru că... acest cod depinde de alte părți ale proiectului meu și de biblioteca FreeRTOS. Mai mult, mi-am bazat codul pe . Deci, dacă doriți să utilizați versiunea mea, va trebui să o portați înapoi în biblioteca originală.

  • hal
  • msc
  • stocare în masă
  • Adaugă etichete

    În articolul precedent am încercat să descriem haosul care a avut loc în 1998-2003. în domeniul protocoalelor pentru împerecherea jucătorilor cu computerele. Perioada 2003-2004 a devenit o perioadă de raționalizare pentru industrie. Multe fluxuri de protocoale proprietare au fost înlocuite cu trei fluxuri armonioase. Mai exact, două și jumătate, pentru că... două dintre aceste fluxuri au fost variații ale aceluiași protocol.

    Acestea erau:

    1. protocol „pur” de stocare în masă
    2. Protocol de stocare în masă cu configurație software suplimentară
    3. Protocolul MTP.

    Pure Mass Storage a fost folosit de cel mai mare număr de producători, în special de companiile mici din Coreea sau China. În Rusia a devenit cel mai popular protocol. Povestea noastră de astăzi este despre el.

    Pentru a desemna stocarea în masă „în viața de zi cu zi”, sunt utilizate două abrevieri - MSC și UMS. MSC (Mass Storage Class) este oficială, iar UMS (opțiuni posibile de decodare: USB/Universal Mass Storage) este „folk”. Ele nu se contrazic, ci se completează reciproc.

    MSC raportează că protocolul este una dintre „clasele de dispozitive” standard aprobate în cadrul specificației USB și, prin urmare, este un standard industrial de jure. UMS vorbește despre universalitatea protocolului, care astăzi este susținut de majoritatea sistemelor de operare și de nenumărate dispozitive finale, făcându-l un standard de facto. Opțiunea de decodare UMS ca USB Mass Storage completează aceste informații, specificând că interfața USB este utilizată ca linie fizică. Literele MS (Mass Storage), comune tuturor abrevierilor, indică faptul că acesta este un protocol conceput pentru a funcționa cu dispozitive de stocare pentru cantități mari de date. Pentru ei a fost dezvoltat acest standard - pentru unități flash, cititoare de carduri, unități HDD mobile. Cum a ajuns în playerele portabile?

    ProtocolMasaStocarea a fost concepută în primul rând pentru astfel de dispozitive. Apariția lui înPlayerele MP3 au fost un pas forțat

    În materialele anterioare, am vorbit în mod repetat despre modul în care playerele audio portabile au apărut și au început să se dezvolte în mod spontan și neașteptat. Industria pur și simplu nu a vrut să le observe, mai întâi din cauza marginalității lor, mai târziu din cauza conexiunii exagerate a acestor dispozitive cu pirateria digitală. Acest lucru a avut multe consecințe, iar una dintre ele a fost că jucătorii au fost „ocoliți” atunci când distribuiau clase de dispozitive USB.

    Să aruncăm o privire la lista acestor clase: există atât plăci de sunet externe, cât și dispozitive de comunicare, și o clasă separată pentru periferice precum mouse-uri și tastaturi, există și clase pentru imprimante, hub-uri USB, camere web, adaptoare wireless. Există și o clasă pentru camere digitale. Și doar playerele audio și multimedia au rămas în categoria „altele”.

    Dintre clasele standardUSB-ul are un loc pentru o varietate de dispozitive. Dar nu pentru playere multimedia

    Timp de cinci ani buni, nimeni nu s-a gândit să dezvolte o clasă separată pentru ei. Producătorii trebuiau să aleagă dintre ceea ce era disponibil.

    În acest context, MSC/UMS a fost singura soluție universală. Dacă vă limitați sarcinile exclusiv la încărcarea „prost” a conținutului în player, atunci nu mai este nevoie de nimic. În plus, protocolul a făcut posibilă transformarea playerului într-un dispozitiv de stocare mobil. Vânzătorii și cumpărătorii obișnuiți chiar și acum descriu jucătorii cu acest protocol ca „lucrând ca o unitate flash”, „se conectează ca o unitate flash”, „nu este nevoie să instalați niciun program”, „puteți stoca fișiere” etc. și așa mai departe.

    O simplă unitate de redare – „MP3-Stick” siMasaProtocol de stocare – făcut unul pentru celălalt

    Această capacitate suplimentară se potrivește bine cu abordarea all-in-one adoptată de producătorii asiatici de playere MP3. Ei au fost pionierii în adaptarea MSC/UMS în playere audio. Ei și Sigmatel, a cărui platformă STMP3400 a început să accepte acest protocol la începutul anului 2003.

    ianuarie 2003 -Sigmatel anunță suportMasaDepozitarea pe platformele lorD-Major

    Avantajele protocolului. Principalul lucru este simplitatea: toate operațiunile sunt efectuate prin shell-uri de fișiere standard, inclusiv. Windows Explorer (Explorer), nu sunt necesare cunoștințe sau pregătire suplimentară pentru a lucra cu acesta.

    Prevalență - Windows Me și 2000 aveau deja suport de bază pentru protocol, Windows XP îl suporta complet. Multe alte sisteme de operare - MacOS, Linux etc. – compatibil cu stocarea în masă.

    OS care acceptă într-o formă sau altaMasaProtocol de stocare

    Astăzi este mai greu să găsești un PC care să nu accepte acest protocol. Suportul în acest caz înseamnă prezența driverelor de protocol ca parte a sistemului de operare.

    MasaPlayer de stocare pe hard disk de 1,8”.Toshiba conectat la PC. CumDispozitivul MSC folosește un driver standardUSBSTOR.SYS, care face parte din sistemul de operare. Ca unitate, folosește și drivere standardWindows. Nu este necesară instalarea driverului suplimentar.

    Întrucât toate lucrările cu conținut se desfășoară, de asemenea, folosind mijloace standard, prin Windows Explorer, utilizatorul nu trebuie să instaleze nimic: tot suportul pentru protocol este deja încorporat în sistemul de operare.

    Jucătorul este vizibil în ExplorerWindows ca altul HDD. Toate lucrările cu conținut sunt efectuate în Explorer sau în oricare manager de fișiere la alegerea utilizatorului. Instalarea suplimentară software nu este necesar

    Se dovedește a fi un adevărat Plug-and-Play: scoate-l din cutie, conectează-l și folosește-l. În ceea ce privește parametrii precum transparența și invizibilitatea pentru utilizator, acest protocol pur și simplu nu are egal.

    În ceea ce privește compatibilitatea cu dispozitive portabile Totul este în regulă și la noi: protocolul nu depinde de sistemele de fișiere și poate funcționa cu oricare dintre ele dacă este acceptat de sistemul de operare.

    De asemenea, este important să existe o specificație de gazdă USB (în mișcare) care vă permite să conectați dispozitive de stocare în masă la alte dispozitive portabile (și neportabile). Astăzi, un player compatibil MSC poate fi conectat la o gamă largă de dispozitive, fie el consolă de jocuri, sistem stereo, radio auto, transmițător FM, alt player.

    Mașinile câștigă popularitateTransmițătoare FM care vă permit să conectați oricareMasaPlayer de stocare

    Dezavantajele protocolului sunt o continuare a avantajelor acestuia. Funcționalitatea sa este de bază, primitivă. De fapt, nu este capabil de altceva decât să copieze date înainte și înapoi.

    Dar datele cu care se ocupă jucătorul sunt mai mult decât un set de caractere binare; Fiecare bucată de conținut, fie că este o melodie sau un fișier, are o serie de proprietăți, cum ar fi titlul, formatul, autorul, durata etc. Unitățile individuale pot face parte din agregate mai complexe, cum ar fi un album sau o listă de redare.

    Mass Storage nu știe despre așa ceva, ceea ce pune toate grijile legate de gestionarea conținutului fie asupra utilizatorului, fie asupra software-ului încorporat al jucătorului. Acesta din urmă este cel mai adesea incapabil să facă față în mod eficient sarcinii de a gestiona o cantitate mare de conținut. Ca urmare, majoritatea jucătorilor MSC/UMS au un mecanism de navigare extrem de slab - prin foldere, similar cu navigarea în Windows Explorer. În același timp, o cantitate semnificativă de informații conținute în metadate și etichete, care este convenabilă pentru clasificarea conținutului, nu este utilizată.

    Informații care pot fi conținute în etichete (folosind exemplul unui programMP3etichetă). Puțin din acest lucru este folosit înJucători de stocare în masă

    Utilizatorul este obligat să-și organizeze conținutul în mod independent, folosind un sistem de subdosare. În același timp, după ce a ales, de exemplu, sistemul de clasificare „autor-album-melodie”, el nu va putea trece rapid și fără durere la sistemul „genul cântecului” sau „anul înregistrării cântecului”. va trebui să zguduie întreaga bibliotecă.

    Organizarea listelor de redare la astfel de jucători este foarte slabă în ceea ce privește capacitățile sale. De obicei, există o singură listă de redare posibilă. Lucrul cu listele de redare este atribuit exclusiv dispozitivului în sine, iar dacă unul dintre fișierele incluse în lista de redare a fost șters prin intermediul unui computer în modul MSC/UMS, acest lucru poate perturba funcționarea întregii liste în ansamblu. O astfel de caracteristică premium precum afișarea copertei albumului (art album sau jachetă) nu este disponibilă în principiu jucătorilor „puri” MSC/UMS. Teoretic, poate fi implementat prin încărcare fisier grafic din folder, dar în practică nimeni nu a făcut asta încă. Și dacă o face, utilizatorul va trebui să pună manual imaginile corespunzătoare în toate folderele. Unii jucători au capacitatea de a afișa cuvintele unei melodii (Versuri), dar aceste cuvinte nu sunt preluate din metadate: utilizatorul trebuie să le pregătească independent folosind un program special.

    Aceasta este toată problema principală a stocării în masă. Playerul este mai mult decât un simplu dispozitiv de stocare mobil pentru munca eficienta el trebuie să aibă o înțelegere profundă a ceea ce este de fapt stocat în memoria lui. Fiind un dispozitiv multimedia modern conceput pentru o gamă largă de utilizatori, nu poate pur și simplu bolborosi tot ce este înregistrat pe el. El trebuie să fie capabil să spună ce urmărim sau ascultăm și pe scurt, cuprinzător și discret, ca un animator de înaltă clasă. El, ca un bibliotecar experimentat, ar trebui să ne ajute să găsim rapid printre mii de cântece exact ceea ce avem nevoie, chiar dacă am uitat numele. În toate acestea, protocolul MSC/UMS, care este extrem de limitat în capacitățile sale, nu îi este de ajutor. Și o nouă lovitură incendiară, o teză de master și un fișier de schimb Windows pentru el sunt doar o serie de date fără chip. Acest lucru transformă protocolul într-un fel de blocaj depersonalizant între două sisteme multimedia puternice - player și PC. Întreaga povară a transformării fluxului de informații fără chip într-o formă ușor de utilizat cade pe umerii acestuia din urmă.

    Pe un PC, totul depinde de utilizatorul însuși: dacă dă dovadă de perseverență și ingeniozitate, va organiza o bibliotecă muzicală care va fi invidia tuturor. Sau poate aruncați totul într-un singur folder până când devine complet imposibil să găsiți ceva acolo.

    De jucător, totul depinde de dezvoltator și nu sunt deloc dornici să facă eforturi mari pentru a dezvolta un software puternic. Drept urmare, animatorul de la astfel de jucători este atât de așa - cu o voce monotonă, va citi titlul melodiei, autorul, cel mai bun scenariu- album. Sau poate doar numele fișierului.

    InterfațăiPod versus interfațăMasaStocare pentru jucătorifluviulh300 arată mai spartan, dar în același timp afișează mult mai multe informații despre piesa redată. În același timp, jucătorul de lairiver este încă un exemplu relativ de succesMasaStocare pentru jucători

    Și este un bibliotecar inutil - așa că, cu mâna, va indica direcția în care să se uite, dar nimic mai mult.

    UtilizatorMasaStocare pentru jucători (CowonX5 în acest caz, în stânga) atunci când se caută o compoziție de interes, poate fi ghidat doar de logica folderelor și fișierelor create de el însuși. Dacă se aplică alte soluții (ca înCreativZenAtingeți, în dreapta) au capacitatea de a căuta liber după parametri

    Există câteva excepții (de exemplu, jucători de la Archos), dar nu sunt multe.

    Această situație are o consecință foarte simplă. Utilizatorii care sunt familiarizați cu un computer, obișnuiți cu conceptul de fișiere și foldere, nu foarte pretențioși la efectele externe și se adaptează rapid la lucruri noi, sunt puternic în favoarea stocării în masă pură. Transparența, deschiderea și utilizarea pe scară largă a protocolului sunt avantaje pentru ei, alături de care toate dezavantajele palesc.

    Companiiiriu siCowon își datorează popularitatea în rândul anumitor segmente de clienți, nu în ultimul rând sprijinului „gratuit”Stocare în masă

    Dar notorii utilizatori „obișnuiți” nu sunt foarte fericiți. Pentru ei, un player nu este încă o unitate flash, nu este stocare pentru fișiere, ci un player. Nu au dorința de a construi cu grijă o piramidă de fișiere și foldere în biblioteca lor muzicală și nici nu doresc să rătăcească prin adâncurile acestei piramide pe ecranul playerului, concentrându-se doar pe numele folderelor. Navigare prin metadate, redare cu imagini frumoase ale albumului, încărcare automată a melodiilor noi pe player - toate acestea sunt mult mai aproape de ele. O dovadă în acest sens este un număr semnificativ de playere MSC/UMS returnate în magazin și schimbate cu iPod-uri în Statele Unite.

    Cu toate acestea, industria este stabilită de producători care nu folosesc purMasaDepozitare

    Există o altă categorie de nemulțumiți de protocol. Acestea sunt studiouri de înregistrare și studiouri de film. Indiferent la orice, Mass Storage nu va putea, cu siguranță, să distingă o piesă „piratată” de una cumpărată cinstit. Un producător ai cărui jucători acceptă descărcarea de conținut prin stocarea în masă „pură” cu greu poate conta pe o cooperare fructuoasă cu casele de discuri majore. Cu siguranță, firme mici nu este chiar nevoie. Dar marile corporații care doresc să ofere utilizatorilor o soluție verticală, inclusiv achiziționarea de conținut, sunt nevoite să țină cont de acest factor.

    Ca rezultat, un producător care dorește să creeze un jucător care:

      Atrăgător pentru „utilizatorul obișnuit” datorită greutății sale, lucru automatizat cu el, navigare convenabilă, rapidă și frumoasă, utilizarea eficientă a metadatelor

      Nu ar provoca respingere în rândul studiourilor de film și înregistrări, ceea ce ar face posibilă organizarea achiziției de conținut care este convenabil pentru acești „utilizatori obișnuiți” (și profitabil pentru companie)

      Nu necesită investiții mari în dezvoltare, atât în ​​timp, cât și în calificările programatorilor

    – forțat să caute soluții dincolo de capacitățile stocării în masă „pure”.