Sunday, February 23, 2020

Raspberry Pi - pi-hole setup notes

In the category of "why didn't I do this sooner" - I finally setup my full network ad-blocking pi-hole. It's great! I can't believe I waited this long. The install is really quite straightforward for a basic system, however there are always options for fine tuning.

The basic install is quite good and blocks quite a bit of junk.

One of the places that has more extensive information on pi-hole options is this smart home beginner article, which ironically also features a ton of ads that are not blocked by a pi-hole default install. :/

When considering which device to run pi-hole on, consider the requirements. It does not have to be a Raspberry Pi, although that's nearly a perfect platform for most users. I ended up choosing a prior-generation Raspberry Pi 3 Model B+ for the lower power consumption and a Samsung 32GB microSDHC EVO, purchased separately.  Given my actual requirements, I probably should have chosen the Raspberry Pi 2, instead. (see below on power details)

My Raspberry Pi 3B+ has built-in WiFi and Bluetooth. Both are features that I will not likely use for my pi-hole. I prefer the reliability (and security) of a wired Ethernet connection for something as important as DNS lookups.

One of the benefits of the 3B+ is that the official Raspberry Pi PoE HAT can be used to power the Raspberry Pi over Ethernet. I didn't go this route.

Update: see my blog on Raspberry Pi Headless Setup. I've found setting up those 4 pre-configured files and copying them to the root of SD card at image-write-time is quite handy and gets the RPi basically operational with minimal fuss.

A key detail is to edit the config.txt file on the root of SD card (found in /boot/config.txt once RPi is running) before inserting into the RPi; add this line to allow serial TTL communication and avoid needing to plug in a monitor and keyboard for initial setup:

enable_uart=1

Note that despite having 5V pins right next door, the Raspberry Pi serial port uses 3.3V logic.

Connect your favorite USB to TTL Serial cable:
Raspberry Pi pinout showing Serial TTL from raspberrypi.org docs
Then login with putty or some other terminal program. For more detailed information on setting up a headless Raspberry Pi, see this sparkfun how-to.

I prefer to run sudo raspi-config to do things like:
  • Assign password
Network Options:
  • assign host name such as "pi-hole"
Localisation Options:
  • Change the locale to en_US.UTF-8 UTF-8
  • Change timezone to Pacific Time
Interfacing Options:
  • Disable Camera
  • Enable SSH
  • Disable VNC
  • Disable SPI
  • Disable I2C
  • Enable Serial
  • Disable 1-Wire
  • Disable Remote GPIO
Advanced Options:
  • Expand Filesystem 
  • GPU Memory Split set to 16
Update raspi-config to the latest version.

Next assign a static IP address via sudo nano /etc/dhcpcd.conf and add these lines:

interface eth0
        static ip_address=192.168.1.77/24
        static routers=192.168.1.254
        static domain_name_servers=127.0.0.1

Put in your values for device IP address, router, and actual DNS (needed for initial install) . After setting up the pi_hole, set the DNS to 127.0.0.1 as shown.

Disable IPV6 unless you know you need it. Edit /etc/sysctl.conf and put these lines at the bottom:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
net.ipv6.conf.tun0.disable_ipv6 = 1


One of the main problems with the Raspberry Pi is the continual writing to the SD card and subsequent (lack of) reliability when in operation for years. See the log2ram install, below, that can help with this. Hackaday also has an article on the coolness of log2ram that refers to this log2ram blog by Erich Styger.

Next, main setup:

# system update
sudo apt-get update && sudo apt-get upgrade

# install essentials
sudo apt-get install git
sudo apt-get install fail2ban
sudo apt-get install dnsutils
sudo apt-get install arpwatch
sudo apt-get install iptables-persistent

# remove things that will not be used:
sudo apt-get purge realvnc-vnc-server --assume-yes

# basic pi-hole install
cd ~/
git clone --depth 1 https://github.com/pi-hole/pi-hole.git
cd "pi-hole/automated install/"
sudo bash basic-install.sh
sudo pihole -a -p

# log2ram install (optional)
cd ~/
curl -Lo log2ram.tar.gz https://github.com/azlux/log2ram/archive/master.tar.gz
tar xf log2ram.tar.gz
cd log2ram-master
chmod +x install.sh && sudo ./install.sh
# REBOOT BEFORE INSTALLING ANYTHING ELSE

This might be a good time to set that static domain_name_servers=127.0.0.1 setting.

