Discover more from Vinnie’s Views
PiCar - Raspberry Pi Car Radio Project - Part VIII
Who ate all the CPU?
It’s been a while since my last installment on the PiCar radio, but rest assured I have been making progress. As you might remember from part 5 of this series I added GPS functionality to the radio using the u-blox NEO-M9N GNSS module.
Most recently I added GPS waypoint tracking feature that allows me to record and manage a list of handy GPS waypoints.
The nice thing about the waypoint screen is that it displays in real-time the distance and heading to to that point as well as what direction I need to turn to hit that waypoint
Which way do we go?
Given my current latitude/longitude coordinates and the coordinates of a target the radio determines the distance and heading by performing great-circle computation using the haversine (half a versed sine) and Spherical Law of Cosines formulas.
The math is fascinating and has been around since the early 1800s and I refer you to this article which does a great job of detailing it. My first crack at the code turned out to fairly successful. I suspect that there is an easier way to do this, but for now this works well.
Keeping the CPU cycles down
A nice thing abut the ARM Cortex-A72 in the Raspberry Pi 4 B I am using in the PiCar project as that there is plenty of CPU power to run this calculation without working up a sweat. Literally. I don’t want to push the CPU too hard because this translates to heat which I need to remove from the radio. By the way, I described the cooling system in section 2 of this series.
One of the thing that will do every so often is to use the htop command to check how much cpu time the various threads of my
carradio program uses up.
If you don’t already have it, you can easily install
sudo apt install -y htop
To make debugging easier I wrote a simple macro that call whenever a start a thread in the
#define PRINT_CLASS_TID printf("%5ld %s\n", (long) syscall(SYS_gettid), __FUNCTION__)
For example, when I launched my debug version of carradio, it prints out the following info.
1477 LEDUpdateLoop 1476 DisplayUpdateLoop 1478 AuxReader 1479 SDRReader 1480 SDRProcessor 1481 OutputProcessor 1482 ChannelManager 1483 GPSReader 1484 CANReader 1485 W1Reader 1486 PiCarLoop 1475 main
I happen to know that the
main is the main thread, and I will run
htop from the shell as such:
$ htop -H -p 1475
this gives my a window which looks something like:
Examining this, we can seethe the main process
1475 is using about 43% of the CPU time, and that biggest offender is thread
1480 which is the
SDRProcessor. This makes sense, I have the radio on, tuned into an broadcast FM station and the thread is running the math that demodulates the radio signal from the Software Defined Radio (SDR) I am using in this project.
I can probably spend some time and dig deeper in that code and see if I can improve it.
But I digress
Well that’s not really what I wanted to talk about in this post. I want to tell you about the snipe hunt I partook this week trying to fix an earlier problem. To my bewilderment I discovered that the
GPSReader was eating something like 50% of the CPU time. How can that be? All this thread does is wait for serial input from the NEO-M9N which happens to be configured on
/dev/ttyAMA1 at 38400 baud and read characters from it. I was careful to setup the port such that it had no-echo and turned off all special processing:
I have a fairly simple loop that uses select() to sleep the thread until there is input and then process it as quick as I can.
(don’t spoil the snipe hunt if you see the problem already)
My first thoughts was that processing the NMEA sentences was taking too long. The NEO-M9N was seeing these things as fast as it could. Which got me to thinking maybe there is a better way than decoding all this ASCII.
I was primarily interesting only a few things from the GPS: The current latitude/longitude, and my heading and speed. To get this data. I need to process two separate NMEA sentences: the RMC and GGA . Wouldn’t it be nice if there was one packet that had both?
In addition, since my radio is not connected to the internet, and thus couldn’t do NTP I was interested in a way to get a fairly accurate representation of the current time. So I can keep the clock set right.
Did I mention before, that one of my motivations for building this project was that the realtime clock in my Jeep drifts about a minute a week.
My GPS is multilingual
One of the cool things about the u-blox NEO-M9N GNSS module is that has the ability to speak multiple protocols (or so I thought). According to the data-sheet, in addition to NMEA 4.10, the NEO-M9N also speaks RTCM 3.3 as well as the u-blox proprietary protocol UBX.
RTCM is typically used for sending differential signal to a GPS receiver from a secondary source like a radio receiver. But UBX on the other hand has a lot of cool features. Digging into the Interface description, I found that the the
UBX protocol is a binary format and much more concise than NMEA. It is fairly easy to decode.
Even better, it turns out that there is a UBX message: UBX-NAV-POSLLH Geodetic position solution found in section 22.214.171.124 of the Interface description that contains both a Latitude/Longitude value as well as Ground Speed and Heading of motion, along with the time. All wrapped up in one 96 byte packet! This sounds like exactly what I want.
I found a fellow on YouTube who walks you through how to decode and setup your device to send only this packet.
The folks at u-blox provides the u-center application that allows you to configure the M9 GPS. And wouldn’t you know it, it runs on Windoze…. sigh..
Well yes I guess I could install Wine on the Raspberry Pi , according to this guy, it can be done. Luckily I had a cheap HP laptop that I used to run HP Tuners when I setup my GM LS motor ECU on the Jeep.
So I installed the u-center there and plugged in the M9 via USB to the laptop. It worked! I started seeing the UBX-NAV-POSLLH messages in u-center. OK next step, I connected the M9 to my Mac and modified the
GPSmgr class in the gpstest code to read the UBX packets. So far so good! I could run it on my Mac via USB.
Then I took a USB cable and connected the M9 in my CarPi and updated it to output UBX packets. This seemed to work. Next step, I ran the gpstest code on the Raspberry Pi, and .. crap, it didn’t work..
I don’t understand. I checked all connections, and found that the M9 was still spewing NMEA sentences but no UBX.. Well let’s see. Because I was running out of USB ports on the CarPi I decided to wire the TXD/RXD serial line on the M9 breakout directly to one of my Raspberry Pi 4’s UART. I talked about this in part 5. So the only difference is that the M9 on the Pi was using the UART vs USB. Maybe there was a setting I forgot.. Then I remembered something in the data-sheet that I read and ignored many months ago..
You’ve got to be kidding me! So after a bunch more research.. Yup, this isn’t going to work. Back to the drawing board.
Back to the future.
Remember above when I mention, so smugly, I might add, that I used select() to sleep the thread while waiting for input. I went back and noticed that I had specified a timeout value of something like 100 microseconds. I had cut and pasted code from something I wrote earlier, that needs to check for device disconnect. This was causing the thread to run even when there was no input and consequently eat up CPU time.
This was completely unnecessary. I removed the timeout value completely, and unsurprisingly the CPU percentage dropped dramatically.
I suppose if I really want to keep the timeout, it makes sense to set it to something like 10 seconds or so. But for now I removed it entirely.
What did we learn from this?
All this reminds me of a line from possibly one of worst movies ever produced, “Burn After Reading”. The CIA director tries to find a lesson in all that has happened..
CIA Superior: What did we learn, Palmer?
Palmer: I don't know, sir.
CIA Superior: I don't fuckin' know either. I guess we learned not to do it again.
Palmer: Yes, sir.
CIA Superior: I'm fucked if I know what we did.
Palmer: Yes, sir, it's, uh... hard to say.