Forum: Mikrocontroller und Digitale Elektronik Problem mit der sprintf - Funktion


von Björn M. (Gast)


Lesenswert?

Hallo,

im Forum habe ich mich schon länger umgeschaut ob eine passende Lösung 
zu meinem sprintf-Problem vorliegt. Leider vergeblich.

Mein Programm funktioniert wie unten angegeben einwandfrei. Der Timer 
liefert ständig einen neuen Wert auf dem Display. Falls ich in der 
sprintf-Funktion int verwende.
1
#define STRINGBUF_LEN 21
2
char StringBuf[STRINGBUF_LEN];
3
int adcVal[3];
4
5
...
6
adcVal[0] = ADC_GetValue();  
7
sprintf(StringBuf, "%d", adcVal[0]);
8
GLCD_DrawString (0, 2*24, (char*)StringBuf);
9
...

Ich möchte aber den ADC-Wert in Volt umrechnen. Was so aussehen soll:
1
#define STRINGBUF_LEN 21
2
char StringBuf[STRINGBUF_LEN];
3
int adcVal[3];
4
float x;
5
6
...
7
adcVal[0] = ADC_GetValue();  
8
x = (adcVal[0] * 5) / 1024;
9
sprintf(StringBuf, "%f", x);
10
GLCD_DrawString (0, 2*24, (char*)StringBuf);
11
...

Wenn ich den berechneten Wert einer float-Variable zuweise und 
anschließend ausgeben möchte, hört der Timer auf neue Werte zu liefern 
und ich bekomme kein float-Ergebnis ausgegeben.

Kann mir jemand hier weiterhelfen?

von Max H. (hartl192)


Lesenswert?

Björn M. schrieb:
> x = (adcVal[0] * 5) / 1024;
Der Compiler macht so eine Ganzzahl Division, du musst mindestens einen 
Operanden nach Float casten.

von Björn M. (Gast)


Lesenswert?

Max H. schrieb:
> Der Compiler macht so eine Ganzzahl Division, du musst mindestens einen
> Operanden nach Float casten.

erst mal danke für deine Antwort. Ich bekomme jetzt zwar ein 
float-Ergebnis ausgegeben aber der Timer funktioniert immer noch nicht, 
beim integer habe ich immer wieder einen neuen Wert geliefert bekommen 
und jetzt leider nicht, das müsste doch damit zusammenhängen oder?

von Björn M. (Gast)


Lesenswert?

Also ich habe das nochmal überprüft.
1
#define STRINGBUF_LEN 21
2
char StringBuf[STRINGBUF_LEN];
3
int adcVal[3];
4
float x;
5
  
6
...
7
adcVal[0] = ADC_GetValue();  
8
x = (adcVal[0] * 0.5) / 1024;
9
sprintf(StringBuf, "%d", adcVal[0]);
10
GLCD_DrawString         (0, 2*24, (char*)StringBuf);
11
...

Selbst wenn ich den Wert x nicht der sprintf-Funktion übergebe, so tut 
der Timer nichts mehr. Dann müsste doch das Problem nicht an der 
sprintf-Funktion liegen. Liegt das Problem dann am Compiler oder 
eventuell an der Stackgröße? Hat jemand damit eine Ahnung, wie ich 
dieses Problem beheben kann.

von Ollie (Gast)


Lesenswert?

Was kommt denn bei

x = adcVal[0] / 512.0;
sprintf(StringBuf, "%f", x);

raus?

Wäre doch schön, wenn was (auch falsch) rauskäme - und man
dann nur noch den richtigen Faktor suchen muss...

von Detlef K. (adenin)


Lesenswert?

Björn M. schrieb:
> x = (adcVal[0] * 0.5) / 1024;

x = (adcVal[0] * 0.5f) / 1024.0f;

^^

: Bearbeitet durch User
von Björn M. (Gast)


Lesenswert?

Ollie schrieb:
> Wäre doch schön, wenn was (auch falsch) rauskäme - und man
> dann nur noch den richtigen Faktor suchen muss...

Es kommt was raus. Ich dachte das Problem liegt bei der 
sprintf-Funktion. Aber so ist es ja nicht auch wenn ich in sprintf kein 
float benutze (sondern ein int) aber überhaupt im Programm mit einer 
float variable rechne, so hört der Timer auf zu arbeiten.

von Björn M. (Gast)


Lesenswert?

Detlef Kunz schrieb:
> x = (adcVal[0] * 0.5f) / 1024.0f;

