Nous effectuons des tests de charge des bibliothèques USB haute vitesse pour STM32F103C8T6

Dans l' article précédent, j'ai montré la vitesse maximale du bus USB pour le microcontrôleur STM32F103 avec la bibliothèque MiddleWare standard. Dans les commentaires, on m'a montré deux bibliothèques maison à la fois qui extraient tout le jus de l'USB FS. Mais l'auteur de l'une des bibliothèques a exprimé l'idée qu'elles fonctionnent rapidement et rapidement et que leur fiabilité n'est pas claire. Il a pensé qu'il serait utile de faire des tests de charge avec des données utiles. Ce n'est que s'ils ne sont pas perdus ou déformés que l'on peut dire que les bibliothèques ont droit à la vie.







Inutile de dire que je pouvais à peine attendre le week-end pour faire mes vérifications. Jetons un coup d'œil aux résultats des tests. Et pour le rendre plus intéressant, nous considérerons en cours de route la technologie d'affichage des variables "à la volée", sans arrêter le cœur du processeur. Eh bien, et la technologie de débogage visuel des fichiers elf collectés par les compilateurs par lots.



Quel type de test nous allons effectuer



L'essence des tests a été discutée dans les commentaires de l'article précédent. Le programme PC d'origine envoie des données. Ce qui n'est pas important. Des données, et c'est tout. Le contrôleur accepte ces données et les ignore en toute sécurité. Parce que le problème était la vitesse de transmission. Les nouvelles bibliothèques peuvent théoriquement soit ignorer certaines données, soit mélanger une paire de tampons. Par conséquent, le débit doit devenir variable dans le temps. Dans de tels cas, une séquence pseudo-aléatoire ou des données incrémentielles sont envoyées.



