Darau USB divaisą

Posted: 2015-12-08 in Darbeliai
Žymos:, ,

Iki šiol gyvenime neteko nieko daryti su USB divaisais. Tame programuotojo lygmenyje, kur su jais reikia kažkaip „šnekėtis“. Na, meluoju, šiek tiek teko. Teko ir USB steką išsinagrinėti gan nuodugniai. Bet programuoti ne kažką teko. Tiesą sakant, niekada ir nenorėjau per daug, nes USB dėl savo nepaprasto lankstumo yra sušiktai sudėtingas dalykas. Tikrai sudėtingas. Ale va, gyvenimas eina, reikia tobulėt…

Pasižiūrėjau STM32 USB-FS pavyzdukų krūvą. Dauguma tų pavyzdukų yra be rimtesnių paaiškinimų, kas ir kaip. T.y. pavyzdukai tai veikia, bet visa kita reikia išrūkyti iš kitur.

Kadangi išsėdėjau nemažai laiko nagrinėdamasis USB subtilybes, tai šį tą papasakosiu ir jumi. Beje, visada sakydavau, kad gabumai mokslui yra susiję ne su galva, o su subine — turi tiek, kiek išsėdi. Turėk kokią nori gerą galvą, jei subinės vietoj neišlaikysi — neišmoksi. Man pagal profesiją subinė yra pirmoj vietoj ir dėl to yra gerai ištreniruota.

Labai daug nesiplėsiu. Ai, o gal ir plėsiuos. Labai dažnai, kai reikia pagaminti kokį paprastą USB aparatą, renkamasi HID profilis (Human Interface Device). Anot internetų „jam nereikia draiverių“. Jo jo, nereikia. Išskyrus tai, kad kadangi daromas koks nors Custom HID, o ne klaviatūra/pelė/linksmakotis, vis tiek kompiuteris pats savaime nežino, ką su tuo daiktu veikti. Mano turėtas Dali-USB valdiklis irgi buvo HID.

Aš turiu fantaziją pasidaryti aparačiuką, kuris turėtų du kanalus. Vienas būtų panašus į nuoseklųjį prievadą, na, kaip koks USB UART. Kitas kanalas būtų perdavinėti mažutėms dvejetainėms žinutėms. Galima būtų išsiversti su HID abiems atvejams. Tiesa, yra vienas HID apribojimas: maksimalus duomenų paketo dydis gali būti tik 64 baitai. Aišku, net ir tekstui perdavinėti to užtenka, juk visokiems debugams keliolikos simbolių pakanka. Bet tai reikštų, kad iš kompiuterio pusės turėtų būti viena programa/gija dirbanti su abiejų tipų duomenimis. Todėl mano tikslas — sudėtinis (composite) USB įrenginys. Na, kompiuteris jį matys tarsi du įrenginius. Aš čia dabar turiu tokią pelytę, kuri apsimeta ir pelyte, ir tuo pačiu klaviatūra — kad galėtų WIN klavišo paspaudimą siųstelėti. Kompiuteris ją mato, kaip du įrenginius, du skirtingus HID profilius. Na, dar trečias yra, bet ten jau nežinau, kas.

USB-FS kaip tik yra toks sudėtinio USB įrenginio pavyzdukas, CDC/HID. CDC profilis yra skirtas visokiems komunikacijos įrenginiams: tai gali būti RS-232, Ethernet, Wi-Fi ir t.t.

Tik mažas pastebėjimas. Pas mane ant stalo yra krūva tokių USB įrenginių, kurie lyg ir atitinka CDC profilį. Bet juos įgrūdus ir pažiūrėjus detalesnę informaciją matosi, kad jie visi yra „Vendor Specific“, 255 profilio. Na, bet jų idVendor ir idProduct tiek paplitę, kad mano Linuxas atpažįsta be problemų ir „draiverių nereikia“.

Na, bet pradėt reikia nuo pradžių. Sakykim, kad ir nuo kažkokio tai veikiančio pavyzduko. Veikiantį ir į vieną vietą gražiai surinktą iš STM32 perdarytą pavyzduką radau viename rusiškame saite. Iš pradžių jis gali pasirodyti painokas, jei niekada neteko susidurti su USB specifikacija.

