Sari la conținut
ELFORUM - Forumul electronistilor

Conversie valoare float la string pentru lcd


M.Adrian

Postări Recomandate

Pentru temperaturi eu folosesc asta:

 

String convertFloatToString(float argument)
{
  char t[6];
  String tAsString;

  // perform conversion
  dtostrf(argument, 5, 1, t);

  // create string object
  tAsString = String(t);
  return tAsString;
}

 

Nu stiu cat de eficienta este dar functioneaza, mai multe despre dtostrf aici: 
https://www.programmingelectronics.com/dtostrf/

 

Link spre comentariu
  • Răspunsuri 30
  • Creat
  • Ultimul Răspuns

Top autori în acest subiect

  • Liviu M

    10

  • M.Adrian

    10

  • bcristian

    2

  • mars01

    2

Top autori în acest subiect

Imagini postate

@digixfunctioneaza, dar este si foarte ineficienta, si ignori posibilitatea ca valoarea sa fie mai lunga de 5 caractere. Faci conversia intr-un array local, care-i pe stack*, apoi copiezi continutul intr-un string, care are in spate un array alocat pe heap*. Daca nu ai nevoie sa pastrezi sirul, e mult mai eficient sa folosesti doar array-ul local. Daca vrei sa capsulezi dtostrf, ca sa folosesti aceleasi valori pt argumente, trimite si arrayul ca argument la functie.

 

*probabil, depinde de arhitectura

 

Discutia despre perfomanta este in general doar academica in contextul unui program care consta in esenta in a masura o valoare si a o afisa.

Link spre comentariu
Acum 16 ore, Liviu M a spus:

Acum, că ai primit răspunsul pe care îl doreai, putem despica puțin firul în 4.

Din câte știu eu, sprintf (și similarele) consumă multe resurse. Într-un topic mai vechi al lui Mircea am discutat despre o abordare mai puțin generală dar mai economică. Poate că ți se potrivește? 

         Va refereati la codul asta? Am vazut in teste ca a facut conversia in 751us, chiar ca e rapid, doar ca nu inteleg complet cum functioneaza, eu inca mai lucrez la operatii cu pointeri, daca il puteti explica sumar ar fi minunat, atat pentru mine cat si pentru cine mai citeste topicul asta pe viitor.

         Ce am inteles totusi pana acum din cod e ca acel sptr e un pointer la un vector, cum e bufferul de la sprintf, prima data e "setat" pe prima adresa din buffer, sptr[0], dupa care incepe sa calculeze cate zeci de mii are numarul in el, de aceea scate Arg cu cate 10000, si pentru fiecare scadere cat timp Arg>10000, se incrementeaza valoarea din adresa curenta din buffer cu 1, prin linia de cod *sptr+=1. Dupa care se incrementeaza adresa din buffer cu sptr++, si se reia procedura dar pentru cifra miilor, apoi a sutelor si cea a zecilor, urmand ca in final, ce a ramas din Arg dupa scaderile repetate sa reprezinte cifra unitatilor, care se salveaza in ultima adresa din buffer cu *sptr = (unsigned char)Arg. Iar pentru afisare pe lcd sau in alta parte se afiseaza cifrele salvate in acel buffer. Daca am gresit sau aveti un mod mai bun de a explica, chiar as vrea sa interveniti.

/* * Unsigned Binary to Decimal conversion. */
void uBin16_Dec(unsigned char* sptr, unsigned int Arg){
   *sptr = 0;
   while (Arg > 10000){
      Arg -= 10000;
      *sptr += 1;
   }    
   sptr++;    
   *sptr = 0;
   while (Arg > 1000){
      Arg -= 1000;
      *sptr += 1;
   } 
   sptr++;
   *sptr = 0;
   while (Arg > 100){
      Arg -= 100;       
      *sptr += 1;    
   }    
   sptr++;
   *sptr = 0;
   while (Arg > 10){
      Arg -= 10; 
      *sptr += 1;
   }    sptr++;
   *sptr = (unsigned char)Arg;
}  

       

       Asa ca o paranteza, eu pana acum asa am facut conversiile pentru afisare pe lcd, asta e codul lui @Mircea din acelasi post.

