2010-08-23 6 views
4

Je voudrais interpréter une chaîne de commande, reçue par un microcontrôleur (PIC16f877A si cela fait une différence) via série.Interprète de commande série de microcontrôleur en C/C++; Façons de le faire;

Les cordes ont une assez simple et la mise en forme droite foward: AABBCCDDEE $ (5 "blocs" de 2 chracters + '$' pour 11 caractères au total) où: $ AA = le nom réel de la commande (pourrait être des lettres, des chiffres, les deux, obligatoires); BB-EE = paramètres (nombres, facultatif); Je voudrais écrire le code en C/C++. Je suppose que je pourrais simplement saisir la chaîne via série, la hacker en blocs, switch() {case} et memcmp le bloc de commande ($ AA). Ensuite, je pourrais avoir un arbre de décision binaire pour utiliser les blocs BB CC DD et EE. Je voudrais savoir si c'est la bonne façon de le faire (Cela me semble un peu moche, sûrement il doit y avoir une façon moins fastidieuse de le faire!).

Répondre

3

Les interfaces ASCII sont moche par définition. Idéalement, vous avez une sorte de structure de cadre, ce que vous avez peut-être, le $ indique la division entre les cadres et vous dites qu'ils ont 11 caractères de longueur. Si toujours 11 c'est bon, si seulement c'est parfois plus difficile, j'espère qu'il y a un $ au début et 0x0A et ou 0x0D/0x0A à la fin (CR/LF). Normalement, j'ai un module de code qui extrait simplement les octets du port série et les met dans un tampon (circulaire). Le tampon datant des jours où les ports série avaient très peu de tampon à bord, mais encore aujourd'hui, surtout avec les microcontrôleurs, c'est toujours le cas. Puis un autre module de code qui surveille le tampon recherchant des trames. Idéalement, ce tampon est assez grand pour laisser le cadre là et avoir de la place pour la trame suivante et ne pas nécessiter d'autre tampon pour conserver les copies des trames reçues. En utilisant le tampon circulaire, ce second module peut se déplacer (en rejetant si nécessaire) le pointeur de tête vers le début du marqueur de trame et attend une valeur de trame complète de données. Une fois qu'un cadre complet semble être là, il appelle une autre fonction qui traite ce cadre. Cette fonction peut être celle que vous demandez. Et "juste le code" peut être la réponse, vous êtes dans un microcontrôleur, de sorte que vous ne pouvez pas utiliser l'application de bureau haut niveau paresseux sur une solution de système d'exploitation. Vous aurez besoin d'une sorte de fonction strcmp si vous êtes créé vous-même ou disponible dans une bibliothèque, ou non en fonction de votre solution. La force brute si (strncmp (& cadre [1], "bob", 3) == 0) alors, sinon si (strncmp (& frame [1], "ted", 3) alors, sinon si ... Certainement mais vous pouvez mâcher votre rom avec ce genre de choses, ou pas, et le tampon requis pour ce genre d'approche peut mastiquer beaucoup de bélier, ce qui est très lisible, facile à maintenir et portable. (maintenable est normalement en conflit avec fiable et/ou performance), mais cela peut ne pas être un problème, tant que vous pouvez traiter celui-ci avant que le suivant arrive, et ou avant que les données non traitées ne tombent du tampon circulaire. tâche la routine vérificateur de trame peut simplement vérifier que le cadre est bon, je place normalement des marqueurs de début et de fin, la longueur et une sorte de somme de contrôle arithmétique et si c'est un mauvais cadre, il est mis au rebut, cela économise beaucoup de code/corruption des données Lorsque la routine de traitement de trame retourne à la recherche de ves le pointeur de tête pour purger le cadre car il n'est plus nécessaire, bon cadre ou mauvais. Le vérificateur de trame peut seulement valider une trame et la remettre à une autre fonction qui effectue l'analyse. Chaque bloc de lego dans cet arrangement a une tâche très simple, et fonctionne sur la supposition que le bloc de lego au-dessous de lui a exécuté sa tâche correctement. Modulaire, orienté objet, quel que soit le terme que vous voulez utiliser rend la conception, le codage, la maintenance, le débogage beaucoup plus facile. (au prix de la performance et des ressources). Cette approche fonctionne bien pour tout flux de type série soit un port série dans un microcontrôleur (avec suffisamment de ressources) ainsi que des applications sur un bureau regardant des données série à partir d'un port série ou des données TCP qui sont également orientées en série.

Si votre micro n'a pas les ressources pour tout cela, alors l'approche de la machine d'état fonctionne également très bien. Chaque octet qui arrive coïncide avec l'état de la machine d'état. Commencer avec le mode attente en attente du premier octet, est le premier octet a $? ne le jetez pas et revenez au repos. si le premier octet est $ alors passez à l'état suivant. Si vous cherchiez à dire les commandes "et", "ajouter", "ou", et "xor", alors le second état se comparerait à "a", "o" et "x", si aucun de ceux-ci alors aller au ralenti. si un a va alors à un état qui compare pour n et d, si un o va alors à un état qui cherche le r. Si la recherche du r dans ou de l'état ne voit pas le r, alors passez à l'état inactif, si c'est le cas, traitez ensuite la commande, puis passez à l'état inactif. Le code est lisible dans le sens où vous pouvez regarder la machine d'état et voir les mots a, n, d, a, d, d, o, r, x, o, r, et où ils mènent finalement, mais généralement pas considéré comme un code lisible. Cette approche utilise très peu de ram, se penche un peu plus sur la rom mais dans l'ensemble pourrait utiliser le moins de rom par rapport à d'autres approches d'analyse syntaxique.Et là encore est très portable, au-delà des microcontrôleurs, mais en dehors d'un microcontrôleur les gens pourraient penser que vous êtes fou avec ce genre de code (enfin pas si c'était verilog ou vhdl bien sûr). Cette approche est plus difficile à maintenir, plus difficile à lire, mais elle est très rapide et fiable et utilise le moins de ressources.