Ja das habe ich korrigiert. Ich bekomme für x = 0.495605 raus. Das 
passt, nur der Timer müsste jetzt noch arbeiten, wie beim int.

von S. R. (svenska)


Lesenswert?

Kann dein sprintf auch mit Float umgehen?
Bist du dir sicher, dass der Puffer groß genug ist (dafür gäbe es dann 
snprintf)?
Wie oft feuert der Timer, und wieviel Zeit verbringt deine CPU im 
Leerlauf (in der Hauptschleife)?
Versuchst du, aus der ISR heraus das Display zu beschreiben?
Was ist das überhaupt für ein Controller, und wie schnell taktest du 
ihn?

von Björn M. (Gast)


Lesenswert?

S. R. schrieb:
> Kann dein sprintf auch mit Float umgehen?

Ja, ich bekomme für x (ist als float deklariert) eine float-Zahl 
(0.495623).

S. R. schrieb:
> Bist du dir sicher, dass der Puffer groß genug ist (dafür gäbe es dann
> snprintf)?

Das mit dem Puffer weiß ich leider nicht. Ich habe etwas gelesen das man 
in der startup-Datei den Stack-Size ändern muss, hat bei mir aber nicht 
geklappt.

S. R. schrieb:
> Wie oft feuert der Timer, und wieviel Zeit verbringt deine CPU im
> Leerlauf (in der Hauptschleife)?

Also ich habe den Timer auf 10 Sekunden gestellt. Das heißt, das 
eigentlich nach 10 Sekunden mein Program neu starten müsste bzw. ich 
alle 10 Sekunden einen neuen Wert vom AD-Wandler bekommen müsste. Aber 
ich bekomme jede Sekunde einen neuen Wert geliefert.

S. R. schrieb:
> Was ist das überhaupt für ein Controller, und wie schnell taktest du
> ihn?

Ich verwende einen MCB4300 Evaluation Board von Keil. Mit einem LPC4357 
Chip.

Ich bin etwas neu in dieser Sache, deswegen verstehe ich nicht jede 
Frage, was meinst du genau mit Takten, spielt das eine Rolle für den 
Timer?

So sollte der Programmcode aussehen und eigentlich funktionieren.
1
#include "LPC43xx.h"
2
#include "Board_ADC.h"
3
#include "Board_GLCD.h"
4
#include "GLCD_Config.h"
5
#include <cmsis_os.h>
6
#include <stdio.h>
7
8
#define STRINGBUF_LEN 21
9
10
extern GLCD_FONT GLCD_Font_16x24;
11
char StringBuf[STRINGBUF_LEN];
12
13
void Timer_Callback (void const *arg);
14
osTimerDef (Timer, Timer_Callback);
15
osTimerId TimerId;
16
17
uint32_t exec;
18
uint32_t timerDelay;
19
uint32_t cnt=0; 
20
21
void Timer_Callback (void const *arg) {
22
  
23
  int adcVal[3];
24
  float x;
25
  
26
  ADC_Initialize();
27
  ADC_StartConversion();
28
  while(ADC_ConversionDone() < 0);
29
  adcVal[0] = ADC_GetValue();  
30
  x = (adcVal[0] * 0.5f) / 1024.0f;
31
  sprintf(StringBuf, "%f", x);
32
  if(adcVal[0] < 999)
33
  GLCD_DrawString         (0, 2*24, "                    ");
34
  GLCD_SetForegroundColor (GLCD_COLOR_BLACK);
35
  GLCD_DrawString         (0, 2*24, (char*)StringBuf);
36
...
37
}
38
39
40
int main(void)
41
{
42
exec = 2;
43
TimerId = osTimerCreate (osTimer(Timer), osTimerPeriodic, &exec);
44
  
45
if (TimerId != NULL) {
46
timerDelay = 10000;
47
osTimerStart (TimerId, timerDelay);
48
}
49
  
50
GLCD_Initialize();
51
52
GLCD_SetBackgroundColor (GLCD_COLOR_BLUE);
53
GLCD_SetForegroundColor (GLCD_COLOR_WHITE);
54
GLCD_SetFont            (&GLCD_Font_16x24);
55
GLCD_DrawString         (0, 0*24, "     ADC-VALUE      ");
56
GLCD_DrawString         (0, 1*24, "                    ");
57
GLCD_SetBackgroundColor (GLCD_COLOR_WHITE);
58
GLCD_SetForegroundColor (GLCD_COLOR_BLUE);
59
GLCD_DrawString         (0, 2*24, "                    ");
60
GLCD_DrawString         (0, 3*24, "                    ");
61
GLCD_DrawString         (0, 4*24, "                    ");
62
GLCD_DrawString         (0, 5*24, "                    ");
63
GLCD_DrawString         (0, 6*24, "                    ");
64
GLCD_DrawString         (0, 7*24, "                    ");
65
GLCD_DrawString         (0, 8*24, "                    ");
66
GLCD_DrawString         (0, 9*24, "                    ");
67
}