void Display_Cap(unsigned int n){
 Capacitance[0] = n/10000 + 48;
 Capacitance[1] = (n/1000)%10 + 48;
 Capacitance[3] = (n/100)%10 + 48;
 Capacitance[4] = (n/10)%10 + 48;
 Capacitance[5] = (T_Value*10)/153 + 48;
 Lcd_Cmd(_Lcd_Clear);
 Lcd_Out(1, 1, "C = ");
 Lcd_Out(1, 5, Capacitance);
 
}

       

       Mi-a placut in schimb varianta cu sprintf() pentru ca face conversia si muta virgula automat, mai afiseaza si - in fata daca e cazul, fara sa pun eu conditii in cod, iar pentru aplicatii nepretentioase m-am gandit ca e bine sa fie o functie in biblioteca de lcd care sa stie deja sa faca asta.

       Voi publica aici si fisierul c cu biblioteca dupa ce il mai retusez putin. Poate ii va fi cuiva de folos.

Link spre comentariu

Ca sa nu mai vorbesc "din pareri", cum tot aveam proiectelul folosit in vara la simularile din topicul lui Mircea, am repetat testele pentru sprintf:

#include <xc.h>
#include <stdio.h>

void main(void) {
  
  unsigned int iNr = 64999;
  unsigned char buf[5];
  
  sprintf(buf, "%d", iNr);
   while(1);
  
  return;
}

Compilarea:

Memory Summary:
    Program space        used   151h (   337) of  8000h words   (  1.0%)
    Data space           used    26h (    38) of  1000h bytes   (  0.9%)
    EEPROM space         used     0h (     0) of   100h bytes   (  0.0%)
    Data stack space     used     0h (     0) of   FDAh bytes   (  0.0%)
    Configuration bits   used     0h (     0) of     5h words   (  0.0%)
    ID Location space    used     0h (     0) of     4h bytes   (  0.0%)

Simularea:

Target halted. Stopwatch cycle count = 3142 (3.142 ms)

Concluzie - sta mult mai bine decat ma asteptam. Asa cum zice bcristian, in majoritatea aplicatiilor de pe forumul asta nu merita bataia de cap asociata altor solutii. s(n)printf face treaba buna si relativ "ieftin".

 

LE Ca sa fie mai usor de comparat, copiez is aici rezultatele compilarii/simularii pentru aceleasi numere si conversia prin impartiri/modulo:

*******************************************************************
  buf[0] = iNr/10000 + 48;
  buf[1] = (iNr/1000)%10 + 48;
  buf[2] = (iNr/100)%10 + 48;
  buf[3] = (iNr/10)%10 + 48;
  buf[4] = iNr%10 + 48;
  
Memory Summary:
    Program space        used    E1h (   225) of  8000h words   (  0.7%)
    Data space           used    18h (    24) of  1000h bytes   (  0.6%)
    EEPROM space         used     0h (     0) of   100h bytes   (  0.0%)
    Data stack space     used     0h (     0) of   FE8h bytes   (  0.0%)
    Configuration bits   used     0h (     0) of     5h words   (  0.0%)
    ID Location space    used     0h (     0) of     4h bytes   (  0.0%)

Target halted. Stopwatch cycle count = 3134 (3.134 ms)

Cele doua metode sunt la fel de rapide/lente, varianta cu sprintf() ocupand ceva mai multa memorie (dar nesemnificativ).

Editat de Liviu M
Link spre comentariu
Acum 5 ore, M.Adrian a spus:

         Va refereati la codul asta?

Da, la codul asta ma refeream. De explicat nu mai e nevoie, ai inteles ce face. *sptr e pe post de buffer in varianta ta, sptr++ insemnand doar ca accesez urmatoarea locatie din memorie pentru urmatorul caracter. Functia se putea scrie si 

