Huky Gas Automation - Page 4

Discuss roast levels and profiles for espresso, equipment for roasting coffee.
Doodads

#31: Post by Doodads »

Brewzologist, nice work!

Do you have a way to limit the torque from the stepper? I'm planning my own stepper gas control and I'm wondering how to use the stepper to turn off the gas without stalling the stepper or breaking the valve when the valve is all the way closed.

User avatar
Brewzologist
Supporter ♡

#32: Post by Brewzologist »

Here are some random ideas that may help your quest to control stepper torque issues:

1) Stepper torque is controlled by amperage, so select a motor with sufficient torque to prevent stalling but not so much that it can break things. Some drivers (like the TB6600) also allow you to configure amperage so you can reduce the torque that way too.
2) Use a separate sensor/switch (if possible) to signal your microcontroller when the valve is closed so you can stop any stepper movement commands.
3) Use deceleration in software when you are getting close to valve closure to reduce forces which may break things.
4) Experiment with your gas valve to find out if flow stops before it's completely closed. Do this by opening slightly from full closure, giving a few seconds and then attempt to light the stove. Any pooled gas will ignite meaning the valve isn't fully closed. For my roaster, gas flow stopped about 1/8-1/4 turn before the valve was completely closed, so this eliminated any stepper torque issues as the valve never needs to be closed tight*.
5) Get a different gas valve that doesn't require a lot of torque for closure. Parker makes many different valves and I may yet upgrade mine to a higher quality needle valve for best precision.

(*As part of my roasting startup procedures I manually set/confirm the valve is 1/8 turn open, then click a calibration button I created in Artisan that sends a command to my Arduino to set the current stepper position as the closed position. All my other gas button presets/sliders in Artisan are offsets from the closed position.)

User avatar
chuckcoffee (original poster)

#33: Post by chuckcoffee (original poster) »

Steve, great progress

I agree that direct coupling is the way to go. The belt gets slack and has lag if you change direction. I did not realize that you could get a stepper motor that will spin freely if no power. My cheap one does not move.

So.. I will get this on order :D

Having air and gas on Arduino is great. I love roasting this way! Keep the updates coming

User avatar
Brewzologist
Supporter ♡

#34: Post by Brewzologist »

Chuck. Good to hear from you. Thought you'd disappeared from HB! :shock:

Regarding the stepper shaft being able to move freely: The TB6600 has an enable pin that lets you lock and unlock the motor. Hook it up to one of the digital pins on your Arduino and initialize it to unlocked in setup(). Then lock it just before the call to move the stepper and unlock immediately afterwards. That way it's almost always free to rotate manually. The only time it stays locked that I couldn't figure out is when power to the Arduino is off and the TB6600 is powered on, but that only occurs on startup/shutdown for me. Those coding kids of yours can figure it out. Have them read the TB6600 manual for details. Post if you have any problems.

Really happy with this. It works great. Sometimes I need to tweak the variable regulator ever so slightly before a roast due to drift to get it back on track to a known kPa. But then it's spot on and great to control the roast with.

User avatar
Brewzologist
Supporter ♡

#35: Post by Brewzologist »

For anyone interested, my source code for fan and gas automation is below. Let me know if you find any bugs. Use at your own risk! :wink:

Some notes:
1) I reorganized my code a bit differently from that posted on GitHub
2) I run a Windows laptop and used Windows named pipes to communicate between Artisan and the Arduino communicator.
3) I did not use the AccelStepper library to control the stepper motor, opting instead to send commands directly to the TB6600.

artisanprog.py - Python script called from within Artisan that just sends a message to arduinoCommunicator.py and returns. (This offloads command execution which minimizes delaying Artisan during a roast session.)
#!/usr/bin/python

import os
import sys
import win32pipe
import win32file

FIFO_WRITE = "\\\\.\\pipe\\in.fifo"
FIFO_READ = "\\\\.\\pipe\\out.fifo"

print  "Sending data to arduino..."

