PINE64::MCP23008 Module on CPAN

pinout ot the MCP23008 GPIO extender chip
pinout ot the MCP23008 GPIO extender chip

My latest module to upload to CPAN is a perl-based driver for the MCP23008 GPIO extender. This chip is very handy when you have ran out of GPIO pins on your single board computer and gives you 8 more digital I/O’s. This has come in handy for me on several occasions when I used an LCD for a project that took too many of my digital pins.

The driver works as you would expect: you can make any combination of inputs or outputs, and read in the state of the inputs. This is an i2c device that defaults to an address of 0x20. You can adjust the address by putting high / low values on the address lines A0 – A2 for a possible 8 different chips on a single i2c bus.

Below is a simple implementation of the module configured as an input and then as an output from the synopsis on CPAN ->

The methods basically use Device::I2C to manipulate the chip’s internal registers. Register 0x00 is the I/O direction register. This is an 8-bit register with each bit position representing a digital pin. Valid values are 0 – 255 where all pins default to outputs. So, to make pin 6 an input, you would call the set_direction() method like so:

set_direction(64);

The MCP23008 has internal 100K Ohm pull up resistors when pins are configured as inputs. This can save you room on your board by not having to build external pull up resistors yourself. This is configured in the GPPU (GPIO Pull-up resistor register) 0x06. The code below enables all 8 of the internal pull up resistors.

enable_pullup(255);

I also implemented the I/O polarity feature. With this enabled, the chip will give you the opposite polarity of the current state of the pin. The function call below reverses the polarity of pin 5.

set_polarity(32);

The main methods of the module are read_pin() and write_pin(). They work as you would expect. It reads / writes values to the GPIO port register 0x09.

$gpext->read_pin(4);
$gpext->write_pin(7, 1);

Here is a description of the GPIO port register straight out of the datasheet:

