Ukazovanje preko terminala in prilagajanje izpisa odčitkov

Moderator: tilz0R

Ukazovanje preko terminala in prilagajanje izpisa odčitkov

OdgovorNapisal/-a s54mtb » 13 Feb 2015, 01:23

Tule bom opisal kratko in enostavno kodo za obdelavo nizov znakov, ki jih ponavadi pošiljamo preko serijskega vmesnika ali USB. Sam strojni del ne bom opisoval, ker je to stvar vsakega izbranega mikrokontrolerja in je ponavadi zreducirano na dva opravila: sprejem in oddaja znaka.

predpostavimo, da nam deluje npr. UART, ki sprejema in oddaja znake preko dveh funkcij:

Koda: Izberi vse
int32_t GetChar ();   // vrne -1, če ni noben znak na voljo, sicer pa 8 bitno kodo sprejetega znaka
PutChar(int32_t znak);  // pošlje znak 


Uporaba 32 bitnih predznačenih vrednosti za 8 bitne znake je sicer precej redundančna zadeva, vendar pri uporabi 32 bitnih ARM mikrokontrolerjev to ne igra vloge. Elegantno rešimo tudi razpoložljivost novega znaka z ne-negativno vrednostjo. Vse negativne vrednosti pa lahko predstavljajo status. Na tak način ne potrbujemo dodatnih funkcij, kot npr. "NewCharAvailable()" ali kaj podobnega.

Sprejem znakov teče ponavadi znotraj glavne neskončne zanke, če uporabljamo RTOS, pa je ponavadi to eno od opravil (task), oz. niti (thread).

V samo zanko lahko dodamo še preprost linijski urejevalnik vnosne vrstice, tako da je obdelan tudi backspace in simbola za programski nadzor pretoka. Najprej si določimo nekaj nadzornih znakov, da bo koda preglednejša:

Koda: Izberi vse
#define CNTLQ      0x11
#define CNTLS      0x13
#define DEL        0x7F
#define BACKSPACE  0x08
#define CR         0x0D
#define LF         0x0A


ter določimo max. dolžino ukazne vrstice in parametrov ukaza (RAM na sodobnih mikrokontrolerjih ni več hud problem in si lahko "privoščimo"). Rezerviramo tudi pomnilnik za oboje.

Koda: Izberi vse
#define MAX_CMD_LEN 40
/** Pomnilnik za ukazno vrstico in argumente */
static char cmdstr_buf [1 + MAX_CMD_LEN];
static char argstr_buf [1 + MAX_CMD_LEN];


"Urejevalnik" ukazne vrstice sedaj zgleda takole:

Koda: Izberi vse
   
   int rx_char;
   char line_buf[2*MAX_CMD_LEN];         // Potrebujemo dvojno dolzino: za ukaz in za argumente
        int line_idx = 0;
   
   while (1)
   {
      rx_char = GetKey();
      while (rx_char>0)
      {
         if (rx_char == CR)  rx_char = LF;   
         if (rx_char == BACKSPACE  ||  rx_char == DEL)
         {    // uporabnik se je zmotil
               if (line_idx != 0) 
               {           
                  line_idx--;                      // zmanjsajmo index
                  SendChar (BACKSPACE);               // izpisi backspace na terminal
                  SendChar (' ');
                  SendChar (BACKSPACE);
               }
         }
         else
         {  // Znak je OK
               SendChar (rx_char);                   // echo
               line_buf[line_idx++] = rx_char;       // shrani znak in povecaj index
         }
         
         // preveri, ali smo na koncu bufferja in ce je prisel CR/LF
         if ((line_idx == 0xff)  ||  (rx_char == LF))
         {
            line_buf[line_idx-1] = 0;                  // Oznacimo se konec stringa
            line_idx = 0;
            cmd_proc(line_buf);                       // zazeni masinerijo za obdelavo vrstice
         }
         rx_char = GetKey();
      }
        // ....... Tu naredi se kaj drugega ..........
   }
Always going the extra mile.
https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 14710
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 2183 krat
Prejel zahvalo: 6096 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 255

Re: Ukazovanje preko terminala in prilagajanje izpisa odčitk

OdgovorNapisal/-a s54mtb » 13 Feb 2015, 12:21

Lotimo se obdelave prejete ukazne vrstice. Najprej si definirajmo strukturo, ki jo bomo uporabili v tabeli ukazov:

Koda: Izberi vse
 // Struktura zapisa posameznega ukaza v tabeli ukazov
struct cmd_st
  {
  const char *cmdstr;
  int id;
  };


Svoje ukaze naštejemo z "enum". To omogoča preprosto dodajanje novih ukazov:

Koda: Izberi vse
enum
  {
      CMD_MOJUKAZ1,
      CMD_MOJUKAZ2,
      CMD_MOJUKAZ3,
      CMD_HELP,
      
      // Dodaj poljubno količino ukazov
      CMD_LAST
  };


Koristno je, če dodamo tudi nek preprost "help":

Koda: Izberi vse
const char helptext[] =
" ------------- HELP --------------\n\r"
" MOJUKAZ1                             Naredi nekaj 1\n\r"
" MOJUKAZ2                             Naredi nekaj 2\n\r"
" MOJUKAZ3                             Naredi nekaj 3\n\r"
" HELP                                     Izpise to besedilo\n\r";



Sedaj moramo "povezati" ukaze in njihove identifikatorje. To seveda shranimo v kodo (const), saj je struktura fiksna:

Koda: Izberi vse
const struct cmd_st cmd_tbl [] =
  {
      { "MOJUKAZ1",             CMD_MOJUKAZ1 },
      { "MOJUKAZ2",             CMD_MOJUKAZ2 },
      { "MOJUKAZ3",             CMD_MOJUKAZ3 },
      { "HELP",               CMD_HELP},      
  };


Najavimo tudi funkcije, ki bodo izvajale naše ukaze:

Koda: Izberi vse
void cmd_mojukaz1(void);
void cmd_mojukaz2(char *argstr_buf);
void cmd_mojukaz3(char *argstr_buf, int x);
void cmd_print_help(void);


Pri tem MOJUKAZ1 nima argumentov, CMD_MOJUKAZ2 ima argumente, CMD_MOJUKAZ3 pa ima argumente in tudi parametre iz samega programa. To je izključno zaradi predstavitve treh različnih možnosti. Implementacija je seveda odvisna od dejanskih potreb.

Potrebujemo funkcijo, ki bo v tabeli ukazov primerjala nize znakov in vrnila identifikator iskanega ukaza, ali pa CMD_LAST, če se noben ukaz v tabeli ne ujema.

Koda: Izberi vse
int cmdid_search (char *cmdstr) {
   const struct cmd_st *ctp;

   for (ctp = cmd_tbl; ctp < &cmd_tbl [CMD_TBL_LEN]; ctp++) {
      if (strcmp (ctp->cmdstr, cmdstr) == 0) return (ctp->id);
   }

   return (CMD_LAST);
}


Če naši ukazi niso "case sensitive", jih najprej pretvorimo v velike črke s funkcijo:
Koda: Izberi vse
char *strupr (char *src) {
   char *s;

   for (s = src; *s != '\0'; s++)
      *s = toupper (*s);

   return (src);
}
Always going the extra mile.
https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 14710
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 2183 krat
Prejel zahvalo: 6096 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 255

Re: Ukazovanje preko terminala in prilagajanje izpisa odčitk

OdgovorNapisal/-a s54mtb » 13 Feb 2015, 12:23

Sledi še interpreter ukazov:

Koda: Izberi vse
void cmd_proc (char *cmd)
{
   char *argsep;
   unsigned int id;
   char tmpstr_buf[MAX_CMD_LEN];

   // pretvorba v velike znake
   strncpy (cmdstr_buf, cmd, sizeof (cmdstr_buf) - 1);
   cmdstr_buf [sizeof (cmdstr_buf) - 1] = '\0';
   strupr (cmdstr_buf);
   //preskoci "prazne" ukaze
  if (cmdstr_buf[0] == '\0')
      return;

   // Locitev ukaza od argumentov, locilo je prvi presledek v nizu znakov
   argsep = strchr (cmdstr_buf, ' ');
   
   if (argsep == NULL) {
     argstr_buf [0] = '\0';
   } else {
     strcpy (argstr_buf, argsep + 1);
     *argsep = '\0';
   }

   //poisci identifikator neznanega ukaza
   id = cmdid_search (cmdstr_buf);
   
   switch (id)
   {
      // na tem mestu klicemo funkcije, ki izvajajo nase ukaze
      case CMD_MOJUKAZ1:
         cmd_mojukaz1();   
      break;

      case CMD_MOJUKAZ2:
         cmd_mojukaz2(argstr_buf);   
      break;

      case CMD_MOJUKAZ3:
         cmd_mojukaz3(argstr_buf, 123);   
      break;


      case CMD_HELP:
        cmd_print_help();
      break;



         /* Neznan ukaz */
      case CMD_LAST:
        puts("? Neznan ukaz!");
        cmd_print_help();
      break;
   }
}


Na koncu moramo samo še napisati funkcije, ki izvajajo naše ukaze:

Koda: Izberi vse
void cmd_mojukaz1(void)
{
  // naredi nekaj
}

void cmd_mojukaz2(char *argstr_buf)
{
  int a;
  a = atoi(argstr_buf);
  if (a<0) puts("MOJUKAZ2 ne sprejema negativnih parametrov!");
  //
}

void cmd_mojukaz3(char *argstr_buf, int x)
{
  // ...
}

void cmd_print_help(void)
{
  puts(helptext);
}

Always going the extra mile.
https://www.buymeacoffee.com/s54mtb
Uporabniški avatar
s54mtb
 
Prispevkov: 14710
Pridružen: 15 Jan 2015, 01:10
Zahvalil se je: 2183 krat
Prejel zahvalo: 6096 krat
Uporabnika povabil: Vrtni palček
Število neizkoriščenih povabil: 255

Re: Ukazovanje preko terminala in prilagajanje izpisa odčitk

OdgovorNapisal/-a Proteus » 13 Feb 2015, 18:52

Zanimivo, da imam dokaj podobno rešitev tudi sam. Bistvo vsega je bila lenoba. Pisanje vmesnika na novo za vsak projekt je malce prehud napor :)
Vse kar moram storiti je definicija polja z spremenljivkami, ki definirajo strukturo menuja z vsemi variablami.
Uporabniški avatar
Proteus
 
Prispevkov: 3363
Pridružen: 18 Jan 2015, 01:31
Kraj: Planet Zemlja
Zahvalil se je: 361 krat
Prejel zahvalo: 737 krat
Uporabnika povabil: s54mtb
Število neizkoriščenih povabil: 136


Vrni se na C in sorodni jeziki

Kdo je na strani

Po forumu brska: 0 registriranih uporabnikov in 1 gost