von Florian (Gast)


Lesenswert?

Hallo,

mach mal die Variablen, die im Interupt verändert werden "volatile". Da 
hatte ich schon öfters Probleme.

Gruß
Florian

von Walter (Gast)


Lesenswert?

das Programm läuft genau einmal durch main, und dann??
Da fehlt wohl
while (1);

von Björn M. (Gast)


Lesenswert?

Florian schrieb:
> mach mal die Variablen, die im Interupt verändert werden "volatile". Da
> hatte ich schon öfters Probleme.

Hab ich gemacht, aber das Programm scheint eine Allergie gegen floats zu 
haben, sobald ich ein float in main oder in der Timer_Callback Funktion 
verwende so hört der Timer auf, aber die float Zahl wird ausgegeben.

Walter schrieb:
> das Programm läuft genau einmal durch main, und dann??
> Da fehlt wohl
> while (1);

Ich bin das Program mit dem debugger durchgegangen und zwar läuft das 
Programm einmal durch main anschließend in:
1
//rt_CMSIS.c
2
// Thread Service Calls declarations
3
SVC_0_1(svcThreadGetId, osThreadId, RET_pointer)
4
SVC_1_1(svcThreadTerminate, osStatus, osThreadId, RET_osStatus)

und danach in die Timer_Callback Funktion.

von Walter (Gast)


Lesenswert?

Björn M. schrieb:
> und danach in die Timer_Callback Funktion.

um danach wieder Reset zu machen??
Da fehlt ein while ( 1 );   !!!!!!!!!!!

von Björn M. (Gast)


Lesenswert?

Walter schrieb:
> um danach wieder Reset zu machen??
> Da fehlt ein while ( 1 );

Das hilft alles leider nicht. Der Timer läuft nicht wenn ich ein float 
benutze.

von Achim K. (aks)


Lesenswert?

Björn M. schrieb:
> Walter schrieb:
>> um danach wieder Reset zu machen??
>> Da fehlt ein while ( 1 );
>
> Das hilft alles leider nicht. Der Timer läuft nicht wenn ich ein float
> benutze.

Timer := Thread? Oder ISR?

Und hast Du in der verwendeten C-Library geschaut, ob sprintf 
Multithread fähig ist (bzw. von einer ISR gerufen werden darf)? Manchmal 
muss man Compiler Flags und/oder spezielle Library Varianten nehmen um 
z.B. sprintf mit "zusätzlichen Hook Funktionen" ins entsprechend 
Multitasking einzubinden.

Vielleicht ist es besser, Du überarbeitest Dein Programm so, dass im 
Timer (ISR) nur Deine eigenen Datenstrukturen und eigene Funktionen 
gerufen werden (bzw. nur Funktionen, die Multithread/ISRfähig sind).

von Björn M. (Gast)


Lesenswert?

Achim K. schrieb:
> Und hast Du in der verwendeten C-Library geschaut, ob sprintf
> Multithread fähig ist (bzw. von einer ISR gerufen werden darf)?

Also ich habe mal in der stdio.h nachgeschaut und folgendes gefunden:
1
#pragma __printf_args
2
extern _ARMABI int _sprintf(char * __restrict /*s*/, const char * __restrict /*format*/, ...) __attribute__((__nonnull__(1,2)));
3
   /*
4
    * is equivalent to sprintf, but does not support floating-point formats.
5
    * You can use instead of sprintf to improve code size.
6
    * Returns: as sprintf.
7
    */

also unterstützt mein sprintf kein floating-point format. es muss ja 
dafür eine alternative geben?

Ich verwende die Funktionen aus diesem Link:

https://www.keil.com/pack/doc/CMSIS/RTOS/html/group___c_m_s_i_s___r_t_o_s___timer_mgmt.html