Aš pasistengsiu labai neišsiplėsdamas kiek papasakoti apie USB HID profilį. Šis profilis yra gan paprastas ir dėl to dažnai naudojamas mėgėjiškiems divaisams kurti. Ypač tokiems, kur nereikia daug bitukų svaidyti.

USB yra paketinis protokolas. Tuo jis man žymiai labiau patinka už nuoseklųjį USART. USART reikia kiekvieną baitą apdoroti individualiai. Pradėjote duomenų transliaciją — ir imtuvas turi po vieną skaityti baitus ir laukti pabaigos. Daug pisnios programuotojui, jei reikia net ir penkis baitus apdorot. Na, sąlyginai daug. O USB perdavinėja duomenis paketais — išsiuntėt 20 baitų masyvą, kitas galas jį pyst ir gavo visą. Vietoj nuskaitinėjimo iš karto galima pulti nagrinėti turinį ir mirksėti lempikėmis.

USB HID yra apribojimas — paketo dydis daugiausiai 64 baitai. Jei to neužtenka, reikia pagalvoti apie Vendor Specific profilį. Man užtenka per akis ir akinius.

Toliau. USB divaisai prisistato kompui pateikdami krūvą įvairių deskriptorių. Seka tokia: įrenginio descriptorius > konfigūracijos deskriptorius (-ai), sąsajų deskriptorius (-ai), endpointų deskriptoriai (retais atvejais vienas). Ši seka yra hierarchinė ir besišakojanti:

USB deskriptoriai | Darau, blė

Įrenginio deskriptoriuje būna bendroji informacija: kokią USB versiją palaiko, aprašančios eilutės, įrenginio versija ir pan. Beigi skaičiukas, kiek įrenginys palaiko konfigūracijų.

Konfigūracijos deskriptorius nurodo sąsajų kiekį, maksimalų sunaudojamos energijos kiekį (mA) ir požymį, kad šitas daiktas bus maitinamas USB linija (arba ne). Labai retas įrenginys turi daugiau, kaip vieną konfigūraciją ir jos dažniausiai būna susiję su energijos valdymu. T.y. jei įrenginys prijungiamas su savo energijos šaltiniu, parenkama kita konfigūracija ir kompui prisistatoma, kad iš jo energijos nebus imama. Daugiau kitų atvejų aš nežinau. Neįsivaizduoju, kam gali prireikti daugiau konfigūracijų. Na, yra dar vienas toks egzotiškas dalykas, kaip modulinis USB įrenginys. Sakykim, turintis kelis lizdus plečiamosioms plokštėms. Užsikrovimo metu jis gali pasitikrinti kokias plokštes turi ir atitinkamai pagal tai užkurti konfigūraciją su turimais subįrenginiais. Bet čia visiška egzotika. Mums to nereikės 🙂 Ai, svarbiausias dalykas: įrenginys gali dirbti tik vienos konfigūracijos būsenoje. T.y. jis kompiuteriui pasako: va, turiu tris konfigūracijas. Kompiuteris tada sako, ok, naudok dabar konfigūraciją Nr. 2. Ir viskas. Kol įrenginio neištrauksite ir kažko nepakeisite, jis taip ir liks dirbti su ta viena konfigūracija.

Sąsajos deskriptorius yra jau tas daiktas, kuris darosi įdomus. T.y. jis nusako, ką konkrečiai per įrenginį mes čia darom. Ar tai bus USB HID, ar CDC, ar Mass Storage, ar dar kas nors. Grubiai tariant, čia jau yra galutinio įrenginio aprašymas. To, kurio mes kaip ir norim. Sąsajų vienas USB įrenginys gali turėti kelias ir tai gan įprasta. Kaip jau minėjau, mano pelė turi net tris sąsajas: HID pelės, HID klaviatūros ir dar kažkokią bbž kam. Nors kompiuteris įrenginį mato kaip vieną sudėtinį (composite) įrenginį.