I didn’t implement all of the features of the MCP23008, but enough to make them useful for most projects. Here is the complete source code for the module:

     1	#!/usr/bin/perl 
     2	use strict;
     3	use Device::I2C;
     4	use IO::Handle;
     5	use Fcntl;
       
     6	package PINE64::MCP23008;
       
     7	our $VERSION = '0.9';
       
     8	#global vars
     9	my ($i2cbus, $addr, $gpext);
    10	my $gpregval = 0; 	#init gpio register value to 0
       
    11	my @pin_nums = (1,2,4,8,16,32,64,128);
       
    12	sub new{
    13		my $class = shift;
    14		my $self = bless {}, $class;
       
    15		#first arg is device address
    16		$addr = $_[0];
       
    17		#second arg i2c bus; optional
    18		$i2cbus = $_[1];
    19		
    20		if($i2cbus eq ''){
    21			$i2cbus = '/dev/i2c-0';
    22		}#end if
    23		
    24		$gpext = Device::I2C->new($i2cbus, "r+"); 
       
    25		#init i2c device
    26		$gpext->checkDevice($addr);
    27		$gpext->selectDevice($addr);
       
    28		#init gp register val to all off
    29		$gpregval = 0; 
       
    30		return $self;
    31	}#end new
       
    32	sub set_direction{
    33		#sets value of the IO direction register
    34		#ie 255 makes all input; 0 makes all output
       
    35		my $direction = $_[1];
    36		$gpext->writeByteData(0x00, $direction);	
    37	}#end set_direction
       
    38	sub enable_pullup{
    39		#when a pin is configured as an input
    40		#you can enable internal 100K pull-up
    41		#resistors by writing to the GPPU register
    42		#0-255 are valid values: 0 - all disabled
    43		#255 - all enabled
    44		
    45		my $en_gppu = $_[1];
    46		$gpext->writeByteData(0x06, $en_gppu);
    47	}#end enable_pullup
       
    48	sub set_polarity{
    49		#sets the polarity of the gpio pins; 
    50		#0 is normal polarity
    51		#255 is all pins reversed
    52		
    53		my $io_pol = $_[1];
    54		$gpext->writeByteData(0x01, $io_pol);
    55	}#end set_polarity
       
    56	sub write_pin{
    57		my $ind = $_[1];
    58		my $iox = $pin_nums[$ind];
       
    59		#1 or 0
    60		my $val = $_[2];
       
    61		if($val == 1){
    62			$gpregval+=$iox; 
    63		}#end if
    64		if($val == 0){
    65			$gpregval-=$iox;
    66		}#end if
       
    67		$gpext->writeByteData(0x09, $gpregval);
    68	}#end write_pin
       
    69	sub read_pin{
    70		my $ind = $_[1];
    71		my $iox = $pin_nums[$ind];
    72		my $pinval = 0; 
       
    73		#read GPIO register
    74		my $regval = $gpext->readByteData(0x09); 
       
    75		#ensure 8 binary places are displayed
    76		my $binout = sprintf("%08b", $regval);
       
    77		#parse eight binary digits into an array
    78		my @pinvals = split(//, $binout);
    79		
    80		#reverse array to match pin #'s
    81		@pinvals = reverse(@pinvals);
       
    82		#value of pin is index of $pinvals
    83		$pinval = $pinvals[$ind]; 
       
    84		return $pinval; 
    85	}#end read_pin
       
    86	1;
    87	__END__

PINE64::MCP300x CPAN Module

My MCP300x module on CPAN for the PINE64A+ boards

One of the drawbacks, in my opinion, of the raspberry pi and the pine64 A+ is the lack of any analog inputs. To read an analog signal requires an external analog to digital converter chip, and will tie up several of your GPIO lines. My new module has routines to read values on an MCP3004 or MCP3008 10-bit analog to digital converter.

Here is some sample code ->

use PINE64::MCP300x; 
my $adc = PINE64::MCP300x->new(10,12,11,13); 
#5 bits because the first is the start bit
my @ch0 = (1,1,0,0,0); 
for(my $s=0;$s<200;$s++){
my ($reading, $binval, $voltage ) = $adc->read300x(\@ch0, 50, 5.01);
$voltage = sprintf("%.3f", $voltage);
print "binval: $binval\tvoltage: $voltage vdc\n";
usleep(500000);}#end for

Here is the output of the script ->

PINE64::MAX7219

I have had another cpan module published! This one is a driver for a MAX7219 8-digit LED display on the PINE64 single board computer. The video above is a demo of some of the module’s capabilities. It is implemented as bit-banged SPI using the perl programming language.

A very easy to use display, it only uses 3 GPIO pins, and could be used to output the value of sensors, or simple menus for projects.

arduino based hvac lead-lag controller

arduino lead-lag hvac control
Installed lead-lag controller
arduino lead-lag hvac control
arduino lead-lag hvac control
arduino hvac lead-lag controller
lead-lag controller using arduino nano

My office is in a telecommunications facility full of routers, radios, fiber-optic equipment etc. Reliable environmental controls are essential as this equipment will fail in extreme heat. I have two 20,000 BTU AC units in my office that run on 250VAC. I created this lead-lag controller to turn on the lead AC unit based on the temperature setting, and if the lead unit has run for six minutes without cooling to the setting, turns on the lag unit as well until the temperature setting is reached.

arduino lead-lag hvac controller
arduino lead-lag hvac controller

I have the programming down and the device built and installed. Initial tests work as designed. I am using some 10A 250VAC power relays. With the unit running on high fan speed and the compressor going the AC units draw about 5 amps; well within the rage of the relays.

the lead / lag controller turns on/off the 250VAC outlets of the air conditioner units

Here is the code ->

// include the library code:
#include <LiquidCrystal.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "RTClib.h"
RTC_DS3231 rtc;

// Sensor input pin
#define DATA_PIN 2
// How many bits to use for temperature values: 9, 10, 11 or 12
#define SENSOR_RESOLUTION 9
// Index of sensors connected to data pin, default: 0
#define SENSOR_INDEX 0

OneWire oneWire(DATA_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress sensorDeviceAddress;

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 10, d5 = 9, d6 = 8, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
int sample_num = 0;

//thermostat setting; init to 72
int setting = 70; 

//heat or cool mode
char mode = 'C';

//relay pins
const int ac1Pin = 5;
const int ac2Pin = 4;
//const int heatPin = 6;

//control buttons
const int modePin = 3;
const int decrPin = 6;
const int incrPin = 0;

//AC ON/OFF flags
int ac1_f = 0;
int ac2_f = 0;
int heat_f = 0;

//on timers
int ac1_timer = 0;
int ac2_timer = 0;

//RTC stuff
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  Serial.begin(9600);

  //AC1 relay
  pinMode(ac1Pin, OUTPUT); 
  pinMode(ac2Pin, OUTPUT);
  //pinMode(heatPin, OUTPUT);

  //set AC1 off
  digitalWrite(ac1Pin, LOW);
  digitalWrite(ac2Pin, LOW);
  //digitalWrite(heatPin, LOW);    

  //control buttons
  pinMode(modePin, INPUT);
  pinMode(decrPin, INPUT);
  pinMode(incrPin, INPUT);

  sensors.begin();
  sensors.getAddress(sensorDeviceAddress, 0);
  sensors.setResolution(sensorDeviceAddress, SENSOR_RESOLUTION);  

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }  
}

  //array to hold 12 readings; 5-per min
  float t_readings[12];

  //sum of readings
  float sum = 0.0;

  //avg of readings
  float average = 0.0;

