New thread on my big ongoing embedded project since the other one was getting too big.
To recap, this is a pilot project for a bunch of my future open hardware T&M and networking projects, validating a common platform that a lot of the future stuff is going to run on.
The primary problem it's trying to address is that I have a lot of instrumentation with trigger in/out ports, sometimes at different voltage levels, and I don't always have the same instrument sourcing the trigger every time.
So rather than moving around cables all the time and adding splitters, attenuators, amplifiers, etc. to the trigger signals I decided to make a dedicated device using an old XC7K70T-2FBG484 I had lying around.
Of course, as with any project, there was feature creep.
I'm standardizing on +48V DC for powering all of my future projects as it's high enough to move a lot of power but low enough to be mostly safe to work around live. So I needed to design and validate an intermediate bus converter to bring the 48 down to something like 12 for the rest of the system to use.
The FPGA has four 10G transceiver pairs on it. I used one for 10GbE (not that I need the bandwidth, but I was low on RJ45 ports on this bench and had some free SFP drops) and the rest are hooked up to front panel SMA ports (awaiting cables to go from PCB to panel) to generate PRBSes for instrument deskew.
Since I'm pinning out the transceivers and am planning to build a BERT eventually, I added BERT functionality to the firmware as well (still need to finish a few things but it's mostly usable now).
And since I have transceivers and access to all of the scope triggers, it would be dumb not to build a CDR trigger mode as well. That's in progress.
While I wait for the front/rear panel cables (expected to ship tomorrow) I've been working on some other parts of the firmware.
In particular, rather than using embedded Linux like most people probably would have, I wanted to keep this bare metal. So I found myself implementing things like a SSH server for bare metal STM32 with no OS (and no dynamic memory allocation).
Currently I'm working on a SNTP client that syncs the STM32 RTC to a network time server. Almost done with the NTP side of things, just have to write the RTC driver.
Other than that, pending TODOs:
* IPv6 support in the TCP/IP stack
* libscopehal driver support for the SCPI commands (already implemented in firmware) to set input threshold and output drive voltage
* Finish the CDR trigger mode. Right now I have 8b/10b and 64b/66b decoding in the FPGA bitstream, but need to add a pattern matching engine and SCPI commands to configure it
* Add commands for deep BER integration (continuous sampling reporting number of PRBS errors since last clear or something)
* SFTP-based OTA firmware update for (at least) the main processor and FPGA. Will probably try and get the front panel processor as well, but neither the supervisor nor the IBC has enough flash for A/B images. I'll probably switch to different STM32L0's with more flash in future projects to enable OTA flashing of the entire system.
I think OTA support is probably the next priority after I finish NTP.
Once I get working OTA support and all of the front/rear panel cables are installed, I'll be able to close the thing up and free up a ton of bench space.
Although I might not rack it right away, since having access to JTAG will still be handy for active firmware development.
NTP client is now pretty-printing a human readable yyyy-mm-dd hh:mm:ss.uuuuuu timestamp with hard-coded UTC-to-local offset.
Next step: make that offset configurable, get the RTC driver written up so I can store the timestamp to the RTC, and add a #define to logtools to make it print the timestamp from the RTC instead of since boot.
NTP on the STM32 is working!
First line: RTC timestamp prior to sync
Second line: timestamp received from time server, after correcting for network latency
Third line: RTC time read back after synchronizing it to match the NTP timestamp
Only thing left is logging library integration.
Let the NTP firmware run over a few hours while having dinner and doing family stuff.
It looks like the local oscillator on my MCU is just a tad fast relative to GPS time (the stratum 1 NTP source on my lab LAN).
The polls are approximately 1024 seconds apart, and I'm running about 2.8 - 2.9 ms fast every time I re-sync the clock to NTP.
Let's call this 2.85 ms of error into 1024 seconds, which comes out to about 2.78 ppm.
Not bad considering i'm using a non-temperature-compensated quartz oscillator with a datasheet tolerance of +/- 25 ppm.
And I think that's all the CLI and library plumbing needed for full NTP integration including the logging library.
Only thing missing is adding configurable UTC offset and DST support at some point, although that's not an immediate priority since I'm the sole user of the system and the next DST transition is quite a ways off...
The reason for the large negative step errors on first sync, if anyone is curious, is that the RTC is clocked by the HSE oscillator (25 MHz source) which does not keep running across resets or power-down.
Or, more importantly, when flashing a new firmware via JTAG.
So any time I firmware update the board the RTC stops for a few seconds and it lags behind actual time until the next NTP sync.
(I wasn't originally planning on using the RTC on this board so I never put a low-speed crystal on it)
Ok, got some housekeeping stuff out of the way. Confirmed some of the display corruption I was seeing on the front panel was due to overflowing the SPI FIFO when data came from the FPGA too fast, but I had plenty of RAM so I just made the FIFO a bit bigger and the problem went away.
Also made the SPI class have compile-time variable FIFO sizes which will simplify things a bunch.
Still probably going to have to make some tweaks to some of these peripheral drivers to make things nicely integrated without adding too much overhead. It's going to be a process.
Anyway, the elephant in the room is OTA firmware update via SFTP. This is going to take some time to get right.
Before I work on that I have a few other more small yaks to shave, like https://github.com/ngscopeclient/scopehal/issues/866.
So I think that ticket will be the next focus, it should be straightforward.
@azonenberg Is this OTA for STM32? Do you have dual bank flash on it? I have some code for an L4+ that does OTA.
@0h00000000 It's a bit nore complex than that. I have a L431 and H735 and FPGA that all need to be updateable via SFTP running on the H735, plus (eventually) signed updates and (in the near term) simple crc verification. The actual flashing is the easy part.
Also fallback code and linker script modifications so i can have a/b images or at least fallback to bootloader if things don't work out.
@azonenberg Yeah, the l4+ has 2MB flash that can be configured as 1MB dual bank with an option bit to select which bank to boot from on reboot, and both banks can be aliased to 0x8000000. I download fresh code into other bank, and hash it and compare with provided hash. That 1MB is divided up with image L4+, and image for ice40 FPGA and image for a SILabs wifi chip. I'll eventually move those images to eMMC at some point.
@0h00000000 Yeah my FPGA is a Kintex-7, I'm not fitting that image in the STM32 flash :)
My flash is single bank on the H735, eight 128K sectors. There's an option bit to select which sector to boot from.
My current setup for the H735 is to keep the default boot from 0x08000000 and put the bootloader in the first sector. The last two sectors are used by the microkvs key-value store that all of my configuration data lives in.
Rather than doing A/B images, my plan is to have the bootloader/updater live in flash sector 0 (should be possible to squeeze into 128 kB easily enough with -Os) while the main application lives in sectors 1-5.
The system will come up in bootloader mode initially then check some health state in backup SRAM to see if the last boot of the main image was successful or if there was a failure (hard fault, watchdog reset, etc).
If boot / image verification fails, or if the user explicitly requested a return to bootloader mode to flash the main processor, the bootloader will remain active. In this state it will be running out of the first flash sector, using the same SSH keys and IP config as the full firmware image, and wait for you to SFTP over an image which will then be written to sectors 1-5.
Once the new image is written and verified, the bootloader will then trigger a reset and jump to it.
@0h00000000 For the FPGA, it's easy because the FPGA has multi-boot support natively and I have enough SPI flash for multiple full bitstreams so I should have no problem doing full A/B there.
The L431 on the front panel also has plenty of flash space (and small sectors) so bootloader + full A/B should be viable there as well.
@azonenberg Oh yeah, I saw this processor/DAC/ADC and thought you might be interested in checking it out, 2x1gsps ADC, 4x500msps ADC, 2x500msps DAC, 6x VSPA vector accel, 6x e200 cores, PCIe. Interesting beast.
https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/layerscape-processors/layerscape-access-la12xx-programmable-baseband-processor:LA12xx