Na, ir kiekviena sąsaja turi dar endpointus. Endpointai — tai duomenų apsikeitimo tarp kompiuterio ir įrenginio kanalai. Kanalų kryptis nurodoma kompiuterio, t.y. USB „šeimininko“ požiūriu. OUT endpointai eina iš kompiuterio, o IN — į kompiuterį. Programuojant USB įrenginį reikia nesusipainioti, kad per OUT duomenys ateina, o per IN — išeina 🙂 Viskas aišku?

Dar vienas niuansas. Iki USB 2.0 versijos tiktai „šeimininkas“ gali inicijuoti duomenų perdavimą. USB įrenginys visada laukia, kol iš jo paprašys duomenukų. O kai paprašo, pasižiūri, ar turi ką siųst. Tai atrodo sunkiai suvokiama, ypač kalbant apie tokius įrenginius, kaip USB klaviatūros ir pelės. Jo jo, kompiuteris šiuos įrenginius turi apklausinėt labai dažnai 🙂 USB  2.0 didžiausias apklausinėjimo greitis yra po klausimėlį kas 125 µS.

Tuo tarpu USB 3.0 įrenginiai jau lyg ir patys gali pasiųsti duomenų „šeimininkui“ net nepaprašius. Na, bet mano STM32 procesoriukai yra USB 2.0, tai tokiu dalyku nesidomėsiu.

Su USB HID įrenginiu tas pats. Tiesa, iš įrenginio pusės to „apklausinėjimo“ nesijaučia. Turim duomenų — siunčiam. Kaip per kokį USARTą. O USB aparatūra pati tvarkosi, kada gali siųsti duomenis, t.y. kai tik gauna užklausą. Užklausos dažnis, beje, nurodomas kiekvieno OUT endpointo deskriptoriuje individualiai. Vieni endpointai gali būti labai „spartūs“, kiti — „lėtesni“. Čia jau mūsų, programuotojų, darbas yra sugalvoti, koks bus įrenginio pasiutimo laipsnis. Taip pat kiekvienam endpointui nurodoma, kokį maksimalų baitų kiekį jis galės prasiųsti vienu paketu.

Endpointai yra keturių tipų:

  • Kontroliniai: vieną kontrolinį endpointą turi visi įrenginiai. Per jį vyksta pradinė įrenginio apklausinėjimo ir susipažinimo stadija bei pasakoma, kurią vieną iš konfigūracijų įrenginiui naudoti.
  • Izochroniniai: tokie, kuriems duodamas garantuotas greitis. Paprastai tai realaus laiko endpointai, naudojami garso ir vaizdo sistemoms. USB magistralėje jiems išskiriamas konkretus baitų per sekundę kiekis.
  • Pertraukimo: tokie, kurie sukelia pertraukimus, t.y. duomenys siunčiami tada, kada įvyksta koks nors įvykis. Pvz. klaviatūros klavišo paspaudimas.
  • Stambūs: arba bulk. Skirti maksimaliai užkišti USB magistralę duomenimis. Jiems nėra garantuotas laikas, bet garantuotas visas likęs laisvas USB srautas. Dažniausiai šie endpointai sutinkami duomenų talpyklose. Raktukuose ten visokiuose, išoriniuose diskuose ir pan. Na, ten, kur skraido tikrai dideli duomenų srautai.

Na va, maždaug aišku. Visi šitie deskriptoriai turi laukelius standartiniais pavadinimais. Jei norite pasinagrinėti mokomaisiais tikslais savo kompiuterio įrenginius, tai Linux ir lsusb -v komanda bus jūsų draugas. Va aš dėl įdomumo įdėsiu tos savo kompozitinės pelės aprašymą:

Bus 002 Device 004: ID 046d:c52b Logitech, Inc. Unifying Receiver
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x046d Logitech, Inc.
  idProduct          0xc52b Unifying Receiver
  bcdDevice           12.03
  iManufacturer           1 
  iProduct                2 
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           84
    bNumInterfaces          3
    bConfigurationValue     1
    iConfiguration          4 
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower               98mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      1 Keyboard
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      59
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval               8
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      2 Mouse
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     148
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval               2
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      93
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0020  1x 32 bytes
        bInterval               2

