Improved code for Toner Reset SP C250SF/DN

This is based on the Raspberry Pi implementation of the Toner chip reset:

https://gist.github.com/joeljacobs/c57550cdb4e68e3b86d6b89fb58f305d

I am using a Raspberry Pi Zero W so the chip is BCM2835 instead and I can use 100Kbps/400KBps instead of 9600 baud as in the original code

The electrical pins we need is clustered on to top left, Pins 1 (3.3V), 3 (I2C SDATA), 5 (I2C SCLK), 9 (Ground)

Raspberry Pi Zero GPIO Pinout, Specifications and Programming language

While looking for the pinouts (https://pinout.xyz/pinout/i2c), I discovered a useful tool called i2cdetect that allows me to find out the address of the chips which means I can write a program automatically figure out the right image to load to the chip without looking:

sudo apt-get install i2c-tools
sudo i2cdetect -y 1
Sorry I forgot where I got this image from.
Please remind me in the comments section if you find out who should I credit it to.

Since I don’t have cheap pogo pins lying around, I took the 2.4mm pitch (the standard size used in PC, Arduino and Raspberry Pi) jumper block I have (so all pins are set at equal lengths to make simultaneous contact) and hope somehow there’s 4 pins that kind of align with the contact, and it did. See pictures here:

Can press the pins down by using jumpers

You might be worried about shorting into the next pin or hooking something up in reverse damaging the chips, but luckily the chips survived. My guess is that it’s a good design to put the Vcc next to Ground on one side instead of making it symmetric so the polarity can be reversed. When reversed, SCL is set to low (Ground), SDA is pulled up to Vcc while there is no power supply, so no damage is done. Brilliant! The worst case for my poorly aligned jumper block is that SDA and Vcc might touch each other, but it doesn’t matter because it’s a perfectly legal hookup (just not communicating)!

So no worries if you didn’t touch the pins right! The only case it might go wrong is if you intentionally flip the block and slide it by two pins (reversing Vcc and Ground). Other cases (you are likely going to run into) are pretty much data lines getting hooked high or low levels while power lines not getting any supplies.

I’ve designed the program that it’ll detect the chip if you hook it up right and immediately program the chip (takes only a second), so you don’t have to hold the jumper for too long to worry about unstable contacts.

#!/bin/bash

# This program detects rewrite the toner chips to "full" for a Ricoh SP C250SF/DN Printer using Raspberry PI (defaults to BCM2835 models such as Raspberry PI Zero)

# The chip data is in file named "black" "cyan" "magenta" and "yellow".
# The pad closest to the edge is GND (-> Pin 9), followed by VCC (-> Pin 1) , DATA (-> Pin 3), and Clock (-> Pin 5).

# Be sure i2c is enabled and installed (it's turned off by default) on Raspbian

# This line is disabled because it takes too long to unregister i2c_bcm2835 to start from a clean slate
# modprobe -r i2c_bcm2835

# This program needs sudo permissions as it involves direct access to hardware
if [[ $EUID -ne 0 ]]; then
  echo "Need root permissions as this code involves direct hardware access. Try sudo"
  exit 2
fi

# Sets the baud rate
modprobe i2c_bcm2835 baudrate=400000

# Install i2cdetect if not exist (This idiom uses short-circuit OR for conditional exec)
command -v i2cdetect > /dev/null 2>&1 || {sudo apt-get install i2c-tools}

# Create I2C address to color map
COLORS=( [50]="yellow" [51]="magenta" [52]="cyan" [53]="black" )
# Detect chip I2C address
I2C_address=$( sudo i2cdetect -y 1 | grep 50 | sed -e 's/50: //;s/-- //g' )
# Keep the 0x5* address lines since only 0x50~0x53 is valid. Strip the 50: header, discard all "--" entries, and you are left with the detected address
HEX_I2C_address="0x$I2C_address"

# LED flash function(s)
target_device="/sys/class/leds/led0/brightness"
# This accounts for breaking changes (by renaming led0 to ACT) from Bookworm
[ -f ${target_device} ] || target_device="/sys/class/leds/ACT/brightness"

function turn_off {
  echo 0 > ${target_device}
}
function turn_on {
  echo 1 > ${target_device}
}
function toggle {
  # PI Zero only has 1 power LED which is on/off 
  # Reads 0 for OFF and 255 for ON
  readin=$(cat ${target_device})
  # Condition 255 to 1 to reduce platform dependency
  state=$[$readin>0]
  # Not equal to 1 is XOR (flip)
  flip=$[$state != 1]
  # Display output
  echo $flip > ${target_device}
}

function flash_once {
  period=${1:-0.5}

  toggle
  sleep $period

  toggle
  sleep $period
}

function flash {
  times=${1:-1}
  period=$2
  for((i=1; i<=times; i++)); do
    flash_once $period
  done
}

if [ -v COLORS[I2C_address] ]; then
  # Meat
  color=${COLORS[I2C_address]}
  echo "Detected toner chip for color: $color"

  echo "Short flashes before starting. Long flashes after done. Ctrl+C if you want to terminate get back to prompt earlier during long flashes."
  flash 5 0.1

   # "address" counter sync up with the hex code index in file
   printf "Writing"
   address=0;
   datafile="${BASH_SOURCE%/*}/${color}"
   for i in $(cat $datafile); do
     i2cset -y 1 ${HEX_I2C_address} $address $i;
     address=$(($address +1));
     printf .
     flash 1 0
   done
   echo "Done!"
  flash 10 0.5
else
  echo "Invalid I2C address for SP C250DN/SF toner chips. ${I2C_address}"
  exit 4
fi

Turns out Linux’s dot sourcing (bash calling) syntax is relative to your current folder, not the scripts’ folder. So if you have one script calling another in the same folder when you are not currently at the same folder as the script (e.g. startup automation), the relative paths will be wrong. So instead of ./ , you should use this prefix instead:

${BASH_SOURCE%/*}/

I chose to flash the board’s only LED light quickly before starting and blink slowly a few times after it’s done for visual clues. It’s entirely optional. Here’s the guts of the code without the fancy indicators:

#!/bin/bash

# Sets the baud rate
modprobe i2c_bcm2835 baudrate=400000

# Create I2C address to color map
COLORS=( [50]="yellow" [51]="magenta" [52]="cyan" [53]="black" )

# Detect chip I2C address
I2C_address=$( sudo i2cdetect -y 1 | grep 50 | sed -e 's/50: //;s/-- //g' )
HEX_I2C_address="0x$I2C_address"

if [ -v COLORS[I2C_address] ]; then
  # Meat
  color=${COLORS[I2C_address]}

  # "address" counter sync up with the hex code index in file
  address=0;
  for i in $(cat ${color}); do
    i2cset -y 1 ${HEX_I2C_address} $address $i;
    address=$(($address +1));
  done
else
  echo "Invalid I2C address for SP C250DN/SF toner chips: ${I2C_address}"
fi

Download the package. Run program_toner

Just in case if people are wondering. The L01 chip’s datasheet is here:


To make a dedicated Raspberry Pi Zero device to run this script (program_toner) repeatedly on boot, I made a script loop_program_toner that runs the same program as infinite loop. I added 5 seconds of slow flashes after a successful write. To prevent the program from retrying too fast (sucking up computing resources) when there’s nothing connected to it, I wait for 1 second between each retries.

#!/bin/bash

if [[ $EUID -ne 0 ]]; then
  echo "Needs root permissions. Try sudo"
  exit -2
fi

while :
do
  bash "${BASH_SOURCE%/*}/program_toner"
  if (($?));
  then
    sleep 1;
  fi
done

There are a few steps needs to auto make this automated:

  1. Have a user account dedicate to autorun the program on boot. In the example here, we’ll use Raspberry Pi’s default admin account
  2. Auto-logged on by running sudo raspi-config, select (1) System Option > (S5) Boot / Auto Login > (B2) Console Autologin
  3. Bless the said account with password free sudo rights: Run sudo visudo and add this to the end (replace admin with something else if you chose a different username)

admin ALL=(ALL) NOPASSWD:ALL
  1. Call loop_program_toner in ./profile (startup script). Can place this at the bottom if your package folder is ~/spc250dn :
sudo ./spc250dn/loop_program_toner

The path can start with . or ~ because you are starting with at home folder anyway.


EDIT (2023-11-1): Linux people lack the backward compatibility decency again! They changed /led0 and /led1 to /ACT and /PWR under /sys/class/leds and they don’t have the decency to put in a symbolic link so old cold won’t break! WTF!

Do they have an idea how many code they’ll break and how many man hours they will waste the world to discover this breaking change when updating the distro? Non-caught exceptions just fall through and shows up as mysterious error messages!

If you want people to get the memo and change their code to match a more sensible notation, feel free to put in a warning stub, but hell no to just flip a switch and watch the world scream! This is apparently not a difficult decision to make when adding backward compatibility would be a huge project because of some catch-22 conditions!

ARM: dts: Standardise on the upstream LED names · raspberrypi/linux@ea14f14 (github.com)

Raspberry Pi Zero only has one LED which is /led0 (/ACT), so ignore /led1 (/PWR).

Loading

Auto mount USB drives

Raspbian OS (Raspberry Pi) do not mount USB drives automatically out of the box.

I’m pretty annoyed by the lack of easy to use packages by 2021 and I still have to do it myself with the instructions here: https://github.com/avanc/mopidy-vintage/wiki/Automount-USB-sticks

These are cookbook instructions, but I’ll add some insights to what each component means so it’s easier to remember the steps.

At top level, to auto-detect and mount USB drives, we need the following components

  • udev: analogous to what happens behind device manager, it keeps track of and updates devices as they are connected and disconnected immediately. Need to register USB sticks by adding a event handler, which triggers a systemd service (see below)
  • systemd: analogous to Windows’ services. Need to register the service by stating what commands it will call on start (mostly mounting) and cleanup (mostly unmounting)
  • automount script: it’s a user defined script that abstracts most of the hard work detecting the partitions on the USB stick, assign the mount point names, and mount them
# Register udev event handler (rules)
# /etc/udev/rules.d/usbstick.rules

ACTION=="add", KERNEL=="sd[a-z][0-9]", TAG+="systemd", ENV{SYSTEMD_WANTS}="usbstick-handler@%k"

# It triggers a systemd call to "usbstick-handler@" service registered under /etc/systemd/system/
# Register systemd service
# /etc/systemd/system/usbstick-handler@.service
# (Note: instructions used /lib instead of /etc. It's better to add it as /etc as this is manually registered as user-defined service rather than from a package)

[Unit]
Description=Mount USB sticks
BindsTo=dev-%i.device
After=dev-%i.device

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/automount %I
ExecStop=/usr/bin/pumount /dev/%I

# %I is the USB stick's device name under /dev, usually sda
# Abstracted the logic of determining the mount point name and mounting to 'automount' (see below)

Create the file /usr/local/bin/automount and give it execution permission: chmod +x /usr/local/bin/automount

#!/bin/bash

# $1 (first argument) is usually "sda" (supposedly USB stick device name) seen from %I in the systemd service commands
PART=$1

# Within the "sda" (USB stick device of interest), extract the partition labels (if applicable) from lsblk command. The first column (name) is dropped
FS_LABEL=`lsblk -o name,label | grep ${PART} | awk '{print $2}'`

# Decide the mount point name {partition label}_{partition name}
# e.g. MS-DOS_sda1
tokens=($FS_LABEL)
tokens+=($PART)
MOUNT_LABEL=$(IFS='_'; echo "${tokens[*]}")
# Using string array makes it easier to drop the prefix if there's no {partition label}
# Bash use IFS to specify separators for listing all elements of the array

# Suggestion: drop --sync for faster USB access (if you can umount properly)
/usr/bin/pmount --umask 000 --noatime -w --sync /dev/${PART} /media/${MOUNT_LABEL}

This automount script is adapted from https://raspberrypi.stackexchange.com/questions/66169/auto-mount-usb-stick-on-plug-in-without-uuid with my improvements.

Loading

Get myself comfortable with Raspberry Pi

I2C is disabled by default. Use raspi-config to enable it. Editing config file /boot/config.txt directly might not work

Locale & Keyboard (105 keys) defaults to UK out of the box. Shift+3 “#” (hash) sign became “£” pound sign. Use raspi-config to change the keyboard.

It reads random garbage partitions for MFT assigned to FAT16 drives. Just use FAT32

USB drives does not automount by default. usbmount is messy as it creates dummy /media/usb[0-7] folders. Do this instead.

Loading

InfiniiMax AutoProbe 1 Caveats

For most mortal souls probing up to 12Ghz, Agilent’s integrated active probe system is called the the AutoProbe 1, which looks like this:

Regular full blown Windows-based Infiniium oscilloscopes takes any AutoProbe 1 probes (as long as the shape fits), but I noticed my DSO6104A (InfiniiVision 6000A series) do not take my 1152A (2.5Ghz) probes nor my fancy 1168A (10Ghz) and 1169A (12Ghz) probes.

Turns out the more compact, embedded (VxWorks) Agilent scopes that boots almost immediately. It’s called the InfiniiVision Series, which covers 1000 X-, 2000 X-, 3000A/T X-, 4000 X-, 6000 X-, 5000, 6000, and 7000 Series.

I’m not rich enough to get my hands on the X series, but I know from the architecture that 5000, 6000 and 7000 series are basically the same scope. 5000 and 6000 series looks almost identical while the 7000 series adds a giant screen and a slightly different keypad layout (the BNC ports do not align with the channel buttons and dials).

Turns out the datasheets shows two caveats:

  • 100Mhz model uses different hardware. They don’t take Autoprobe interface as there’s absolutely no reason why you need an active probe to get 100Mhz single ended. Agilent skipped the hardware for it (thus the autoprobe pins) altogether although they kept the recessed space reserved for Autoprobe so they don’t have to mold a different front bezel just for the 100Mhz models.
  • They basically take only Generation I AutoProbe I, namely the 1130 series
  • Gen 0 (not an official name) AutoProbe 1 does not work: 1152A (2.5Ghz single ended) for 54845A. These differential probes: 1153A, 1154A, 1155A, 1159A are also considered too old. They were intended to work with old Infiniiums such as 54845A
  • Gen 2 AutoProbe 1 (only 10Ghz 1168A/B and 12Ghz 1169A/B models) does not work. These embedded scopes usually max out at 1.5Ghz, with the exception that 6000X goes up to 6Ghz, which is still way below 10Ghz
  • N2800 series are Autoprobe I, but it’s Gen III (has a bigger butt extending away from the AutoProbe I hole), so it doesn’t work
  • The rest are Autoprobe II and III that’s beyond our mortal souls (and way out of the league of InfiniiVision scopes)
https://www.keysight.com/us/en/assets/7018-06782/data-sheets/5968-7141.pdf

Loading