Atmel AVR
První kroky - programátor
programátorů je víc a mají i odlišný software. Některé programátory dělají většinu v hardware, některé to emulují v software. Jsou tedy mezi nimi rozdíly například v rychlosti
jako programátor lze prý využít zařízení s GPIO, třeba Raspberry PI
než si postavím/koupím svůj, tak jsem si zapůjčil USBasp se standardním ISP konektorem
zapojil jsem piny ISP konektoru na čip přes nepájivé pole
čip prý může být normálně v obvodu, stačí vyvést požadované piny
čip nepotřebuje (jestli vůbec smí) externí zdroj napájení, napájí se přímo z ISP konektoru
čip běží i s připojeným progrmátorem (pokud má zapojeny obě země). Při programování se akorát resetuje.
chtěl jsem postupovat podle návodu na ABClinux, ale článek je už zastaralý. Používají tam paralelní programátor, který je úplně jiný než ten USBasp
USBasp adaptér nemá podepsaný ovladač pro MS Windows, takže u třeba Win8 se musí nejprve vypnout kontrola podpisů u ovladačů. V Linuxu se to chytne hned, bez nějakých driverů
Doinstaloval jsem si akorát soft pro komunikaci s mikroprocesorem
avrdude
avrdude -c usbasp -p atmega8
avrdude: warning: cannot set sck period. please check for usbasp firmware update.
avrdude: error: programm enable: target doesn't answer. 1
avrdude: initialization failed, rc=-1
Double check connections and try again, or use -F to override
this check.
# avrdude -c usbasp -p atmega8
avrdude: warning: cannot set sck period. please check for usbasp firmware update.
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e9307
avrdude: safemode: Fuses OK (E:FF, H:D9, L:E1)
avrdude done. Thank you.
/etc/udev/rules.d/99-USBasp.rules
# Set Group for USBasp
SUBSYSTEM=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", GROUP="djbuldog"
Kompilace
Atmel studio
funguje pouze pro Windows
obsahuje i simulátor (ale pouze pro některé mikroprocesory)
založit nový C executable projekt, vybrat ATmega8A procesor
nastavit v menu Tools - External Tools, založit nový:
Title: usbasp ATmega8
Command: B:\downloads\avr\avrdude.exe
Arguments: -c usbasp -p atmega8 -U flash:w:$(TargetDir)$(TargetName).hex:i
[x] Use Output window
Linux
avr-libc
- Makefile
MCU=atmega8a
AVRDUDEMCU=atmega8
CC=avr-gcc
OBJCOPY=avr-objcopy
PROJECT=pokus1
CFLAGS=-g -mmcu=$(MCU) -Wall -Wstrict-prototypes -Os -mcall-prologues
# add simulavr and gtkvawe
all: $(PROJECT).hex $(PROJECT).bin
$(PROJECT).hex : $(PROJECT).elf
$(OBJCOPY) -R .eeprom -O ihex $(PROJECT).elf $(PROJECT).hex
$(PROJECT).bin : $(PROJECT).elf
$(OBJCOPY) -R .eeprom -O binary $(PROJECT).elf $(PROJECT).bin
$(PROJECT).elf : $(PROJECT).o
$(CC) $(CFLAGS) -o $(PROJECT).elf -Wl,-Map,$(PROJECT).map $(PROJECT).o
$(PROJECT).o : $(PROJECT).c
$(CC) $(CFLAGS) -c $(PROJECT).c
load: $(PROJECT).hex
#uisp -dlpt=/dev/parport0 --erase --upload --verify if=$(PROJECT).hex -dprog=dapa -v=3 --hash=12
avrdude -c usbasp -p $(AVRDUDEMCU) -U flash:w:$(PROJECT).hex:i
simulate: $(PROJECT).bin
simulavr -d $(AVRDUDEMCU) -P simulavr-disp $(PROJECT).bin
clean:
rm -f *.o *.map *.elf *.hex *.bin
Ladění
avr-gdb
simulavr
avr-gdb
(gdb) file pokus.elf
(gdb) target remote localhost:1212
(gdb) load
file - načítá debug symboly
target - připojení ke vzdálenému serveru přes tcp protokol
load - odeslání programu na server (ten který ladíme). Nemusí se provádět, pokud spustíme simulavr už nad binárkou
devsupp.c:332: MESSAGE: attach: IO Reg 'PIND' at 0x0030: created
devsupp.c:316: MESSAGE: attach: IO Reg 'DDRD' at 0x0031: ref = 0x0030
devsupp.c:316: MESSAGE: attach: IO Reg 'PORTD' at 0x0032: ref = 0x0030
devsupp.c:332: MESSAGE: attach: IO Reg 'PINC' at 0x0033: created
devsupp.c:316: MESSAGE: attach: IO Reg 'DDRC' at 0x0034: ref = 0x0033
devsupp.c:316: MESSAGE: attach: IO Reg 'PORTC' at 0x0035: ref = 0x0033
devsupp.c:332: MESSAGE: attach: IO Reg 'PINB' at 0x0036: created
devsupp.c:316: MESSAGE: attach: IO Reg 'DDRB' at 0x0037: ref = 0x0036
devsupp.c:316: MESSAGE: attach: IO Reg 'PORTB' at 0x0038: ref = 0x0036
(gdb) x/t 0x800035
0x800035: 00100000
Test rozsvěcování LED diody
cílem je nahrát jednoduchý program do mikroprocesoru a otestovat výstup
LED dioda bliká s frekvencí 1s
asi nefunguje při připojeném programátoru. Musí být odpojen ISC konektor (stačí vytáhnout programátor z USB) a tím pádem přivedeno externí napájení (možná stačí odpojit ten reset pin)
LED dioda připojena na pin 14 (PB0) přes rezistor 220R
odpor 220R slouží k redukci napětí na LED diodě. Byl vypočítán pomocí ohmova zákonu. Pokud by se nepoužil, tak by LEDka asi umřela. LED má spotřebu cca 13mA. Odpor snižuje napětí na LED z 5V na zhruba 2.14V (220 * 0.013)
DC Current per I/O Pin ....................................... 40.0mA
DC Current V CC and GND Pins................................. 300.0mA
- ledtest.c
1 #include <avr/io.h>
2 #include <util/delay.h>
3
4 int main(void)
5 {
6 DDRB |= (1 << PINB0);
7 PORTB &= ~(1 << PINB0);
8
9 while(1)
10 {
11 _delay_ms(1000);
12 PORTB ^= (1 << PINB0);
13 }
14 }
DDRx je registr pro nastavování pinů portu x na vstup/výstup. Bit n registru odpovídá pinu n. Hodnota 1 odpovídá výstupu, hodnota 0 vstupu.
řádek 6 tedy říká: nastav pin 0 portu B (PB0) na výstup
port PB0 odpovídá podle nákresu v datasheetu právě pinu 14
PORTx registr má zvlášť význam pro vstupní a zvlášť pro výstupní piny. U výstupních pinu hodnota v registru odpovídá výstupní hodnotě. Tedy nastavení bitu n v registru PORTx na jedničku způsobí nastavení logické jedničky (napětí 5V) na výstupním pinu n portu x.
řádek 7 tedy říká: nastav pin 0 portu B na hodnotu 0 a všechny ostatní nech jak byly
řádek 12: nastav na pinu PB0 opačnou hodnotu
Test funkce Hall senzoru
Zapojení: pokud se na senzor díváme zepředu (zkosená hrana směrem k nám), tak úplně levá nožička je pro napájení (VCC), střed je zem (GND), pravá noha výstupní napětí.
Při prvním zapojování se mi podařilo otočit vstupní a výstupní napětí. Senzor to přežil, akorát při tomto zapojení nefungoval
Pracovní napětí je doporučené 4.5V - 6V. Já jsem použil 5V.
Bez magnetického pole to ukazovalo cca 2.5V (tj střed). Při přiblížení jednoho pólu magnetu (kulatý feritový magnet cca 20 x 5 mm) to ukazovalo až 3V. Při otočení magnetu napětí klesalo až na 2V.
U dostatečně silného magnetu to dává cca skutečně 0-2.5 a 2.5-5V
0-5V je úplně ideální, protože ADC na ATmega8 digitalizuje přesně 0-5V (0-1024)
Pro snímání polohy joysticku lze použít
2 vzájemné otočené magnety. Jeden bude způsobovat výchylku 0-2.5V, druhý 2.5-5V. Joystick bude krásně vycentrovaný, protože bez pole je tam přesně polovina. Čím dám magnety dál od sebe, tím větší bude null zone.
anebo stačí magnet jeden, ale je potřeba, aby měl póly daleko od sebe. Uprostřed pole je magnetizmus totiž nulový, a když se jde k pólům, tak se mění přesně podle potřeby. Testoval jsem to vytvořením válce spojením kulatých magnetů za sebe
Tlačítko
na digitální vstupy je možné přivést přímo signál
digitálních vstupů ale není moc, tak se často používá pro více tlačítek matice.
Jednoduché tlačítko
-
ve zkratce velikost pull-up odporu (R1) udává, jak velký proud poteče mezi VCC a GND (pokud stiskneme tlačítko)
R1=0 odpovídá nekonečnému proudu = zkrat
pin uvnitř MCU (mikroprocesor) bývá vnitřně připojen na GND přes velký odpor (R2), třeba 100k.
pokud tlačítko není stisknuto VCC je na GND připojen přes R1 + R2.
funguje to jako dělič napětí. Pokud R1=R2, tak na pinu (mezi odpory) napětí 1/2 VCC.
velikost R1 se proto doporučuje zhruba 1/10 R2 - napětí se vyhodnotí určitě úspěšně jako HIGH
Matice
-
všechny piny jsou input.
sloupce se postupně přepínají do stavu output, čímž dochází k jakoby vybrání sloupce
sloupce se musí přepínat zpět na input, jinak může dojít ke zkratu
Test ovládání LED diod tlačítkem
cílem je otestovat vstup i výstup: rozsvěcovat postupně tři LED diody v řadě a po stisknutí tlačítka změnit směr
LED diody připojeny přes odpor 220R na piny 14 (PB0), 13 (PD7), 12 (PD6)
tlačítko přivádí GND na pin 28 (PC5)
- led-switch.c
#include <avr/io.h>
#include <util/delay.h>
#define UP 0
#define DOWN 1
int main(void)
{
/* Init LEDs */
DDRD |= (1 << PIND6);
DDRD |= (1 << PIND7);
DDRB |= (1 << PINB0);
/* Init push button */
DDRC &= ~(1 << PINC5);
PORTC |= (1 << PINC5);
uint8_t active=0;
uint8_t direction=DOWN;
uint8_t btn_last=1;
uint8_t counter=0;
while(1)
{
if ((btn_last)&&(!(PINC & (1<<PINC5)))) {
++direction;
direction%=2;
}
btn_last=(PINC & (1<<PINC5));
if (counter++ > 10) {
counter=0;
if (direction==UP)
++active;
else
--active;
switch(active) {
case 2:
PORTD &= ~(1 << PIND6);
PORTD |= (1 << PIND7);
PORTB &= ~(1 << PINB0);
break;
case 3:
PORTD |= (1 << PIND6);
PORTD &= ~(1 << PIND7);
PORTB &= ~(1 << PINB0);
break;
default:
PORTD &= ~(1 << PIND6);
PORTD &= ~(1 << PIND7);
PORTB |= (1 << PINB0);
active=(direction==UP)?1:4;
}
}
_delay_ms(50);
}
}
Cílem bylo rozsvěcovat LED diody po 500 ms. Pokud se stav tlačítka vyčítal jen jednou za 500 ms, byla reakční doba nepříjemně pomalá. Tlačítko se muselo podržet delší dobu, jinak se stisk vůbec nezachytil.
Cyklus jsem zrychlil 10x. Zavedl jsem pomocnou proměnou counter, která stav LED diod mění až po dosažení hodnoty 10.
50 ms pro čtení tlačítka bylo už dobré, ale asi by bylo lepší použít 20 ms.
U tlačítka se ukládá poslední stav, aby bylo možné detekovat stisknutí. Jako stisknutí se bere pouze přechod z logické hodnoty 1 (nestisknutý) na hodnotu 0 (stisknutý, přivedeno GND na pin).
Bez zavedení tohoto mechanismu by se směr při stisknutém tlačítku neustále měnil.
Změnu směru by bylo také možné po drobné úpravě vyvolat až uvolněním tlačítka
při psaní kódu jsem nejvíce bojoval s tím, jak zapsat kód efektivně a jednoduše. Čip má omezené zdroje.
po zkompilování v Atmel studiu se ukazuje spotřeba paměti výsledného kódu, pohybovala se okolo 150 B (1.5% max velikosti). Dá se podle toho zkoušet různé kombinace zápisu stejné funkce. Napsaný kód určitě není nejlepší možný, ale funguje :)
chyby v kódu se docela špatně ladí. Psaní kódu jsem si rozdělil na menší části, u kterých se lépe ověřovala správnost. Například pro zkoumání kódu pro tlačítko jsem napsal kód pouze na rozsvěcování ledky podle stavu tlačítka
chtělo by to nějaký pěkný simulátor :) V Atmel studiu pro ATmega8A asi žádný není.
Test rotačních enkodérů
PIN A: __--__--__--__-- // weight 1
PIN B: ___--__--__--__-- // weight 2
values: 00132013201320132
otáčka doprava je potom sekvence 0→1→3→2→0, otáčka doleva 0→2→3→1→0
jednotlivé stavy trvají různě dlouhou dobu. Závisí to například na rychlosti otáčení, konstrukci
hodnoty PINů A/B se musí vyčítat v dostatečně krátkém čase, jinak může být nějaký stav přeskočen
trvání stavu > 0 typicky nebývá delší než 2 ms
je třeba počítat s tím, že při rychlém vyčítání stavů bývá jeden stav detekován opakovaně
enkodéry jsou mechanické, během otáčení se negeneruje dokonalý signál bývá tam šum - skoky (bouncing) mezi logickou 0 a 1
odstraňováním se zabývá debouncing, který může proveden hardwarově (
kondenzátor) nebo softwarově (zohledňování historie stavů)
#define sbi(x, i) x |= (1 << i)
#define cbi(x, i) x &= ~(1 << i)
#define gbi(x, i) (!(x & (1 << i)))
#define ROT1_A_PORT PORTC
#define ROT1_A_DDR DDRC
#define ROT1_A_PIN PINC
#define ROT1_A_P PC3
#define ROT1_B_PORT PORTC
#define ROT1_B_DDR DDRC
#define ROT1_B_PIN PINC
#define ROT1_B_P PC4
/* Init Rotary Encoder */
cbi(ROT1_A_DDR, ROT1_A_P);
cbi(ROT1_B_DDR, ROT1_B_P);
cbi(ROT1_BTN_DDR, ROT1_BTN_P);
Algoritmus 1
můj vlastní, založen na myšlence sledování změn stavu
otáčení detekuje pomoci změny stavu 0→1 nebo 0→2
s HW debouncing asi nejlepší řešení (taky je moje) :D
uint8_t inline readRotary(void) {
uint8_t rotval_now;
rotval_now = gbi(ROT1_A_PIN, ROT1_A_P);
rotval_now += gbi(ROT1_B_PIN, ROT1_B_P)*2;
if ((rotval_last==0)&&(rotval_now==1)) {
rotval_last=rotval_now;
return RIGHT;
}
if ((rotval_last==0)&&(rotval_now==2)) {
rotval_last=rotval_now;
return LEFT;
}
rotval_last=rotval_now;
return NOCHNG;
}
otáčka detekována těsně před „lupnutím“
optimální frekvence vyčítání byla 1 ms
0 ms - projevoval se hodně bouncing. Při plynulém pomalejším otáčení v jednom směru to přeskakovalo (detekovalo více otáček než se provedlo) a chybovalo (detekován opačný směr než směr otáčení)
8 ms - dost dobrý, akorát extrémně rychlé otáčení už nebylo detekováno úplně 100%
16 ms - pořád dobrý, extrémně rychlé otáčení už začínalo mít problémy s detekcí
24+ ms - vynechává
zapojení kondenzátorů 2x 100nF mezi GND a PINA/PINB znatelně zlepšilo chování při 0 ms.
nedetekoval jsem žádné chyby ani přeskakování.
extrémně rychlé otáčky nedělaly problém. Asi nejlepší výsledky.
kondenzátory na 1 ms delay nemá asi žádný vliv
Algoritmus 2
#define ROTA gbi(ROT1_A_PIN, ROT1_A_P)
#define ROTB gbi(ROT1_B_PIN, ROT1_B_P)
Varianta A
algoritmus obsahuje cyklus, který čeká na nastavení pinu na logickou nulu. Během čekání se neprovádí žádné instrukce.
pro vyčítání hodnot je opět nejlepší 1 ms
při 0 ms se občas přeskakuje, chybuje. Kondenzátor by to vyřešil.
uint8_t inline readRotary2(void) {
if(ROTA & (!ROTB)){
loop_until_bit_is_set(ROT1_A_PIN, ROT1_A_P);
if (ROTB) return RIGHT;
}
if(ROTB & (!ROTA)){
loop_until_bit_is_set(ROT1_B_PIN, ROT1_B_P);
if (ROTA) return LEFT;
}
if (ROTA & ROTB){
loop_until_bit_is_set(ROT1_A_PIN, ROT1_A_P);
return (ROTB)?RIGHT:LEFT;
}
return NOCHNG;
}
Varianta B
úprava pro použití bez čekacího cyklu
při 0 ms to chybuje, přeskakuje
při 8 ms to občas malinečko chybuje (když jsou oba UP, tak vlastně detekuje vždy otáčení doleva)
uint8_t inline readRotary3(void) {
if(ROTA & (!rotval_last)) rotval_last=1;
if (ROTB & ROTA & (rotval_last)) {
rotval_last=2;
return LEFT;
}
if(ROTA & (!ROTB) & rotval_last) {
rotval_last=2;
return RIGHT;
}
if ((!ROTA)&!(ROTB)&(rotval_last==2)) rotval_last=0;
return NOCHNG;
}
Algoritmus 3
-
v algoritmu je implementován SW debouncing
reaguje uprostřed „lupnutí“, takže super
při 0 ms to reaguje asi nejlépe ze všech alg. Občas to sice přeskočí nebo chybuje, ale je to mnohem lepší než ostatní.
při 8 ms je to ale nic moc. Často vynechává, občas chybuje, rychlé otáčky to nedává vůbec
uint8_t inline readRotary5(void) {
static uint8_t waitcount = 0;
if ( gbi(ROT1_B_PIN, ROT1_B_P) ) { //we have a trigger signal
if ( !gbi(ROT1_A_PIN, ROT1_A_P) ) { // rotating left or right?
if ( rotval_last != RIGHT ) //debounce for polling
waitcount++;
rotval_last = RIGHT;
}
else {
if ( rotval_last != LEFT ) //debounce for polling
waitcount++;
rotval_last = LEFT;
}
}
else {
rotval_last = NOCHNG;
waitcount = 0;
}
if ( waitcount > 1 ) { //only give a L/R value after non-wait transition
waitcount = 0;
return rotval_last;
}
return NOCHNG; //ignore
}
USB HID
-
univerzální USB stack pro AVR mikroprocesory
umožňuje vytvořit libovolné USB 1.1 zařízení
licence umožňuje free použití, ale projekt musí být zveřejněn s kompletním návodem na použití
další omezení je v nějakých vendor kódech?
obsahuje hodně tutoriálů, například:
AVR HID - USB HID využívající 6x ADC
-
-
-
Lightweight USB Framework for the USB-enabled Atmel AVR8, formerly known as MyUSB
původně jsem myslel, že jde o konkurenci k V-USB, ale evidentně jde jen o framework pro AVR USB HW
má hodně volnou licenci..
-
-
-
Test V-USB
Teorie
-
pro komunikaci s USB je potřeba minimálně frekvence 12 Mhz
na USB se používají napěťové úrovně 3.3V, přestože poskytované napájení je 5V
pro převod 5V na 3.3V se nejčastěji používají následující zapojení
snížení VCC pro mikroprocesor použitím stabilizátoru
čisté řešení, poskytuje stabilní napětí bez šumu
některé AVR nezvládají 12Mhz při 3.3V VCC (ATmega8a má operační napětí od 2.7V, tak nevím)
je potřeba low power stabilizátor, protože USB zařízení při idle musí celkově brát pouze 500uA. Pull-up odpor 1k5 bere 200uA, takže na stabilizátor zbývá jen 299uA
-
snížení VCC pro mikroprocesor sériovým zapojením dvou diod
oproti stabilizátoru nespotřebovává proud
napětí ale kolísá podle odběru obvodu, což působí problémy analogovým částem (například ADC)
snížení napětí na D+, D- pomocí zenerových diod 3V6
při signalizování 1 bere obvod výrazně vyšší proud než při 0, což může způsobit podivné chování
je potřeba zvolit low power diody a raději chování diody při 1/0 přeměřit (napětí při 1 = 3.3V, proud obvodu < 500uA)
-
inicializace
zkontrolovat, že I/O piny pro USB jsou nastaveny na input s vypnutým pull-up rezistory (default stav po resetu)
usbDeviceDisconnect(), čekat 100ms, usbDeviceConnect() - pro jistotu, aby se host případně reinicializoval
usbInit() - inicializace driveru
sei() - zapne interrupts
následuje nekončený cyklus, ve kterém se každých 50 ms musí volat usbPoll()
mezi hostem a zařízením se posílají zprávy (na hosta control-in, na zařízení control-out). Zprávy mají strukturu usbRequest_t.
při přijetí zprávy se v kódu zavolá funkce usbFunctionSetup(uchar setupData[8]), která rozhodne, co se má provést za akci (například zaslat data ze zařízení, rozsvítit ledku, přijmout blok dat, …)
příjem zpráv ze zařízení
pokud jsou všechna data k dispozici, tak lze předat přímo v usbFunctionSetup
pokud se musí postupně vyčítat, tak se musí vytvořit funkce pro čtení dat usbFunctionRead
posílání dat na zařízení
pokud nám stačí data v kontrolní zprávě usbRequest_t (8B celkem), tak stačí v usbFunctionSetup
jinak musíme vytvořit usbFunctionWrite, která příjme payload stream
pokud se host uspí, USB zařízení by mělo přejít do suspend režimu. To musí zařídit přímo aplikace (V-USB neřeší). Pozná se to tak, že ustane aktivita na USB sběrnici. Více
zde
Zprovoznění demo HID mouse
zapojení krystalu
krystal mezi XTAL1/2, potom přes 27pF na zem
-
vůbec netuším, jaký je rozdíl mezi Startup time: XX CK + YY ms. Asi jde jen o inicializaci? Fungují mi všechny :)
ALE BACHA… crystal není ex. oscilátor :)
po přepnutí na externí oscilátor zařízení jen s krystalem vůbec nefunguje, protože nemá hodinový impulz :)
naštěstí to lze napravit
já jsem to oživil pomocí Raspberry PI.
stáhl jsem si a rozchodil wiringPI (
raspigpio)
vytáhl jsem si GND a GPIO0 z PI
nejprve jsem k pinům připojil měřák a spustil blink.sh z examples. Ukazoval impulzy 3.3V
potom jsem přivedl piny na XTAL1 a GND AVR MCU, Upravil blick.c na usleep(1) a AVR připojil k napájení (programátoru)
nic to nedělalo, dokud jsem mezi piny nepřidal kondenzátor 27pF
AVR MCU ožil, začal provádět program, začal komunikovat s programátorem, opravil jsem nastavení FUSE bitů
Předělání HID mouse na gamepad s rotačním enkodérem
HID description
-
-
v obvodu doporučují použít vratnou tepelnou pojistku RXE025 (chrání proti zkratu napájení)
dále doporučují zapojit Reverse current
protection diode, aby se externí napájení nevracelo do počítače
zenerky doporučují právě 500mW, menší (200mW) prý nejsou vhodné
ten 68R funguje prý pro snížení proudu mezi počítačem a zařízením a také jako zakončovací odpor (terminating resistor)
ten 1k5 pull-up rezistor se prý používá při připojení na 3.3V VCC. U 5V se prý používá 2k2
odpory se používají 1/4 Wají 1/4 W
The host and device talks over channels called „endpoints“, endpoints are identified by a number. There are some endpoints that a reserved for special use, while others can be configured to operate in different modes (interrupt, bulk, etc).
The host will always first use the „control endpoint“ (endpoint 0) first to request a description of the device
Then the configuration descriptor is requested, which also contains the number of endpoints available on the device. Each endpoint has its own descriptor as well.
Úpravy HID mouse
-
musel jsem změnit USB_CFG_INTERFACE_SUBCLASS na hodnotu 1 (to asi není potřeba) a opravit velikost USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH v usbconfig.h
přidal jsem vlastní knihovnu pro práci s LED a rotačními enkodéry (setLED, readRotary ..)
stav tlačítek se reportuje 1x za USB_CFG_INTR_POLL_INTERVAL
myšlenka je vyčítat hodnoty enkodéru hned a pamatovat si poslední směr až do reportování
pokud není spuštěn program, který by joystick vstupy odebíral, tak vůbec nenastává ten interrupt
po spuštění programu to zahlásilo stisk tlačítek, protože si to pamatoval
pokud se otáčí moc rychle, tak on vlastně hlásí pořád stisknuté tlačítko
vyřešil jsem to prokládáním. Vždy se jednou reportuje a jednou posílá prázdná hodnota tlačítek
prokládání ale zpomaluje reakční dobu, takže jsem začal zkoušet různé pool intervaly
původně tam bylo 100ms, minimum je prý 10ms
při 50ms to bylo v XP docela zpožděné, v XPL při rychlejším otáčení vynechávalo
při 20ms to ale už nereagovalo úplně dobře. Jakoby host systém nezachytil hodnotu
prokládání jsem nahradil pomocí podmíněného prokládání
stávalo se, že bylo potřeba nastavit button na ON, ale zrovna se reportovala prázdná hodnota tlačítek, takže se zbytečně čekalo na další interrupt
pokud je při interrupt směr enkodéru stejný jako předchozí, hlásí se vždy nula (vytváří pulz). Pokud jsou různé, tak se reportuje
zkracování interrupt intervalu
přes jstest –event jsem ověřil, že i při nejkratším intervalu (10ms) se pulzy tlačítka dostanou na hosta. Při max rychlosti otáčení zachyceno takových minimálně 6/10 pulzů. Pomalé otáčení zachyceno vždy všechno
count=0
jstest --event /dev/input/js1 |
while read line; do
echo $line | grep -q "number 1, value 1" && {
count=$((count+1))
echo $count
}
echo $line | grep -q "number 0, value 1" && {
count=$((count-1))
echo $count
}
done
ve Win XP, Win7, XPL se ale při 10ms pulzy nějak nezpracovávají. Jakoby nebyly. Podle mě je to skutečně tím, že aplikace nečte hodnoty dostatečně rychle. Že se prostě koukne na aktuální stav jednou za čas.
obecná optimální hodnota mi přijde 80ms. S tím nemá problém Windows ani XPL. To bohužel ale na rychlé otáčení nestačí a rotační enkodér postrádá smysl. Při max rychlosti otáčení zachyceno takových minimálně 2/10 pulzů. Lze asi cítit i mikro zpoždění při jedné otáčce
nejlepší hodnota pro XPL je 50ms. Při této hodnotě se už neztrácí žádné pomalé pulzy. Při max rychlosti to bývá minimálně cca 3/10. Od 80ms to ale žádný převratný rozdíl není, takže globálně je asi 80ms lepší
zavedení reportsleep
interrupt interval jsem dal na 10ms (používá to například i Logitech Extreme, viz lsusb -v)
pokud se zareportuje nějaká hodnota, nastavuje se sleep na 8. Jelikož je interval 10ms, dostávám cca 80ms
rozdíl mezi intervalem 80ms a 10×8 je v tom, že otáčka je reportována okamžitě :)
rotační enkodéry přes HID tedy fungují, ale tlačítka nejsou vhodná na reportování rychlých pulzů. Například rolovací kolečko u myši se reportuje jako relativní číselná hodnota (wheel). Není to button.
řešení by mohlo být vytvoření ovladače na straně hosta, který by pulzy převáděl rovnou na XPL dataref
TODO
záhada s axis
hodnoty axis jsem z počátku vyčítal z jstest aplikace
pak jsem to nahradil pomocí jstest-gtk, který navíc přehledně zobrazuje stav kalibrace (lze i upravit)
úpravy kalibrace se po odpojení joysticku ztratí
-
maximální zobrazená hodnota u joysticku v Linuxu může evidentně být 32768
pokud v HID deskriptoru nepoužiji vůbec LOGICAL_MAXIMUM/MINIMUM nebo nastavím hodnotu 0,
tak jstest ukazuje přesně zaslané hodnoty (raw hodnoty)
jako joystick se to ale úplně používat nedá, protože on nemá natavené hranice a střed (dead zone)
po kalibraci to je použitelné
pokud v HID deskriptoru použiji logical min/max
joystick v jstest začne vypisovat divné hodnoty. Pro některé bity se hodnota vůbec nezmění. Je to asi tím, že vypočtená hodnota přeteče zobrazovaný rozsah. Místo toho se zobrazí prostě buď minimum nebo maximum. Působí to, jako by se nic nedělo
kalibrace zobrazená v jstest-gtk je nějaká divná. Hodnota Min a Max u range neodpovídá log min/max.
i když kalibraci ručně opravím, tak axis nefungují stále dobře. Je to nějaký divný
u joysticku je rozsáhla dead zone, přestože u input je nastaveno, že nechci žádnou null position
nápady
zkusit deskriptor z happy pedals :)
zachytit HID deskriptor a zkontrolovat, že není třeba zmršenej :)
zachytit HID deskriptor z logitech joysticku… zajímalo by mě, co to přesně posílá (pro porovnání)
vybrečet se na fóru V-USB
| log min/max | variable type | start + inc | alg notice |
| 32k/32 | int16_t | -32k + 100 | pak to přeteče a půjde od -32k |
| 0/32k | uint16_t | 0 + 100 | nejvyšší bit vždy nulový &7fff |
| -32k/0 | int16_t | -32k + 100 | nejvyšší bit vždy jednička OR 0x8000 |
| 0/1023 | uint16_t | 0 + 10 | nejvyšších 6bitů vždy nulových &3ff |
| -512/511 | int16_t | -512 + 10 | když větší než 511, tak -512 |
| 0/64k | uint16_t | 0 + 100 | pak přeteče a půjde od nula |
Test ADC
Plány
test SPI
-
slave bude atmega8
master bude arduino nebo raspi…
slave bude obsahovat hodnotu X podle otáčení rotačního enkodéru
master tuhle hodnotu vypíše (terminal raspi nebo serial monitor arduino
rozšíření:
Zdroje