"Maintenant je vais vous montrer un portrait ... Hmm ... Je vous préviens que c'est un portrait ... Quoi qu'il en soit, veuillez le traiter comme un portrait ...Dans cet article, nous parlerons du développement et du débogage des programmes pour le CC1350 MK dans l'environnement de développement CCS recommandé par le fabricant. Les mérites (et ils le sont) et les inconvénients (et comment sans eux) des produits ci-dessus seront affectés. Il n'y aura pas de captures d'écran dans le texte pour montrer (encerclé) l'emplacement de l'icône de compilation dans l'environnement de programmation intégré ou sélectionner un fichier dans un répertoire. Reconnaissant la possibilité fondamentale d'articles dans un style similaire, je vais essayer de me concentrer sur les questions conceptuelles dans l'espoir que mon lecteur sera en mesure de comprendre les détails.
Le but de cet opus, en plus de partager l'expérience acquise, est de tenter de susciter une saine envie chez les fabricants nationaux de MK, qui sont des concurrents directs de TI ("dans le pays où nous prospérons") - la tâche est franchement ingrate, mais ils disent qu'une pierre porte une goutte.
Je soulignerai tout de suite qu'il ne s'agira que de Windows 7 (d'ailleurs, seulement) version 7, bien que le site Web de TI ait une option pour Mac et Linux, je ne les ai pas essayés, je suis tout à fait prêt à croire que tout n'est pas si cool là-bas, mais pourquoi penser sur le mauvais (ou vice versa, tout y est super, mais alors pourquoi l'envie).
Alors, que nous apprend le site Web de TI - pour commencer à travailler avec les modules d'évaluation, vous devez effectuer trois étapes nécessaires:
- Acheter des modules d'évaluation - terminé.
Remarque sur les marges (PNP): Vous devrez également le faire, car dans l'environnement de programmation en question, je n'ai pas (personnellement) réussi à trouver la possibilité d'émuler du matériel pour le débogage, du moins là où je cherchais. - Installez l'environnement de développement - téléchargez, exécutez le programme d'installation, tout a fonctionné. Nous connectons le module d'évaluation à l'USB - le bois de chauffage monte tout seul et tout a fonctionné à nouveau - terminé. Lorsque vous essayez de programmer l'appareil, nous recevons un message sur la nécessité de mettre à jour le firmware, nous sommes d'accord et encore une fois tout s'est avéré. En général, il n'y a rien à écrire si c'était toujours et partout ...
- Allez étudier le cours TI SimpleLink Academy 3.10.01 pour SimpleLink CC13x0 SDK 3.10 - une suggestion étrange, il semble que j'apprenne - juste pour gâcher, mais tant pis, j'ouvre le lien correspondant et je suis stupéfait - combien de choses sont fourrées ici.
Nous voyons ici du matériel de formation sur l'utilisation des pilotes matériels SYS / BIOS et du système d'exploitation TI-RTOS et sur l'utilisation de la pile réseau NDK, y compris USB, et sur l'utilisation de protocoles sans fil et de nombreux autres aspects du travail avec des représentants de diverses familles MK produits par la société. Et toute cette richesse s'accompagne d'exemples prêts à l'emploi, et si nous tenons compte de la présence de manuels d'utilisation et de descriptions de modules, il n'y a peut-être plus rien à souhaiter. Mais il existe également des utilitaires qui facilitent le travail de préparation et de configuration du code de programme, de flashage et de débogage de diverses manières, et cette richesse est également très documentée.
Pnp: si quelqu'un est enclin à considérer ce matériel comme de la publicité par rapport à l'entreprise, ses produits et son système de programmation, alors il aura très probablement raison et je suis vraiment très impressionné par le volume de logiciels détectés. Sa qualité sera discutée plus loin et, j'espère, les soupçons de parti pris seront dissipés, je n'étais pas complètement aveuglé par le sentiment et je continue de voir parfaitement les défauts de l'objet de description, donc ce n'est pas l'amour de la jeunesse, mais un sentiment sérieux d'un spécialiste adulte. J'ai peur d'imaginer le montant des coûts matériels nécessaires pour créer et maintenir un tel volume de logiciels et de documentation, mais cela n'a évidemment pas été fait en un mois, et l'entreprise comprend probablement ce qu'elle fait.
D'accord, jusqu'à ce que nous reportions l'étude des matériaux pour plus tard, nous comprendrons tout "le long du chemin avec la pépite" et ouvrirons audacieusement CCS. Il implémente le concept d'espaces de travail, reçu du parent - Eclipse. Personnellement, le concept de projet est plus proche de moi, mais personne ne nous dérange pour garder exactement un projet dans l'espace, alors allons-y.
Mais alors les choses empirent un peu - nous ouvrons l'espace de travail (RP) pour notre carte de débogage et voyons beaucoup de projets (en règle générale, en deux versions - pour RTOS et pour «le fer nu»). Comme je l'ai dit plus tôt, ce n'est pas un crime, mais le fait que de nombreux projets contiennent les mêmes fichiers avec des modules logiciels identiques n'est pas génial du tout. Le code est dupliqué plusieurs fois et la prise en charge des modifications devient une tâche très simple. Oui, avec une telle solution, il est beaucoup plus facile de transférer le projet en copiant simplement le répertoire, mais pour de telles choses, il y a export du projet, et il est assez bien implémenté. Les liens vers les fichiers dans l'arborescence du projet sont correctement pris en charge, de sorte que la décision d'inclure les fichiers eux-mêmes dans les exemples fournis ne peut pas être considérée comme satisfaisante.
Nous poursuivons nos recherches - nous allons commencer à travailler avec un projet terminé, mais pas à clignoter une LED, bien qu'il y en ait deux sur la carte de débogage, mais en travaillant avec un port série, un exemple uartecho prêt à l'emploi. Nous créons un nouveau RP, incluons le projet qui nous intéresse et ... rien ne vient de lui, il ressort clairement du message qu'il est nécessaire d'inclure un projet connexe dans le RP. Il n'est pas très clair pourquoi cela est fait, mais il n'est pas difficile de répondre aux exigences de l'environnement, après quoi le projet commence à être assemblé.
Pnp: sur la machine domestique, j'ai utilisé la commande Importer le projet et toutes les inclusions nécessaires se sont produites par elles-mêmes. Lorsque des projets exactement liés sont indiqués, je ne sais pas, laissons l'analyse de cet aspect pour l'avenir.
Nous compilons, flashons et commençons le débogage. Nous trouvons un phénomène intéressant - l'exécution pas à pas n'est pas correctement affichée lorsque l'on considère la bibliothèque de travail avec un port série - les coûts d'optimisation. Nous désactivons l'optimisation dans les paramètres du compilateur (quels paramètres ne sont pas là, y a-t-il vraiment des gens qui les connaissent tous et, en outre, les utilisent tous), assemblons à nouveau le projet - et rien ne change. Il s'avère que seuls les fichiers inclus sont inclus dans l'arborescence du projet, au moins sous forme de liens. Nous ajoutons des liens vers les sources de la bibliothèque et après la reconstruction, tout est correctement débogué (à condition que nous ayons la possibilité de générer des informations de débogage activées).
Pnp: mais j'ai trouvé des options pour activer la vérification de la conformité MISRA-C.
Pnp: une autre façon est d'utiliser la commande "Nettoyer ..." avec l'assemblage suivant, la commande "Construire tout" pour une raison quelconque n'affecte pas le projet associé.
Ensuite, nous constatons que tout n'est pas toujours débogué normalement, parfois nous nous trouvons dans des zones de code machine pour lesquelles le compilateur ne trouve pas la source. Étant donné que l'environnement de programmation nous fournit tous les fichiers nécessaires au travail - le résultat du préprocesseur, du code assembleur et de la carte de l'éditeur de liens (il suffit de se rappeler d'activer les options correspondantes), nous nous tournons vers cette dernière. Nous trouvons deux zones du code du programme - à partir de 0x0000. et à partir de 0x1000. (Les architectures 32 bits sont bonnes pour tout le monde, mais l'écriture d'adresses n'est pas leur point fort). Nous nous tournons vers la documentation du microcircuit et découvrons qu'à l'intérieur il y a une zone ROM mappée spécifiquement à 0x1000., Et il contient la partie intégrée des bibliothèques. Il est avancé que son utilisation améliore les performances et réduit la consommation par rapport à l'espace d'adressage 0x000. Pendant que nous maîtrisons MK, nous ne sommes pas tellement intéressés par les derniers paramètres, mais la commodité du débogage est cruciale. Vous pouvez désactiver l'utilisation de la ROM (mais pour nos besoins) en définissant l'option NO_ROM sur le compilateur, ce que nous faisons et réassemblons le projet.
Pnp: passer à un sous-programme dans la ROM semble très drôle - il n'y a pas de longue transition dans le système de commande, donc la transition est d'abord effectuée avec un retour au point intermédiaire dans la zone d'adresse basse (0x0000), et il y a déjà la commande de démarrage PC, dont les paramètres ne sont pas reconnus par le désassembleur. Quelque chose que je ne peux pas croire, comme si avec de tels frais généraux, vous pouvez gagner en vitesse, bien que pour de longues routines - pourquoi pas.
Soit dit en passant, une question intéressante est de savoir comment il est généralement garanti que le contenu de la ROM correspond aux codes sources aimablement fournis par l'entreprise. Je peux immédiatement suggérer un mécanisme pour intégrer des fonctions supplémentaires (bien sûr, de débogage et de service) dans la ROM, qui pour l'utilisateur-programmeur MK seront complètement invisibles. Et personnellement, je ne doute pas que les développeurs de la puce connaissent également de nombreux autres mécanismes qui mettent en œuvre une telle fonctionnalité, mais nous mettrons fin à l'attaque de la paranoïa.
D'un autre côté, je ne peux que saluer l'apparition d'un tel analogue du BIOS, car à long terme, cela fera du rêve des développeurs une réelle portabilité du code entre différentes familles MK avec un cœur une réalité. On note également la particularité de la mise en œuvre de l'interaction avec des modules logiciels "embarqués". Si, lors des premières tentatives de création d'un mécanisme similaire implémenté dans les modèles TivaC, il y avait un superviseur d'appel auquel on avait accès avec le numéro de groupe et le numéro du point d'entrée dans le sous-programme, ce qui a provoqué une surcharge importante, alors ici la résolution des communications est au niveau de l'éditeur de liens en raison du double nom des fonctions et Des sauts longs directs vers les sous-programmes de la ROM sont insérés. Cette opération est beaucoup plus rapide à exécuter, mais nécessite une recompilation du projet lors de la modification du modèle d'utilisation.
Maintenant que nous sommes complètement prêts pour un débogage pratique, nous revenons à notre projet et commençons à déboguer tranquillement le programme avec accès aux codes sources des modules (enfin, c'est ce que je pensais ...), ce qui nous permettra de nous faire une opinion sur la qualité de ces textes. Le projet à l'étude met en œuvre un miroir du canal de communication série et est extrêmement pratique à des fins de formation. Bien sûr, nous avons pris l'option en utilisant RTOS, je ne vois pas la moindre raison de ne pas l'utiliser dans notre configuration (beaucoup de mémoire et de mémoire programme).
Immédiatement, nous notons que les codes source sont présentés en C, ce n'est souvent pas très pratique, de nombreuses constructions de langage semblent lourdes par rapport à leurs analogues sur les avantages, mais les créateurs étaient plus préoccupés par la compatibilité des codes que par le sucre syntaxique. Bien qu'il soit possible de créer une version C ++ des bibliothèques, la compilation conditionnelle est connue depuis longtemps et est utilisée partout, mais cela entraîne des coûts matériels supplémentaires. Certes, la direction de l'entreprise sait ce qu'elle fait, et mes commentaires sont une sorte "d'analyse rusée", mais il me semble que j'ai aussi le droit à mon avis.
Je connais également l'approche opposée, lorsque la bibliothèque est conçue à l'aide des derniers outils C ++, et lorsqu'on me demande quoi faire pour les développeurs qui utilisent des compilateurs qui ne répondent pas aux dernières spécifications, la réponse parfaite est de passer à de nouvelles versions ou non cette bibliothèque (je recommande fortement la deuxième option dans de tels cas). Mon opinion personnelle est que si nous voulons vraiment que notre produit soit utilisé (et TI le veut clairement, et ne crée pas la bibliothèque sur le principe de "me le déposer, voici un nouveau tambour pour vous"), alors son approche est certainement vraie.
Le code source du programme est classique - initialisant l'environnement matériel et logiciel, créant des tâches et lançant un sheduler dans le module principal, le texte de la tâche dans un module de compilation séparé. Dans l'exemple considéré, la tâche est exactement une - mainThread, le but n'est pas tout à fait clair d'après le nom, et aussi, ce qui m'embrouille un peu - le nom du fichier contenant le texte source ne coïncide pas avec le nom de la fonction (uartecho.c - bien que le nom parle ici) bien oui La recherche dans l'environnement de programmation est implémentée de manière standard (menu contextuel ou F3 sur le nom de l'entité) et cela ne pose aucun problème.
Le processus de définition des paramètres de tâche avant de commencer est assez attendu:
- créer une structure de paramètres (locale, bien sûr),
- lui donner des valeurs par défaut,
- définir des paramètres autres que standard, et
- utiliser la structure lors de la création de la tâche.
Malgré le type de naturel de ces opérations, ce n'est pas évident pour tous les auteurs de bibliothèque, et j'ai vu diverses implémentations dans lesquelles, par exemple, il n'y avait pas d'étape 2, ce qui a conduit à un comportement de programme amusant (pour un observateur extérieur, pas pour un programmeur). Dans ce cas, tout va bien, la seule question qui se pose est de savoir pourquoi les valeurs par défaut ne sont pas constantes, c'est probablement un héritage du passé maudit.
PNP: dans le bien connu FREE-RTOS une approche légèrement différente est adoptée avec les paramètres de tâche indiqués directement dans le corps de l'appel de l'API de la fonction de création de tâche. Les avantages et les inconvénients de ces approches sont les suivants:
- + vous permet de ne pas spécifier explicitement les paramètres qui correspondent aux valeurs par défaut, + ne nécessite pas de se souvenir de l'ordre des paramètres, -plus verbeux, -des coûts de mémoire plus importants, -vous devez connaître les paramètres par défaut, -crée un objet intermédiaire nommé
- - nécessite de spécifier tous les paramètres, - nécessite de se souvenir de l'ordre des paramètres, + est plus compact, + nécessite moins de mémoire, + ne nécessite pas d'objets intermédiaires nommés.
Il existe une troisième méthode, préconisée par l'auteur de cet article (dans le style de TURBO), qui a son propre ensemble - + vous permet de ne pas spécifier explicitement les paramètres qui correspondent à la norme, + ne nécessite pas de se souvenir de l'ordre des paramètres, -multi-verbal, -des coûts de mémoire plus importants, -vous devez connaître les paramètres par défaut, + fonctionne dans le style lambda, + rend les erreurs standard difficiles à implémenter, -semble quelque peu étrange à cause des nombreux crochets droits.
Eh bien, il existe une autre quatrième option, sans inconvénient, mais nécessitant un C ++ non inférieur à 14 - nous nous léchons les lèvres et passons.
Nous commençons le débogage, exécutons le programme et ouvrons l'un des deux ports série fournis par la carte de débogage dans la fenêtre de terminal fournie par l'environnement de programmation. Lequel des deux ports (l'un est le débogage, le second est probablement l'utilisateur, vous pouvez voir leurs numéros dans le système) est difficile à dire à l'avance, parfois le plus jeune, parfois le senior, enfin, au moins cela ne change pas lorsque vous reconnectez la carte, vous pouvez donc l'écrire sur la carte. Eh bien, un inconvénient supplémentaire - les terminaux ouverts ne sont pas enregistrés avec le projet et ne sont pas restaurés lorsque vous ouvrez une session de débogage, bien qu'ils ne se ferment pas lorsque vous le quittez. Nous vérifions le fonctionnement du programme et découvrons immédiatement un autre inconvénient - le terminal ne peut pas être configuré, par exemple, il fonctionne essentiellement dans le style Unix avec une fermeture / r, j'ai perdu le contact avec un tel minimalisme, bien que personne ne nous dérange en utilisant un programme de terminal externe.
Pnp: Nous notons une autre caractéristique du débogage, eh bien, cela est vrai pour tout environnement de développement - lorsque nous changeons de tâche avec un sheduler, nous perdons le focus, les points d'arrêt nous aideront à résoudre ce problème.
Pour commencer, considérons le processus de création d'une instance d'un port série - tout semble être standard ici, une structure est utilisée, dont les champs sont affectés des paramètres requis de l'objet. Notez que sur les avantages, nous avons la possibilité, complètement absents de C, de masquer très bien toute initialisation «sous le capot», mais j'ai déjà exprimé des arguments possibles en faveur de la deuxième solution. Il existe une fonction d'initialisation de la structure de réglage, et c'est bien (paradoxal que cela puisse paraître, cette fonction ne semble pas obligatoire pour les auteurs de certaines bibliothèques). À ce stade de l'histoire, la lune de miel se termine et la vie ordinaire
(conjugale) commence.
Une étude attentive des sources montre que tout n'est pas si bon. Quel est le problème - la fonction d'initialisation copie les valeurs par défaut de l'objet qui se trouve dans la région constante dans notre structure de contrôle, et c'est merveilleux, mais pour une raison quelconque:
- l'objet est global, bien qu'il soit utilisé par la seule fonction pour initialiser les paramètres (à un moment donné, une pratique similaire coûte à Toyota un montant décent) - d'accord, l'ajout de la directive statique est facile;
- l'objet de contrôle est nommé, en C il n'y a pas de belle solution à ce problème, ou plutôt, il y a une solution avec une copie anonyme et je l'ai donnée dans un article de longue date, mais beaucoup de crochets droits ne permettent pas d'appeler cette option vraiment belle, en plus il y a une solution d'une beauté impressionnante, mais quoi rêver d'un rêve de pipe;
- tous les champs de l'objet sont clairement redondants en profondeur de bits, même les champs de bits (énumérations de deux valeurs possibles) sont stockés dans des mots de 32 bits;
- les constantes de mode énumérées sont définies sous la forme de définitions, ce qui rend la vérification impossible au stade de la compilation et nécessaire au moment de l'exécution;
- en répétant une section d'une boucle infinie à différents endroits d'échecs possibles, il serait beaucoup plus correct de faire un (dans ce cas vide) gestionnaire;
- Eh bien, toutes les opérations de configuration et de démarrage d'une tâche peuvent (et doivent) être cachées dans une fonction ou même dans une macro.
Mais l'initialisation du tampon de réception est bien faite - nous utilisons de la mémoire pré-réservée, pas de manipulation du tas, la chaîne d'appel est quelque peu compliquée, mais tout est lisible.
Pnp: dans la fenêtre de débogage, sous nos yeux, la pile des appels, tout se fait comme il se doit et à fond - respect et respect. La seule chose quelque peu surprenante est qu'une tentative de masquer cette fenêtre mène à la fin de la session de débogage.
Eh bien, et une autre décision quelque peu inattendue - définir le nombre possible d'objets dans l'énumération, pour les ports série et pour cette carte de débogage égal à 1, dans le style
typedef enum CC1310_LAUNCHXL_UARTName { CC1310_LAUNCHXL_UART0 = 0, CC1310_LAUNCHXL_UARTCOUNT } CC1310_LAUNCHXL_UARTName;
De telles solutions sont standard pour les transferts réels, mais pour la description des objets matériels - et je ne savais pas que c'était possible, bien que cela fonctionne pour moi. Nous avons terminé l'initialisation du fer, passons à autre chose.
Dans une tâche en cours d'exécution, nous observons une boucle infinie classique dans laquelle les données d'un port série sont lues par la fonction
UART_read(uart, &input, 1);
et immédiatement renvoyé par fonction
UART_write(uart, &input, 1);
. Allons dans le premier et voyons une tentative de lecture des caractères du tampon de réception
return (handle->fxnTablePtr->readPollingFxn(handle, buffer, size))
(comment je déteste de telles choses, mais en C, c'est tout simplement impossible autrement), nous allons plus loin et nous nous retrouvons dans UARTCC26XX_read, et à partir de là, nous entrons dans l'implémentation du tampon en anneau - une fonction
RingBuf_get(&object->ringBuffer, &readIn)
. Ici, la vie ordinaire entre dans une phase aiguë.
Je n'aimais pas dire que je n'aimais pas ce module particulier (fichier ringbuf.c), il a été écrit horriblement et personnellement, j'aurais honte de la place d'une compagnie d'auteurs respectée de cette partie avec honte (vous pouvez toujours me prendre à leur place, mais j'ai peur que le niveau de salaire de nos collègues indiens ne me conviendra pas), mais je ne sais probablement pas quoi. Surveillez vos mains:
1) la relance des pointeurs de lecture / écriture est mise en œuvre par le reste de la division
object->tail = (object->tail + 1) % object->length;
et il n'y a aucune optimisation du compilateur lors de l'exécution de cette opération comme l'application d'un masque de bits, et il ne peut pas y en avoir, car la longueur du tampon n'est pas une constante. Oui, dans ce MK, il y a une opération de division matérielle et c'est assez rapide (j'ai écrit à ce sujet), mais cela ne prend jamais 2 cycles d'horloge, comme dans la mise en œuvre correcte avec une relance honnête (et j'ai aussi écrit à ce sujet),
Pnp: J'ai récemment vu une description de la nouvelle architecture M7 dans l'implémentation et je ne me souviens de personne, donc pour une raison quelconque, la division de 32 par 32 a commencé à être effectuée en 2-12 cycles au lieu de 2-7. Soit il s'agit d'une erreur de traduction, soit ... je ne sais même pas à quoi penser.
2) de plus, ce fragment de code est répété à plusieurs endroits - macros et inlines pour les mauviettes, règle ctrl + C et ctrl + V, le principe DRY traverse la forêt,
3) un compteur complètement redondant de places tampons remplies a été mis en place, ce qui a entraîné l'inconvénient suivant,
4) sections critiques en lecture et en écriture. Eh bien, je peux toujours croire que les auteurs de ce module ne lisent pas mes messages sur Habré (bien que ce comportement soit inacceptable pour les professionnels du domaine du firmware), mais ils devraient être familiers avec le Mustang Book, là, cette question est examinée en détail,
5) comme une cerise sur le gâteau, un indicateur de la taille maximale du tampon a d'ailleurs été introduit, avec un nom très flou et une description complètement absente (cette dernière s'applique généralement à l'ensemble du module). Je n'exclus pas que cette option puisse être utile pour le débogage, mais pourquoi la faire glisser dans la version - avons-nous des cycles de processeur avec la RAM?
6) en même temps, le traitement de dépassement de tampon est complètement absent (il y a un signal de retour -1 sur cette situation) - même en Arduino c'est le cas, nous laisserons de côté la qualité de ce traitement, mais son absence est encore pire. Ou les auteurs se sont-ils inspirés du fait bien connu que des hypothèses sont vraies concernant un ensemble relativement vide, y compris le fait qu'il n'est pas vide?
En général, mes commentaires sont entièrement cohérents avec la première ligne du démotivateur sur le sujet de la révision du code "10 lignes de code - 10 commentaires".
Soit dit en passant, l'avant-dernière des lacunes constatées nous fait penser à des choses plus globales - mais comment pouvons-nous même implémenter la classe de base afin de pouvoir effectuer sa profonde modification. Sécuriser tous les champs est une idée douteuse (bien que probablement la seule juste), insérer un appel de fonctions amies dans les héritiers ressemble beaucoup à des béquilles. Si, dans ce cas particulier, il existe une réponse simple à la question de l'introduction d'un indicateur de saturation du tampon - une classe générée avec des écritures et des lectures qui se chevauchent et un compteur supplémentaire, alors pour implémenter la lecture sans avancer le tampon (comme dans ce cas) ou remplacer le dernier caractère placé (j'ai vu tel implémentation du tampon en anneau), vous ne pouvez pas vous passer de l'accès aux données internes de la classe parente.
Dans le même temps, il n'y a rien à redire sur la mise en œuvre de la lecture effective à partir de l'interface série - l'entrée est bloquante, en l'absence d'un nombre suffisant de caractères dans le tampon de réception, un sémaphore est armé et le contrôle est transféré au sheduler - tout est mis en œuvre avec précision et correctement. Personnellement, je n'aime pas vraiment contrôler l'équipement dans une procédure à usage général, mais cela réduit l'imbrication des procédures et réduit l'indice de complexité cyclomatique, peu importe ce que cela signifie.
Faisons maintenant attention à la transmission des données reçues au canal série, car lors de la création de l'objet, il n'était fourni qu'avec un seul tampon en anneau - celui qui le recevait. En effet, le tampon interne du matériel est utilisé pour transmettre des caractères, et lorsqu'il est rempli, l'attente de disponibilité est entrée (au moins en mode de fonctionnement bloquant). Je ne peux pas m'en empêcher, afin de ne pas critiquer le style des fonctions correspondantes: 1) pour une raison quelconque, l'objet a un pointeur généralisé, qui à l'intérieur de la fonction se transforme constamment en pointeur vers des caractères
*(unsigned char *)object->writeBuf);
2) la logique du travail est complètement opaque et légèrement confuse. Mais tout cela n'est pas si important, car il reste caché à l'utilisateur et "n'affecte pas la vitesse maximale".
Dans le processus de recherche, nous rencontrons une autre fonctionnalité - nous ne voyons pas le code source de certaines fonctions internes en mode débogage - cela est dû à un changement de nom pour différentes options de compilation (ROM / NO_ROM). Remplacez le fichier source requis (C: \ Jenkins \ jobs \ FWGroup-DriverLib \ workspace \ modules \ output \ cc13xx_cha_2_0_ext \ driverlib \ bin \ ccs /./../../../ driverlib / uart.c--) Je n'ai pas réussi (mais je n'ai pas essayé très fort), bien que j'aie trouvé la source (bien sûr, dans le fichier du fichier uart.c, merci, capitaine), heureusement, ce fragment est simple et il est facile d'identifier le code assembleur avec le code source en C (surtout si vous connaissez les fonctionnalités de l'équipe ITxxx). Je ne sais pas comment résoudre ce problème pour les bibliothèques avec des fonctions complexes, nous penserons quand le besoin se fera sentir.
Et enfin, une petite remarque - je suis prêt à croire que le matériel de l'implémentation du canal série pour les modèles MK CC13x0 est le même que pour CC26x0, et la duplication du contenu d'un fichier appelé UARTCC26XX.c.c ne peut pas être appelée la bonne solution, mais la création d'un fichier de définition intermédiaire avec inclusion J'accueillerais le fichier source, remplaçant les fonctions et le commentaire correspondant, car cela rendrait le programme plus compréhensible, et cela devrait toujours être le bienvenu, vout.
Donc, le cas de test fonctionne, nous avons beaucoup appris sur la structure interne des bibliothèques standard, avons noté leurs forces et leurs mauvais côtés, en conclusion de la revue, nous allons essayer de trouver la réponse à la question qui préoccupe généralement le programmeur dans le dilemme «OS ou non OS» - le temps de changement de contexte. Deux voies sont possibles ici: 1) la prise en compte du code source est plutôt une voie théorique, elle nécessite un niveau d'immersion dans le sujet que je ne suis pas prêt à démontrer, et 2) une expérience pratique. Bien sûr, la seconde méthode, contrairement à la première, ne donne pas des résultats absolument corrects, mais «la vérité est toujours concrète» et les données obtenues peuvent être considérées comme adéquates si les mesures sont organisées correctement.
Pour commencer, afin d'estimer le temps de commutation, nous devons apprendre à évaluer le temps d'exécution global de divers fragments de programme. Dans cette architecture, il existe un module de débogage, dont une partie est un compteur système. Les informations sur ce module sont assez accessibles, mais le diable, comme toujours, se cache dans les détails. Tout d'abord, essayons de configurer le mode nécessaire avec les poignées directement via l'accès aux registres. Nous trouvons rapidement le bloc de registre CPU_DWT et nous y trouvons à la fois le compteur CYCCNT lui-même et le registre de contrôle pour lui CTRL avec le bit CYCCNTENA. Naturellement, ou, comme on dit, bien sûr, rien ne s'est passé et le site Web d'ARM a une réponse à la question pourquoi - il est nécessaire d'activer le module de débogage avec le bit TRCENA dans le registre DEMCR. Mais le dernier registre n'est pas si simple - dans le bloc DWT, il n'est pas là, dans d'autres blocs, il est paresseux de rechercher - ils sont assez longs, mais je n'ai trouvé aucune recherche par nom dans la fenêtre du registre (mais ce serait bien de l'avoir). Nous entrons dans la fenêtre de mémoire, entrons l'adresse du registre (elle nous est connue depuis la date) (d'ailleurs, pour une raison quelconque, le format hexadécimal de l'adresse n'est pas par défaut, vous devez ajouter le préfixe 0x avec des stylos) et, tout à coup, nous voyons une cellule de mémoire nommée avec le nom CPU_CSC_DEMCR. C'est drôle, pour dire le moins, pourquoi la société a renommé les registres en comparaison avec les noms proposés par le donneur de licence de l'architecture, c'était probablement nécessaire. Et exactement, dans le bloc de registres CPU_CSC, nous trouvons notre registre, y mettons le bit souhaité, retournons au compteur, activez-le, et tout a fonctionné.
Pnp: il y a toujours une recherche par nom, elle est appelée (naturellement) par la combinaison Ctrl-F, elle n'existe que dans le menu contextuel, mais dans celle habituelle elle est annulée, je m'excuse auprès des développeurs.
Je note immédiatement un autre inconvénient de la fenêtre de mémoire - l'impression du contenu est interrompue en indiquant les cellules nommées, ce qui rend la sortie déchirée et non figée segmentée en 16 (8.32, 64, substituez les mots nécessaires). De plus, le format de sortie change lorsque la fenêtre est redimensionnée. Peut-être que tout cela peut être configuré selon les besoins de l'utilisateur, mais, sur la base de ma propre expérience (et de quoi d'autre dois-je procéder), je déclare que la définition du format de sortie de la fenêtre d'affichage de la mémoire n'est pas une solution intuitive. Je suis entièrement en faveur de l'activation d'une fonctionnalité aussi pratique que l'affichage des zones de mémoire nommées dans la fenêtre de visualisation, sinon de nombreux utilisateurs ne le sauraient jamais, mais des précautions doivent également être prises pour ceux qui veulent consciemment la désactiver.
Soit dit en passant, je n'abandonnerais pas complètement la possibilité de créer des macros (ou des scripts) pour travailler avec l'environnement, car j'ai dû faire ce réglage de registre (pour permettre la mesure du temps) à chaque fois après la réinitialisation du MK, car je considère la correction de code en insérant des manipulations de registre à des fins de débogage pas très correct. Mais, bien que je n'aie jamais trouvé de macros, travailler avec des registres peut être grandement simplifié du fait que des registres individuels (nécessaires) peuvent être inclus dans la fenêtre d'expression, et ainsi faciliter et accélérer considérablement le travail avec eux.
Pour souligner que le sentiment de l'ingénieur pour la famille MK ne s'est pas refroidi (sinon je réprimande différents aspects de l'environnement de développement), je note que le compteur fonctionne bien - je n'ai trouvé aucun cycle supplémentaire dans aucun des modes de débogage, mais avant que cela ne se produise être, au moins dans la série MK, développé par LuminaryMicro.
Ainsi, nous décrivons le plan d'expérience pour déterminer le temps de changement de contexte - créer une deuxième tâche qui incrémentera un certain compteur interne (dans une boucle infinie), démarrer le MC pendant un certain temps, trouver la relation entre le compteur système et le compteur de tâches. Ensuite, démarrez le MK pour un temps similaire (pas nécessairement exactement le même) et entrez 10 caractères à un rythme environ une fois par seconde. On peut s'attendre à ce que cela entraîne 10 commutations vers la tâche d'écho et 10 commutations vers la tâche de compteur. Oui, ces changements de contexte seront effectués non pas en fonction de la minuterie du sheduler, mais en fonction de l'événement, mais cela ne devrait pas affecter le temps d'exécution total de la fonction étudiée, nous commençons donc à mettre en œuvre le plan, à créer la tâche de compteur et à la démarrer.
Ici, nous trouvons une caractéristique de RTOS, au moins dans la configuration standard - elle n'évince pas «pour de vrai»: si la tâche prioritaire est constamment prête à être exécutée (et la tâche de compteur est cela) et ne donne pas le contrôle au sheduler (n'attend pas de signaux, ne s'endort pas, pas bloqué par des drapeaux, etc.), alors aucune tâche de moindre priorité ne sera exécutée à partir du mot. Ce n'est pas Linux, dans lequel diverses méthodes sont utilisées pour garantir que tout le monde obtienne un quantum, "afin que personne ne s'en offense". Ce comportement est tout à fait attendu, de nombreux RTOS légers se comportent de cette façon, mais le problème s'avère plus profond, car la gestion ne reçoit pas les tâches de priorité égale à celles constamment préparées. C'est pourquoi dans cet exemple, je mets la tâche d'écho, qui est en attente, la priorité est supérieure à la tâche de compteur constamment prête, sinon cette dernière capturera toutes les ressources du processeur à temps.
Nous commençons l'expérience, la première partie (en attendant juste le temps d'exécution) a donné les données sur le rapport des compteurs 406181 / 58015 = 7 - c'est tout à fait attendu. La deuxième partie (avec 10 caractères consécutifs pendant ~ 10 secondes) donne les résultats 351234k-50167k * 7 = 63k / 20 = 3160 cycles, le dernier chiffre est le temps associé à la procédure de changement de contexte dans les cycles MK. Personnellement, cette valeur semble un peu plus importante que prévu, nous poursuivons nos recherches, il semble qu'il y ait encore quelques actions qui gâchent les statistiques.
PNP: une erreur courante d'un expérimentateur est de ne pas évaluer les résultats précédemment attendus et de croire aux ordures reçues (salut à 737 développeurs).
Il est évident («oui, tout à fait évident») que le résultat, en plus de la commutation de contexte réelle, contient également le temps nécessaire pour effectuer les opérations de lecture d'un caractère à partir du tampon et de sa sortie sur le port série. Ce qui est moins évident, c'est qu'il a également le temps nécessaire pour traiter une interruption à sa réception et pour placer le caractère dans le tampon de réception). Comment pouvons-nous séparer un chat de la viande - pour cela, nous avons une astuce - nous arrêtons le programme, saisissons 10 caractères et le démarrons. Nous pouvons nous attendre (nous devrions regarder la source) qu'une interruption à la réception ne se produira qu'une seule fois et immédiatement tous les caractères seront envoyés du tampon de réception à l'anneau un, ce qui signifie que nous verrons moins de surcharge. Il est également facile de déterminer le moment de la livraison au port série - nous afficherons chaque deuxième caractère et résoudrons les 2 équations linéaires résultantes avec 2 inconnues. Et c'est possible et encore plus simple - de ne rien déduire, ce que j'ai fait.
Et voici les résultats de ces manipulations délicates: nous faisons l'entrée par le paquet et les ticks manquants deviennent plus petits - 2282, désactivons la sortie et les coûts tombent à 1222 ticks - c'est mieux, même si j'espérais 300 ticks.
Mais avec le temps de lecture, rien de tel ne peut être trouvé; il est mis à l'échelle en même temps que le temps de changement de contexte souhaité. La seule chose que je puisse offrir est de désactiver le minuteur interne au début de la saisie du caractère reçu et de le rallumer avant d'entrer dans l'attente du suivant. Ensuite, deux compteurs fonctionneront de manière synchrone (à l'exception de la commutation) et cela peut facilement être déterminé. Mais une telle approche nécessite une implémentation profonde des programmes système dans les textes, et le composant de gestion des interruptions restera toujours. Par conséquent, je propose de me limiter aux données déjà obtenues, qui nous permettent d'affirmer fermement que le temps de commutation de tâche dans le TI-RTOS considéré ne dépasse pas 1222 cycles d'horloge, ce qui pour une fréquence d'horloge donnée est de 30 microsecondes.
PNP: encore beaucoup - j'ai compté des cycles à 100: 30 pour sauvegarder le contexte, 40 pour déterminer la tâche terminée et 30 pour restaurer le contexte, mais nous obtenons un ordre de grandeur plus. Bien que l'optimisation soit désactivée maintenant, activez –o2 et voyez le résultat: cela n'a pas beaucoup changé - il est devenu 2894 au lieu de 3160.
Il existe une autre idée - si le système d'exploitation prend en charge la commutation de tâches poste à poste, vous pouvez exécuter deux tâches avec des compteurs, obtenir par magie des données sur le nombre de commutateurs en un instant et calculer la perte du compteur système, mais en raison des particularités du sheduler, à propos desquelles je déjà dit, cette approche ne conduira pas au succès. Bien qu'une autre option soit possible - pour faire du ping-pong entre deux tâches d'égal à égal (ou même d'égal à égal) via un sémaphore, il est facile de calculer le nombre de changements de contexte ici - vous devrez l'essayer, mais ce sera demain.
L'enquête traditionnelle à la fin de l'article sera cette fois consacrée non pas au niveau de présentation (il est clair pour tout lecteur impartial qu'il est au-delà des louanges et dépasse toutes les attentes), mais au sujet du prochain article.