void loop() {
  
  // set the cursor to (0,0):
  lcd.setCursor(0, 0);

  //reset sum and avg on each iteration
  sum = 0;
  average = 0;

  //12 sample loop; takes 1 min
  for(int x=0;x<12; x++){
    lcd.setCursor(0, 0);
    sensors.requestTemperatures();
    // Measurement may take up to 750ms

    float temperatureInCelsius = sensors.getTempCByIndex(SENSOR_INDEX);
    float temperatureInFahrenheit = sensors.getTempFByIndex(SENSOR_INDEX);
  
    Serial.print("Temperature: ");
    Serial.print(temperatureInCelsius, 1);
    Serial.print(" Celsius, ");
    Serial.print(temperatureInFahrenheit, 1);
    Serial.print(" x: ");
    Serial.print( x );
    Serial.println(" Fahrenheit"); 

    //push reading into array
    t_readings[x] = temperatureInFahrenheit;
  
    lcd.print(temperatureInFahrenheit);
    lcd.print("F");
    //lcd.setCursor(0, 1);
    //lcd.print(sample_num);
    //sample_num++;

    if(digitalRead(modePin) == LOW){
      Serial.println("MODE BUTTON PRESSED! ENTERING SETUP....");
      setTemp();
    }//end if mode pressed

    //print setting to lcd row 2
    lcd.setCursor(0, 1);
    lcd.print("Setting: ");
    lcd.print(setting);    

    //print mode
    lcd.setCursor(15,0);
    lcd.print(mode);

    delay(5000);  //sleep 5 sec
  }//end for loop 12 sample

  //average samples
  for(int i=0; i<12; i++){
    sum = sum + t_readings[i];
  }//end for avg
    Serial.print("sum: ");
    Serial.println(sum);
    average = sum / 12;
    Serial.print("avg: ");
    Serial.println(average);

    //AC relay control
    if(average > setting && mode == 'C'){
      digitalWrite(ac1Pin, HIGH);

      //flag logic
      if(ac1_f == 0){ ac1_f = 1;}

      //timer logic
      ac1_timer += 60;    //60 because of the 12 samples/min cycle
      Serial.print("AC UNIT 1 ON ");
      Serial.print(ac1_timer);
      Serial.print(" seconds ");

      if(ac1_timer > 60){//turn on ac2 after 30 minutes
        if(ac2_f == 0){ 
          ac2_f = 1; 
          digitalWrite(ac2Pin, HIGH);
        }
        else{
          //increment ac2_timer
          ac2_timer += 60;
        }
        Serial.print("AC UNIT 2 ON ");
        Serial.print(ac2_timer);
        Serial.print(" seconds ");
      }//end if turn on AC2

      //poll RTC for time
      DateTime now = rtc.now();
   
      Serial.print(now.year(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.day(), DEC);
      Serial.print(" "); 
      Serial.print(" (");
      Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
      Serial.print(") ");
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.println(now.second(), DEC);
    }//end if
    if(average < setting && mode == 'C'){
      //turn AC1 OFF, reset timer & flags
      digitalWrite(ac1Pin, LOW);
      ac1_f = 0;
      ac1_timer = 0;
      Serial.print("AC UNIT 1 OFF ");

      //if on, turn off AC2
      if(ac2_f == 1){
          digitalWrite(ac2Pin, LOW);
          ac2_f = 0;
          ac2_timer = 0;
          Serial.print("AC UNIT 2 OFF ");
      }//end if
      
      //poll RTC for time
      DateTime now = rtc.now();
      
      Serial.print(now.year(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.day(), DEC);
      Serial.print(" "); 
      Serial.print(" (");
      Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
      Serial.print(") ");
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.println(now.second(), DEC);      
    }//end if
    
    delay(2000); 
}

void setTemp(){
  int su_flag = 1;
  int mode_flag = 1;
  Serial.println("SETUP MODE ->");
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set temp: ");
  lcd.print(setting);
  delay(1000);
  //lcd.clear();
  while(su_flag == 1){
    if(digitalRead(decrPin) == LOW){
      setting--;
      lcd.setCursor(10, 0);
      lcd.print(setting);
    }//end if exit setup
    if(digitalRead(incrPin) == LOW){
      setting++;
      lcd.setCursor(10, 0);
      lcd.print(setting);
    }//end increment temp
    if(digitalRead(modePin) == LOW){
      //exit setup
      su_flag = 0;
    }//end exit setup mode
    delay(250);
  }//end while
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set mode: ");
  lcd.print(mode);
  delay(1500);
  while(mode_flag == 1){
    if(digitalRead(decrPin) == LOW){
      mode = 'C';
      lcd.setCursor(10, 0);
      lcd.print(mode);
    }//end if
    if(digitalRead(incrPin) == LOW){
      mode = 'H';
      lcd.setCursor(10, 0);
      lcd.print(mode);
    }//end if heat mode
    if(digitalRead(modePin) == LOW){
      mode_flag = 0;
    }//end if exit mode set
    delay(250);
  }//end while set mode
  lcd.clear();
}//end setTemp function

perl interface to 18B20 1-wire temperature sensor

raspberry pi zero interfacing with 18B20 1-wire temperature sensor

I am pretty impressed with the responsiveness and accuracy of the 1-wire 18B20 temperature sensor. This is the first 1-wire sensor I have ever used. To get it going on the pi zero, I had to enable the 1-wire protocol via raspi-config, and edit the /boot/config.txt file to tell it what pin to use. Like gpio, 1-wire has a file system interface, which makes perl a great tool to interface to 1-wire devices.

Interestingly, each 1-wire device has a unique serial number, so it is possible to have literally thousands of 1-wire devices on a single bus; certainly far more than i2c allows. Here is the command line output of a manual reading from the 18B20:

file system interface on pi zero for a 1-wire 18B20 temperature sensor
file system interface on pi zero for a 1-wire 18B20 temperature sensor

The part of the output that says t=26500 is the temperature reading in celsius i.e. 26.500 or 79.7F.

Here is my perl code for interfacing with the 18B20 ->

#!/usr/bin/perl -w
use strict;

=pod
reads a 1wire 18B20
include this in your script, 
returns fahrenheit
=cut

#my $temp = read_18b20();
#print "temp: $temp\n";

sub read_18b20{
	`ls /sys/bus/w1/devices > 1w.txt`;
	open OW, "1w.txt" or die $!;
	my $temp_sensor = '';
	while(<OW>){
		if($_ =~ /(28-............)/){
			#print "serial number:  $1\n";
			$temp_sensor = $1;	
		}#end if
	}#end while
	close OW;

	my ($tempC, $tempF) = (0,0);
	`cat /sys/bus/w1/devices/$temp_sensor/w1_slave > 1w_reading.txt`;	
	open RD, "1w_reading.txt" or die $!;
	while(<RD>){
		if($_ =~ /t=(\d\d)(\d\d\d)/){
			$tempC = $1.".".$2;
			#print "celsius: $tempC\n";	
		}#end if
	}#end while
	close RD;
	$tempF = $tempC * (1.8) + 32;
	#print "farenheit: $tempF\n";
	
	return $tempF; 
}#end read_18b20

1;

Here is sample output from a script that samples the sensor every 2 seconds. I breathed on the 18B20 to demonstrate the responsiveness.

samples from a 1-wire 18B20 temperature sensor using perl interface
samples from a 1-wire 18B20 temperature sensor using perl interface

arduino servo example analysis

futaba s3003 arduino servo example script
futaba s3003 arduino servo example script

I admit I know little about pulse width modulation, and have limited experience with robotics or servos. I’ve had a futaba s3003 servo in my toolbox for some time, and done nothing with it. On a lazy day when I had some down time, I decided to get it out and do some experimentation.

I’ve been doing some labor intensive projects at work lately, and while hard work is very rewarding, it takes a lot of physical and mental energy and crowds out innovation. Just out of boredom, I burned the most basic servo script example provided by the arduino IDE into a nano clone I recently bought, and analyzed it with pulseview to better understand pwm.

close up of PWM controlling a futaba s3003 servo
close up of PWM controlling a futaba s3003 servo

Above is a trace of the output of the default arduino ide servo script with me tuning a 10K pot. In this example script, the position of the servo corresponds to the position of the potentiometer. Just at a glance, you can see pulses bunched up at different intervals. What I empirically observed is that when the servo was at position 1, the pulse width was about 0.55ms in duration at the standard 50Hz required by the servo. This pulse was constant, and the servo was stationary. If the pulse width changes at all, it changes position proportionally.

The futaba s3003 has 180 degrees of rotation. At position 3 (180 degrees) the constant pulse width is about 2.4msec. as you can see in the graphic below:

futaba s3003 servo at 180 degrees of rotation

This simple analysis has demystified pwm somewhat for me. Next, I hope to experiment with some pwm on the raspberry pi using perl.

rssi vs. relative humidity

receive signal strength indication vs. relative humidity data logger
receive signal strength indication vs. relative humidity data logger

I finally got around to deploying my rssi / temperature / relative humidity data logger and recorded nearly 6,000 samples of each metric over a five day period. This radio system operates in the ISM band, and is susceptible to propagation issues due to atmospheric conditions, especially humidity. The logic board provides a terminal that outputs a DC voltage that represents the rssi in dBm. My data logger uses a raspberry pi zero w, and reads the rssi voltage with an MCP3004 analog to digital converter using a bit-banged driver I wrote in perl. Each reading is timestamped using time derived from a DS3231 i2c real-time clock chip.

I use a HTU21 temperature / relative humidity sensor break out board from adafruit. This chip is MUCH MORE STABLE & RELIABLE than the DHT22 I was originally using.

temperature vs. relative humidity data capture
temperature vs. relative humidity data capture

For starters, here is the temperature in fahrenheit (red) vs. the relative humidity (green). Predictably, when the temperature rises, the relative humidity goes down and vice versa.

rssi level vs. relative humidity at 900MHz
rssi level vs. relative humidity at 900MHz

Now here is the relative humidity vs. the rssi. This radio is about 20 miles away from the transmitter over flat terrain with an output of 37dBm (5 watts), and has an average rssi of about -90dBm. You can see from the above graph that at about sample 4,000 when the relative humidity takes a noticeable dip that the rssi has a corresponding, albeit small, increase: precisely what we would expect to see.

Nothing surprising here. It just proves the effectiveness of my data logger device. It would be useful in situations where an RF path is going up and down due to atmospheric conditions or other obstructions, and real-time data would be useful in diagnosing problems and coming up with solutions like a higher gain antenna or increasing height.

ssh to a raspberry pi zero over usb

connecting to a rapsberry pi zero via usb
connecting to a raspsberry pi zero via usb

I am working on a data logging project based on the raspberry pi zero.  NOT a pi zero w,  just the pi zero.  Adding software packages to the pi zero is a little more difficult, because it has no wifi or ethernet connection.  I have been taking the sd card out ant putting it in a pi 3b+ to get packages that I need then putting it back in the pi zero.  I have tried and failed to use minicom and zmodem to transfer deb files over UART.

I found this great tutorial that explains how to ssh into a pi via the usb port on the pi.  I did everything it said and was still not able to connect to the pi.  I did an ifconfig on the pi, and saw that it had a APIPA on it’s usb network interface like so:

usb network interface on pi zero
usb network interface on pi zero

I had a usb network interface appear on my laptop, but no ipv4 address:

usb network interface on ubuntu machine
usb network interface on ubuntu machine

I finally got it to work by issuing an ifconfig command to my laptop’s usb interface and assigning an IP address in the same class B subnet :

ifconfig on usb network interface APIPA
ifconfig on usb network interface APIPA

Another benefit to a standard pi zero having ip connectivity via usb is that you can use piscope for troubleshooting.  Here is piscope examining i2c frames polling an HTU21D relative humidity sensor:

examining an HTU21D with piscope on a pi zero with usb network interface
examining an HTU21D with piscope on a pi zero with usb network interface

dht22 indoor use only

In my empirical observations experimenting with a dht22 temperature / humidity sensor,  I have come to the conclusion that they are most definitely for INDOOR USE ONLY!  Any time I placed one outside, they quickly max out at 99.9% humidity,  and don’t recover until I dry them out, and place them indoors.  I have ordered a HTU21D-F sensor to replace the dht22 in my rf propagation / humidity experiment.

The dht22 does work pretty well indoors.  I took over 14K samples over a 30 hr period and got clean results.

dht22 sensor temperature and humidity graph
dht22 sensor temperature and humidity graph

The temperature is in red and the humidity in green.  For most of my data logging projects using a pi, I sore the results in CSV format.  I used perl’s CSV database functions to query results and create graphs.  This is the sql query I use on the to csv tables to get the above graph:


select data.index, data.temp, temp_humidity.humidity from data outer join temp_humidity on data.index = temp_humidity.index

piscope is totally AWESOME with perl!

this is a screenshot of piscope running on my laptop analyzing the gpio lines of a raspberry pi over IP that is running my 24-port battery load test analyzer
this is a screenshot of piscope running on my laptop analyzing the gpio lines of a raspberry pi over IP that is running my 24-port battery load test analyzer program

I would assume many makers are familiar with using a logic analyzer in conjunction with sigrok + pulseview.  I love these resources.  They allow you to analyze precisely what is happening on your digital IO pins on whatever microcontroller you are using whether it be arduino, raspberry pi, etc.  They can also analyze signals at the protocol level such as i2c and are so inexpensive every maker should be equipped with these tools.

Pulseview SPI
Pulseview SPI scan

I do not like to reinvent the wheel in most cases.  I wanted to use some dht22 temp / humidity sensors on an RF signal strength project I am still working on.  As I have mentioned in earlier posts, I chose to use the RPi::PIGPIO::Device::DHT22 module on cpan to read my sensor.  This required the pigpiod daemon to be running on the raspberry pi.  I am very impressed with PIGPIO.  It allows you to very easily read / write to a raspberry pi ‘s GPIO lines over TCP/IP.  Just think of the possibilities.

The author of PIGPIO also offers an incredible logic analyzer for the raspberry pi called piscope.  I may never use a standard logic analyzer on a pi ever again.  You can invoke piscope on any linux computer once you have installed it to analyze the gpio on a remote pi.

invoking piscope to monitor the gpio lines on a raspberry pi
invoking piscope to monitor the gpio lines on a raspberry pi

After this, launch piscope:

run piscope logic analyzer
run piscope logic analyzer

I am just beginning to experiment with piscope, but so far it is very user friendly.  This is a trace of the SDA and SCL lines on the pi reading an MCP9808 temperature sensor.

piscope logic analyzer zoomed in on the i2c lines of an MCP9808 temperature sensor
piscope logic analyzer zoomed in on the i2c lines of an MCP9808 temperature sensor

This trace was taken over the net.  I didn’t have to get out my logic analyzer and connect any test leads.  Here is a trace of a poll and response from a dht22 sensor connected to gpio 24.

piscope logic analyzer reading a dht22 via perl
piscope logic analyzer reading a dht22 via perl

You can see it go low, then the sensor sends its reading, and goes high again.  pigpiod is definitely a resource hog, but that is hardly a consideration for my uses of the pi in my projects.  I will definitely be incorporating piscope into my future projects.

piscope by default uses port 8888 on the pi you are monitoring.  Out of curiosity,  I scanned the incoming frames with tcpdump.

analyzing piscope frames on port 8888 using tcpdump
analyzing piscope frames on port 8888 using tcpdump

It sends a lot of traffic over the network.

piscope network traffic on linux mint's system monitor
piscope network traffic on linux mint’s system monitor

Here is a video of me launching piscope, and live traffic from a pi 3B+.