Lawrence Technological University
College of Arts and Science
Department of Mathematics and Computer Sciences

Handouts

Arduino testing 2000 Saturn Idle Air Control

   This is a small example of using an Arduino as an inexpensive auto repair tool. Diagnostic OBDII adapters that work with smart phones and tablets have become quite inexpensive. But such adapters that can also do in circuit manipulation of components are still expensive. This workbench demo is testing and controlling the Idle Air Control valve while unplugged from the car's wire harness.

   This IAC valve is a small stepper motor. During this repair the valve was removed from the throttle body so a considerable carbon buildup could be removed. The IAC was cleaned with enough throttle body cleaner to probably remove any lubrication from the movable shaft. The plunger shaft or "pintle" seemed to have a lot of end play and so a replacement IAC (BWD 21819) was installed. You can test the coils with a digital multimeter -- in this case 50 ohms each. You can plug the valve back into the harness and toggle the ignition key to check for pintle movement. If the pintle comes too far out or perhaps all the way out and separates while you toggle the ignition -- or the new valve arrives in the package with the pintle too far extended -- the problem is how to retract the pintle enough to mount or remount the valve. BWD suggests that pressing the pintle back into the valve with the mounting bolts will damage their replacement valve.

   This project then uses the Arduino to bench test the IAC valve by moving the valve pintle in and out by either pressing an "In" or an "Out" button or by an "i" or "o" command sent over a serial connection to the Arduino.

   Resources used:

   Here is the test setup with the valve and buttons and a detail shot of just the motor shield. The Arduino is powered by either a 9 volt battery or the USB serial connection. The motor shield is powered by a car battery, or in this case a Radio Shack 3 Amp supply made for testing things like car radios, or even a smaller supply since the average current draw during this experiment was less than 50mA.

test setup

motor shield

   The Arduino code:

// IAC_stepper_test.ino
// Code to test a 2000 Saturn 1.9L Idle Air Control valve using Arduino UNO and
// Adafruit's Version 1.2 (older) motor shield and matching library.
// This shield uses a lot of pins but Digital 2 and 13 are still free.
// J.Miller
// Time-stamp: <2017-04-28 14:12:58>
#include <AFMotor.h>
// Buttons to move the pintle in (retract-open more) or out (extend-close).
int buttonIpin = 13; // To draw the pintle in. Counter-clockwise rotation.
int buttonOpin = 2;  // To push the pintle out. Clockwise rotation.
// Connect a stepper motor with 125 (a guess) steps per revolution.
// to motor port #2 (M3 and M4)
AF_Stepper motor(125, 2);
#define BUF_SIZE 21
char inbuf[BUF_SIZE];
char *p = inbuf;
char *e = p + BUF_SIZE -1;

void setup() {
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("Stepper test!");
  Serial.println("Send ixx to draw in or oxx to push out xx steps");
  Serial.println("(Default is 10 steps.  Maximum is 999 steps.)");
  Serial.println("e.g. o50 will turn the motor 50 steps forward (out)");
  motor.setSpeed(10);  // 10 rpm   
  pinMode(buttonIpin, INPUT_PULLUP);  // Active low.
  pinMode(buttonOpin, INPUT_PULLUP);  // Active low.
}

void loop() {
  checkSerial();
  checkButtons();
}

void checkSerial() {
  char rc;
  if (Serial.available() > 0) {
    rc = Serial.read();
    // Serial.print(rc,HEX); 
    if (p < e && (rc == 'i' || rc == 'o' || (rc >= '0' && rc <= '9'))) {
      Serial.print(rc); // Echo.
      *p++ = rc;        // Save.
    } else if (p > inbuf && (rc == '\r' || rc == '\n')) {  // End of some input.
      *p = 0;        //  Terminate the string.
      p = inbuf;     //  Reset pointer.
      Serial.println();
      do_commands();
    }
  }
}

