Jutilise Serial.write()
pour transmettre 53 octets au PC. Pour la mesure du temps, jutilise micros()
(avant et après la fonction décriture). Il y a un délai de 1 s après chaque transmission.
Lheure de la fonction Serial.write()
est de 532 us avec 1000000 bauds et de 360 us avec 9600 bauds.
La fonction Serial.write()
est clairement asynchrone car la transmission de 53 octets avec un débit de 9600 bauds est de 53 * 8/9600 * 1e6 = 44167 us. (Au fait, dans le cas dun débit de 1000000 bauds, il nest pas si évident que la fonction soit asynchrone.)
Jutilise Serial.availableForWrite()
avant Serial.write()
pour confirmer quil y a suffisamment despace dans le tampon (cela renvoie 63 à chaque fois, la taille de la mémoire tampon est par défaut de 64).
Je ne comprends pas ces nombres. Utilisation memcpy()
pour copier les 53 octets ne prend que 32 us. La copie dans le tampon série est-elle différente de la fonction memcpy()
? Et pourquoi il y a une différence dans les temps de copie lorsque le débit en bauds est différent? Serial.write () est encore plus lent avec des débits en bauds plus élevés selon les résultats. Pourquoi Serial.availableForWrite()
renvoie 63 alors que le tampon la taille est de 64 (selon SERIAL_TX_BUFFER_SIZE)?
Mise à jour: Merci pour toutes vos réponses.
Jai essayé une autre bibliothèque pour la communication série: https://github.com/greiman/SerialPort
Il semble être plus rapide que loriginal. Jutilise 2 fois e mesures et un retard dans le code, ces 3 deltaTimes représentent toute létape comme suit:
writeTime + memcpyTime + delaymicros = computedTime! = realTime
Les 2 premiers temps sont mesurés, le delaymicros est le retard théorique (900 us), je peux calculer le temps de pas à partir de ceux-ci. Ce temps de pas calculé est différent du temps réel (le temps de pas mesuré, je mesure également le pas entier). Il semble que le temps supplémentaire peut être trouvé dans le délai.
Bibliothèque SerialPort: 100 + 30 + 900 = 1030! = 1350
writeReal = 100 + 1350 – 1030 = 430
Série Arduino: 570 + 30 + 900 = 1500! = 1520
writeReal = 570 + 1520 – 1500 = 590
Ensuite, jai mesuré le temps de delaymicros (qui soit 900 us en théorie), le temps manquant sy trouve. Jai programmé un délai de 900 us, mais le délai réel était denviron 1200 dans le premier test, et 920 dans le second test.
Ces mesures peuvent prouver lexistence des interruptions, car mesurer uniquement les fonctions décriture ne « t donner tout le temps décriture (en particulier avec la bibliothèque série téléchargée). La bibliothèque téléchargée peut fonctionner plus rapidement, mais nécessite une plus grande mémoire tampon en raison derreurs (256 fonctionne correctement au lieu de 64).
Voici le code : Jutilise un checkSum dans la fonction sendig (qui est 570-532 = 38 us). Jutilise Simulink pour recevoir et traiter les données.
struct sendMsg1 { byte errorCheck;//1 unsigned long dT1;//4 unsigned long dT2;//4 unsigned long t;//4 unsigned long plus[10];//40 };//53 sendMsg1 msg1; byte copyMsg1[53]; unsigned long time1; unsigned long time2; unsigned long time3; unsigned long time4; void setup() { Serial.begin(1000000); } void loop() { sensorRead(); sendMsg1_f(); //time3 = micros(); delayMicroseconds(900); //time4 = micros(); } void sensorRead() { time3 = micros(); msg1.t = micros(); for (unsigned long i = 0; i < 1; i++) { memcpy((void*)&(copyMsg1[i*sizeof(sendMsg1)]), (void*)&msg1, sizeof(sendMsg1)); } time4 = micros(); msg1.dT2 = time4 - time3; } void sendMsg1_f() { time1 = micros(); msg1.errorCheck = 0; for (int i = 0; i < sizeof(sendMsg1) - 1; i++) { msg1.errorCheck += ((byte*)&msg1)[i]; } Serial.write((byte*)&msg1,sizeof(sendMsg1)); time2 = micros(); msg1.dT1 = time2 - time1; }
Commentaires
- Pouvez-vous fournir le code que vous avez utilisé pour obtenir ces résultats?
- 1 octet de tampon est perdu à cause de limplémentation du tampon en anneau. queue une tête de tampon plein peut ‘ t pointer vers le même index, car queue une tête sont sur le même index dans cas de tampon vide
An swer
Si vous regardez limplémentation:
size_t HardwareSerial::write(uint8_t c) { _written = true; // If the buffer and the data register is empty, just write the byte // to the data register and be done. This shortcut helps // significantly improve the effective datarate at high (> // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown. if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) { // If TXC is cleared before writing UDR and the previous byte // completes before writing to UDR, TXC will be set but a byte // is still being transmitted causing flush() to return too soon. // So writing UDR must happen first. // Writing UDR and clearing TC must be done atomically, otherwise // interrupts might delay the TXC clear so the byte written to UDR // is transmitted (setting TXC) before clearing TXC. Then TXC will // be cleared when no bytes are left, causing flush() to hang ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *_udr = c; #ifdef MPCM0 *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0); #else *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << TXC0))); #endif } return 1; } tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE; // If the output buffer is full, there"s nothing for it other than to // wait for the interrupt handler to empty it a bit while (i == _tx_buffer_tail) { if (bit_is_clear(SREG, SREG_I)) { // Interrupts are disabled, so we"ll have to poll the data // register empty flag ourselves. If it is set, pretend an // interrupt has happened and call the handler to free up // space for us. if(bit_is_set(*_ucsra, UDRE0)) _tx_udr_empty_irq(); } else { // nop, the interrupt handler will free up space for us } } _tx_buffer[_tx_buffer_head] = c; // make atomic to prevent execution of ISR between setting the // head pointer and setting the interrupt flag resulting in buffer // retransmission ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { _tx_buffer_head = i; sbi(*_ucsrb, UDRIE0); } return 1; }
Il répond en gros à toutes les questions.
Si le tampon est vide et quil nenvoie rien, il enverra directement le caractère pour quil soit synchrone (mais lenvoi du caractère sera beaucoup plus rapide que la surcharge des rappels de fonction et ainsi de suite)
Non, vous ne pouvez pas utiliser memcpy, car il remplace juste le tampon, mais il ne fait rien dautre, comme démarrer le registre de données prêt interruption ni réglage correct des compteurs de tête / queue (cest un tampon rond, donc il peut y avoir un scénario où vous écraserez quelque chose en dehors du tampon)
Et la fonction décriture est appelée pour chaque caractère séparément (toutes les autres spécialisations décriture utilisent celui-ci)
Aussi si le tampon est plein, il « attendra quil y ait un espace pour un autre caractère.
Réponse
La copie dans le tampon série nest pas la même chose que
, non.
Le tampon série est un tampon circulaire . Pour cette raison, de nombreux calculs sont nécessaires pour déterminer exactement où placer le caractère suivant dans la mémoire tampon. Cela prend du temps. memcpy()
copie simplement un bloc de mémoire directement sur un autre. Il ne fait aucune vérification, et il ne peut « pas senrouler comme un tampon circulaire.
La raison pour laquelle des vitesses de transmission plus élevées semblent plus lentes est que chaque caractère est pris du tampon par une interruption.Plus le débit en bauds est élevé, plus linterruption est déclenchée souvent, et donc plus le processeur passe de temps à traiter cette interruption pour envoyer le caractère suivant. De même, moins le processeur dispose de temps pour traiter le placement des données dans la mémoire tampon série.
Réponse
La fonction que vous testiez est probablement Serial.write(const uint8_t *buffer, size_t size)
. Si vous le recherchez dans HardwareSerial.h, vous verrez
using Print::write; // pull in write(str) and write(buf, size) from Print
et limplémentation est dans Print.cpp:
/* default implementation: may be overridden */ size_t Print::write(const uint8_t *buffer, size_t size) { size_t n = 0; while (size--) { if (write(*buffer++)) n++; else break; } return n; }
Ceci écrit loctet un par un, et pour chaque octet, tout le code de HardwareSerial::write(uint8_t c)
est exécuté. Cela inclut les tests de tampon plein ou vide, et larithmétique de mise à jour de _tx_buffer_head
.
Comme indiqué dans le commentaire au-dessus du code, il aurait été possible de remplacez cela par une implémentation spécialisée. En principe, vous pouvez copier le tampon linéaire dans le tampon en anneau en utilisant au plus deux appels à memcpy()
et en mettant à jour _tx_buffer_head
une seule fois. Ce serait probablement plus efficace que limplémentation actuelle, au moins pour les tailles de tampon dans la plage que vous utilisez (proche de, mais inférieure à la capacité du tampon en anneau).
Cela en vaudrait-il la peine? Peut-être pour votre cas dutilisation. Mais cela pourrait aussi rendre le code plus complexe et nécessiter plus de flash. Et lavantage sera probablement vraiment minime pour les personnes qui écrivent de petits tampons. Je ne suis pas sûr quune pull request mettant en œuvre ce type doptimisation puisse être acceptée. Vous pouvez essayer si vous en avez envie.