Forum: Mikrocontroller und Digitale Elektronik STM32 Uart Float Bytes senden


von Simon (Gast)


Lesenswert?

Hallo,
ich versuche im Moment mit einem Nucleo-F411RE Board die 4 Bytes eines 
Floats über UART zu senden. Dazu habe ich mit CubeMX das Grundgerüst 
generiert, welches eigentlich nur den UART2 mit 115200 Baud 
initialisiert und folgenden Code geschrieben:
1
float f = 1.0;
2
HAL_UART_Transmit(&huart2, (uint8_t*)&f, 4, HAL_MAX_DELAY);
Mit diesem Code empfange ich allerdings auf der PC-Seite 6 Byte. Wenn 
ich f=0.1412 setze, empfange ich sogar 8 Byte.
Wenn ich einen Text sende kommt dieser richtig an.
1
HAL_UART_Transmit(&huart2, (uint8_t*)"Hallo\n", 6, HAL_MAX_DELAY);

Kann mir eventuell jemand erklären woran das liegt?
Ich würde mich über jede Hilfe freuen :)

Anbei noch der generierte Initialisierungscode:
1
void SystemClock_Config(void)
2
{
3
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
4
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
5
6
  /** Configure the main internal regulator output voltage 
7
  */
8
  __HAL_RCC_PWR_CLK_ENABLE();
9
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
10
  /** Initializes the CPU, AHB and APB busses clocks 
11
  */
12
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
13
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
14
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
15
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
16
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
17
  {
18
    Error_Handler();
19
  }
20
  /** Initializes the CPU, AHB and APB busses clocks 
21
  */
22
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
23
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
24
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
25
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
26
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
27
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
28
29
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
30
  {
31
    Error_Handler();
32
  }
33
}
34
35
/**
36
  * @brief USART2 Initialization Function
37
  * @param None
38
  * @retval None
39
  */
40
static void MX_USART2_UART_Init(void)
41
{
42
43
  /* USER CODE BEGIN USART2_Init 0 */
44
45
  /* USER CODE END USART2_Init 0 */
46
47
  /* USER CODE BEGIN USART2_Init 1 */
48
49
  /* USER CODE END USART2_Init 1 */
50
  huart2.Instance = USART2;
51
  huart2.Init.BaudRate = 115200;
52
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
53
  huart2.Init.StopBits = UART_STOPBITS_1;
54
  huart2.Init.Parity = UART_PARITY_NONE;
55
  huart2.Init.Mode = UART_MODE_TX_RX;
56
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
57
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
58
  if (HAL_UART_Init(&huart2) != HAL_OK)
59
  {
60
    Error_Handler();
61
  }
62
  /* USER CODE BEGIN USART2_Init 2 */
63
64
  /* USER CODE END USART2_Init 2 */
65
66
}
67
68
/**
69
  * @brief GPIO Initialization Function
70
  * @param None
71
  * @retval None
72
  */
73
static void MX_GPIO_Init(void)
74
{
75
76
  /* GPIO Ports Clock Enable */
77
  __HAL_RCC_GPIOA_CLK_ENABLE();
78
79
}

von foobar (Gast)


Lesenswert?

Ich vermute mal, dass dein Empfänger mit den nicht-ASCII-Bytes 
durcheinanderkommt.  Was empfängst du denn? (Die Bytes bitte, nicht die 
dadurch repräsentierten ASCII-Zeichen.)  Was kommt an, wenn du z.B. 
"\x3e\x10\x96\xbc",4 sendest?

von Teddy (Gast)


Lesenswert?

Erstell mal eine Union aus char und float. Schreibe deine float zahl in 
die den member float und übertrage dein byte mit dem member char.

von schmurx (Gast)


Lesenswert?

Du solltest Dich mit dem Unterschied zwischen Strings und einer 
beliebigen Folge von Bytes in C beschäftigen.

Woran erkennt Dein HAL_UART_Transmit das Ende der Übertragung? 
Vielleicht an einer `\0`?
Und selbst wenn es Dir gelingt, eine Float-Zahl binär zu übertragen,
muss das auf der Empfängerseite nicht das Gleiche bedeuten wie auf der 
Senderseite.