void uBin16_Dec(unsigned char sptr[5], unsigned int Arg){
   sptr[0] = 0;
   while (Arg > 10000){
      Arg -= 10000;
      sptr[0] += 1;
   }    
   sptr[1] = 0;
   while (Arg > 1000){
      Arg -= 1000;
      sptr[1] += 1;
   } 
   sptr[2] = 0;
   while (Arg > 100){
      Arg -= 100;       
      sptr[2] += 1;    
   }    
   sptr[3] = 0;
   while (Arg > 10){
      Arg -= 10; 
      sptr[3] += 1;
   }    
   sptr[4] = (unsigned char)Arg;
}  

Habar n-am daca varianta cu array are dezavantaje (memorie folosita...), cum ziceam functia am "plagiat-o" de pe forumul uChip si acolo asa a fost prezentata.

 

Editat de Liviu M
Link spre comentariu

Eu evit float cît de mult se poate. Dimensiunea folosită cel mai des e numărul de biţi pe care e ADC-ul, rotunjit în plus. Motivul? De fiecare dată cînd măsor ceva ştiu la ce interval de date mă aştept să afişez, cu cîte zecimale, dacă are semn sau nu. Dau şi cîteva exemple de măsurători:

1. Curent între -100,00mA şi +100,00mA =>signed int / unsigned int + flag pentru semn.

2. Tensiune între 0...+32,000V => unsigned int.

3. Tensiune între -2...+2V, exprimată în microvolţi => unsigned long, flag pentru semn, virgulă fixă.

Link spre comentariu
La 03.11.2021 la 19:59, Liviu M a spus:

Ca sa nu mai vorbesc "din pareri", cum tot aveam proiectelul folosit in vara la simularile din topicul lui Mircea, am repetat testele pentru sprintf:

#include <xc.h>
#include <stdio.h>

void main(void) {
  
  unsigned int iNr = 64999;
  unsigned char buf[5];
  
  sprintf(buf, "%d", iNr);
   while(1);
  
  return;
}

Compilarea:

Memory Summary:
    Program space        used   151h (   337) of  8000h words   (  1.0%)
    Data space           used    26h (    38) of  1000h bytes   (  0.9%)
    EEPROM space         used     0h (     0) of   100h bytes   (  0.0%)
    Data stack space     used     0h (     0) of   FDAh bytes   (  0.0%)
    Configuration bits   used     0h (     0) of     5h words   (  0.0%)
    ID Location space    used     0h (     0) of     4h bytes   (  0.0%)

Simularea:

Target halted. Stopwatch cycle count = 3142 (3.142 ms)

Concluzie - sta mult mai bine decat ma asteptam. Asa cum zice bcristian, in majoritatea aplicatiilor de pe forumul asta nu merita bataia de cap asociata altor solutii. s(n)printf face treaba buna si relativ "ieftin"

       Am recitit topicul si am observat ca la mine nu prea se aplica treaba asta, nu stiu de ce, cand apelez o singura data functia sprintf mi se ocupa memoria de program de la vreo 23% pana la 68%, folosesc MPLAB X v5.5 si XC8 v 1.34, nu inteleg de ce. Sa fie oare de la versiunea de compilator? Vad ca la dvs e ocupata abia 1% memoria de program cu sprintf(), mie imi ocupa 45% din memorie.

Link spre comentariu

       Da, e compilatorul gratuit, nu m-am gandit la asta, e posibil sa fie o cauza, totusi, mi se pare enorm de mult sa ocupe doar functia aia 45% din toata memoria de program a microcontrolerului folosit.

image.thumb.png.5c9adef06695fceb797c1bb89259110c.png

 

Link spre comentariu

Ca sa putem compara, trebuie sa stim cat o avem fiecare. Memoria din controller, bineinteles. :)

M-am uitat acum in proiect si pentru teste eu am folosit PIC16F18877, cu 32 kwords memorie flash si 4 k RAM. A ta cat e?

In rest, xc8 v1.42, varianta lite/free, adica fara optimizari.

 

LE intre timp au aparut si datele despre memorie, da' las postul asa cum a iesit.

Editat de Liviu M
Link spre comentariu

