Ich entwickle ein Projekt auf einem Nucleo STM32F722 und möchte auf eine
fallende Flanke an einem bestimmten Eingangspin reagieren. Dazu habe ich
einen Interrupt und einen Handler angelegt.
1
staticvoidMX_GPIO_Init(void)
2
{
3
// ...
4
GPIO_InitStruct.Pin=GPIO_PIN_1;
5
GPIO_InitStruct.Mode=GPIO_MODE_IT_FALLING;
6
GPIO_InitStruct.Pull=GPIO_NOPULL;
7
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
8
}
9
10
voidEXTI1_IRQHandler(void)
11
{
12
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_1);
13
14
// check something (required)
15
if((GPIOA->IDR&0x00f0)!=0x0070)
16
return;
17
18
// signal start of handler (for scope)
19
GPIOE->BSRR=0x0001;
20
21
// ...
22
}
Der STM32F722 läuft mit 128 MHz (siehe Clock Config im Anhang).
Im Oszilloskop sieht man, daß zwischen Flanke und ausgelöstem Signal 0,7
us vergehen. Das halte ich für viel zu lang - da wäre Pollen ja
schneller.
Gibt es eine Möglichkeit, den Handler zu beschleunigen? Bspw. spart die
Verwendung von BSRR gegenüber HAL_GPIO_WritePin 0,2-0,3 us ein.
lars schrieb:> Bspw. spart die> Verwendung von BSRR gegenüber HAL_GPIO_WritePin 0,2-0,3 us ein.
Man kann lange drüber streiten, ob so ein HAL im Normalfall den Job
vereinfacht oder nicht. Aber in zeitkritischem Code hat sowas nichts
verloren, wenn es nicht bloss Makros sind, aus denen am Ende der gleiche
Code rauskommt.
Das __HAL_GPIO_EXTI_CLEAR_IT ist nur ein Makro für (EXTI->PR =
(_EXTI_LINE_)), also wohl schnell.
Das die HAL langsam ist, ist mir klar, darum verwende ich sie ja auch
nicht im Handler. Dennoch ist der Interrupt zu langsam.
Welche Ausgaben von objdump sind denn relevant? Oder willst Du letztlich
den generierten Assembler-Code?
Ist die Clock Config denn korrekt, oder benötigt APB1 einen höheren
Takt?
lars schrieb:> Der STM32F722 läuft mit 128 MHz (siehe Clock Config im Anhang).>> Im Oszilloskop sieht man, daß zwischen Flanke und ausgelöstem Signal 0,7> us vergehen. Das halte ich für viel zu lang - da wäre Pollen ja
Das wären ca. 90 Takte. An welchem APB hängt der GPIO?
Die APB können m.E. nicht mit dem Core-Takt laufen. ich weiß aber auch
nicht über welchen Weg der Interrupt gemeldet wird.
lars schrieb:> Welche Ausgaben von objdump sind denn relevant? Oder willst Du letztlich> den generierten Assembler-Code?
Es geht um den Code des Handlers, zumindest bis zum Messpunkt.
> Ist die Clock Config denn korrekt,
Messen?
- Laut STM erreicht man die 12 Zyklen ISR Latenz nur dann wenn man ITCM
und DTCM benutzt.
- Gemischte Zugriffe auf Device Memory (also auf Peripherien) können
extrem teuer werden, weil der vorherige Zugriff zuerst ausgeführt werden
muss bevor der nächste Zugriff gemacht werden kann, da die CPU
Seiteneffekte nicht kennt. Hier bringt dich die längere Pipeline um
deine Takte. Du machst WRITE, READ, WRITE. Ich vermute es wäre besser
wenn du zuerst GPIOA->IDR in eine lokale variable liest, dann den
Interrupt bestätigst, dann die if prüfung mit der variable und dann den
write auf die GPIO. (READ, WRITE, WRITE). Man kann da wohl mit der MPU
was machen.
- Sind Caches aktiviert?
*TCM ist ein sehr guter Hinweis, sonst laufe ich ja mit 4 Wait States
(wobei 120 MHz geschickter als 128 MHz wären).
Deine Hinweise bzgl. GPIO-Zugriff sind auch sehr praktisch.
Aber selbst im optimalen Fall halte ich 12 Zyklen (das sind ca. 94 ns)
für mich für etwas zu viel. Mit Polling
1
while(GPIOE->IDR&0x0002);
schaffe ich das geschätzt in 3-4 Zyklen (und PE1 bleibt hinreichend
lange aktiv).
lars schrieb:> Mit Polling> while (GPIOE->IDR & 0x0002);>> schaffe ich das geschätzt in 3-4 Zyklen (und PE1 bleibt hinreichend> lange aktiv).
Aber nur wenn keine Interrupts auftreten. Und wenn der externe Trigger
ausbleibt, dann hängt das Programm an der Stelle fest.
Das gehört für mich in die Kategorie: "Spätere Kundenwünsche werden von
der Rechtsabteilung bearbeitet." SCNR ;-)
Welches Problem wolltest Du eigentlich lösen? Eventuell sind
Capture-Timer oder getriggertes DMA eine bessere Variante. Bei komplexen
µC lagert man (echt-)zeitkritisches lieber an spezialisierte Hardware
aus.
Jim M. schrieb:> Aber nur wenn keine Interrupts auftreten. Und wenn der externe Trigger> ausbleibt, dann hängt das Programm an der Stelle fest.
Das wäre in meinem Fall OK, da das Programm sonst nichts zu tun hat.
> Welches Problem wolltest Du eigentlich lösen?
Ich möchte (zumindest experimentell) einen ca. 2 MHz Bus bedienen, also
für bestimmte Adressen einen berechneten Wert zurückliefern. Der Read
Enable löst den Interrupt aus, dann muß ich aber noch auf die Adresse
prüfen.
Natürlich bietet sich dafür ein FPGA an, ich will aber schauen, ob ein
STM32 auch dafür geeignet ist. Ein F1/F2 wäre eigentlich ausreichend,
aber ich benötige den FMC des F7.
lars schrieb:> Ich möchte (zumindest experimentell) einen ca. 2 MHz Bus bedienen, also> für bestimmte Adressen einen berechneten Wert zurückliefern. Der Read> Enable löst den Interrupt aus, dann muß ich aber noch auf die Adresse> prüfen.
Das wollte ich auch schonmal machen (Z80-Bus). Ich habe mich dann damit
begnügt einen Bussniffer zu haben, der blockweise scannt und das
Ergebnis auf dem Display darstellt.
Möglicherweise wäre was gegangen, wenn mit /WAIT arbeitet.
Hier scheint der FPGA-Einsatz gerechtfertigt.
Ein Controller mit integrierter parametrierbarer PIO wäre was...
lars schrieb:> Ich möchte (zumindest experimentell) einen ca. 2 MHz Bus bedienen, also> für bestimmte Adressen einen berechneten Wert zurückliefern. Der Read> Enable löst den Interrupt aus, dann muß ich aber noch auf die Adresse> prüfen.
Da wär ausnahmsweise ein oller ARM7/ARM9 im Verteil (oder ganz andere
Architekturen). Die Cortex-M haben zwar eine deutliche Optimierung bei
Interrupts vorzuweisen, aber gegen den separaten halben Registersatz der
FastIRQs der klassischen ARMs sind sie chancenlos.