osTimerCreate und osTimerStart. Mein Programm springt nach dem Durchlauf 
von main() in die Funktion Timer_Callback() und holt sich hier den 
AD-Wert und danach ist aus, falls ich ein float benutze.
1
#include "LPC43xx.h"
2
#include "Board_ADC.h"
3
#include "Board_GLCD.h"
4
#include "GLCD_Config.h"
5
#include "cmsis_os.h"
6
#include <stdio.h>
7
8
#define STRINGBUF_LEN 21
9
10
extern GLCD_FONT GLCD_Font_16x24;
11
char StringBuf[STRINGBUF_LEN];
12
13
void Timer_Callback(void const *arg);
14
osTimerDef(Timer, Timer_Callback);
15
uint32_t exec;
16
17
void Timer_Callback(void const *arg) {
18
  
19
  volatile int adcValue;
20
  volatile float x;
21
  
22
  ADC_Initialize();
23
  ADC_StartConversion();
24
  while(ADC_ConversionDone() < 0);
25
  adcValue = ADC_GetValue();  
26
  x = (adcValue * 0.5f) / 1024.0f;
27
  sprintf(StringBuf, "%f", x);
28
  GLCD_DrawString         (0, 2*24, (char*)StringBuf);
29
}
30
31
int main(void)
32
{
33
  osTimerId id;
34
  uint32_t timerDelay;
35
  osStatus status;
36
  
37
        ...
38
  
39
  exec = 2;
40
  
41
  id = osTimerCreate(osTimer(Timer), osTimerPeriodic, &exec);
42
  
43
  if(id) {
44
    timerDelay = 1000;
45
    status = osTimerStart(id, timerDelay);
46
    
47
    if(status != osOK)
48
      GLCD_DrawString(0, 8*24, "Timer: started");
49
    else
50
      GLCD_DrawString(0, 9*24, "Timer: not started");
51
  }
52
}

von S. R. (svenska)


Lesenswert?

Björn M. schrieb:
>> Kann dein sprintf auch mit Float umgehen?
> Ja, ich bekomme für x (ist als float deklariert) eine float-Zahl
> (0.495623).

Gut.

>> Bist du dir sicher, dass der Puffer groß genug ist (dafür gäbe es dann
>> snprintf)?
> Das mit dem Puffer weiß ich leider nicht.

Mir ging es nur darum, dass die Zahlendarstellung nicht größer ist als 
der Puffer, in den du sie reinschreiben willst. Deswegen sollte man 
statt sprintf() auch immer snprintf() benutzen.

> Ich habe etwas gelesen das man in der startup-Datei den Stack-Size
> ändern muss, hat bei mir aber nicht geklappt.

Du kannst in solchen RTOSen die Stackgröße pro Task festlegen. Die 
Stacks selbst können unterschiedlich alloziiert werden, im Extremfall 
auch einfach als globales Array oder so.

In der Startup-Datei wird meist nur der Systemstack (Kernelstack) 
betrachtet, um überhaupt C-Code ausführen zu können. Das RTOS sollte den 
sowieso beim Start durch einen eigenen ersetzen.

>> Wie oft feuert der Timer, und wieviel Zeit verbringt deine CPU im
>> Leerlauf (in der Hauptschleife)?
> Also ich habe den Timer auf 10 Sekunden gestellt. [...]

Mir ging es darum, ob vielleicht die ISR durch das Float so langsam 
wird, dass deine Hauptschleife nicht mehr hinterherkommt. Aber das 
scheint nicht das Problem zu sein.

>> Was ist das überhaupt für ein Controller, und wie schnell taktest du
>> ihn?
> Ich verwende einen MCB4300 Evaluation Board von Keil. Mit einem LPC4357
> Chip. Ich bin etwas neu in dieser Sache, deswegen verstehe ich nicht
> jede Frage, was meinst du genau mit Takten, spielt das eine Rolle für
> den Timer?

Für den Timer nicht. Aber wenn du beispielsweise versuchst, einen Timer 
alle 10µs auszulösen, aber die CPU selbst nur mit ein paar kHz läuft, 
dann kann das nicht gut gehen (weil dann die meisten Interrupts 
ignoriert werden, und die Hauptschleife selbst nicht mehr ausgeführt 
wird).

Das wollte ich ausschließen. Aber mit 10 Sekunden Zykluszeit und 
mehreren MHz Taktfrequenz ist das kein Problem.

> So sollte der Programmcode aussehen und eigentlich funktionieren.
> [...]