def sendCommand(str):
    try:
        fileHandle = win32file.CreateFile(FIFO_WRITE, win32file.GENERIC_WRITE, 0, None, win32file.OPEN_EXISTING, 0, None)    
        win32file.WriteFile(fileHandle, str)
        win32file.CloseHandle(fileHandle)
    except:
        print "Error: Arduino pipe not open"
    else:
        print "Data sent"


if (sys.argv[1] == 'FAN'):
	sendCommand("FAN:%s" % sys.argv[2])
elif (sys.argv[1] == 'GAS'):
    sendCommand("GAS:%s" % sys.argv[2])
elif (sys.argv[1] == 'CAL'):
    sendCommand("CAL:%s" % sys.argv[2])
elif (sys.argv[1] == 'END'):
    sendCommand("END:0")
arduinoCommunicator.py - Python script run outside of Artisan in a Windows command window that receives messages from artisanprog.py and then calls the Arduino to execute them.
#!/usr/bin/python

import sys
import serial
import os
import serial.tools.list_ports
import win32pipe
import win32file

ports = serial.tools.list_ports.comports()
arduinoPort = None
for p in ports:
    if 'Arduino' in p[1]:
        arduinoPort = p[0]
        print p[1] + ' found at ' + p[0]
        break

if (arduinoPort is None):
    print "Arduino not connected?"
    sys.exit(-1)

arduino = serial.Serial(arduinoPort, 9600, timeout=2)

FIFO_WRITE = "\\\\.\\pipe\\out.fifo"
FIFO_READ = "\\\\.\\pipe\\in.fifo"

def sendToArduino(str):
    arduino.write(str)

def parseCmd(cmd):
    exit_flag=0
    print cmd
    try:
        cmd, val = cmd.split(':')
    except ValueError:
        print "Bad command!";
        return exit_flag

    if (cmd == 'FAN'):
        sendToArduino("FAN:%s\n" % val)
    elif (cmd == 'GAS'):
        sendToArduino("GAS:%s\n" % val)
    elif (cmd == 'CAL'):
        sendToArduino("CAL:%s\n" % val)
    elif (cmd == 'END'):
        exit_flag=1
    return exit_flag


p = win32pipe.CreateNamedPipe(FIFO_READ, win32pipe.PIPE_ACCESS_INBOUND, win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_WAIT, 1, 65536, 65536,300,None)
print  "Pipe open for data from Artisan..."

while True:
    try:
        win32pipe.ConnectNamedPipe(p, None)
        data = win32file.ReadFile(p, 4096)
        win32pipe.DisconnectNamedPipe(p)
        if data[0] == 0:
            exit_loop = parseCmd(data[1])
            if exit_loop == 1:
                break
        else:
            print 'ERROR', data[0]
    except:
        x=1

win32File.CloseHandle(p)
roaster.ino - Source code installed on Arduino microcontroller that receives messages from arduinoCommunicator.py and then executes commands to control an attached PWM fan and stepper motor.
// Define stepper motor digital pins used on the Arduino for TB6600 driver
#define ENA_PIN 2
#define DIR_PIN 3
#define STEP_PIN 4
// Define PWM fan control digital pin used on Arduino.  I used an Interfan model PM240-24D-1751B-2TP fan
#define FAN_CTRL_PIN 11

// Arduino only supports a 255 range for PWM. (e.g. 0-255, so 254/100=2.54)
#define FAN_PWM_RANGE 2.54

// Physical 360 degree rotations of Huky needle valve from off to max desired gas output (5.5 kPa)
#define MAX_VALVE_360 3.25
// number of stepper motor steps in 360 degrees (set on TB6600 driver board for 1/32 microsteps)
#define STEPS_360 6400
// stepper motor speed in uSec.  increasing value slows down speed
#define STEP_DELAY 37
// direction and lock defines for stepper motor
#define CW 1
#define CCW 0
#define LOCK 0
#define UNLOCK 1

// global variable that holds the current stepper position
int _nCurrStepPos;