Na, iš to, ką papasakojau, jau viskas turėtų būti aišku 🙂 Neturi mano pelė OUT endpointų, t.y. kompiuteris jai nieko nesiunčia. Tik jinai siunčia (kai paklausta, aišku).

Bet štai pastebėsite gal tokius „Report Descriptors:  ** UNAVAILABLE **“. Unavailable dėl to, kad pelė pririšta prie kernelio draiverio ir lsusb negali jos pilnai „perimti“. Reportai yra HID įrenginių klasės specifika. Tai tarsi dar vienas hierarchijos lygis po endpointais. Trumpai tariant, reportai labai smulkiai aprašo, kokiais duomenų paketais per endpointus gali apsikeisti kompiuteris ir USB įrenginys. Aprašomas kiekvieno reporto dydis baitais, jo paskirtis, numeriukas ir t.t. Kurti reportų deskriptorius yra užknisantis reikalas ir tam geriausia būtų pasinaudoti kokia nors programulke, kad ir šita, rekomenduojama pačių usb.org. Beje, apie HID reportus tikrai labai rekomenduoju pasiskaityti čia ir čia.

HID reportų yra visa krūva standartinių ir „Vendor specific“. Standartiniai yra iš tikro standartiniai — visokie pelių ir kvailatūrų duomenukai. Va iš tų reportų ir gaunasi, kad „HID įrenginiams nereikia draiverių“. Jei jūsų HID įrenginys turės aprašytus standartinius reportus ir, pavyzdžiui, siuntinės klavišo paspaudimo reportus, tai OS interpretuos tą įrenginį kaipo klaviatūrą. Na, bet nesitikėkit, kad sukūrę kokį nors HID termometrą pamatysite kamputyje prie laikrodžio rodomą temperatūrą 😀 Va jums ir „nereikia draiverių“… Nuosavus sukurtus HID įrenginius ir jų reportus reikia patiems ir nuskaitinėti. Tiesa, su jais nuskaitinėjimas yra labai paprastas: su kokia nors USB biblioteka bandom nuskaityt, jei pareina timeout’as, tai nėra ką nuskaityt. Ir lygiai taip pat nusiunčiam įrenginiui baitų krūvelę. Viskas tikrai paprasta. Tik svarbu žinot vieną dalyką: pirmasis duomenų baitas visada yra reporto numeris. Jei įrenginys siunčia 10 baitų ilgio reportą, tai jis realiai perduos 11 baitų — pirmasis pasakys, kurį reportą įrenginys siunčia. Beje, jei reportas aprašytas kaip 10 baitų ilgio, tai leidžiama siųsti ir trumpesnį reportą, niekas dėl to nenukentės. Ilgesnio — jau nebe.

Taigi realiai HID paketo apribojimas iki 64 baitų reiškia apribojimą iki 63 ir plius reporto numerėlis. Be abejo, reporto numerėlis — irgi naudinga informacija, pagal jį žinom, ką įrenginys siunčia. Pavyzdžiui, ar dvejetainius duomenis, ar teksto eilutę.

Dabar taip. Aš pasiėmiau Rajos HID pavyzduką (tą, iš anksčiau pateiktos rusiškos nurodos) ir nusprendžiau jį toliau savo reikmėms vystyti. STM32 USB-FS pavyzdukai yra šlykštoki, o Raja gan gražiai perdarė viską ir sudėjo „arčiau“ programuotojo. Aš jo pavyzduką susikompiliavau, įsidėjau į vieną devboarduką ir paleidau. Rajos aprašyti reportai suveikė iš karto. Tada aš nusprendžiau pridėti dar vieną, Nr. 5, reportą, kurį pabandžiau panaudoti vietoj USART’o, t.y. siuntinėti paketais tekstinius duomenukus.

Pamiršau paminėti, kad visi reportų ir visi kiti deskriptoriai yra aprašinėjami grynais skaičiukais pagal usb.org specifikacijas. Dvejetainiai duomenys ten eina. Dėl to labai sunku visa tai skaityti ir interpretuoti. Reikia gerų pavyzdukų su komentarais ir tų visų pabaisiškų baitų masyvų generatorių.