M-am prins, eu am convertit un intref (int), tu probabil ai convertit un float.

La conversia numarului -999.99, am si eu cu totul alte "consumuri":

Memory Summary:
    Program space        used   EC7h (  3783) of  8000h words   ( 11.5%)
    Data space           used    75h (   117) of  1000h bytes   (  2.9%)

OK, m-am linistit, stiam eu ceva, sprintf() poate fi mare consumator de resurse.

Link spre comentariu
Acum 42 minute, Liviu M a spus:

Ca sa putem compara, trebuie sa stim cat o avem fiecare. Memoria din controller, bineinteles. :)

M-am uitat acum in proiect si pentru teste eu am folosit PIC16F18877, cu 32 kwords memorie flash si 4 k RAM. A ta cat e?

In rest, xc8 v1.42, varianta lite/free, adica fara optimizari.

 

LE intre timp au aparut si datele despre memorie, da' las postul asa cum a iesit.

       Da, aici era problema, eu am testat codul pe un 16F1937 cu vreo 8.1kwords flash, mister elucidat :rade:.

image.png.baade90769204bc1db5331d17988927e.png

       Iar codul nu e secret, e biblioteca de lcd la care ziceam ca lucrez, o postez acum, dar inca mai trebuie cosmetizata putin, in rest pana acum e functionala, mai trebuie sa verific macro-urile de la inceput pentru comenzi, sa vad ca fac ce trebuie.

#include<xc.h>
#include<stdio.h>
#include<math.h>

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7

#define BLINKON 0x0D    //activeaza clipirea cursorului
#define BLINKOFF 0x0F   //opreste clipirea cursorului
#define CLEFT 0x10      //muta cursorul la stanga
#define CRIGHT 0x14     //muta cursorul la dreapta
#define CLEAR 0x01      //sterge display
#define CON 0x0E        //afiseaza cursorul _
#define COFF 0x0C       //nu mai afiseaza cursorul
#define DON 0x0A        //porneste display
#define DOFF 0x0       //inchide display

#define Enable() ((EN=1), __delay_ms(1), (EN=0))
void LCD_Init(unsigned char rows, unsigned char cols);                 //16x2  20x2 40x2 16x4 20x4
void LCD_Write(unsigned char data);
void LCD_CMD(unsigned char command);
void LCD_Putchar(unsigned char linia, unsigned char coloana, unsigned char c);
void LCD_Putstring(unsigned char linia, unsigned char coloana,unsigned char *s);
void LCD_SetCursor(unsigned char linia, unsigned char coloana);
//void LCD_PrintFloat(unsigned char linia, unsigned char coloana, float valoare,unsigned char zecimale);
void LCD_CreateCustomChar(unsigned char buffer[8], unsigned char pozitie_CGRAM, unsigned char *nume_caracter);
char coloane=16, linii=2,sptr[5];

void LCD_Init(char rows, char cols)  //Initializeaza display-ul cu numarul de caractere si linii.
{
    __delay_ms(20);
    LCD_CMD(CLEAR);
    __delay_ms(5);
    LCD_Write(0x02);
    __delay_ms(5);
    LCD_Write(0x06);
    __delay_ms(5);
    LCD_Write(0x0C);
    __delay_ms(5);
    LCD_Write(0x10);
    __delay_ms(5);
    LCD_Write(0x2C);
    __delay_ms(5);
    coloane=cols;
    linii=rows;
}

void LCD_Write(unsigned char data)         //LSB primul apoi MSB. Functia trimite in secvente de 4biti datele catre display. Nu afecteaza bitul RS
{
    D4=(data>>4)&0x01;
    D5=(data>>5)&0x01;
    D6=(data>>6)&0x01;
    D7=(data>>7)&0x01;
    Enable();
    D4=data&0x01;
    D5=(data>>1)&0x01;
    D6=(data>>2)&0x01;
    D7=(data>>3)&0x01;
    Enable();
}

