Anbei mal ne UART-Routine für AVRs mit FIFO fürs Senden und Empfangen.
Funktion:
Die FIFO arbeitet mit Ein- und Ausgabeindex. Gegenüber Pointern ergibt
sich dadurch kürzerer und schnellerer Code.
In der Regel reichen max 258 Byte Empfangspuffer (HW-Puffer + SW-Puffer)
aus.
Sind beide Indexe gleich, dann ist nichts im Puffer. Die Eingabefunktion
(Receiveinterrupt) schreibt was in den Puffer und erhöht ihren Index.
Die Ausgaberoutine holt das Byte ab und erhöht ihren Index gleichfalls.
Sind beide Indexe gleich, ist der FIFO wieder leer.
Da die Indexe nur bis zum FIFO-Limit wachsen dürfen, macht das Erhöhen
das Macro ROLLOVER. Es erhöht den Wert und setzt ihn wieder auf Null,
wenn er das Maximum erreicht.
Die Funktion ugetchar0() liefert ein Byte aus dem FIFO zurück. Aber ist
nichts im FIFO, dann wartet sie, bis was empfangen wurde. Wenn in dieser
Zeit aber andere Tasks auszuführen sind, ist es daher sinnvoll, erstmal
ukbhit0() aufzurufen, ob überhaupt was im FIFO ist.
Analog gibt es beim Senden die Funktion utx0_ready() zum Prüfen, ob der
FIFO noch Platz hat. Damit kann man statt warten, bis ein Byte gesendet
wurde, was anderes machen.
Ein Empfangspufferüberlauf wird nicht abgetestet. Wer will, kann ihn
hinzufügen. In der Regel sichert man sich mit einem Protokoll gegen
falsche Daten bei Überlauf, z.B. CRC-Test.
Fallgrube 1:
Ich war es leid, ständig mit Fehlermeldungen bombardiert zu werden, wenn
ich mal nen anderen AVR verwende.
Der Hauptteil der Arbeit bestand daher darin, das Namens-Chaos bei den
AVRs in geordnete Bahnen zu lenken.
In der uart0.h sind daher ne ganze Menge Macros drinne, die die
verschiedenen Namen umdefinieren, damit ein Code alle AVRs erschlägt.
Die Funktionsnamen enden alle auf die Ziffer 0.
Wenn man einen AVR mit 2..4 UARTs benutzt, kann man den Code einfach für
die anderen UARTs kopieren und die 0 durch 1..3 ersetzen.
Fallgrube 2:
Da hier Interrupts mit main-Funktionen kommunizieren, müßten die Indexe
als volatile definiert werden, damit sie der AVR-GCC nicht wegoptimiert.
Dies würde aber in den Interrupts besonders weh tun, da es mehr Code
bedeutet.
Daher werden nur die Zugriffe in den main-Funktionen als volatile
gecastet (per Macro in meiner mydefs.h).
Fallgrube 3:
Die Kommunikation Interrupts mit Funktionen birgt die Gefahr, daß ein
Interrupt genau innerhalb eine Funktion zuschlägt und damit falsche
Werte liest oder setzt.
Die in meinem Code gewählte Reihenfolge der Instruktionen ist
interruptfest, d.h. nirgends müssen atomare Zugriffe unter
Interruptsperre gemacht werden.
Will jemand was an dem Code ändern, muß er das berücksichtigen.
Außerdem muß man mit Interruptsperre arbeiten, wenn man die Indexe auf
16Bit erweitern will oder Pointer statt Indexe verwenden will.
16Bit Zugriffe sind ja auf nem 8Bitter nicht interruptfest.
Peter
Peter Dannegger wrote:
> Anbei mal ne UART-Routine für AVRs mit FIFO fürs Senden und Empfangen.>>> Funktion:>> Die FIFO arbeitet mit Ein- und Ausgabeindex. Gegenüber Pointern ergibt> sich dadurch kürzerer und schnellerer Code.
Wieso ergibt sich denn kürzerer und schnellerer Code mit Index statt
Pointern? Bei Pointern muss nur dereferenziert werden (ld mit der
Adresse im X,Y,Z register). Bei Indizes muss noch gerechnet werden..
Oder liege ich da falsch?
Simon K. wrote:
> Wieso ergibt sich denn kürzerer und schnellerer Code mit Index statt> Pointern? Bei Pointern muss nur dereferenziert werden (ld mit der> Adresse im X,Y,Z register). Bei Indizes muss noch gerechnet werden..
Aber bei Pointern muß jede Operation 16-bittig gemacht werden (Laden,
Rückschreiben, Test auf Gleichheit, Test auf Endwert, Setzen auf
Startwert).
Dagegen sind die beiden zusätzlichen 8-Bit Additionen beim Indexzugriff
nur pillepalle.
Ich hab mal nur die Empfangsroutine auf Pointer umgestellt und schon
stieg der UART Code von 322 auf 378 Byte.
Anbei der Code.
Peter
Wäre es nicht besser, UART und FIFO zu entflechten? Damit wäre FIFO
unabhängig vom UART verwendbar. In meinen Anwendungen benutze ich auch
FIFOs, teilweise mehrere in einer Anwendung. Wenn der Code dann immer
wiederholt werden muss, spart man ja nix, und Klarheit gewinnt man auch
nicht...
Georg johann Lay wrote:
> Wäre es nicht besser, UART und FIFO zu entflechten? Damit wäre FIFO> unabhängig vom UART verwendbar.
Der FIFO ist speziell für die UART ausgelegt, z.B. die Enable/Disable
Interupt Befehle, um den HW-Puffer mit zu benutzen.
Auch würde ein Warten in den Funktionen einen Deadlock bedeuten, wenn
der FIFO nicht mit Interrupts verwendet wird.
> In meinen Anwendungen benutze ich auch> FIFOs, teilweise mehrere in einer Anwendung.
Kannst Du dafür mal Beispiele nennen?
Ich brauche nen FIFO ausschließlich in der UART, da nur dort die Bytes
einzeln reinkommen bzw. Zeit zum Senden brauchen.
> Wenn der Code dann immer> wiederholt werden muss, spart man ja nix, und Klarheit gewinnt man auch> nicht...
Man könnte dafür Macros schreiben, das spart aber nix, da der Code für
jeden FIFO separat existieren muß. Die FIFOs sollen sich ja nicht
gegenseitig stören.
Man könnte ein Array aus n FIFOs definieren und den FIFO per Argument
auswählen.
Damit wird der Code aber nur größer und langsamer, da dann nie direkt
auf die FIFO-Indexe zugefriffen werden kann sondern die erst umständlich
indirekt ausgelesen werden müssen.
Peter
Peter Dannegger wrote:
> Georg johann Lay wrote:>> In meinen Anwendungen benutze ich auch>> FIFOs, teilweise mehrere in einer Anwendung.>> Kannst Du dafür mal Beispiele nennen?> Ich brauche nen FIFO ausschließlich in der UART, da nur dort die Bytes> einzeln reinkommen bzw. Zeit zum Senden brauchen.
Zwei FIFOs brauchts in der genannten Anwendung für den UART, eine
weitere brauch ich für Timer1. Timer1 muss einerseite per Auftrag ne
HW-PWM erzeugen können, aber auch eine Zeitmessung (Kapazitätsmessung an
einer LED zur Helligkeitsmessung) via InCapture. (Die beiden
Verwendungen schliessen sich aus, erlauben aber kleiner Verzögerungen in
der Ausführung. Das InCapt muss mit einer Soft-PWM synchronisiert
werden, da die LED auch während der Helligkeitsmessung leuchten soll).
Insgesamt verwende ich 4 FIFOs (2*ISR-UART (IN+OUT), 1*Timer1-Service,
1*HW-PWM-Muster).
Da ich meine Anwendungen sämtlich nicht-blockierend schreibe, gibt es in
der main-Schleife keine Warte- oder Poll-Schleifen. Es wäre also nicht
möglich, auf die OFF-Phase der Soft-PWM zu warten, um eine Messung
einzufügen.
Die Anwendung ist zugegeben recht speziell, ich will aber net jedesmal
FIFO neu coden und verwende auch anderswo, wo es nur eine FIFO braucht,
den allgemeinen Code. Ich nehem dort ein paar Ticks mehr an
Ausführungszeit in kauf.
Peter Dannegger wrote:
> Fallgrube 2:>> Da hier Interrupts mit main-Funktionen kommunizieren, müßten die Indexe> als volatile definiert werden, damit sie der AVR-GCC nicht wegoptimiert.> Dies würde aber in den Interrupts besonders weh tun, da es mehr Code> bedeutet.> Daher werden nur die Zugriffe in den main-Funktionen als volatile> gecastet (per Macro in meiner mydefs.h).
Ja, volatile in einer ISR ist nicht toll. Eine Alternative zum
volatile-Cast ist eine Barrier... nicht unbedingt hübscher, aber eben
eine weitere Möglichkeit.
1
__asmvolatile("":::"memory");
Das sagt gcc, dass sich der Inhalt des Speichers möglicherweise geändert
hat, und entsprechende REG-Inhalte nicht mehr verwendbar sind. Eine
Barrier am Anfang der main-Loop erspart mir volatile und Casterei. Da
ich wie gasagt keine Poll-Schleifen verwende, reicht diese eine Barrier,
egal wieviele Werte in einer ISR manipuliert werden.
Georg johann Lay wrote:
> Zwei FIFOs brauchts in der genannten Anwendung für den UART, eine> weitere brauch ich für Timer1. Timer1 muss einerseite per Auftrag ne> HW-PWM erzeugen können, aber auch eine Zeitmessung (Kapazitätsmessung an> einer LED zur Helligkeitsmessung) via InCapture.
Ich habe eine ähnliche Anwendung, Temperaturregelung mittels STM160 am
ICP, und Heiztransistor am OC1B (OC1A geht ja nicht zusammen mit ICP).
Allerdings verwende ich da keinerlei FIFO.
Der ICP-Interrupt macht die Puls/Periodenmessung und wenn der Regler den
Wert nicht ausliest, wird er einfach mit der neuen Messung
überschrieben.
Ein FIFO wäre da nur hinderlich, ich will ja den möglichst aktuellsten
Meßwert und nicht irgendwelche uralten, sonst spinnt die Regelung.
Ich verwende sehr gerne diesen Overwrite Modus zur Parameterübergabe,
dann brauche ich mich nicht um nen Pufferüberlauf zu kümmern.
Man könnte natürlich universelle Macros zum Erzeugen einer FIFO
schreiben aber ich wollte das Beispiel nicht zu kompliziert machen.
Man hätte dann ganz schön rumbasteln müssen, um die Unterschiede
zwischen dem Empfangs- und dem Sende-FIFO irgendwie zu lösen. Das wäre
dann erheblich auf Kosten der Lesbarkeit gegangen.
Insbesondere in Zusammenhang mit Interrupts versuche ich den Code
einfach zu halten, da dabei schnell Fehler passieren können.
Wenn man also den Interruptcode kurz hält, kann man gut sehen, was er
bewirken könnte, wenn er an jeder Stelle des Main zuschlägt.
Mit irgendwelchen großen Macros oder Unterfunktionen darin, geht diese
Übersicht verloren. Da hilft dann nur noch rohe Gewalt (cli/sei an jeder
möglichen Stelle).
> Ja, volatile in einer ISR ist nicht toll. Eine Alternative zum> volatile-Cast ist eine Barrier... nicht unbedingt hübscher, aber eben> eine weitere Möglichkeit.>
1
>__asmvolatile("":::"memory");
2
>
Kannte ich bisher noch nicht.
Dürfte aber das Kind mit dem Bade ausschütten, d.h. sämtliche Variablen
betreffen, nicht nur die von Interrupts geänderten.
Da ist das Casten also zielgenauer.
Peter
Hallo Peter,
Vorerst einmal Danke für das geniale Prog.
Benutze WinAVR-20081205 mit AVR-Studio.
Folgendes Problem. Das Program läuft einwandfrei wenn ich die
Optimierung abschalte, sobald ich aber mit -Os optimiere werden mir die
volatile gecasteten Variablen wegoptimiert...
Vielleich wer eine Ahnung warum das so ist?
Ich hab jetzt die "Interrupt" Variablen fix als volatile deklariert.
Dann funktioniert es auch mit der Optimierung.
Was ich nicht verstehe, warum soll der Code bei fixer volatile
Deklaration größer werden als bei gecastetem volatile?
Dank
lg
Rudi
gast wrote:
> Frage, muss man hier nicht die Interrupts sperren?
Nein, weil erst hier der Interrupt den neuen Index bekommt und damit das
neue Zeichen senden kann:
1
tx_in=i;
Und das ist eine atomare Instruktion, da 8-bittig.
Außerdem würde die Sperre an der von Dir vorgeschlagenen Stelle einen
Deadlock bedeuten, da dann der Interrupt nie den Puffer freigeben kann.
Peter
P.S.:
Da der FIFO prinzipiell immer ein Byte freilassen muß, darf man das neue
Byte schon vorher reinschreiben.
gast wrote:
> Wieso Deadlock verstehe ich nicht sorry.
Weil bei vollem Puffer in der while-Schleife gewartet wird, bis wieder
Platz ist. Aber wie soll Platz frei werden, wenn zu diesem Zeitpunkt der
TX-Interrupt ausgeschaltet ist?
Hallo,
vielen Dank für den Code, damit hatte ich ratzfatz die serielle
Schnittstelle angebunden.
Ich hab etwas in dieser Art versucht (naja, in Realität nicht ganz so
schlimm):
1
...
2
uputs0("Ich schreibe einen Roman und der ist laaaaaaaang...");
3
...
Der Compiler hat dann beim Optimieren das uputchar0 genommen und in die
Schleife vom uputs0 mit reingepackt (hab ich im Assembler gesehen) -
soweit Ok. Jetzt hat er aber in dieser Schleife das tx_in in einem
Register gespeichert. Da das nicht volatile deklariert ist, war das
vollkommen legal. Nachteil ist allerdings, daß dann dieser Code im
uputchar0 nur noch das Register verändert:
1
tx_in=i;
Dann kommt der Interrupt:
1
ISR(USART0_UDRE_vect)
2
{
3
if(tx_in==tx_out){// nothing to sent
4
UTX0_IEN=0;// disable TX interrupt
5
return;
6
}
7
...
und schaltet sich wieder ab - er kann von den neuen Daten nix
mitbekommen, denn er weiß ja nix von dem Register. Irgendwann ist dann
der Puffer voll und das while im uputchar0 wird zur Endlosschleife.
Der Fix sieht so aus, daß man den Schreibzugriff auf das tx_in im
uputchar0 volatile machen muss:
1
...
2
vu8(tx_in)=i
3
...
Das gilt genauso für den Schreibzugriff (ROLLOVER) auf rx_out im
ugetchar0.
Außerdem verbraucht mein "Roman" aus dem Beispiel oben wertvolles SRAM.
Ich hab daher noch eine kleine Funktion gebaut, die den Text direkt aus
dem Flash liest:
1
uputs0_pgm_P("Ich schreibe einen Roman und der ist laaaaaaaang...");
Durch ein Makro wird der Text automatisch in den Flash gelegt. Wenn man
die Daten schon im Flash hat gehts so:
1
unsignedcharlongtext[]PROGMEM="dies ist ein langer Text";
2
3
voidprinttext(void)
4
{
5
uputs0_pgm((PGM_P)longtext);
6
}
Beide Änderungen hab ich in einen Patch gepackt und diesem Beitrag
angehängt.
Vielen vielen dank für den Code! Das erleichtert einem das Programmieren
einer UART ja wesentlich!
Was ich bisher nicht ganz verstehe is das Stückchen hier im uart Header:
usable: RX0_SIZE + 2 (4 .. 258)
Die Begrenzung auf 256 is klar, aber wieso dann das "+2"?
@peter
ich habe mal generell eine frage zum thema FIFO und atomare operationen.
ich habe meine fifos bisher immer so gestaltet das ich neben dem
schreib/lese-index noch einen differenzindex benutze. dieser dient als
generelle abfrage dafür ob ich zeichen in meinem puffer habe oder nicht
(um mir die abfrage schreib-leseindex ist gleich zu sparen). wenn dieser
als byte ausgeführt wird müsste es ja dennoch möglich sein im
hauptprogramm atomare zugriffe zu gewährleisten, oder ?
beispiel :
volatile char rptr = 0; // read pointer
volatile char wptr = 0; // write pointer
volatile char diff = 0; // diff pointer
volatile char data [100];
isr (irgendwas)
{
if (diff < 100)
{
diff++;
wptr++;
if (wptr > 100) { wptr = 0; } // rollover
data [wptr] = int_data_register;
}
}
char fifo_read (void)
{
if (diff > 0)
{
diff --;
rptr ++;
if (rptr > 100) { rptr = 0; } // rollover
return data [rptr];
}
return 0;
}
meine frage ist nun : kann es zu kollisionen kommen dadurch das ich 2
indexe (schreib/differenzindex bzw lese/differenzindex) verwende, oder
ist lediglich die gewährleistung des atomaren zugriffs auf den
differenzindex (dadurch das ich eben die ausführung der schreib/lese
funktionen des FIFOs an die diff-variable koppele) entscheidend ?
Rene Böllhoff schrieb:
> wenn dieser> als byte ausgeführt wird müsste es ja dennoch möglich sein im> hauptprogramm atomare zugriffe zu gewährleisten, oder ?
Wieso "dennoch"? Eher "deswegen". Aber nur weil es nur eine
8-Bit-Variable ist, ist nicht jeder Zugriff automatisch atomar.
> if (diff > 0)
Das ist ein atomarer Zugriff auf diff (weil nur gelesen wird).
> diff --;
Dieser ist nicht atomar, und daher problematisch.
Das ist ein Read-Modify-Write-Zugriff. Was wenn zwischen Read und Write
dein "irgendwas"-Interrupt auftritt?
Stephan M. schrieb:
> Was ich bisher nicht ganz verstehe is das Stückchen hier im uart Header:> usable: RX0_SIZE + 2 (4 .. 258)
Der FIFO darf sich nicht einholen, daher Size - 1.
Die UART hat noch einen Empfangspuffer für 3 Byte, also: Size - 1 + 3
Der Sendepuffer ist aber nur 2 Byte, also: Size - 1 + 2
Peter
>> diff --;>Dieser ist nicht atomar, und daher problematisch.>Das ist ein Read-Modify-Write-Zugriff. Was wenn zwischen Read und Write>dein "irgendwas"-Interrupt auftritt?
ok. soweit klar. aber dann müsste bei peters code das makro ROLLOVER in
der "ugetchar0" ja ebenfalls problematisch sein, da das "++x" im
ROLLOVER-Makro ja ebenfalls kein atomarer zugriff ist oder vertue ich
mich da ?!
ich habe mir mit meiner 3-index-lösung eine interrupt-sperre eingebaut,
aber es ist natürlich unschön jedesmal den interrupt zu sperren.
da mal eine frage (auch auf die gefahr hin das ich nun ge/erschlagen
werde) :
wenn ich den interrupt global sperre (mit cli) in der funktion dann ein
interrupt auftritt, und ich den interrupt wieder freigebe, geht mir
dieser dann verloren oder wird unmittelbar nach freigabe des interrupts
dieser ausgeführt ?
Rene Böllhoff schrieb:
> ok. soweit klar. aber dann müsste bei peters code das makro ROLLOVER in> der "ugetchar0" ja ebenfalls problematisch sein, da das "++x" im> ROLLOVER-Makro ja ebenfalls kein atomarer zugriff ist oder vertue ich> mich da ?!
Welche negativen Folgen soll der nicht-atomare Zugriff denn dort haben?
rx_out wird im Interrupt ja nicht verändert (nur gelesen).
Rene Böllhoff schrieb:
> ok. soweit klar. aber dann müsste bei peters code das makro ROLLOVER in> der "ugetchar0" ja ebenfalls problematisch sein
Nö, der Interrupt liest zwar den Ausgabeindex, aber er schreibt ihn
nicht.
Daher kann die Mainfunktion diesen gefahrlos ändern.
Dein "diff" wird jedoch in Main und Interrupt geschrieben.
Deine zusätzliche Variable ist nur dann einen Tick schneller, wenn der
FIFO leer ist.
Aber sie benötigt deutlich mehr Code.
Peter
> Die UART hat noch einen Empfangspuffer für 3 Byte, also: Size - 1 + 3> Der Sendepuffer ist aber nur 2 Byte, also: Size - 1 + 2
Woher hast Du das mit der Größe des Empfangs-/Sendepuffers im UART? Ich
hab wie blöd im Datenblatt vom meinem atmega168 genau nach dieser Größe
gesucht und nix gefunden.
Rene Böllhoff schrieb:
> wenn ich den interrupt global sperre (mit cli) in der funktion dann ein> interrupt auftritt, und ich den interrupt wieder freigebe, geht mir> dieser dann verloren oder wird unmittelbar nach freigabe des interrupts> dieser ausgeführt ?
Letzteres.
Gerd E. schrieb:
> Woher hast Du das mit der Größe des Empfangs-/Sendepuffers im UART? Ich> hab wie blöd im Datenblatt vom meinem atmega168 genau nach dieser Größe> gesucht und nix gefunden.
Steht normalerweise bei der UDR-Beschreibung.
1
The receive buffer consists of a two level FIFO.
Dazu kommt dann noch das Input-Shift-Register, also 3 Bytes effektiv.
>Nö, der Interrupt liest zwar den Ausgabeindex, aber er schreibt ihn>nicht.>Dein "diff" wird jedoch in Main und Interrupt geschrieben.
ah ok. da lag der knoten in meinen hirnwindungen. verstanden.
aber mit einer interruptsperre sollte das problem ja dann umschifft
sein.
ich fand die diff-geschichte recht angenehm da man am diff direkt sehen
kann wieviel noch im puffer steht, ohne den pufferstand aus dem schreib
und lese index berechnen zu müssen.
Hallo Forum,
mich macht folgender Sachverhalt seit 3 Tagen wahnsinnig! Ich habe das
obige Projekt für meinen UART verwendet (Benutze einen Atmega644 20PU
mit 14,7456MHz).
Folgendes: Wenn ich das Projekt auf meinen Atmega flashe, sendet dieser
stetig "A4". Er sendet kein "Hallo Welt" usw. Ich verstehe einfach
meinen Fehler nicht.
Das Projekt ist soweit unverändert (Dank Peter`s Vorarbeit), trotzdem
hänge ich dieses nochmal an.
Vielen Dank im voraus
Gabriel
PS.: Wenn ich den Reset-Button drücke, bekomme ich Hex "90 00".
Hallo zusammen,
ich wollte den Code unverändert Testen mit AVR Studio 4.19.
Ich bekomme aber folgende Fehler:
undefined reference to `init_uart0'
undefined reference to `uputs0_'
undefined reference to `uputs0_'
undefined reference to `ukbhit0'
undefined reference to `ugetchar0'
undefined reference to `uputchar0'
undefined reference to `utx0_ready'
undefined reference to `ukbhit0'
undefined reference to `ukbhit0'
undefined reference to `uputchar0'
undefined reference to `uputchar0'
undefined reference to `uputchar0'
undefined reference to `ugetchar0'
undefined reference to `uputchar0'
undefined reference to `ukbhit0'
collect2.exe: error: ld returned 1 exit status
Kann mir da jemand Helfen wo ich suchen muss?
Grüße
Tim
Hi,
erstmal vielen Dank für die Bibliothek. Ich hab sie vor einem halben
Jahr in einem Projekt eingesetzt, und sie hat wunderbar funktioniert.
Als ich sie heute jedoch in einem anderen Projekt mit einem AT90PWM3B
benutzen wollte, habe ich ein Problem festgestellt:
In der Headerdatei steht
1
usart0.h:
2
...
3
#ifndef UDR0
4
#define UDR0 UDR
5
#endif
6
...
was ja eigentlich dafür sorgt, dass das richtige Register verwendet
wird. In der Headerdatei für den AT90PWM3B
(/usr/avr/include/avr/io90pwm3b.h) steht jedoch:
1
#define UDR _SFR_MEM8(0xC6)
2
#define UDR0 0
3
#define UDR1 1
4
#define UDR2 2
5
#define UDR3 3
6
#define UDR4 4
7
#define UDR5 5
8
#define UDR6 6
9
#define UDR7 7
Somit kann die Auswahl nicht mehr richtig funktionieren.
Als Lösung würde ich vorschlagen, den Namen zu ändern und ihn dann in
einem konditionalen Define ersetzen zu lassen:
1
usart0.h:
2
...
3
#if (!defined UDR0 | UDR0 == 0)
4
//#ifndef UDR0
5
#define UDR0_ UDR
6
#else
7
#define UDR0_ UDR0
8
#endif
9
...
In der Quelldatei müssten dann noch die beiden Vorkommen von UDR0 zu
UDR0_ geändert werden.
Ich hoffe, dass das jemandem hilft. Vielleicht mag Peter den Vorschlag
ja sogar übernehmen?
viele Grüße,
mox