Peu importe l'approche une fois la commande interprétée, vous devez vous assurer que vous pouvez exécuter la commande sans perdre d'octets sur le port série, soit par des performances déterministes du code ou des interruptions ou quoi que ce soit.

Les interfaces ascii de la ligne de base sont toujours moche, le code pour eux, peu importe le nombre de couches de bibliothèques que vous utilisez pour faciliter le travail, les instructions résultantes qui sont exécutées sont moche. Et une taille ne correspond à personne par définition. Commencez simplement à coder, essayez une machine d'état et essayez le if-then-else-strncmp, et les optimisations entre les deux. Vous devriez voir rapidement quelle est celle qui fonctionne le mieux avec votre style de codage, les outils/processeurs et le problème à résoudre.

+0

Merci! Vos réponses et celles de shodanex ont été de loin les plus utiles pour moi. La machine à états finis semble être le bon choix, même si elle n'est pas aussi intuitive que l'approche modulaire. Et, oui, les interfaces ASCII sont moche. Pas mon choix si: P – Pav

2

Cela dépend de la fantaisie que vous voulez obtenir, du nombre de commandes différentes et de la possibilité d'ajouter de nouvelles commandes fréquemment.

Vous pouvez créer une structure de données qui associe chaque chaîne de commande valide avec un pointeur de fonction correspondant - une liste triée accessible avec bsearch() est probablement correcte, bien qu'une table de hachage soit une alternative qui peut avoir de meilleures performances (puisque l'ensemble de les commandes sont connues à l'avance, vous pouvez construire un hachage parfait avec un outil comme gperf).

L'approche bsearch() pourrait ressembler à ceci:

void func_aa(char args[11]); 
void func_cc(char args[11]); 
void func_xy(char args[11]); 

struct command { 
    char *name; 
    void (*cmd_func)(char args[11]); 
} command_tbl[] = { 
    { "AA", func_aa }, 
    { "CC", func_cc }, 
    { "XY", func_xy } 
}; 

#define N_CMDS (sizeof command_tbl/sizeof command_tbl[0]) 

static int comp_cmd(const void *c1, const void *c2) 
{ 
    const struct command *cmd1 = c1, *cmd2 = c2; 

    return memcmp(cmd1->name, cmd2->name, 2); 
} 

static struct command *get_cmd(char *name) 
{ 
    struct command target = { name, NULL }; 

    return bsearch(&target, command_tbl, N_CMDS, sizeof command_tbl[0], comp_cmd); 
} 

Alors si vous avez command_str pointant vers une chaîne à partir du port série, vous souhaitez faire d'envoyer la fonction droite:

struct command *cmd = get_cmd(command_str + 1); 

if (cmd) 
    cmd->cmd_func(command_str); 
+0

Merci! Je vais devoir lire sur les tables de hachage et les fonctions de hachage: D – Pav

5

Ne pas trop le concevoir! Cela ne veut pas dire coder aveuglément, mais une fois que vous avez conçu quelque chose qui semble pouvoir faire l'affaire, vous pouvez commencer à l'appliquer. La mise en œuvre vous donnera des informations sur votre architecture. Par exemple, lorsque vous écrivez votre cas de commutateur, vous pouvez vous voir réécrire du code très similaire à celui que vous venez d'écrire pour le cas précédent. En fait, écrire un algorithme vous aidera à voir un problème auquel vous n'avez pas pensé ou une simplification que vous n'avez pas vue.

Ne visez pas le meilleur code au premier essai. Visez

  • facile à lire
  • facile à déboguer

Prendre des mesures litlle. Vous n'avez pas à mettre en œuvre le tout en une seule fois.

  • Récupérez la chaîne du port série. Ça a l'air facile, non? Eh bien, faisons cela en premier, en imprimant simplement les commandes.
  • Séparez la commande des paramètres.
  • Extrayez les paramètres. L'extraction sera-t-elle la même pour chaque commande?Pouvez-vous concevoir une structure de données valide pour chaque commande?

Une fois que vous avez fait les choses correctement, vous pouvez commencer à penser à une meilleure solution.

+0

Merci! J'étais sur le point de faire exactement cela .. Commencer à coder aveuglément, c'est: D – Pav

0

Je ne sais pas si vous travaillez encore sur ce sujet. Mais je travaille sur un projet similaire et trouvé un interpréteur de ligne de commande intégré http://sourceforge.net/projects/ecli/?source=recommended. C'est vrai, ils avaient des applications embarquées à l'esprit.

La fonction cli_engine aide vraiment à prendre les entrées de votre ligne de commande.

Attention: il n'y a pas de documentation autre qu'un fichier readme. Je travaille encore sur quelques bugs intégrant le framework mais cela m'a définitivement donné une longueur d'avance. Vous devrez vous occuper de comparer les chaînes (c'est-à-dire en utilisant strcmp) vous-même.

Questions connexes