Last time, the board learned to send and receive text: press the button, and a message will appear on the computer. Hello world; send a sentence How are you?it replies with a greeting. What was issued at that time was “sentence” – a string of characters for people to read, using Uart_SendString() Sent out byte by byte.
The sentences are useful enough, but not economical enough. If you want to send three numbers, you have to convert each number into a string, spell it with commas, and add a line break. It is tedious to write, and you have to rewrite it when the numbers change. And more importantly:If you read a sentence to people, you will not be able to understand it in the curve area.
This article does two things: first use printf() Make it easier to “send data outbound”, and then let the same batch of data grow into a curve in VOFA+. For example, use the four quantities when adjusting the PID earlier – at first I could only stare at the four rows of numbers jumping back and forth on the OLED, but now I can see them as four lines.
let printf() speak from the board
printf() It is the formatted output function in the standard library. You have used it when writing C on your computer. It can also be used in microcontrollers, but it doesn’t know “where to write” by default – it hands the characters to an underlying function fputc()this function determines where to send it.
Then the wiring is very simple:We change this underlying function to “send a byte through the serial port”,printf() The entire output path is automatically connected to USART1.
#include
int fputc(int ch, FILE *f)
{
(void)f;
Uart_SendByte((uint8_t)ch);
return ch;
}
Uart_SendByte() It is the polling sending function from the last time, unchanged.fputc() every time printf() Call once and send one character;printf() Internally, we need to convert numbers into characters, fill in spaces, and handle formats. These are not our responsibility.
Now you can send it like this:
printf("left=%d, right=%d\r\n", left_actual, right_actual);
%d will be replaced by the value of the variable and appears on the screen left=173, right=486. Adding variables later is just to write one more variable in the format string. %dno need to convert by hand.
You can’t draw a curve when reading a log for others.
The line of words above makes people feel comfortable: there are names, equal signs, and commas. But throw it to the curve area of VOFA+, and not a single line will come out.
The reason was mentioned earlier when I recognized the VOFA+ interface:The log format for people to read is not the format for curves to read.. When VOFA+ draws a picture, it needs to split a row of bytes into “the value of which channel” according to fixed rules. It doesn’t know left=nor does it need an equal sign – it just wantsnumber itself。
So the two paths have to be taken separately:
- If you want people to understand it, use
left=173, right=486This kind of journal with a name; - If you want the software to draw a curve, just send the numbers and arrange them as specified in the agreement.
For the same data, both methods have their own uses. Let’s talk about the most common protocol first.
FireWater: A line of numbers is a set of curves
FireWater’s rules are as short as one sentence:In one line, several numbers separated by commas, ending with \r\n Finish.
173,486\r\n
This line will be interpreted as two curves: the first number is the value of channel 0, the second is the value of channel 1. A few more numbers means a few more channels. Use it directly when sending printf():
printf("%d,%d\r\n", left_actual, right_actual);
In conjunction with the last PID project, send the four quantities of the speed loop together, exactly four channels:
printf("%d,%d,%d,%d\r\n",
left_target, left_actual, right_target, right_actual);
There are only two things to do in VOFA+: select the serial port as the data source and select the protocol. FireWaterand then connect. Four lines immediately appeared in the curve area: two were stepped targets, and two were the process of catching up with the actual value——At first, only four numbers could be seen jumping on the OLED, but now the relationship between them has become a shape.
By the way: Since the text is sent, these numbers can also be seen in the terminal window at the same time. It is very convenient to check “whether it was sent or not.”
You can also send decimals without relying on floating point
Quantities in PID are often decimals: the error may be -1.67the gain is 1.20. When I want to send it out, I usually think of it first %f——
printf("%.2f\r\n", error);
This line of work may not work as you want in a microcontroller, and the reasons will be discussed separately below. Let’s remember a more reliable method first:Enlarge the decimal to an integer and then send it.
Multiply the error by 100,-1.67 It becomes -167;The receiving end can just read it as “one percent”. This is the same trick as the “error × 100 and then display” in the previous tracking article:
printf("%d,%d\r\n", (int32_t)(error * 100.0f), line_mask);
This has three advantages: it does not rely on floating point formatting (saving a lot of code), sends fewer bytes, and the curve area can still be used; agree on which line -167 represent -1.67 That’s it.
All cross-serial port numbers later in this article follow this idea: don’t use floating point if you can use integers. I’m not afraid of trouble, mainly because I want to avoid a specific pitfall.
The pit: micro libraries and floating point formatting
printf("%.2f", x) When I couldn’t type it out, my first reaction was that I had written it wrong. Only after checking it did I find that the problem was in the library.
AC5 (i.e. Armcc)of stdio.h inside,printf A class of functions has two sets of declarations:
- full version
printf、sprintf、scanf:Support floating point format; - Underlined cropped version
_printf、_sprintf、_scanf: The comments in the header file are very straightforward——“Equivalent to printf, but does not support floating point format, can be used to reduce code size”。
and microlib (micro library) itself is a “volume-first” library: The official description of it is “highly optimized for extremely small code size”, and it is clearly stated that it does not comply with the ISO C library standard. So in the micro library, what is linked is the cropped version that does not support floating point format——Hooked Use MicroLIB hour,%f Cannot produce normal results.
The switch is on Options for Target → Target page Use MicroLIB Above, both options come with their own costs:
| choose | Floating point formatting | Program size |
|---|---|---|
hook Use MicroLIB |
The ____ does not work | Small |
| Uncheck (use standard library) | Can be used | Significantly larger |
Really need it %fjust uncheck and recompile; if you don’t want to touch this switch, just use the fixed-point zoom in the previous section.%d Works well in any configuration.
Let me tell you in full:Change to AC6 (armclang), this restriction will be different from now on. The official ARM documentation states that AC6 micro-library supports printf()the only conversions not supported are %lc、%ls、%a These three (wide characters and hexadecimal floating point),%f is supported. The boundaries of different tool chains and different versions are different. When encountering floating-point printing exceptions, first read the set of documents you have, and don’t rush to change the code.
There is another thing that is easy to overlook: the floating point operation of the micro library itself also deviates from the standard – the official list is “the results of operations involving NaN, infinity, and denormalized numbers are uncertain” “very small but non-zero results will return 0” “IEEE exceptions cannot be marked” “the sign of zero is uncertain” “only the default rounding mode is supported”. Daily parameter adjustment uses regular and ordinary values, and these corners will not be touched; but if the thing you are calculating happens to be close to these situations, you should be aware of it.
JustFloat: When there is a lot of data and fast requirements
FireWater is text, and a number takes up several bytes:173 is 3 characters,-1.67 Multiplied by 100 it is 4. When there are many channels and the transmission speed is fast, these characters will fill up the serial port.
JustFloat goes the other way:The value of each channel is sent directly as a 4-byte floating point number.a fixed frame trailer is attached to the end of a frame, telling the software “this frame ends here.” The four bytes at the end of the frame are 00 00 80 7F——It is what “positive infinity” in floating point numbers looks like in memory; the normal measured value cannot be equal to positive infinity, so using it as a separator will not cause a crash. (The specific format is subject to the VOFA+ document.)
static void JustFloat_Send(const float *values, uint8_t count)
{
static const uint8_t tail[4] = { 0x00U, 0x00U, 0x80U, 0x7FU };
uint8_t i;
uint8_t j;
for (i = 0U; i < count; i++)
{
const uint8_t *p = (const uint8_t *)&values[i];
for (j = 0U; j < 4U; j++)
{
Uart_SendByte(p[j]);
}
}
for (j = 0U; j < 4U; j++)
{
Uart_SendByte(tail[j]);
}
}
(const uint8_t *)&values[i] This sentence treats floating-point numbers as 4 bytes “as they appear in memory”. STM32 is a little-endian machine. Floating-point numbers are stored in memory with low-order byte first, which is exactly the order required by VOFA+.
When using it, the protocol drop-down box of VOFA+ must be selected. JustFloat. When is it worth switching over?When there are many channels, frequent transmission, or when you do not want the accuracy to be affected by scaling such as “multiplying by 100”. If you have less data and are just looking at trends, FireWater is more useful – after all, you can read what it sends out.
Let VOFA+ change the parameters in turn
The curve is “seeing”, and the next step is “changing”. The most annoying actions to adjust PID are: change a number, recompile, redownload, and run again. After dozens of back and forths, all the time was spent waiting for downloading.
The control area of VOFA+ can place slide bars.When dragging it, the software will send a text command you set back to the serial port.. The board received the same “one-line command” as last time, except that there was an extra value in the command:
P 120
I 12
D 30
According to the rules set earlier, these numbers areAn integer magnified 100 times:P 120 Indicates that Kp takes 1.20. The analysis on the board is completed using integer operations, and no floating point formatting is required at all:
static void HandleTuneCommand(const char *cmd)
{
char name = cmd[0];
uint8_t i = 1U;
int32_t value = 0;
while ((cmd[i] == ' ') || (cmd[i] == '\t'))
{
i++;
}
while ((cmd[i] >= '0') && (cmd[i] <= '9'))
{
value = value * 10 + (int32_t)(cmd[i] - '0');
i++;
}
if (name == 'P')
{
left_speed_pid.kp = (float)value / 100.0f;
right_speed_pid.kp = (float)value / 100.0f;
}
else if (name == 'I')
{
left_speed_pid.ki = (float)value / 100.0f;
right_speed_pid.ki = (float)value / 100.0f;
}
else if (name == 'D')
{
left_speed_pid.kd = (float)value / 100.0f;
right_speed_pid.kd = (float)value / 100.0f;
}
else
{
printf("?\r\n");
return;
}
printf("OK %c %d\r\n", name, value);
}
left_speed_pid、right_speed_pid These are the two sets of PIDs in the previous speed loop. The member names are based on how they are written in your own structure;Change the left and right togetherdon’t change just one side – otherwise the two wheels will run with different gains and the car will start to arc.
cmd[0] Take the first character to decide which parameter to change. The following digits are accumulated one by one to form an integer, and then divided by 100 to return to a decimal. I will reply after receiving the board. OK P 120you can confirm on your computer that this change has taken effect.
At this step, the parameter adjustment loop becomes:Drag the slider → the board changes parameters immediately → the curve changes on the spot. It’s still the same idea of ”change a number and see the change”, except that compilation and downloading are omitted in the middle.
A chain, both ends must be aligned
Putting the things in this article together, the data has to go through four levels in the middle from variables to curves:
变量 -> printf 格式化 -> fputc 重定向 -> Uart_SendByte -> USART1 -> CH340 -> VOFA+
If there is an error in any level, the phenomenon will be different and can be investigated separately:
- Nothing can be seen in the terminal window:Confirm first
fputc()Is it really linked in (function name, return type,#include), and then confirm the serial port parameters and wiring – those were checked last time. - Numbers can be seen in the terminal, but the curve area is empty.: The wrong protocol was selected, or logs with names instead of pure numbers were sent. Go back and see what that line looks like.
- Typing floating point numbers is weird: For the boundary of the micro library, see the previous section.
- Curves one by one: Sending too frequently, the main loop is occupied by sending. It can’t be solved here –
Uart_SendByte()The loop of “waiting for the register to be empty” is still standing there. All that can be done now is to slow down the pace of sending; making the sending itself not occupy people is another thing that needs to be done.
Finally the orange cat said
printf() The reason why it can speak in the microcontroller is because it leaves “where to write” to the bottom layer. fputc(); We change this function to serial port transmission, and the entire output path is connected to USART1. butLogs for people to see and formats for curves to see are two different things: FireWater wants a line of pure numbers separated by commas and the end of the line. \r\n,173,486\r\n Just two curves,left=173, right=486 Then nothing can be drawn. It is not necessary to use decimals %f Send, multiply by 100 to become an integer, and read it as one hundredth at the receiving end, which not only saves bytes but also circumvents the limitations of the micro library – AC5 stdio.h Here, underlined cropped version printf In black and white it says “Floating point format is not supported”, and the micro library is the one that prioritizes size; %f Just uncheck Use MicroLIBthe price is that the size becomes larger (this restriction will be different after switching to AC6, please refer to the document on hand). When the data is more and faster, consider JustFloat: 4 bytes of floating point per channel, end of frame 00 00 80 7F. Finally, the slider can send commands back to the board,P 120 Such integer commands allow parameters to be changed online – after a link is opened, viewing and adjustment are truly one thing.
Finally the spider said
No, I’ve been working on it for a long time, but you said the board can’t be remotely Bluetoothed?