Mano padaryti pakeitimai. Pirmiausia, pridėjau Nr. 5 reporto deskriptorių faile usb_desc.c:

    0x85, 0x05,                    //   REPORT_ID (5)
    0x09, 0x05,                    //   USAGE (Vendor Usage 5)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x40,                    //   REPORT_COUNT (64)
    0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)*/

Kadangi reporto deskriptorius pailgėjo 10 baitų, tai usb_desc.h faile reikėjo prie 79 tuos 10 pridėti:

#define RHID_SIZ_REPORT_DESC                89

Nu ir kaip ir viskas. Dar pasirašiau variadinę funkcijikę spausdinimui į USB paketus ir penktojo reportuko siuntimus (faile hw_config.c):

static char pusbuffer[64];
void print_usb(char *format, ...)
{
	va_list args;
	va_start (args, format);
	pusbuffer[0] = 5; // Reporto numerėlis!
	vsnprintf (&pusbuffer[1], 63, format, args); // Pildom eilutės buferį nuo antrojo baito, kad reporto numerėlio nesugadintume
	va_end (args);

	PrevXferComplete = 0; // Įjungiam flažoką, kad darom siuntimą (pagal kitą viso šito kratinio pavyzdį)
	USB_SIL_Write(EP1_IN, pusbuffer, strlen(pusbuffer));
	SetEPTxValid(ENDP1);
}

Na va. Dabar mainą papildžiau dar tokiu durnu cikliuko skaitliuku, kuris siuntinėja USB paketus ne visai iš karto, o kiek rečiau. Eilinis busy wait’as:

while (1)
{
	i++;
	if (bDeviceState == CONFIGURED)
	{
		if (PrevXferComplete) {
			if (i>=200000) {
				RHIDCheckState();
				//i = 0;
			}
			if (i >= 400000) {
				print_usb("Blahaha ir buteliukas romo su %d g.", i);
				i = 0;
			}
		}
	}
}

Nu, jokio raketų mokslo.

Ai, dabar kompiuterio pusė. Viskas primityviai, su Pythonu ir mano labai pamėgta PyUSB v1 biblioteka. Ne 0.4.xx, kur su Jubuntu kartu shipinama, o va iš čia, pilnai pitoninė implementacija. Šita biblioteka labai paprasta ir lengva naudoti, be to, žymiai lankstesnė nei libusb wrapper’is. Na, pitonistinė tokia 🙂 Kas man, aišku, labai prie dūšios. Tai štai paprastas pavyzdukas, kuris nuskaitinėja modifikuotą Rajos USB divaisą. Iš divaiso nuolat siunčiami reportai 4 ir 5. Į jį galima nusiųsti kitus reportus. Pradžioje išspausdinama nedidukė įrenginio struktūra, nuo konfigūracijos iki endpointų. Viskas tikrai labai paprasta:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import usb.core
import usb.util

VENDOR_ID = 0x0483
PRODUCT_ID = 0x5711

device = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)

if device is None:
	sys.exit(u"Neradau divaiso")
else:
	print(u"Radau divaisą!", device)

if device.is_kernel_driver_active(0):
	try:
		device.detach_kernel_driver(0)
		print(u"Atkabinau kernelio draiverį")
	except usb.core.USBError as e:
		sys.exit(u"Neatsikabinom nuo kernelio: %s" % str(e))


for cfg in device:
	sys.stdout.write(str(cfg.bConfigurationValue) + '\n')
	for intf in cfg:
		sys.stdout.write('\t' + \
                             str(intf.bInterfaceNumber) + \
                             ',' + \
                             str(intf.bAlternateSetting) + \
                             '\n')
		for ep in intf:
			sys.stdout.write('\t\t' + \
                                 str(ep.bEndpointAddress) + '\t' + str(ep.wMaxPacketSize) + \
                                 '\n')