(a) C-Code bitte in in [c]-Tags einschließen.
(b) Du hättest ja mal vorher verraten können, dass du mit einem OS 
arbeitest. ;-)
(c) Hardware (ADC) initialisiert man am besten nur einmal, nach dem 
Einschalten. In deinem Fall vermutlich, bevor du das OS startest.
(d) Deine Idle-Loop fehlt. Deine main()-Funktion rennt bis zum Ende und 
dann stürzt der Controller ab (bzw. startet neu). Es gibt kein 
übergeordnetes Betriebssystem, welches ein endendes Programm abfangen 
kann. Das könnte übrigens auch der Grund sein, weswegen deine 10 
Sekunden nicht stimmen: 1 Sekunde für die Initialisierung, ein 
Durchlauf, ein Reset.
(e) Du initialisierst das LCD, nachdem du das OS gestartet hast. In 
der Zwischenzeit kann ein Timer feuern und das LCD benutzen, während es 
noch nicht initialisiert ist (Race Condition).
(f) Ich weiß nicht, ob sprintf() und Freunde in der Newlib reentrant 
sind (also gleichzeitig aktiv sein können). Solange du es nirgendwo 
anders verwendest, sollte das kein Problem sein. Aber innerhalb der ISR 
solltest du es trotzdem nicht benutzen, und auch LCD-Zugriffe vermeiden. 
Dafür ist die Hauptschleife da.

von Björn M. (Gast)


Lesenswert?

S. R. schrieb:
> Hardware (ADC) initialisiert man am besten nur einmal, nach dem
> Einschalten. In deinem Fall vermutlich, bevor du das OS startest.

Okey also initialisere ich gleich am Anfang von main() das LCD und ADC, 
so dass ich in der Timer_Callback-Funktion nur den Wert vom ADC hole und 
in main() ausgebe.

S. R. schrieb:
> (d) Deine Idle-Loop fehlt. Deine main()-Funktion rennt bis zum Ende und
> dann stürzt der Controller ab (bzw. startet neu).

Meinst du mit Idle-Loop eine Endlosschleife while(1); ?

S. R. schrieb:
> Es gibt kein
> übergeordnetes Betriebssystem, welches ein endendes Programm abfangen
> kann.

Das verstehe ich nicht ganz, oder ist damit wieder das mit Idle-Loop 
gemeint?

von S. R. (svenska)


Lesenswert?

Björn M. schrieb:
> Okey also initialisere ich gleich am Anfang von main() das LCD und ADC,
> so dass ich in der Timer_Callback-Funktion nur den Wert vom ADC hole und
> in main() ausgebe.

Exakt. Wenn du allerdings so fragst, ist vermutlich schon das OS 
übertrieben und du hättest vielleicht drauf verzichten sollten. ;-)

>> (d) Deine Idle-Loop fehlt. Deine main()-Funktion rennt bis zum Ende und
>> dann stürzt der Controller ab (bzw. startet neu).
>
> Meinst du mit Idle-Loop eine Endlosschleife while(1); ?

Wenn dein Design alles in Interrupthandlern erledigt, dann ja. Und du 
kannst dann in dieser Schleife auch die CPU schlafen legen - sie wird 
dann vom nächsten Interrupt aufgeweckt und verbraucht im Durchschnitt 
weniger.

Wenn du deine Aktionen (Wert auf LCD ausgeben usw) in main() machst, 
dann hast du zwar eine Endlosschleife, aber die ist nicht leer. Denn 
dort werden dann nacheinander die Ergebnisse aus den Interrupthandlern 
verarbeitet. Und wenn die Schleife einmal durchgelaufen ist, dann hältst 
du die CPU auch bis zum nächsten Interrupt an. Dieser Ansatz ist aus 
meiner Sicht zu empfehlen.

>> Es gibt kein übergeordnetes Betriebssystem, welches ein endendes
>> Programm abfangen kann.
>
> Das verstehe ich nicht ganz, oder ist damit wieder das mit Idle-Loop
> gemeint?

Wenn du in einem PC-Programm dein main() mit einem return beendest, dann 
wird das Betriebssystem benachrichtigt, dass das Programm fertig ist.

Dieses Betriebssystem fehlt aber auf einem Controller, deswegen darf 
main() niemals enden. Passiert das trotzdem, holt sich die CPU ihre 
Rücksprungadresse vom Stack und springt da hin. Die Rücksprungadresse 
war aber nie gültig. Wenn du Glück hast, hat dein Startup-Code da die 
Adresse vom Reset-Handler eingetragen, dann startet dein Programm 
einfach neu. Alternativ führt die CPU so lange halbzufällige Bytes aus, 
bis sie entweder neu startet, eine bekannte Funktion trifft und/oder 
abstürzt.

Gruß

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.