Operational check for log2ram:

# not blank if working properly:
mount | grep log2ram

# not blank if working properly:
df -h | grep log2ram

If it appears the log files need more space, edit the /etc/log2ram.conf
.
See firewall notes for supported operating systems. Secure the RPi with IP Tables (optional):

# Flush the tables to apply changes
sudo iptables -F
sudo ip6tables -F

# Default policy to drop everything but our output to internet
sudo iptables -P FORWARD DROP
sudo iptables -P INPUT   DROP
sudo iptables -P OUTPUT  ACCEPT

# do the same for IPv6
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P INPUT   DROP
sudo ip6tables -P OUTPUT  ACCEPT

# Allow established connections (the responses to our outgoing traffic)
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow IPv6 established connections (the responses to our outgoing traffic)
sudo ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow local programs that use loopback (Unix sockets)
sudo iptables -A INPUT -s 127.0.0.0/8 -d 127.0.0.0/8 -i lo -j ACCEPT

# allow incoming SSH/SCP conections to this machine from 192.168.1.0/24 only
sudo iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 22 -m state --state NEW -j ACCEPT

# In case a Windows drive is mapped, uncomment this line:
# sudo iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 445 -m state --state NEW -j ACCEPT

# pi-hole ports
iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT
iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT
iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT
iptables -I INPUT 1 -p tcp -m tcp --dport 67 -j ACCEPT
iptables -I INPUT 1 -p udp -m udp --dport 67 -j ACCEPT
iptables -I INPUT 1 -p tcp -m tcp --dport 4711 -i lo -j ACCEPT

# save iptables to be in place after a reboot
sudo /sbin/iptables-save > ~/iptables.txt
sudo cp ~/iptables.txt /etc/iptables/rules
iptables-persistent
iptables --list


To make the IP Table changes stick, I chose iptables-persistent

See also: Linux Iptables: How to specify a range of IP addresses or ports.

If your pi-hole sits on a different network then all traffic it "sees" will appear to come from a single router IP address.

Some people may be interested in unbound recursive DNS server solution (see also this how-to). I didn't initially have much luck with it, and in fact I later saw my first system crash on a different Raspberry Pi within 24 hours of installing it.

Once everything is setup, there are MANY more lists to make the pi-hole even better. Thanks Jermal Smith for suggestions on other lists to block from his pi-hole blog.

For more information on manually adding domains, see the pihole command. Local lists can be appended by using the FILE:// syntax, although the data in that file does not seem to be successfully added to block list. No warning or other message is given:


file://c:/download/pi-hole/my_block_list.txt

Yet after refresh, the entry is not in the block domain list search:


So perhaps a GitHub gist is a better home for custom lists.

To add a large list of lists, such as this one from Jermal Smith, simply add them to the respective /etc/pihole/adlists.list on the pi-hole. Be sure to include only URL's and not the title text in the first line.

Some essentials that I needed to whitelist:


aka.ms - used for Visual Studio updates


A final fine-tuning: See the Raspberry Pi Power Requirements. It's a shame to waste standby power for things not being used. Note that the Raspberry Pi 3 Model B+ I chose typically uses 500mA. In contrast, the Raspberry Pi 2 uses only 350mA, Part of the difference is the on-board WiFi and Bluetooth capabilities.

These can be manually turned off:


sudo iwconfig wlan0 power off
This can be added to /etc/rc.local. Also consider editing /etc/modprobe.d/raspi-blacklist.conf as noted in the forum topic:


#wifi
blacklist brcmfmac
blacklist brcmutil
#bt
blacklist btbcm
blacklist hci_uart

or

# ref: https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=138610
# bluetooth
blacklist hci_uart
blacklist btbcm
blacklist btintel
blacklist rfcom
blacklist btqca
blacklist btsdio
blacklist bluetooth


And edit the /boot/config.txt:


dtoverlay=pi3-disable-wifi
dtoverlay=pi3-disable-bt


Note the 1000BaseT has a higher power requirement.

My Raspberry Pi operates at about 50°C; (specs qualified from -40°C to 85°C)

For more information, see the pi-hole discourse.

Sunday, February 16, 2020

C# on the Radiona ULX3S using the nanoFramework dotnet CLR

It has been over a year since I first learned about the Radiona ULX3S development board. This is the perfect "crossover" board that has appeal for developers interested in electronics, FPGA development, and even just regular software development. With both an Espressif ESP32 and the Lattice ECP5, along with a ton of peripherals and GPIO pins - there's something for everyone here.