// buffers used to receive data from Artisan
#define CMD_SIZE 10
char buff[CMD_SIZE];

struct inCmd {
    char *cmd;
    unsigned int val;
} cmd;

void setup() {
	Serial.begin(9600);

	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN, LOW);
	
	// Initialize fan pin and turn it off
	pinMode(FAN_CTRL_PIN, OUTPUT);
	analogWrite(FAN_CTRL_PIN, 0);

	// initialize Arduino pins used by the TB6600 driver
	pinMode(ENA_PIN, OUTPUT);
	digitalWrite(ENA_PIN, UNLOCK);
	pinMode(DIR_PIN, OUTPUT);
	digitalWrite(DIR_PIN, LOW);
	pinMode(STEP_PIN, OUTPUT);
	digitalWrite(STEP_PIN, LOW);

	_nCurrStepPos = 0;
}

unsigned int _i = 0;
void loop() {
    while (Serial.available()) {
        //read a line ending with \n and store in buff
        char ch = Serial.read();
        buff[_i] = ch;
        if (ch == '\n' || _i >= CMD_SIZE) {
            parseCommand();
            runCommand();
            _i = 0;
            break;
        }
        ++_i;
    }
}

void parseCommand() {
    //parse command in format COMMAND:VALUE and store cmd/val in cmd struct
    char *valpos;
    for (int x = 0; x < CMD_SIZE; ++x) {
        if (buff[x] == ':') {
            valpos = &buff[x + 1];
            buff[x] = '\0';
        } else if (buff[x] == '\n') {
            buff[x] = '\0';
            break;
        }
    }
    cmd.cmd = buff;
    cmd.val = atoi(valpos);
}

void runCommand() {
    if (strncmp("FAN", cmd.cmd, CMD_SIZE) == 0) {
        SetFanSpeed(cmd.val);
    } else if (strncmp("GAS", cmd.cmd, CMD_SIZE) == 0) {
        MoveStepper(cmd.val);
    } else if (strncmp("CAL", cmd.cmd, CMD_SIZE) == 0) {
        SetCurrentStepperPos(cmd.val);
    } else {
        //Serial.print("Got unknown command: "); Serial.println(cmd.cmd);
    }
}

void SetFanSpeed(int nPercent) {
  //convert fan percentage to an Arduino supported PWM range and send to fan
  int x = int(nPercent * FAN_PWM_RANGE);
  analogWrite(FAN_CTRL_PIN, x);
}

void SetCurrentStepperPos(int nPercent)
{
	// during initial testing, the value of nPercent is recorded for each kPa position on the gauge
	// when starting roaster, after manually setting the needle valve to 0 kPa on the gauge call this function
	// where nPercent=0.  All subsequent gas settings are offsets from this starting valve position.
	
	_nCurrStepPos = nPercent * int(int(MAX_VALVE_360 * STEPS_360)/100);
}

void MoveStepper(int nPercent)
{
	// convert percent into absolute step location and move stepper shaft relative to current location
       // Note: whether to run CW or CCW depends on how motor coils of 4 wire stepper are attached to driver board.  May need to swap below.
	int nNewPos = nPercent * int(int(MAX_VALVE_360 * STEPS_360)/100);
	if (nNewPos == _nCurrStepPos) {
		return;
	}
	if (nNewPos > _nCurrStepPos) {
		RunStepper(nNewPos - _nCurrStepPos, CCW);
	} else {
		RunStepper(_nCurrStepPos - nNewPos, CW);
	}
	_nCurrStepPos = nNewPos;
}

void RunStepper(int nSteps, byte dir ) {
	// lock motor shaft, move nSteps in selected direction, and then unlock motor shaft
	digitalWrite(ENA_PIN, LOCK);
	digitalWrite(DIR_PIN, dir);  
	for (int x = 0; x < nSteps; x++) {
		digitalWrite(STEP_PIN, 1);
		delayMicroseconds(STEP_DELAY);
		digitalWrite(STEP_PIN, 0);
		delayMicroseconds(STEP_DELAY);
	}
	digitalWrite(ENA_PIN, UNLOCK);
}