void do_commands() {
  int direction; // in:0 out:1
  int steps;
  while (*p) {   //  Parse and execute command(s) in buffer.
    if (*p == 'i' || *p == 'o') { // start of a command.
      direction = *p == 'i' ? 0 : 1;
      steps = 0;
      p++;
      for (int i = 0;i<3;i++) { // Max 999 steps.
        if (*p >= '0' && *p <= '9') {
          steps = steps * 10 + *p++ - '0';
        } else {
          break;
        }
      }
      if (direction == 0) {
        pull_in(steps);
      } else {
        push_out(steps);
      } 
    } else {
      p++;  // Just skip any extra characters.
    }
  } // End of command(s) execution.
  p = inbuf; // Reset buffer pointer to start after buffer is processed.
}

void pull_in(int steps) {
  steps = steps > 0 ? steps : 10;
  Serial.print("Double coil ");
  Serial.print(steps);
  Serial.println(" steps inward (ccw)");
  motor.step(steps, BACKWARD, DOUBLE);
}

void push_out(int steps) {
  steps = steps > 0 ? steps : 10;
  Serial.print("Double coil ");
  Serial.print(steps);
  Serial.println(" steps outward (cw)");
  motor.step(steps, FORWARD, DOUBLE);
}

void checkButtons() {
  if (digitalRead(buttonIpin) == LOW) pull_in(10);
  if (digitalRead(buttonOpin) == LOW) push_out(10);
}

   The 125 steps per revolution was from a random IAC valve that happened to have specs available. So how close was the guess? Holding down the "out" button pops the pintle out for examination: The base is a 3.5mm screw with a 1.0mm thread pitch. Pressing a button 10 times (100 steps) moves the pintle an average of 4.15mm. So using interactive Ruby:

irb> # 3.5 mm screw 1.0 mm threads.
irb> # 100 steps moves 4.15mm
irb> # 1 step moves 0.0415 mm
irb> # 1 revolution moves 1 thread 1.0mm
irb> steps_in_revolution = 1.0 / 0.0415
=> 24.096385542168672
irb> degrees = 360.0 / steps_in_revolution
=> 14.940000000000001
Looks like a 24 step or 15° stepper would be a far better estimate. Then we can change the steps from 125 to 24 and increase the rpms to 50 to keep the pintle movement speed about the same. Running 240 steps then moves the pintle about 1.0cm, better! Maybe making the default number of steps be 24 instead of 10? Then each button push would move the pintle 1mm.

   Adafruit and Sparkfun have quite a number of different boards that are shields (or wings in Adafruit's overly cute feather line of boards) that use I2C to make stepper control easy. Here is one such setup with a tiny display and built in buttons that is mounted on a Adafruit Feather M0.

motor wing and OLED

   The Arduino code:

// featherm0stepper.ino
// Adafruit OLED FeatherWing on Feather M0 with Motor Shield
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
// Create the OLED display.
Adafruit_SSD1306 display = Adafruit_SSD1306();
// Create the motor shield object with the default I2C address.
Adafruit_MotorShield AFMS = Adafruit_MotorShield(); 
// Connect a stepper motor with 200 steps per rev. (1.8 degree) to port #2.
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 2);
// 32u4, M0, and 328p
#define BUTTON_A  9
#define BUTTON_B  6
#define BUTTON_C  5
#define LED      13
void setup() {
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("Stepper test!");
  AFMS.begin();  // create with the default frequency 1.6KHz
  myMotor->setSpeed(10);  // 10 rpm
  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // I2C addr 0x3C (for the 128x32)
  Serial.println("OLED begun");
  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(1000); 
  // Clear the buffer.
  display.clearDisplay();
  display.display();
  // Display directions.
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("  Button A to rotate 360 cw");
  display.println("  Button B to rotate 360 ccw");
  display.setCursor(0,0);
  display.display(); // actually display all of the above
}

void loop() {
  if (!digitalRead(BUTTON_A)) { 
    Serial.println("Double coil steps forward");
    myMotor->step(200, FORWARD, DOUBLE); 
  }
  if (!digitalRead(BUTTON_B)) {
    Serial.println("Double coil steps backwards");
    myMotor->step(200, BACKWARD, DOUBLE); 
  }
  delay(1000);
}

Revised May 4, 2017