Radiona ULX3S from https://radiona.org/ulx3s/
The ULX3S is coming soon to CrowdSupply!

If you just want to get started with C#, see my notes here or visit docs.nanoframework.net.

A lot has changed since I first heard the name Espressif way back in 2015 and ordered this thing called a "Diymall® Esp8266 Esp-12e Serial Wifi Wireless Transceiver Module for Arduino UNO 2560 R3 Nano" from Amazon. Ooph. What a name.






And what an ordeal it was to get that device going... I remember the convoluted process of manually holding some GPIO pin low while it powered up, and then releasing another, all with level-shifting the 5V USB-TTL on a rats-nest breadboard of jumper wires.  The "excitement' was seeing the old modem-style "AT" command prompt. Admittedly, it was pretty cool. How really surreal that was - to see the Hayes-like AT prompt for a WiFi device.

ESP Boot Modes from the ESP8266 WiKi

Indeed it seemed interesting to have modem-like commands available in such a small and inexpensive package (who knows what it would take to actually send and receive WiFi packets with AT commands!). The documentation was pretty bleak and ridiculously incomplete at the time.

Fast forward just 5 years (that's 103 in technology years) - and Espressif continues to amaze with awesome documentation and ridiculously inexpensive hardware. And the languages! I've not seen an AT prompt on any of my devices for a long time. Starting with Lua on the ESP8266, the tech community soon realized that other languages could be implemented on the Espressif hardware. Of  course, C/C++ was the first and most obvious choice for embedded devices. But then something really crazy came along: MicroPython on the inexpensive ESP8266 WiFi device. Just a few months after seeing that first AT prompt, I backed the MicroPython on the ESP8266 crowd funding project. One thing led to another and now there are many languages available, and of course the next gen ESP32 hardware platform. It was when looking at the languages on the ESP32 Wikipedia page, of all places - that I learned about a truly amazing language capability: C# on an embedded device!

I've been programming C/C++ on and off over the course of many years. At the Day Job however, my focus these days is on C# in the Visual Studio environment. That's where I am probably most comfortable and the most productive. For programming "Arduino Style" C/C++ on embedded devices, my go-to tool has been the Visual Micro add-in extension for Visual Studio. With hundreds of thousands of downloads and a solid 5 star rating, I'm clearly not alone in liking Visual Micro. I've mentioned them numerous times in prior blogs, such as this one for the ULX3S.



If you've never used the Visual Micro extension, I highly recommend giving it a try.

Back to the topic of this blog. C#. On. an. embedded. device. WOW! As mentioned above, I originally learned of this capability on the Wikipedia page. There's also a reference on the Espressif web site with a relatively simple name of "nanoFramework":

"Developers can harness the powerful and familiar Microsoft Visual Studio IDE and their .NETC# knowledge to quickly write code without having to worry about the low-level hardware intricacies of a microcontroller. Desktop .NET developers will feel “at home” and are able to use their skills in embedded systems development, enlarging the pool of qualified embedded developers. It includes a reduced version of the .NET Common Language Runtime (CLR) and features a subset of the .NET base class libraries along with the most common APIs included in the Universal Windows Platform (UWP) allowing code reuse from desktop applications, IoT Core applications, thousands of code examples and open source projects. Using Microsoft Visual Studio, a developer can deploy and debug the code directly on real hardware." --Espressif Systems
Ok, so C# on an embedded device is amazing in itself. If you've read some of my prior blogs, you've seen my interest in single-step JTAG debugging the Espressif chips. The ESP8266 in particular can be quite difficult, depending on the vendor. Still, even with JTAG working, I've never been able to get the real experience of full debugging as seen in Visual Studio with high-level language debugging, or even the Atmel Studio with the Atmel ICE for their chips.

Enter nanoFramework. This is one of the most amazing implementations I've seen in quite some time. They have the .Net CLR on an embedded device! Not just the CLR, but the fully integrated Visual Studio debugging, single-step, hover-text values and more. Getting started is pretty straightforward: first step of course is to install the nanoFramework extension from the Visual Studio Marketplace. Note there are two flavors: the nanoFramework for VS2017 and a preview nanoFramework for VS2019.

I do think their instructions are a bit overly complicated for the first time user. For one - compiling the entire framework is certainly not required to program C# on your device, yet it is featured front and center on the getting started page. I have some notes of my own on Programming the ULX3S ESP32 using C# in Visual Studio. Basically the firmware needs to be uploaded to the device, then Visual Studio needs to connect with Device Explorer.

I tweeted this video of single-step blinky in C# to show single step in action.

Beyond blinky, I was curious as to what else could be done with this framework. So how about a simple "Hello World" printed on the screen? Ha! What a twisty little passage all alike rabbit-hole that turned out to be.

First, I discovered there's currently no direct support of the SSD1331 SPI display that I am using with the ULX3S. However, there are plenty of Arduino-style libraries out there. Hm. Arduino libraries... ah yes, it is one thing to be able to write code, it is another issue altogether to actually be able to leverage the existing libraries for peripherals. All the open source code out there that allows the ESP32 to use Ardunio libraries has certainly helped the self-sustaining fire of maker interest and development. But what about C#?

I started out in the #Targets-ESP32 thread on the nanoFramework Discord. Yes, they need multiple threads for different platforms, as they also have C# working on STM32TI CC13x2, CC26x2, CC32xx, and NXP MIMXRT1060_EVAL boards! As my issue was not ESP32 specific, they kindly referred me to the #UI thread (UI meaning "display hardware", not the UI of the nanoFramework extension in Visual Studio). It is there that I learned a lot of great information from some clearly talented developers that very kindly and patiently answered my newbie questions.

I only recently started using Discord. I think I like gitter channels more, but both seem to be weak for linking to specific comments of interest. Part of this blog is for me to gather up the key tidbits all in one place. Be sure to check out the ULX3S gitter channel, too.

I learned quite a bit about the SSD1331 display during a exchange with emard in ULX3S issue #8. For the nanoFramework here I started with a known-working project, my ULX3S Visual Micro SSD1331 Display Example. In particular, the key pin numbers:

// working ULX3S  
#define oled_csn  17 // aka cs - chip select
#define oled_dc   16 // aka ds aka a0 -  SPI data or command selector pin
#define oled_resn 25 // aka rst - reset
#define oled_mosi 15 // aka mosi - data
#define oled_clk  14 // aka sclk - clock
#define oled_miso -1 // 12 not used

from the FPGA perspective:

N2 to N3 for oled_csn /CS  to wifi_gpio17 (GPIO17)
P1 to L1 for oled_dc /DC   to wifi_gpio16 (GPIO16)
P2 to E3 for oled_resn/RES to wifi_gpio25 (GPIO25)
P3 to J1 for oled_mosi/SDA to sd_cmd      (GPIO15)
P4 to H2 for oled_clk /SCL to sd_clk      (GPIO14)

See also this code on the espressif/arduino-esp32 repo for VSPI and HSPI initialization and pins:


void setup() {
  //initialise two instances of the SPIClass attached to VSPI and HSPI respectively
  vspi = new SPIClass(VSPI);
  hspi = new SPIClass(HSPI);
  
  //clock miso mosi ss

  //initialise vspi with default pins
  //SCLK = 18, MISO = 19, MOSI = 23, SS = 5
  vspi->begin();
  //alternatively route through GPIO pins of your choice
  //hspi->begin(0, 2, 4, 33); //SCLK, MISO, MOSI, SS
  
  //initialise hspi with default pins
  //SCLK = 14, MISO = 12, MOSI = 13, SS = 15
  hspi->begin(); 
  //alternatively route through GPIO pins
  //hspi->begin(25, 26, 27, 32); //SCLK, MISO, MOSI, SS

  //set up slave select pins as outputs as the Arduino API
  //doesn't handle automatically pulling SS low
  pinMode(5, OUTPUT); //VSPI SS
  pinMode(15, OUTPUT); //HSPI SS

}

I tried to use the existing SPI capabilities of the nanoFramework, but I was unable to get the display to cooperate. Nothing happened. But why?

This is where more serious hardware debugging tools are needed. One method I used in the past to debug some serial port communication problems - involved a Digital Storage Oscilloscope (in my case, a Rigol DS1054z). Even though that one is relatively small as oscilloscopes go, it was at my workbench and I was working at the kitchen table (and ok, the workbench needs to be cleaned up an organized). Not only inconvenient, but also perhaps not the most practical (power cord, size, etc). Although my serial decoding was an interesting exercise on the oscilloscope - what I really needed is a logic analyzer.

It was well over 5 years ago that I first started using the Saleae logic analyzer software. The cool thing here is that it is a computer-based debugging tool. Whereas the oscilloscope is an oscilloscope all on its own, complete with its own power cord and display - the Saleae is just a USB peripheral. It doesn't even have a wall-wart power supply: just a USB connection and logic probes.

Sure enough - after a ridiculously long time trying to figure out the display problem with software, the logic analyzer instead made the problem abundantly obvious: nothing was happening as the D/C (data command) pin. I was hoping that somehow the SPI driver would have just "known" how to control the bus for the display during NativeInit. Silly me. Of course not. Nope.



I opened this issue to implement more flexible SPI pin definitions, in particular something to manage that D/C pin. @AdrianSoundy responded with:
"The DC and Reset pins are not part of the SPI bus. They are specific to the displays. You have to create separate gpio pins for those functions."
Hm. Well, yes, I guess that is technically true. Still, I wanted to see if I could get my SPI  display to work in C#, so I took the advice and wrote my own DC-pin-controlling code to manage the data/control line, and let the native drivers handle the actual data transfer.

I created this nanoFramework SSD1331 example to manually bit-bang the SPI bus. As of the date of this blog, there's not a lot there. I leveraged the Adafruit SSD1331 C++ library to create my own limited C# OLED SSD1331 class library. I also created another class library project, this one an Arduino-style nanoFramework "pins" helper to allow the use of the familiar pinMode and digitalWrite functions when converting the Adafruit library to C#.

It is a bit of a mess at the moment, but it does have the capability of initializing the display and poking some pixels into place. It doesn't sound like much - but again, doing this from C# in Visual Studio is what made it really quite cool. I had this little victory tweet at the end of that weekend:


Drawing a line is one thing... drawing it quickly, well that's another. After the excitement had warn off (ok, it was just a blue line)... I realized just how slowly the pixels were being drawn. Why? Did I mention the little twisty passages? Yes, this rabbit hole goes much deeper.

To start, let's go back to the known-working SPI display example. See also this blog where I go into more detail with that example.

Again, there's nothing like a logic analyzer to see what's going on with the voltage levels of ones and zeros on the wires. Looking at the source code, the SPI bus for the display gets interesting at startup time, or specifically during Adafruit_SSD1331::begin - where initialization bytes are sent to the display:


void Adafruit_SSD1331::begin(uint32_t freq) {
    initSPI(freq, SPI_MODE0);

    // Initialization Sequence
    sendCommand(SSD1331_CMD_DISPLAYOFF);   // 0xAE
    sendCommand(SSD1331_CMD_SETREMAP);          // 0xA0
#if defined SSD1331_COLORORDER_RGB
    sendCommand(0x72);    // RGB Color
#else
    sendCommand(0x76);    // BGR Color
#endif
    sendCommand(SSD1331_CMD_STARTLINE);  // 0xA1
    sendCommand(0x0);
    sendCommand(SSD1331_CMD_DISPLAYOFFSET);  // 0xA2
    sendCommand(0x0);
    sendCommand(SSD1331_CMD_NORMALDISPLAY);   // 0xA4
    sendCommand(SSD1331_CMD_SETMULTIPLEX);  // 0xA8
    sendCommand(0x3F);     // 0x3F 1/64 duty
    sendCommand(SSD1331_CMD_SETMASTER);   // 0xAD
    sendCommand(0x8E);
    sendCommand(SSD1331_CMD_POWERMODE);   // 0xB0
    sendCommand(0x0B);
    sendCommand(SSD1331_CMD_PRECHARGE);   // 0xB1

...etc


This is where I had a chance to try out my new Saleae logic analyzer. It's funny how once you buy something, you start noticing other people that have that same "thing". I noticed that @GregDavill had posted something on Twitter with his Saleae - and so I commented on it. His reply was crazy! I don't think I'll be using mine as a coaster anytime soon LOL. So here's my new Saleae, sans mug, hooked up to my Radiona ULX3S:

ULX3S with SSD1331 display on a breadboard with Saleae Logic Analyzer
In order to most easily connect the logic analyzer, I disconnected the display from the main ULX3S board and plugged it into the breadboard. Here, I would have preferred that the Saleae had male connector pins instead of female, but a few short M-M jumpers to the rescue.

I did buy my own Dupont crimping too la few years back, so I will probably make my own Saleae male-pin probe set for breadboards. The only think needed is some wire and a Dupont header kit such as this one on ebay.

So back to the setup: Not completely intuitive is the trigger and decoding of the SPI signals. The first thing is to setup the start of capture based on the falling edge of the clock:


The next is to tell the Saleae software *which* pins are being used for decoding. First select the SPI decoder on the right side of the app:


Next, click on the little gear and "edit settings" to select which channels are which SPI pins:


The good thing is that if you don't need to change protocols, the software remembers this even after uninstalling and re-installing new software. So until you change debugging projects, the setup of the logic analyzer stays consistent.

From a software perspective, we just simply expect the bytes to be sent. But to actually see what's going on, the logic analyzer is indispensable:


Here we can see the same hex codes in the Salaea decoded from the logic analyzer probes as seen in the code segment, above. Setup of the decoder in the software was also vastly easier than the serial decoder mentioned above on my oscilloscope. The first byte sent on the SPI bus is 0xAE, the command turn turn the display off during initialization. How cool is that?

Although the Salaea software is not implemented with the proper Windows UI (banner toolbar across the top, File-Save, Help-About, etc)... for the most part it is quite easy to use and intuitive. One of the less-obvious features is that you can double click on the decoded values in the lower right, and the display auto-scrolls and auto-scales to the trace for that value. Very nice.

Once I had a benchmark of sending a byte of SPI data in under 8 micro-seconds, let's see what happens when manually bit-banging the pins from C#. Here's the code to send an SPI byte:


        private void spiWrite(byte b)
        {
            for (byte bit = 0; bit < 8; bit++)
            {
                if ((b & 0x80) > 0)
                {
                    SPI_MOSI_HIGH();
                }
                else
                {
                    SPI_MOSI_LOW();
                }
                SPI_SCK_HIGH();
                b <<= 1;
                SPI_SCK_LOW();
            }
        }


This is pretty straightforward and almost identical to the Adafruit C/C++ code in their SPITFT library (minus the hardware-specific conditional compile directives) that I based my C# code on. See also the spiWrite() in esp32-hal-spi.c.

Next, I tried to use the built-in nanoFramework SPI driver, but manually control the D/C line.


private void WriteCommand(byte command)
        {
            _buffer1[0] = command;

            // the time between the next two statements is a whopping 0.3ms
            _DS.Write(GpioPinValue.Low);
            _DS.Write(GpioPinValue.High);

            // here we manually bring DS low
            _DS.Write(GpioPinValue.Low);
            _spi.Write(_buffer1); // all 8 bits in about 54uS
            _DS.Write(GpioPinValue.High);
        }


Here's what it looks like on the logic analyzer:


Notice in particular the vastly longer delay in switching the D/C line as compared to the bits sent during the native _spi.Write(). There's nearly three-quarters of a millisecond window for the D/C line, but only about 54 microseconds to send the entire 8 bits of data:


When I posted this information on the Discord channel, @terryfogg suggested that I review a similar example being worked on. From what I could tell, it was VSPI with a different set of pins and also confirmed that C# is just too slow for this kind of, so instead this was using native code. Ah yes, the twisty little passage - the Rabbit hole goes on to another whole level: calling native code from C# on an embedded device.

The next level of complexity? Interop of course. To get started, there's a pretty good tutorial on Interop in the .Net nanoFramework. Yes, interop of course pretty much screws the entire concept of managed code, but with the benefit of vastly better performance. Hm. See the links at the bottom of this page for more information on calling unmanaged code from C#. I think that's probably a whole topic in and of itself. Stay tuned...


Full disclosure: I purchased the Saleae Logic Pro 16 at a substantial discount since I (sadly) don't do this fun stuff for a living. Saleae has a "Electronics Enthusiasts (non-commercial use)" discount. My primary motivation was the USB3 sampling speeds of up to 125MHz to be used as I dig deeper into the ULX3S FPGA capabilities. The discount of course made all the difference for my hobbyist budget. In fact, there's an "Enthusiast Discount Extreme Edition" that I hope to qualify for with this blog entry.

I purchased the ULX3S early last year from Goran after reading about it on Hackaday and sending an email. Although I am listed as one of the members of the Radiona CrowdSupply team, I am not employed by Radiona, nor do I receive any financial compensation from them.

All opinions expressed here are my own and not representative of my employer.


Resources, Inspiration, Credits, and Other Links:

Find gojimmypi at gojimmypi.github.io

I'm currently working on my new blog home at  gojimmypi.github.io After implementing a variety of features such as dark mode , syntax hi...