void LCD_Putchar(unsigned char linia, unsigned char coloana, unsigned char c)        //Scrie caractere pe display. Exemplu de utilizare: LCD_Putchar('A');
{
    LCD_SetCursor(linia, coloana);
    RS=1;
    LCD_Write(c);
}

void LCD_Putstring(unsigned char linia, unsigned char coloana,unsigned char *s)     //Scrie stringuri pe display. Exemplu de utilizare: LCD_Putstring("Ana are mere");
{
    LCD_SetCursor(linia, coloana);
    RS = 1;
    while(*s)
        LCD_Write(*s++);
}

void LCD_CMD(unsigned char command)      //Trimite comenzi la display folosind ca argument unul din macro-urile definite la inceputul bibliotecii. Exemplu de utilizare: LCD_CMD(CLEAR);
{
    RS=0;
    LCD_Write(command);
}

void LCD_SetCursor(unsigned char linia, unsigned char coloana)    //Pozitioneaza cursorul in pozitia dorita. Exemplu de utilizare: LCD_SetCursor(0,13);
{
    char adresa=0x00;
    if(linii==2&&(coloane==16||coloane==20||coloane==40))
    {
        if(linia==0)
            adresa=0x80|0x00|coloana;  //0x80 comanda set ddram, 0x00 adresa de inceput a randului
        if(linia==1)
            adresa=0x80|0x40|coloana;
    }
    if(linii==4&&coloane==16)
    {
        if(linia==0)
            adresa=0x80|0x00|coloana;
        if(linia==1)
            adresa=0x80|0x40|coloana;
        if(linia==2)
            adresa=0x80|0x10|coloana;
        if(linia==3)
            adresa=0x80|0x50|coloana;
    }
    if(linii==4&&coloane==16)
    {
        if(linia==0)
            adresa=0x80|0x00|coloana;
        if(linia==1)
            adresa=0x80|0x40|coloana;
        if(linia==2)
            adresa=0x80|0x14|coloana;
        if(linia==3)
            adresa=0x80|0x54|coloana;
    }
    LCD_CMD(adresa);
}
void LCD_PrintFloat(unsigned char linia, unsigned char coloana, float valoare, unsigned char zecimale) 
{                                                   
    char buffer[9];
    if(zecimale==3)
        sprintf(buffer,"%.3f",valoare);
    if(zecimale==2)
        sprintf(buffer,"%.2f",valoare);
    if(zecimale==1)
        sprintf(buffer,"%.1f",valoare);
    if(zecimale==0)
        sprintf(buffer,"%.0f",valoare);
    LCD_Putstring(linia,coloana,buffer);
}

void LCD_CreateCustomChar(unsigned char buffer[8], unsigned char pozitie_CGRAM, unsigned char *nume_caracter)
{
    *nume_caracter=pozitie_CGRAM;
    for(char i=0;i<8;i++)
    {
        LCD_CMD(0x40+(pozitie_CGRAM*8)+i);
        RS=1;
        LCD_Write(buffer[i]);//putchar
    }
    //Creeaza caractere speciale in CGRAM. Numarul maxim de caractere ce poate fi memorat in CGRAM este 8.
    //Caracterele sunt reprezentate intr-o rezolutie de 5x8 puncte ca mai jos
    //xxxxx
    //xxxxx
    //xxxxx
    //xxxxx
    //xxxxx
    //xxxxx
    //xxxxx
    //xxxxx
    //buffer[8]: Reprezinta un vector in care sunt memorate valori pe 5 biti in binar sau hexazecimal care corespund randurilor de mai sus.
    //           O valoare reprezinta un rand din figura, de exemplu, valoarea 0b10001 va aprinde doar primul si ultimul punct.
    //pozitie_CGRAM: Reprezinta una din cele 8 pozitii disponibile pentru memorarea caracterelor speciale.
    //nume_caracter: Reprezinta numele dat caracterului special pentru afisarea usoara a lui. De exemplu generam o stea iar argumentul pentru nume_caracter va fi "stea".
    //Exemplu de utilizare: char buffer1[8]={0b01110,0b11111,0b10001,0b10001,0b10001,0b10001,0b10001,0b11111},  baterie;
    //                      LCD_CreateCustomChar(buffer1,0,&baterie); //Creeaza in spatiul 0 de memorie caracterul special dupa vectorul buffer1 si ii salveaza adresa cu numele baterie
    //                      LCD_Putchar(baterie); //afiseaza caracterul special din adresa baterie
}

 

       Iar functia main e asta, masoara tensiunea si curentul date de o sursa/baterie, si ca sa testez si functia de generat caractere am facut cate un simbol de baterie mai mult sau mai putin descarcata.

