Io Expander

Hi all.
I need to read fan round/minuts using Nactua fan.
So I need to convert normal function to detect event on gpio
GPIO.add_event_detect(TACH, GPIO.FALLING, fell)
where
TACH = 24 # Fan’s tachometer output pin
PULSE = 2 # Noctua fans puts out two pluses per revolution
AND fell is function to detect rounds

def fell(n):
global t
global rpm

dt = time.time() - t
if dt < 0.005: return # Reject spuriously short pulses

freq = 1 / dt
rpm = (freq / PULSE) * 60
t = time.time()

Last for detect rounds
try:
while True:
print “%.f RPM” % rpm

Can anyone help me if possibile ?
regards

The “Preformatted Text” option </> will do code tags for you when posting code.
Just a FYI post, it retains indents etc and makes reading posted code a lot easier.
I have that breakout, haven’t tried what your attempting though.
I will have a good look at your code once you repost it.

Hi,
many thanks for your indication, so I repost the code.

The principal function that I need to convert in python for use ioexpander is

GPIO.add_event_detect(TACH, GPIO.FALLING, fell)

You can find the call in the code below, I send you all code of my file.
The code is necessary to read RPM of the fan Nactua. All code is possibile to convert using ioexpander but I don’t know how to convert the event detect.

Thanks
Herbert

import RPi.GPIO as GPIO
import time

# Pin configuration
TACH = 24       # Fan's tachometer output pin
PULSE = 2       # Noctua fans puts out two pluses per revolution
WAIT_TIME = 1   # [s] Time to wait between each refresh

# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(TACH, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Pull up to 3.3V

# Setup variables
t = time.time()
rpm = 0

# Caculate pulse frequency and RPM
def fell(n):
    global t
    global rpm

    dt = time.time() - t
    if dt < 0.005: return # Reject spuriously short pulses

    freq = 1 / dt
    rpm = (freq / PULSE) * 60
    t = time.time()

# Add event to detect
GPIO.add_event_detect(TACH, GPIO.FALLING, fell)

try:
    while True:
        print "%.f RPM" % rpm
        rpm = 0
        time.sleep(1)   # Detect every second

except KeyboardInterrupt: # trap a CTRL+C keyboard interrupt
    GPIO.cleanup() # resets all GPIO ports used by this function

Ok, looks like I miss read that first post. Sorry about that.
What I have is this.
IO Expander Breakout (pimoroni.com)

And, even so, I got lost in your code. Not your fault, its just above my skill level python wise.

Ok, thanks.
The link of Pimoroni explains many features but I can’t find the listening activation on a pin:

GPIO.add_event_detect(pin_input, GPIO.FALLING, function_to_call)

Need to know only this step, if exists.
Herbert

What Expander are you using?
And can you post a link to the directions your following?

What exactly is the question? Doesn’t the code work? If not, then what do you get?

Hi to all,
thanks for your help.
I use Pimoroni Ioexpander breakout, for new users only two link is possible to post so nothing link for Pimoroni.

I need to expand pwm pin and gpio of my raspberry. I use a fan to cool down the CPU in particular Noctua 5v.
I use this guide to create service for check temperature and regulate the fan speed. In secondary I add the service that check the RPM fan. I convert and adapt the python code for use ioexpander pin with this fan Use PWM to Control Fan Speed. For this service is all ok.

The second service check and echo the RPM of fan code on github
In this service/code for convert to use ioexpander the problem is

# Add event to detect
GPIO.add_event_detect(TACH, GPIO.FALLING, fell)

This istruction normal using with GPIO, I not understand how to convert using IOexpander python library.
So now I hope everything is clearer.
Regards.
Herbert

Ok, I was wondering why there wasn’t any IO Expander code in your Python file?
Thats what made me confirm what you were using.

I can kind of see what your trying to do. Not sure I can help though? I will have a look see when I get a chance.

Thanks,
for your clarification I post the first code that set speed of fan based on cpu temperature convertet with ioexpander:

#!/usr/bin/env python3
import RPi.GPIO as GPIO
import time
import signal
import sys
import os

import ioexpander as io

import MySQLdb