von STK500-Besitzer (Gast)


Lesenswert?

schmurx schrieb:
> Woran erkennt Dein HAL_UART_Transmit das Ende der Übertragung?
> Vielleicht an einer `\0`?

Nö. Die Funktion will die Länge des Arrays mitgeteilt bekommen. Die ist 
in diesem Fall mit 4 angegeben, was der Länge eines floats in Bytes 
entspricht.


@TO:
Welches Terminalprogramm verwendest du denn?
Die Methode mit der Union hat sich bei mir bewährt, solange die Endians 
Maschinen übereinstimmen.

von Stefan F. (Gast)


Lesenswert?

Das heir ist definitiv in Ordnung:

> float f = 1.0;
> HAL_UART_Transmit(&huart2, (uint8_t*)&f, 4, HAL_MAX_DELAY);

Die 4 weist den Mikrocontroller an, genau 4 Byte zu senden. Und das 
funktioniert auch, da bin ich 100% sicher.

Im Ruhezustand hat die serielle Leitung HIGH Pegel. Vielleicht hast du 
mangels Pull-Up während der Start-Phase kurzzeitig zufällige Signale auf 
der Leitung, die dein Empfänger mit zählt.

Benutze mal das Hammer Terminal oder einen Logic Analysator zum 
Untersuchen.

von W. W. (Gast)


Lesenswert?

Simon schrieb:
> und folgenden Code geschrieben:
1
float f = 1.0;
2
HAL_UART_Transmit(&huart2, (uint8_t*)&f, 4, HAL_MAX_DELAY);

Unabhängig vom eigentlichen Problem (das ich nicht kenne), hast du hier 
undefiniertes Verhalten, weil du float* nach uint8_t* castest (strict 
aliasing).

Regeln für C++, aber gelten analog für C ebenso:

https://en.cppreference.com/w/cpp/string/byte/memcpy.html
> Where strict aliasing prohibits examining the same memory as values of
> two different types, std::memcpy may be used to convert the values.


https://en.cppreference.com/w/cpp/language/object#Strict_aliasing
> Accessing an object using an expression of a type other than the type
> with which it was created is undefined behavior in many cases, see
> reinterpret_cast for the list of exceptions and examples.

D.h. du solltest es eher so machen:
1
float f = 1.0;
2
uint8_t foo[sizeof(f)];
3
memcpy(foo, &f, sizeof(foo));
4
HAL_UART_Transmit(&huart2, foo, sizeof(foo), HAL_MAX_DELAY);

Der compiler erkennt dann, dass du float nach uint8_t array casten 
möchtest und optimiert das memcpy komplett raus. Es ist also identisch 
zu deinem (uint8_t*) cast, nur ohne undefiniertes Verhalten.

von Stefan F. (Gast)


Lesenswert?

W. W. schrieb:
> Unabhängig vom eigentlichen Problem (das ich nicht kenne), hast du hier
> undefiniertes Verhalten, weil du float* nach uint8_t* castest (strict
> aliasing).

Das bezweifle ich stark. Ein Pointer ist ein Pointer.
Solange der Compiler das zulässt (warum sollte er nicht?) wird der 
Pointer never ever zu einer falschen Position im Speicher zeigen.

Und selbst wenn ich mich irren sollte, so wird doch aus der 4 keine 6. 
Die Schnittstelle müsste also trotzdem 4 Bytes senden.

von W. W. (Gast)


Lesenswert?

Stefanus F. schrieb:
> W. W. schrieb:
>> Unabhängig vom eigentlichen Problem (das ich nicht kenne), hast du hier
>> undefiniertes Verhalten, weil du float* nach uint8_t* castest (strict
>> aliasing).
>
> Das bezweifle ich stark. Ein Pointer ist ein Pointer.
> Solange der Compiler das zulässt (warum sollte er nicht?) wird der
> Pointer never ever zu einer falschen Position im Speicher zeigen.
>
> Und selbst wenn ich mich irren sollte, so wird doch aus der 4 keine 6.
> Die Schnittstelle müsste also trotzdem 4 Bytes senden.