device.set_configuration()
#device.reset()
# Šiaip reikėtų šitą resetą padaryti. Deja, su Rajos divaisu kažkodėl neveikia, užsilenkia divaisas tada ir sustoja.
# Priežasties nežinau, neišsiaiškinau. Net ir set_configuration() nebūtinas, veikia ir taip. Žodžiu, kažkoks labai
# primityvus divaisas, arba čia HID taip elgiasi. Mistika.

endpoint = device[0][(0,0)][0]
endpoint2 = device[0][(0,0)][1]

while(1):
	try:
		data = device.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize)
		if data[0] == 4:
			print(data)
		elif data[0] == 5:
			print(str(bytearray(data[1:])))
		device.write(endpoint2.bEndpointAddress, bytearray([1, 0]))
		device.write(endpoint2.bEndpointAddress, bytearray([2, 0]))
		device.write(endpoint2.bEndpointAddress, bytearray([3, 4]))
		
	except usb.core.USBError as e:
		print(u"Nesiskaito: %s" % str(e)) 


device.attach_kernel_driver(0)

O šitas kodas paleistas spausdina va duomenukus iš RHID divaiso:

$ sudo python read-rhid.py 
(u'Radau divaisą!', )
Atkabinau kernelio draiverį
1
	0,0
		129	64
		1	64
Blahaha ir buteliukas romo su 991810 g.
array('B', [4, 215, 0, 0, 0])
Blahaha ir buteliukas romo su 538952 g.
array('B', [4, 215, 0, 3, 4])
array('B', [4, 215, 0, 3, 4])
Blahaha ir buteliukas romo su 549233 g.
array('B', [4, 215, 0, 3, 4])
array('B', [4, 215, 0, 3, 4])
Blahaha ir buteliukas romo su 549235 g.
array('B', [4, 215, 0, 3, 4])
array('B', [4, 215, 0, 3, 4])
Blahaha ir buteliukas romo su 549231 g.
array('B', [4, 215, 0, 3, 4])
array('B', [4, 215, 0, 3, 4])
Blahaha ir buteliukas romo su 549230 g.

Kaip matote, rašymas į įrenginį pakeičia jo atgal siunčiamus duomenis su ketvirtu reportėliu, kuris konsolėje spausdinamas kaip baitų masyvas. Tuo tarpu penktasis reportėlis gražiai išspausdinamas kaip teksto eilutė. Gėris ir grožis. Beje, jei kartais įdomu, ko gi ten pirmoji teksto eilutė su dideliu skaitliuku gaunasi, tai čia dėl to, kad įrenginys skaitliuką suka ir nesiųsdamas duomenukų. O ir vienas siuntimas kiek trukdo kitam, kas irgi matosi. Apskritai, darbas su USB šitam procesoriukui — nors ir nelabai sudėtinga, bet ir nelabai paprasta užduotis.

Va dėl ko nemyliu STM32, tai dėl to, kad nepateikė USB kodo valdymo pavyzdžio be savo kelių lygių „draiverių“ ir pan. O galėjo pateikti kokį ypač paprastą pavyzduką su tiesioginiais registrų sukalibravimais ir pan. Ne, jiems reikėjo padaryti keturis abstrakcijos lygius, kad būtų „paprasčiau“. Gaunasi taip, kad supratimui apie USB valdymą tie visi lygiai truputuką kenkia, sunku per visus kodo gabalus naršyti ir visas sąsajas sugaudyti. Dėl to ir ėmiau jau vieną sumažintą ir supaprastintą pavyzduką.

Dar čia man buvo klausimas komentaruose apie endpointų buferių adresaciją. Na, USB įrenginiui išskiriama 512 baitų atminties. Tą atmintį tarpusavyje dalinasi visi endpointai duomenų apsikeitimui. Grubiai tariant, yra tokis registras, vadinamas USB_BTABLE. Toliau, kiekvienam endpointui reikia nurodyti įeinančio (RX) ir išeinančio (TX) buferio adresus. Na, RHID pavyzduko usb_conf.h faile yra tokios eilutės:

/*-------------------------------------------------------------*/
/* EP_NUM */
/* defines how many endpoints are used by the device */
/*-------------------------------------------------------------*/
#define EP_NUM     (2)