# Configuration
FAN_PIN = 1            # BCM pin used to drive PWM fan

WAIT_TIME = 10           # [s] Time to wait between each refresh

PWM_FREQ = 25           # [kHz] 25kHz for Noctua PWM control

# Configurable temperature and fan speed
MIN_TEMP = 50
MAX_TEMP = 60
ISTERESI_DOWN = 1
ISTERESI_UP = 1
FAN_LOW = 100
FAN_HIGH = 255
FAN_OFF = 0
FAN_MAX = 255
FAN_MID = FAN_OFF / 2

speed= 0
step=0
# Get CPU's temperature
def getCpuTemperature():
	res = os.popen('vcgencmd measure_temp').readline()
	temp =(res.replace("temp=","").replace("'C\n",""))
	print("temp is {0}".format(temp)) # Uncomment for testing
	return temp

# Set fan speed
def setFanSpeed(vel):
	global speed
	#fan.start(speed)
	ioe.output(FAN_PIN, int(vel))
	speed=vel
	return()

# Handle fan speed
def handleFanSpeed():
	
	temp = float(getCpuTemperature())
	# Turn off the fan if temperature is below MIN_TEMP
	if temp < MIN_TEMP - ISTERESI_DOWN:
		setFanSpeed(FAN_OFF)
		print("Fan OFF") # Uncomment for testing
	# Set fan speed to MAXIMUM if the temperature is above MAX_TEMP
	elif temp > MAX_TEMP:
		setFanSpeed(FAN_MAX)
		print("Fan MAX") # Uncomment for testing
	# Caculate dynamic fan speed
	else:
		if temp > MIN_TEMP + ISTERESI_UP:
			step = (FAN_HIGH - FAN_LOW)/(MAX_TEMP - MIN_TEMP)   
			temp -= MIN_TEMP
			setFanSpeed(FAN_LOW + ( round(temp) * step ))
			print("Fan Load : {} Fan is {}%".format(FAN_LOW + ( round(temp) * step ),round((speed / int(FAN_MAX) * 100),1) )) # Uncomment for testing
		else:
			#temp -= MIN_TEMP
			print("Speed mantain : {} fan is {}%".format(speed,round((speed / int(FAN_MAX) * 100),1)))
	return ()

try:

    # Setup GPIO pin
	BRIGHTNESS = 1                # Effectively the maximum fraction of the period that the LED will be on
	PERIOD = int(255 / BRIGHTNESS)  # Add a period large enough to get 0-255 steps at the desired brightness

	ioe = io.IOE(i2c_addr=0x18)

	ioe.set_pwm_period(PERIOD)
	ioe.set_pwm_control(divider=1)  # PWM as fast as we can to avoid LED flicker

	ioe.set_mode(FAN_PIN, io.PWM,invert=False)
    
	setFanSpeed(FAN_OFF)
	# Handle fan speed every WAIT_TIME sec
	while True:
		handleFanSpeed()
		
		time.sleep(WAIT_TIME)

except KeyboardInterrupt: # trap a CTRL+C keyboard interrupt
	setFanSpeed(FAN_MID)
	#GPIO.cleanup() # resets all GPIO ports used by this function

The second code, in link to another post, is dedicated for check RPM.
The istruction GPIO.add_event_detect need to convert using IOExpander.
Herbert

Hello,
I’m have the same issue.
The script to manage fan speed works correctly but I can’t retrieve the tach info with the script.
I try to found the equivalent of the function GPIO.add_event_detect() but without success still have 0 RPM.

When I look in the function code ioe.on_interrupt, it call _gpio.add_event_detect() so I think it’s the right function.

I look at the exemple weather.py which use interrupt and I write this script but without success it doesn’t enter the callback function.

#!/usr/bin/env python3
import time
import ioexpander as io
import RPi.GPIO as GPIO

# Configuration
TACH_PIN = 2  # Tach pin used to control speed fan
WAIT_TIME = 1.0  # [s] Time to wait between each refresh
PULSE = 2  # Noctua fans puts out two pluses per revolution

# Setup variables
t = time.time()
rpm = 0