Ich schrieb ja, es sei unabhängig vom eigentlichen Problem, aber 
solche casts sind schlichtweg UB.  Die funktionieren eventuell 
vielleicht bei Vollmond und Compiler Version X. GCC macht i.d.R. 
vermutlich das Richtige, aber es ist und bleibt UB und darf jederzeit 
anders (falsch) funktionieren.

von Stefan F. (Gast)


Lesenswert?

W. W. schrieb:
> Ich schrieb ja, es sei unabhängig vom eigentlichen Problem

Ja stimmt, ich habe meine Worte unpassend gewählt.

von W. W. (Gast)


Lesenswert?

Vielleicht ist es für den Fall eines uint8_t* doch OK:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

§6.5. Abs. 7:
> An object shall have its stored value accessed only by an lvalue expression that 
has one of
> the following types: 88)
> — a type compatible with the effective type of the object,
> — a qualified version of a type compatible with the effective type of the 
object,
> — a type that is the signed or unsigned type corresponding to the effective type 
of the
> object,
> — a type that is the signed or unsigned type corresponding to a qualified 
version of the
> effective type of the object,
> — an aggregate or union type that includes one of the aforementioned types among 
its
> members (including, recursively, a member of a subaggregate or contained union), 
or
> — a character type.

§6.2.5 Abs. 15:
> The three types char, signed char, and unsigned char are collectively called
> the character types. The implementation shall define char to have the same 
range,
> representation, and behavior as either signed char or unsigned char. 45)

Demnach ist es also zulässig nach uint8_t* zu casten.

Das Problem könnte aber sein, dass uint8_t nicht zwingend ein unsigned 
char sein muss.

von Stefan F. (Gast)


Lesenswert?

W. W. schrieb:
> Das Problem könnte aber sein, dass uint8_t nicht zwingend ein unsigned
> char sein muss.

Damit bewegen wir uns allerdings in Richtung einer akademischen 
Diskussion fernab jeder Realität. Die Sprache C verleitet uns regelrecht 
dazu, da sie einerseits hardware-nah sein will, andererseits aber 
Annahmen über die Hardware verbietet.

Und jetzt zurück zur Praxis:
Auf welchem Computer ist ein unsigned char nicht gleichen einem uint8_t?

Ich denke: keine.
Und ich erwarte auch nicht, dass sich daran jemals etwas ändert.

von Markus F. (mfro)


Lesenswert?

W. W. schrieb:
> Ich schrieb ja, es sei unabhängig vom eigentlichen Problem, aber
> solche casts sind schlichtweg UB.

M.E. in diesem Fall nicht.

Pointer aliasing inkompatibler Typen ist undefiniertes Verhalten, das 
ist richtig, aber pointer aliasing zu char Typen (und das ist hier 
gegeben) ist (wg. endianess) lediglich implementation defined (zumindest 
in C, in C++ weiß ich's nicht).

Wär' das nicht so, wäre deine memcpy()-Lösung auch UB.

von Simon (Gast)


Lesenswert?

Stefanus F. schrieb:
> Benutze mal das Hammer Terminal oder einen Logic Analysator zum
> Untersuchen.

Vielen dank. Es lag tatsächlich an meinem Terminal. Sowohl cutecom als 
auch das Serial Terminal Plugin für CLion zeigen mir hier einfach 
falsche Werte an...

von Jemand (Gast)


Lesenswert?

Stefanus F. schrieb:
> Ich denke: keine. Und ich erwarte auch nicht, dass sich daran jemals
> etwas ändert.

Bei GCC und Clang gab es Bestrebungen uint8_t als extended Integer, also 
non-character Type zu definieren, gescheitert ist das hauptsächlich an 
den großen Änderungen, die an den Compilern nötig wären! (Mal davon 
abgesehen, dass das gar nichts mit der Zielplatform zu tun hat)

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.