User avatar
chuckcoffee (original poster)

#36: Post by chuckcoffee (original poster) »

Steve

The motor is rated 3.6V but I gather it can handle 24 volts no issues?

Can you show the hookup for power supply and motor into the motor driver.

Also what part of the code puts the motor in free spinning neutral state?

User avatar
Brewzologist
Supporter ♡

#37: Post by Brewzologist »

chuckcoffee wrote:Steve
The motor is rated 3.6V but I gather it can handle 24 volts no issues?
No issues. The stepper driver determines the voltage and amperage at the motor. In this case, it's handled by the TB6600 and all you need to do is select an amperage setting on the dip switch. I set mine to 1.5 amps which is what the motor is rated to.
chuckcoffee wrote: Can you show the hookup for power supply and motor into the motor driver.
See the manual here: https://s3.us-east-2.amazonaws.com/step ... +Motor.pdf
I used the common cathode connection wiring. Also read about how to determine which of the 4 wires from the stepper are paired in the manual. As for the Arduino; simply connect the positive leads of the ENA, DIR and PUL to any open digital pins on the Arduino.

Another good tutorial here: https://www.makerguides.com/tb6600-step ... -tutorial/
chuckcoffee wrote: Also what part of the code puts the motor in free spinning neutral state?
See the roaster.ino file I posted. Specifically its: digitalWrite(ENA_PIN, UNLOCK);

User avatar
Brewzologist
Supporter ♡

#38: Post by Brewzologist »

Here are some first Impressions of the benefits of automating the gas needle valve on my Huky. (this post includes my previous fan modification too):

Alarms
I can now automate even more Artisan Alarms. See annotations in red below. For example, I always do a 1 min power-off soak at the start of my roasts, go to 50% fan and 5kPa at 1 min, and turn the fan on 100% at drop; all now completely automated. This lets me focus on the most important part of the roast; from DE till Drop.

(Important note: I set a 5 sec time delay and 'If-Alarm' dependency between the calibrate-gas-off-position event and the first gas setting. In testing I noted if you call them back-to-back that setting the gas sometimes happened before the gas calibration event, resulting in wrong gas valve positions. I haven't debugged why this happens, but adding a delay and alarm dependency fixed it.)



Artisan Software PID Control
For general info on Artisan's software PID see: Artisan PID Control. One great use of gas automation is to use Artisan's software PID for roaster warm-up and between batch protocols (BBP). As I've learned the hard way (and Rao emphasizes in CRBP), following a consistent warm-up and BBP is an important step to achieve repeatable roasts. By using the Ramp/Soak PID control I can now attend to other roast preparation tasks while Artisan automatically warms up my roaster per my desired protocol and holds my charge temp+3F until I'm ready to Start/Charge. Nice!


User avatar
MaKoMo

#39: Post by MaKoMo »

Brewzologist wrote: Artisan Software PID Control
For general info on Artisan's software PID see: Artisan PID Control. One great use of gas automation is to use Artisan's software PID for roaster warm-up and between batch protocols (BBP). As I've learned the hard way (and Rao emphasizes in CRBP), following a consistent warm-up and BBP is an important step to achieve repeatable roasts. By using the Ramp/Soak PID control I can now attend to other roast preparation tasks while Artisan automatically warms up my roaster per my desired protocol and holds my charge temp+3F until I'm ready to Start/Charge. Nice!
Excellent application of the PID R/S system. I will soon have further good news on this for you...

User avatar
Brewzologist
Supporter ♡

#40: Post by Brewzologist »

MaKoMo wrote:Excellent application of the PID R/S system. I will soon have further good news on this for you...
Marko; Thanks for all you've done in Artisan. I continually discover additional functionality to help my roasting.

If you're enhancing PID R/S, adding the ability to set a fan slider value at each step would be useful as well :D. (eg. I use a lot of convection during warmup steps, but at BBP I reduce fan in preparation for a soak at charge.)