ioe = io.IOE(i2c_addr=0x18, interrupt_pin=TACH_PIN)
ioe._gpio.setup(ioe._interrupt_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
ioe.set_pin_interrupt(TACH_PIN, True)


def fell():
    global t
    global rpm
    print('FELL ON')
    dt = time.time() - t
    if dt < 0.005:
        return  # Reject spuriously short pulses
    freq = 1 / dt
    rpm = (freq / PULSE) * 60
    t = time.time()
    ioe.clear_interrupt()


ioe.on_interrupt(fell)
ioe.clear_interrupt()

try:
    while True:
        print("%.f RPM" % rpm)
        time.sleep(WAIT_TIME)

except KeyboardInterrupt:  # trap a CTRL+C keyboard interrupt
    print('end script')

I’m a developper but not in python maybe a step by step debug will be usefull ?
I don’t know if it’s an issue with the code or with connections. I plug the INT pin of the IO Expander to the INT pin of the HyperPixel 4.0 pin. Maybe there is a things to do, special configuration to passtrough interruptions to the raspberry ?

I made a step forward on the resolution =)

I try the script without the HyperPixel 4.0. The IO expander is plug into pin 1,3,5,7 and 9.
I modify the init section of the script :

ioe = io.IOE(i2c_addr=0x18, interrupt_pin=4)
ioe._gpio.setwarnings(False)
ioe._gpio.setup(ioe._interrupt_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
ioe.set_pin_interrupt(TACH_PIN, True)

And it’s work !
I have a little issue about this part of the callback :

    if dt < 0.005:
       return  # Reject spuriously short pulses

With it the script stop working if the fan is too quick, without it works but I’m not sure of the fiability of the calculate value. Maybe it can be improve ?

Now, I will try to found the right init section to work with the HyperPixel 4.0

Still looking for the right configuration.
I found on the web that normally the INT pin of the IO Expander is link to GPIO27 of the raspberryPi but according to this page : Hyperpixel4 at Raspberry Pi GPIO Pinout, GPIO27 is for touch interrupt.
So, I don’t know how to figure it out. Anyone can help me ?

I already sent an email directly to Pimoroni support but no answer yet.

Finally, I found solution for my need.

To retrieve the speed of a fan with the IO expander, you need to use a function wich can count number of interruptions it received.
All is in the weather example script → https://github.com/pimoroni/ioe-python/blob/master/examples/weather.py
In this script, there is a part on a anemometer which is the solution.
The new script :

import time
from threading import Lock
import RPi.GPIO as GPIO
import ioexpander as io

# Configuration
TACH_PIN = 2  # Tach pin on the IO Expander used to control tach
WAIT_TIME = 1.0  # [s] Time to wait between each refresh
PULSE = 2  # Noctua fans puts out two pluses per revolution

# Setup variables
rpm = 0
wind = 0
wind_overflows = 0
last_wind = 0
lock = Lock()

def handle_interrupt(stuff):
    global wind, last_wind, wind_overflows
    lock.acquire(blocking=True)
    ioe.clear_interrupt()
    new_wind, _ = ioe.read_switch_counter(TACH_PIN)
    if new_wind < last_wind:
        wind_overflows += 1
    last_wind = new_wind
    wind = (wind_overflows * 128) + new_wind

    lock.release()


ioe = io.IOE(i2c_addr=0x18, interrupt_pin=4)
ioe._gpio.setwarnings(False)
ioe._gpio.setup(ioe._interrupt_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
ioe.set_pin_interrupt(TACH_PIN, True)

ioe.on_interrupt(handle_interrupt)
ioe.clear_interrupt()

last_wind_counts = 0

while True:
  lock.acquire(blocking=True)
  wind_hz = (wind - last_wind_counts) / PULSE
  print(f"""
  Fan Speed: {wind} ({int(wind_hz * 60)} RPM)
  """)
  last_wind_counts = wind
  lock.release()
  time.sleep(WAIT_TIME)

For my need, this solution doesn’t work due the fact I use an HyperPixel screen. As the screen used every pins of the RPi. I found another solution, count for 1 second the number of status change on the pin of the IO Expander which is connect to the FAN with a pull up resistor.

1 Like