/*-------------------------------------------------------------*/
/* --------------   Buffer Description Table  -----------------*/
/*-------------------------------------------------------------*/
/* buffer table base address */
/* buffer table base address */
#define BTABLE_ADDRESS      (0x00)

/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x18)
#define ENDP0_TXADDR        (0x58)

/* EP1  */
/* tx buffer base address */
#define ENDP1_TXADDR        (0x100)
#define ENDP1_RXADDR        (0x140)

Kaip ir visai neaišku, kas čia per skaičiukai. O dar neaiškiau būtų, jei paimtumėm kokį nors HID+CDC kompozitinį įrenginį ir pažiūrėtumėm jo adresus:

/* Endpoints used by the device */
#define EP_NUM     (4)  /* EP0 + EP1 For HID + EP2/EP3 for CDC  */

/* buffer table base address */
#define BTABLE_ADDRESS      (0x000)

/* EP0, RX/TX buffers base address */
#define ENDP0_RX_ADDRESS   (0x40)
#define ENDP0_TX_ADDRESS   (0x80)

/* EP1 Tx buffer base address */
#define HID_IN_TX_ADDRESS  (0x150)

/* EP1 Rx buffer base address */
#define HID_OUT_RX_ADDRESS (0x160)

/* EP2 Tx buffer base address */
#define BULK_IN_TX_ADDRESS  (0xC0)

/* EP2 Rx buffer base address */
#define BULK_OUT_RX_ADDRESS (0x110)

/* EP3 Tx buffer base address */
#define INT_IN_TX_ADDRESS   (0x100)

Nu bardaks kažkoks visiškasis… jamam datašytą ir bandom aiškintis galus:

RM0008 apie STM32F10x procesorius. USB endpointų buferių adresacija | Darau, blė

Tai nedaug ką paaiškina, bet paaiškina tiek, jog buferiams gražiai išskirstomi tie vargani 512 baitų. Svarbu per daug nenukrypti nuo EP0 pavyzdukų, o kitus buferius galima koreguoti pagal savo norus. Svarbu žinoti, kiek maksimaliai duomenų vienu endpointu bus perduodama ir pagal tai atmintį paskirstyt. Kaip matom iš pavyzdžio, nebūtina atminties išskirstyt gražiai EP1, EP2, EP3… T.y. endpointus reikia aprašyti iš eilės, bet atmintį jiems suskirstyti galima ir ne iš eilės.

Įdomus velnias yra dvigubai buferizuoti (double buffered) endpointai. Tai paprastai būna bulk tipo endpointai. Vienas buferis gali gulėti užpildytas, kol kitas pumpuojamas, o paskui jie gali būti sukeisti vietomis. Na, kad įrenginio atsako laikas būtų geresnis. Bulk endpointais paprastai pumpuojami dideli duomenų kiekiai, tai kad įrenginys spėtų tuos duomenis kramtyt, jam sudaroma galimybė naudoti du buferius. Vienas, sakykim, pripildomas, tada USB aparatūra persijungia į antrą. Programinė įranga doroja pirmąjį buferį, o USB aparatūra tuo metu sau toliau pildo antrąjį. Na ir kaitaliojasi tie buferiai šitaip.

Trumpai tariant, endpointų adresaciją reikia parinkti pagal reikalingus duomenų kiekius endpointams. HID atveju maksimalus paketo dydis yra 64 baitai. Jei duomenys bus svaidomi į abi puses, tai svarbu, kad EP1 RX ir TX adresai skirtųsi per tuos 64 baitus. Tą matome Rajos USB HID pavyzdyje šešioliktainiais skaičiukais: 0x140-0x100 = 0x40 = 64 dec.

Viskas būtų gerai, bet čia va vienoj vietoj aprašomi buferių adresai, bet jų iniciavimui dar reikia nurodyt ir buferių dydžius. O buferių dydžius radau kažkodėl usb_desc.h failiukyje čia:

//HID Maximum packet size in bytes
#define wMaxPacketSize  0x40
#define EP1TxCount wMaxPacketSize
#define EP1RxCount 2