#include "mcc_generated_files/mcc.h"
#include <xc.h>
#define _XTAL_FREQ 16000000
#include "lcd.c"
void buton();
void main()
{
    char coloana=0;
    SYSTEM_Initialize();
    TRISD=0x00;
    ANSELD=0x00;
    PORTD=0x00;

    TRISA=0xFF;
    ANSELA=0xFF;
    ADC_Initialize();
    LCD_Init(2,16);
    LCD_CMD(BLINKOFF);
    LCD_CMD(CLEAR);
    LCD_CMD(COFF);
    char stare_actuala_1=0, stare_anterioara_1=0, stare_actuala_2=0,stare_anterioara_2=0;
         int copie_tensiune;       
    int rezultat_ADC=0;
    LCD_Putstring(0,0,"V=");
    LCD_Putchar(0,7,'V');
    
    int vect_tensiune[5];
    char buffer1[8]={0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b11111,0b11111},baterie;
    char buffer2[8]={
        0b01110,
        0b10001,
        0b10001,
        0b11111,
        0b11111,
        0b11111,
        0b11111,
        0b11111},baterie2;
    char buffer3[8]={0b01110,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111},baterie3;
    
    char res[10];
    
    LCD_CreateCustomChar(buffer1,0,&baterie);
    LCD_CreateCustomChar(buffer2,1,&baterie2);
    LCD_CreateCustomChar(buffer3,2,&baterie3);
    float tensiune,curent,putere;
    LCD_Putstring(1,7,"W|");
    while(1)
    {

        __delay_ms(500);
       // ADC_Initialize();
        //ADC_StartConversion(ADC_canal_1);
       // while(ADC_IsConversionDone());
        tensiune=ADC_GetConversion(ADC_canal_1);
        __delay_ms(100);
       // ADC_StartConversion(Curent);
       // while(ADC_IsConversionDone());
        curent=ADC_GetConversion(Curent);
        __delay_ms(100);
        copie_tensiune=tensiune*5000/1024;
        tensiune=tensiune*5000.0/1024.0/1000.0/0.0909;
        
        curent=curent*5000.0/1024.0/1000.0;
        putere=tensiune*curent;
        LCD_PrintFloat(0,2,tensiune,2);
        LCD_SetCursor(0,8);
        LCD_Putstring(0,8,"|I=");
       // LCD_PrintFloat(0,11,curent,2);
        LCD_Putchar(0,15,'A');
        LCD_Putstring(1,0,"P=     ");
      //  LCD_PrintFloat(1,2,putere,1);
        __delay_ms(200);

        if(tensiune<15)
            LCD_Putchar(1,10,baterie);
        
        if(tensiune>=15&&tensiune<=30)
            LCD_Putchar(1,10,baterie2);
        
        if(tensiune>30)
            LCD_Putchar(1,10,baterie3);
    }
}

 

Link spre comentariu

Creează un cont sau autentifică-te pentru a adăuga comentariu

Trebuie să fi un membru pentru a putea lăsa un comentariu.

Creează un cont

Înregistrează-te pentru un nou cont în comunitatea nostră. Este simplu!

Înregistrează un nou cont

Autentificare

Ai deja un cont? Autentifică-te aici.

Autentifică-te acum



×
×
  • Creează nouă...

Informații Importante

Am plasat cookie-uri pe dispozitivul tău pentru a îmbunătății navigarea pe acest site. Poți modifica setările cookie, altfel considerăm că ești de acord să continui.Termeni de Utilizare si Ghidări