Ne voulant pas passer beaucoup de temps à générer une séquence pseudo-aléatoire pour la source et la destination (l'algorithme doit être le même), je me suis limité à un mot incrémental de 32 bits. Le principal inconvénient de cette approche est que jusqu'à trois octets sur quatre peuvent coïncider dans des paquets voisins. Mais un, et le tout premier, sera différent. Donc pour le test d'aujourd'hui, cela me semble acceptable.



Le fait est que le protocole USB est un paquet. Et le paquet lui-même est capturé au niveau matériel. Il ne devrait pas y avoir de situation à l'intérieur du bloc où un octet est corrompu, puis des données utiles sont à nouveau envoyées. Au moins à cause des problèmes que nous voulons attraper dans les bibliothèques maintenant, cela ne se posera pas. Si les données seront corrompues, alors globalement. Si les anciennes données sont écrasées par les nouvelles, le premier octet sera écrasé en premier et il est différent dans différents paquets.



En principe, tout le monde pourra réécrire mon code dans une version différente des données de test... Aujourd'hui, je vais simplement envoyer un mot de 32 bits incrémenté, et à sa réception, vérifier que l'incrément se passe sans casser la séquence.



Comment allons-nous suivre le résultat



Comment allons-nous savoir que tout fonctionne? Une LED ne suffira pas. Eh bien, pour ajouter un UART, vous devez coder en dur le code de quelqu'un d'autre. Vous pouvez faire vos erreurs. Utilisons une fonctionnalité que je connais depuis longtemps, mais que je ne l'ai toujours utilisée que dans l'environnement de développement Keil. Aujourd'hui, je vais vous montrer comment l'utiliser dans Eclipse. Des commentaires au dernier article, je me suis rendu compte que tout le monde ne connaît pas cette technologie.



Le port de débogage JTAG ne permet de travailler que lorsque le cœur du processeur est arrêté. Ce n'est pas acceptable pour l'USB. Là et pendant le fonctionnement normal, les arrêts sont chargés de temporisations, et dans notre cas, même si nous ne rattrapons pas la temporisation, la vitesse peut être sous-estimée. Heureusement, le port de débogage SWD vous permet de surveiller la mémoire à la volée. En 2016, j'ai vérifié à l'aide d'un oscilloscope, qui vous permet de définir la synchronisation par durée d'impulsion, l'accès à la mémoire par SWD ne ralentit pratiquement pas le cœur du processeur. Mais comment l'utilisons-nous ?



La première chose sur laquelle nous allons nous appuyer aujourd'hui est la capacité de CubeIDE (qui est un Eclipse dopé) à afficher des variables à la volée. Nous allons créer un groupe de variables, où le programme affichera de nombreuses informations utiles, et nous commencerons à les suivre à l'écran. Beaucoup de gens le savent, mais jusqu'à présent pas tout le monde. Que tout le monde le sache maintenant.



Et la seconde est ce que les gars et moi avons récemment trouvé. Personne dans notre bureau ne le savait. Il s'avère que si vous construisez un projet avec un compilateur par lots, en y incorporant des informations de débogage Dwarf-2, ce fichier elf peut être ouvert dans Eclipse pour le débogage et vous pouvez obtenir un lien complet avec ses sources. Dans le même temps, les sources elles-mêmes n'ont pas besoin d'être connectées au projet.Le débogueur extraira automatiquement les chemins vers eux à partir des informations de débogage. Maintenant, je fais toujours ça. Un projet GCC ou CLang est construit, et je connecte simplement elf à Eclipse et le trace, sans perdre de temps à attacher le projet lui-même à cet Eclipse. Parfois, ils m'envoient même des fichiers elfes collectés par des chaînes d'outils qui ne sont pas sur ma machine. Même compilé sous Linux (et je travaille avec Windows). La méthode fonctionne même dans ces cas, tant que le projet est envoyé en ensemble complet : elfe et ses sources. Aujourd'hui, cela nous aidera à ne pas finaliser les projets de l'auteur au niveau de leur structure. Je vais juste tout construire sur la base du makefile "natif", puis me connecter avec le débogueur au fichier elf.



On se forme pour se connecter



La première chose que nous devons faire dans CubeIDE est un projet pour STM32F103. "Attendez une minute !", s'exclame le lecteur attentif... "L'auteur vient de promettre qu'il n'aura rien à faire avec le projet original !!!" C'est d'accord. C'est la bizarrerie de CubeIDE. Nous avons besoin d'un projet pour STM32F103. Quelconque. L'essentiel est qu'il soit sous STM32F103. Nous le créons, le collectons et l'oublions. Ce qu'il contient n'a pas d'importance. Le fait même de son existence dans l'environnement de développement est important.



Maintenant, dans CubeIDE, nous allons dans les paramètres du débogueur. Par exemple, comme ceci :







Nous n'aurions pas à pervertir avec la création du projet de gauche si nous sélectionnions l'élément GDB Hardware Debugging. Je le choisis toujours dans les Eclipses régulières. J'ai essayé de le sélectionner ici :







Hélas. Le projet de gauche ne sera pas nécessaire, mais la fonctionnalité d'affichage des variables en temps réel serait indisponible. Par conséquent, hélas et ah. Choisissez l'application STM32 Cortex-M C / C ++. J'ai déjà deux configurations là-bas. Maintenant, pour m'assurer que je ne vous ai pas trompé, je vais en créer un troisième. Pour ce faire, je double-clique ici :







je nommerai la configuration Article :







Vous devez sélectionner le chemin du fichier elf :







j'ai choisi ce chemin (il ne doit y avoir aucune lettre russe nulle part dans le chemin) :







Et ici l'erreur clignote . Le voici, le rouge :







Pour le supprimer, je dois sélectionner un projet lié au STM32F103. C'est ici que vous devez accéder à Parcourir :







et sélectionnez le projet de gauche précédemment créé.







Le rouge (signe d'erreur) a disparu :







Oups ! Sur cette figure, vous pouvez voir qu'après avoir sélectionné un projet, le nom du fichier elf a sauté. L'elfe de ce projet a été enregistré. J'ai dû sélectionner celui dont j'avais besoin après avoir à nouveau spécifié le projet. Pas étonnant que j'aie parcouru tous les points.



Comme nous n'avons rien à collecter ici (nous collectons tout par lots), nous devons cocher la case pour que le système n'essaie pas de faire un travail inutile :







Sur cet onglet - tout. Allez dans l'onglet Débogueur. C'est vrai, rien n'a besoin d'être changé ici. Du moins, si tout est configuré de la même manière :







En fait, vous n'avez besoin de rien changer ailleurs. Eh bien, commençons le débogage ?







Techniquement, oui. Sur le plan organisationnel - nous devons d'abord préparer le code que nous allons exécuter.



Vérification de la première bibliothèque



Alors, téléchargez le projet stm32samples / F1-nolib / CDC_ACM sur master eddyem / stm32samples GitHub pour la paternité EddyEm...



N'oubliez pas d'ajouter la formation des informations de débogage dwarf-2 au makefile : Idem





avec le texte :

CFLAGS	+= -O2 -g -gdwarf-2 -D__thumb2__=1 -MD

      
      





Nous commençons à éditer le code.



Il y a une boucle infinie dans la fonction main (). Je ne lui laisserai que des cornes-oui-jambes :

    while (1){
        IWDG->KR = IWDG_REFRESH; // refresh watchdog
        usb_proc();
        get_USB();
    }

      
      





Je vais créer une fonction get_USB () fonctionnelle comme celle-ci :

uint32_t loop = 0;
uint32_t errors = 0;
uint32_t errState = 0;
int32_t lastData = 0;
int32_t show = 0;
int32_t pkt = 0;

#define USBBUF 63
char tmpbuf[USBBUF+1];
int32_t* pData = (int32_t*) tmpbuf;
// usb getline
char *get_USB()
{
    int x = USB_receive((uint8_t*)tmpbuf) / sizeof(uint32_t);
    int i;

    show += 1;

    if(!x) return NULL;

    pkt += 1;
    //     -    
    //   !
    if (pData [0] == 0)
    {
         lastData = 0;
         errState = 0;
         loop += 1;
    }
    //    
    if (errState)
    {
         return NULL;
    }
    //   
    for (i=0;i<x;i++)
    {
          // !
          if (pData[i]!=lastData++)
          {
             //  !
             errState = 1;
             //   
             errors += 1;
             //     
             return NULL;
          }
    }
    //        
    return  NULL;
}

      
      





Un tas de variables globales sont faites afin de les surveiller en temps réel. Les locaux sont perdus à chaque démarrage. Les globales sont visibles pour toujours.



La variable show indiquera que le débogueur affiche réellement tout. Il est incrémenté à chaque entrée de fonction, qu'il y ait eu des données ou non. Et nous appelons la fonction dans une boucle infinie tout le temps.



La variable pkt montrera que les données arrivent vraiment (au début elles ne venaient pas de moi). Il n'augmentera que si nous ne sortons pas du fait qu'il n'y avait rien de l'USB.



lastDatamontrera combien nous avons déjà compté dans le test. Cela garantira que nous travaillons vraiment avec de gros blocs de données. La valeur de cette variable à la fin du test indique la taille du bloc en doubles mots. Pour comprendre combien d'octets se sont écoulés, vous devez multiplier la valeur par 4. La



boucle augmentera lorsqu'un bloc de données arrivera, en commençant à zéro. En gros, c'est le numéro du test. Eh bien, ou le numéro d'exécution. Lorsque je collecte des statistiques pour le traçage, il y a pas mal de ces exécutions. Différentes tailles des blocs demandés, multipliées par des répétitions pour faire la moyenne des résultats.



errState- une variable auxiliaire qui évite l'apparition d'une boule de neige d'erreurs. A la première erreur, il vole jusqu'à un et arrête d'analyser les données avant de commencer un nouveau test.



erreurs - le compteur des erreurs qui se sont produites une fois. Au début, je me suis moi-même trompé de logique, puis ce compteur augmentait constamment. Mais si tout va bien, il ne devrait pas augmenter.



J'ai presque oublié. J'ai également commenté la vérification du drapeau dans la fonction USB_receive :





La même chose avec le texte :

uint8_t USB_receive(uint8_t *buf){
    if(/*!usbON ||*/ !rxNE) return 0;
...

      
      







Ce drapeau est défini lorsque le terminal définit la vitesse du port COM virtuel. Mon programme de test, en revanche, ouvre le périphérique directement via le pilote WinUSB et ne fait rien pour personnaliser la fonctionnalité CDC. La chose la plus simple à faire était d'ignorer ce drapeau.



Bien. Construisez le projet avec un compilateur batch et exécutez-le dans le débogueur CubeIDE. Comme on m'a dit, tous les lecteurs n'aiment pas les dessins animés. Pour certains, ils détournent l'attention de la lecture du texte. Mais c'est vraiment agréable à voir.







C'est tic tac ! C'est tic tac ! C'est tic tac !



Bien. Ajoutez le remplissage du tableau au programme de test :

    QByteArray data;
    data.resize(totalSize);

    uint32_t* dwPtr = (uint32_t*) data.constData();
    for (uint32_t i = 0;i<totalSize/4;i++)
    {
        dwPtr[i] = i;
    }

      
      





Et on fait le test. On obtient ce genre de beauté dans les variables (zéro erreur) :







Et voici un graphique de la vitesse :







Les valeurs sont légèrement inférieures à celles d'un fonctionnement complètement au ralenti, mais toujours agréables. En général, ST-LINK a été ajouté à mon système et le nombre de bits s'exécutant sur USB dépend des données pompées (parfois un bit de synchronisation peut être inséré).



Un cheval sphérique travaille dans le vide, mais qu'en est-il du vrai ?



Il y a un problème potentiel avec tout ce système. Maintenant, la fonction de réception de données est constamment appelée dans une boucle infinie. Nous n'avons pas d'emplois utiles particuliers pour le moment. Et s'ils le sont ? Ensuite, l'appel de cette fonction peut se produire non pas immédiatement après l'arrivée du paquet, mais avec un retard.



Allons-nous tester différentes options ? Sur l'esprit - ce serait nécessaire, mais il n'y a pas de temps. Je suis actuellement occupé au travail avec un contrôleur complètement différent. Et puis je me suis juste amusé le week-end. Par conséquent, nous irons dans l'autre sens. Nous collerons la réception des données au fait de leur arrivée.



Ces choses sont faites avec des interruptions. Mais dans le dernier article, il a été dit que si nous étendons le gestionnaire d'interruption USB, le matériel commencera à envoyer des NAK et tout le charme de la bibliothèque en question sera réduit à néant. Comment obtenir une interruption, mais ne pas s'attarder sur l'interruption ?



Eh bien, le chemin est connu ici. Dans le gestionnaire d'interruption USB, nous devons faire en sorte qu'immédiatement après l'avoir quitté, l'interruption soit également déclenchée, mais une autre. Et là, nous allons rapidement, avec une faible latence garantie, transférer les données de la mémoire tampon matérielle dans notre mémoire interne. Sur quelle interruption s'asseoir ? Examen du code de démarrage. À savoir, les gestionnaires d'interruption. Notre tâche est de trouver celui qui n'est pas utilisé.



Voici le fichier qui nous intéresse

\stm32samples-master\F1-nolib\inc\startup\vector.c



Permettez-moi d'emprunter effrontément une interruption du troisième UART. En fait, nous n'utilisons pas non plus le premier. Mais ce sera peut-être plus tard. Et je n'ai jamais utilisé le troisième de ma vie. Par conséquent, personnellement, je vais m'asseoir avec insolence sur ce gestionnaire particulier. Voici comment cela est décrit :



[NVIC_USART3_IRQ] = usart3_isr, \



Connaissant le nom, créez une fonction dans le fichier main.c :

void usart3_isr()
{
    NVIC_ClearPendingIRQ(USART3_IRQn);
    get_USB();
}

      
      





Ce sera une sorte de fonction de rappel. Et déjà il nous appellera le code que nous avons récemment écrit. Et commentons l' appel à get_USB () dans une boucle infinie.



Maintenant, nous devons définir cette interruption sur une priorité inférieure afin qu'elle n'interfère avec personne. Dans la vraie vie, vous devrez peut-être faire preuve de créativité pour choisir une priorité. Mais aujourd'hui, je ne prendrai que le quinzième. Nous ajoutons le code suivant à la partie d'initialisation de la fonction main() :

    NVIC_SetPriority(USART3_IRQn, 15);
    NVIC_EnableIRQ(USART3_IRQn);

      
      





Eh bien, maintenant vient la partie amusante. Dans le gestionnaire d'interruption USB, ajoutez une provocation pour déclencher l'interruption USART3, s'il y a eu un appel à notre point de terminaison :





Le même texte.
#include "stm32f10x.h"
void usb_lp_can_rx0_isr(){
   LED_off(LED0);
    if(USB->ISTR & USB_ISTR_RESET){
    }
    if(USB->ISTR & USB_ISTR_CTR){
        // EP number
        uint8_t n = USB->ISTR & USB_ISTR_EPID;

        if (n == 1)
        {
             NVIC_SetPendingIRQ(USART3_IRQn);
        }
        // copy status register
        uint16_t epstatus = USB->EPnR[n];
        // copy received bytes amount

      
      









Comme la priorité est faible, rien ne se passera jusqu'à la fin de l'interruption USB. Mais dès que cela se termine, ils nous appelleront immédiatement. Parce que nous n'avons pas encore d'autres interruptions. Même avec la quinzième priorité, nous serons les VIP.



Nous lançons. Au début, il est effrayant que la variable show n'augmente pas. Mais c'est normal. Maintenant, la fonction n'est pas appelée inconditionnellement, mais seulement après l'interruption réelle. Il faut donc commencer les tests.



Vous pouvez regarder le processus de test pour toujours.







Et voici la métrique de vitesse :







Vérification de la deuxième bibliothèque



Maintenant, nous vérifions la bibliothèque usb / 5.CDC_F1 sur le principal COKPOWEHEU / usb GitHub par COKPOWEHEU... Une description de cette bibliothèque peut être trouvée ici : USB sur les registres : STM32L1 / STM32F1 / . C'est là que nous disposons de fonctions de rappel pour gérer l'activité des points de terminaison. Ici, nous allons le réparer. La variable show n'est plus nécessaire. Nous sommes toujours appelés à l'arrivée des données. Sinon, on obtient pratiquement le même code.

uint32_t loop = 0;
uint32_t errors = 0;
uint32_t errState = 0;
int32_t lastData = 0;
int32_t pkt = 0;

void data_out_callback(uint8_t epnum){
  int i;
uint8_t buf[ ENDP_DATA_SIZE ];
int32_t* pData = (int32_t*) buf;

  int len = usb_ep_read_double( ENDP_DATA_OUT, buf) / sizeof (uint32_t);
  if(len == 0)return;

    pkt += 1;
    //     -    
    //   !
    if (pData [0] == 0)
    {
         lastData = 0;
         errState = 0;
         loop += 1;
    }
    //    
    if (errState)
    {
         return NULL;
    }
    //   
    for (i=0;i<len;i++)
    {
          // !
          if (pData[i]!=lastData++)
          {
             //  !
             errState = 1;
             //   
             errors += 1;
             //     
             return NULL;
          }
    }


}

      
      





Lors de la vérification avec moi, CubeIDE pour une raison quelconque a incorrectement déterminé l'adresse de départ. Peut-être y a-t-il une sorte d'incompatibilité avec le même projet "de gauche". Reportons cela pour une étude séparée. Jusqu'à ce que je commence à comprendre, mais dès le début, j'ai entré la valeur correcte du registre PC. Le code s'est exécuté et a commencé à fonctionner. On fait le test. Le nombre d'erreurs est également nul :







La vitesse est également correcte :







Conclusion



Les deux bibliothèques USB russes ont fait face à des tests de charge approximatifs. Aucun d'eux n'a quitté la course. Certes, je sais de première main que les tests ne prouvent pas l'absence d'erreurs, ils révèlent leur présence. Mais les tests spécifiquement cités n'ont rien révélé. Cela donne l'espoir que n'importe laquelle de ces bibliothèques peut être utilisée.



En cours de route, nous avons maîtrisé le remplacement de la sortie de débogage en surveillant un certain nombre de variables en temps réel via le port SWD. En gros, nous maîtrisions également le débogage de toutes les applications construites par lots dans Eclipse, mais en cours de route, en raison du mélange des deux projets, j'ai eu quelques difficultés que j'ai dû surmonter en corrigeant directement le registre du PC. Mais dans un Eclipse normal, ce genre de mixage n'est pas nécessaire. Et à la fin, même avec l'aide d'une faucille, d'un marteau et d'une sorte de mère, le but ultime était toujours atteint. Le débogage a été fait. Dans le même temps, les codes sources sur Syakh étaient toujours affichés dans Eclipse.



Épilogue



Lorsque l'article était déjà écrit, mais qu'il était encore en train d'être téléchargé sur Habr, un matériel aussi merveilleux est apparu pour la paternité DSarovsky... Là aussi, l'accès à l'USB est implémenté, mais cela se fait via une bibliothèque réalisée dans mon style préféré - le style de Konstantin Chizhov.



Je suis simplement obligé de constater l'existence d'une bibliothèque réalisée dans une si belle version. Pour le moment, nous avons vérifié les performances avec son auteur et avons découvert que jusqu'à présent, sa vitesse est typique, pas maximale. Mais il est possible que lorsque vous lisez ces lignes, il ait déjà été overclocké. Par conséquent, je laisserai un lien vers celui-ci parmi d'autres. Elle n'a qu'à décoller ! Les bibliothèques de ce style ne peuvent s'empêcher de décoller !



All Articles