Incializavimas tuo tarpu usb_prop.c:

void HID_Reset(void)
{
  /* Set HID_DEVICE as not configured */
  pInformation->Current_Configuration = 0;
  pInformation->Current_Interface = 0;/*the default Interface*/

  /* Current Feature initialization */
  pInformation->Current_Feature = RHID_ConfigDescriptor[7];
  SetBTABLE(BTABLE_ADDRESS);
  /* Initialize Endpoint 0 */
  SetEPType(ENDP0, EP_CONTROL);
  SetEPTxStatus(ENDP0, EP_TX_STALL);
  SetEPRxAddr(ENDP0, ENDP0_RXADDR);
  SetEPTxAddr(ENDP0, ENDP0_TXADDR);
  Clear_Status_Out(ENDP0);
  SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
  SetEPRxValid(ENDP0);

  /* Initialize Endpoint 1 */
  SetEPType(ENDP1, EP_INTERRUPT);
  SetEPTxAddr(ENDP1, ENDP1_TXADDR);
  SetEPRxAddr(ENDP1, ENDP1_RXADDR);
  SetEPTxCount(ENDP1, EP1TxCount);
  SetEPRxCount(ENDP1, EP1RxCount);
  SetEPRxStatus(ENDP1, EP_RX_VALID);
  SetEPTxStatus(ENDP1, EP_TX_NAK);

  /* Set this device to response on default address */
  SetDeviceAddress(0);
  bDeviceState = ATTACHED;
}

Va kaip viskas susiję, bet kodo struktūra man šiaip sau… kyla minčių savo savo nuosavą.

Pabaigai. Norėjau pasidaryti USB-CAN sąsają kompiuteriui. Jau sakiau, kad man įtarimų sukėlė CAN pertraukimo funkcija USB_LP_CAN1_RX0_IRQHandler. Ne šiaip sau. Paskaitęs datašytą radau va tokį pastebėjimą:

The USB and CAN share a dedicated 512-byte SRAM memory for data transmission and
reception, and so they cannot be used concurrently (the shared SRAM is accessed through
CAN and USB exclusively). The USB and CAN can be used in the same application but not
at the same time.

Šūds. Persijunginėti tarp CAN ir USB bei tikėtis, kad niekas nebus pamesta, nerealu. Kad būtų galima naudotis ir CAN, ir USB vienu metu, reikia procesoriuko iš Connectivity Line serijos. Todėl papildomai užsisakiau minimalų devboarduką su STM32F107. Tas turi dar ir Ethernet, antrą CAN beigi USB OTG. Beveik nieko iš jo galimybių neišnaudosiu, bet USB-CAN tiltas bus.

Nuorodėlės visokios:

Nu va, viskas, prirašiau čia jums paklodę, o dabar einu morkinio višisuazo valgyti. Neturėjau, blė, bulvių namuose, tai sudėjau tris morkas. (Iš tikro, tai turėjau tų bulvių, bet buvau nepadėjęs į vietą, todėl neradau.) Beje, skonis liuks irgi, o ir šiaip morkas mėgstu.

Advertisements
Komentarai
  1. n\a parašė:

    Sveikas, realiai ten su tais buferiais viskas kaip aisku. BTABLE_ADDRESS tai offsetas visiems adresams ir nematau tikslo naudoti kitoki nei 0x00.

    ENDP0_RX_ADDRESS reiksme nustatoma priklausomai kiek endpointu bufferiu yra naudojama. Tarkim naudojam tik ENP0 RX tai ENDP0_RX_ADDRESS turetu buti 0x04, nes BTABLE vieta uzims tik ADDR0_TX ir COUNT0_TX, ir sekantis laisvas adresas yra 0x04.

    ENDP0_RX_ADDRESS reiksme statom i 0x40 kai norim, kad butu naudojami visi endpointu buferiai, nes COUNT7_RX adresas yra 0x3E ir toliau laisva vieta prasideda nuo 0x40

    Kitu RX ir TX adresai yra apskaiciuojami nuo ENDP0_RX_ADDRESS pridedant buferio dydi, kaip ir rasei.

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s