Go Down

Topic: Position recording through linear encoder and playback on an salvaged printer (Read 4475 times) previous topic - next topic

dattasaurabh82

Hello.
I recently salvaged an old printer and I was able to retrieve the bed for linear motion with an linear encoder, strip and a DC motor.
I was successful to jack in the encoder to Arduino and see the positions of the print head(When I moved it with my hands).
I than stored the position values in the EEPROM flash memory of Arduino with a button click and  replayed the position on the serial monitor with another button click.

What I want to do is move the print head by my hand and record the positions with the first button click and then after clicking the second button I want the print head to move automatically to the positions I moved it previously.

I do not want a very sophisticated PID control for it. I'm a tinkerer and want to start step by step. A help with code snippet for the part of motor control to go to targeted positions as recorded would be helpful.

The code till now I've written is
Code: [Select]


#include <EEPROM.h>

uint8_t recordButtonPin = 11;
uint8_t playButtonPin = 10;
//uint8_t ledPin = 13;

#define SAMPLE_DELAY 25

// Interrupt information
// 0 on pin 2
// 1 on pin 3
#define encoderI 2
#define encoderQ 3 // Only using one interrupt in this

volatile int count;

const int motorPinA = 12;
const int motorPinB = 13;



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

  count=0;

  pinMode(encoderI, INPUT);
  pinMode(encoderQ, INPUT);
  attachInterrupt(0, handleEncoder, CHANGE);


  pinMode(recordButtonPin, INPUT);
  digitalWrite(recordButtonPin, HIGH);
  pinMode(playButtonPin, INPUT);
  digitalWrite(playButtonPin, HIGH);
  //pinMode(ledPin, OUTPUT);

  Serial.println("Linear Encoder recorder/player");

  pinMode(motorPinA, OUTPUT);
  pinMode(motorPinB, OUTPUT);
}

void loop(){
  // Serial.println(count);
  // delay(10);

  if (! digitalRead(recordButtonPin)) {
    delay(10);
    // wait for released
    while (! digitalRead(recordButtonPin));
    delay(20);
    // OK released!
    recordEncoder();
  }

  if (! digitalRead(playButtonPin)) {
    delay(10);
    // wait for released
    while (! digitalRead(playButtonPin));
    delay(20);
    // OK released!
    spitEncoder();
  }
}




void handleEncoder(){
  if(digitalRead(encoderI) == digitalRead(encoderQ))
  {
    count++;
  }
  else{
    count--;
  }
}



void recordEncoder(){
  uint16_t addr = 0;
  Serial.println("Recording Position");


  while (digitalRead(recordButtonPin)){
    // Serial.print("Actual count: ");
    //Serial.println(count);

    volatile int constrainedCount = constrain(count, 0, 4000);
    volatile int mappedCount = map(constrainedCount, 0, 4000, 0, 255);

    Serial.print("mapped count: ");
    Serial.println(mappedCount);

    EEPROM.write(addr, mappedCount);
    addr++;
    if (addr == 512) break;
    delay(SAMPLE_DELAY);
  }
  if (addr != 512) EEPROM.write(addr, 255);

  //digitalWrite(ledPin, LOW);

  Serial.println("Done Recording");
  delay(250);
  // return();
}


void spitEncoder(){
  uint16_t addr = 0;
  Serial.println("Spitting what you just recorded");

  while (digitalRead(playButtonPin)){
    volatile int x = EEPROM.read(addr);


    if (x == 512) break;
    Serial.println(x);
    delay(SAMPLE_DELAY);
    addr++;
    if (addr == 512) break;
  }
  //if (addr != 512) EEPROM.read(addr);
  Serial.println("Done Spitting");
  delay(250);
  //return();
}

void moveForward(){
  digitalWrite(motorPinA, HIGH);
  digitalWrite(motorPinB, LOW);
}

void moveBackward(){
  digitalWrite(motorPinA, LOW);
  digitalWrite(motorPinB, HIGH);
}

   

I'm struggling with the logic for my next part of using the stored position data to control the DC motor simply and not with a Kp Ke etc etc PID equations ..

Advance bunch of thanks

Pseudo code :

Code: [Select]

if(pos != old pos){
if(pos>old pos){
move forward  // or backward
}
if(pos?old pos){
move opposite direction
}
}
if(pos == old pos){
........
I don't know how to direct the motor for the next logged  position
........
}

Robin2

I took a cheap (used) printer to pieces recently and I think the encoder and motor are as you describe. I have thrown away the bed so I am no in a position to try what you are trying. My printer also had a rotary encoder disk which I might experiment with.

I have been wondering, however, if it is possible for the DC motor to move the print-head to a specific location and to stop there. My suspicion is that it can't, and the printer works by knowing where it is (while still moving) so that it sends the ink drops at the correct location.

The analogy in my mind is the bicycle paper-boy in US films where he tosses the paper into each driveway as he cycles past - but if he stopped he would fall off.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

dattasaurabh82

Hello There.
Nice to  hear that you are also trying something exciting.
Few notes on the discussion.
I don't think the motor have to ever stop as the values will always be changing. So the motor can be kept running to the positions.
By the way I'm going to try out the pseudo code today with out the
Code: [Select]

if(pos == old pos)

part  as I suspect the case will never arrive.

I'll log my findings here..

Wish me luck

Robin2


I don't think the motor have to ever stop as the values will always be changing. So the motor can be kept running to the positions.


From your earlier post
Quote
What I want to do is move the print head by my hand and record the positions with the first button click and then after clicking the second button I want the print head to move automatically to the positions I moved it previously


I assumed you wanted the print head to stop when it got to the same position your hand had moved it to.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

MarkT



I have been wondering, however, if it is possible for the DC motor to move the print-head to a specific location and to stop there. My suspicion is that it can't, and the printer works by knowing where it is (while still moving) so that it sends the ink drops at the correct location.


If you have position sensing and a motor you can set up a closed-loop control loop to
seek and hold position.  In general a PID loop will do the job well in this sort of set up.
feed position error into the PID, its output to the motor driver (note its a bipolar
signal for position feedback).
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

Robin2


If you have position sensing and a motor you can set up a closed-loop control loop to
seek and hold position.


This question just arises from my curiosity and lack of knowledge of this subject.

I can understand how a stepper motor holds position because it has discrete positions. But I don't see how a simple DC motor can hold position unless there is a brake or something similar. I imagine that without a brake all it can do is oscillate between the two positions either side of the desired position. Your comment suggests that I am missing something in my concept of how this works.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

MarkT

[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

dattasaurabh82

Let me make it simple for all..
Say i have a list of predefined positions available.
23
33
45
36
57
67
78
52
47
30
12
.
.
.
etc etc.
coming from EEPROM  of Arduino as the loop goes on.
It is getting stored in a variable. ยจ
Now is that possible to actuate the motor so that it approximately goes to those positions. 
Is there a simple way .
My earlier logic didn't work.
Tried out several other logics..

Here's the new code
(Didn't Work. the motor didn't move a  bit. But the data was coming out of eeprom swiftly..).

Here's the new code MarkT:

Code: [Select]

#include <EEPROM.h>

uint8_t recordButtonPin = 11;
uint8_t playButtonPin = 10;
//uint8_t ledPin = 13;

#define SAMPLE_DELAY 25

// Interrupt information
// 0 on pin 2
// 1 on pin 3
#define encoderI 2
#define encoderQ 3 // Only using one interrupt in this

volatile int count;

const int motorPinA = 12;
const int motorPinB = 13;

volatile int pos, newPos;
volatile int posrchd = 1;







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

  count=0;

  pinMode(encoderI, INPUT);
  pinMode(encoderQ, INPUT);
  attachInterrupt(0, handleEncoder, CHANGE);


  pinMode(recordButtonPin, INPUT);
  digitalWrite(recordButtonPin, HIGH);
  pinMode(playButtonPin, INPUT);
  digitalWrite(playButtonPin, HIGH);
  //pinMode(ledPin, OUTPUT);

  Serial.println("Linear Encoder recorder/player");

  pinMode(motorPinA, OUTPUT);
  pinMode(motorPinB, OUTPUT);

  digitalWrite(motorPinA, LOW);
  digitalWrite(motorPinB, LOW);
}







void loop(){
  // Serial.println(count);
  // delay(10);

  if (! digitalRead(recordButtonPin)) {
    delay(10);
    // wait for released
    while (! digitalRead(recordButtonPin));
    delay(20);
    // OK released!
    recordEncoder();
  }

  if (! digitalRead(playButtonPin)) {
    delay(10);
    // wait for released
    while (! digitalRead(playButtonPin));
    delay(20);
    // OK released!
    spitEncoder();
  }
}









void handleEncoder(){
  if(digitalRead(encoderI) == digitalRead(encoderQ))
  {
    count++;
  }
  else{
    count--;
  }
}









void recordEncoder(){
  uint16_t addr = 0;
  Serial.println("Recording Position");


  while (digitalRead(recordButtonPin)){
    Serial.print("Actual count: ");
    Serial.println(count);

// Since EEPROM has trouble storing values in the range of 1XXX... (as the count ranges from 0 - 4000)
//I'm storing them as small range
// and later remap them in the range of 0 - 4000.
//those won't be accurate but I don't need precise controls.

    volatile int constrainedCount = constrain(count, 0, 4000);
    volatile int mappedCount = map(constrainedCount, 0, 4000, 0, 255);

    Serial.print("mapped count: ");
    Serial.println(mappedCount);

    EEPROM.write(addr, count);
    addr++;
    if (addr == 512) break;
    delay(SAMPLE_DELAY);
  }
  if (addr != 512) EEPROM.write(addr, 255);

  //digitalWrite(ledPin, LOW);

  Serial.println("Done Recording");
  delay(250);
  // return();
}







void spitEncoder(){
  uint16_t addr = 0;
  Serial.println("Spitting what you just recorded");

  while (digitalRead(playButtonPin)){
    volatile int x = EEPROM.read(addr);

    pos = map(x, 0, 255, 0, 4000);

    if (x == 255) break;
    Serial.print("dumped Val");
    Serial.println(x);
    Serial.print("Demapped(actually remapped) Val");
    Serial.println(pos);


    if(posrchd){              // position reached flag
      newPos = pos;
      posrchd = 0;
    }
    posrchd = go_to_target(newPos);

    delay(SAMPLE_DELAY);
    addr++;
    if (addr == 512) break;
  }
  //if (addr != 512) EEPROM.read(addr);
  Serial.println("Done Spitting");
  delay(250);
  //return();
}







volatile int go_to_target(volatile int target){
  int temp = 0;
  if(target < 3600 && target > 100){  // Boundary limit readings
    if(pos < target){
      moveForward();
      temp = 0;
    }
    else if(pos > target){
      moveBackward();
      temp = 0;
    }
    else temp = 1;
  }
  return temp;
}








void moveForward(){
  digitalWrite(motorPinA, HIGH);
  digitalWrite(motorPinB, LOW);
}

void moveBackward(){
  digitalWrite(motorPinA, LOW);
  digitalWrite(motorPinB, HIGH);
}



And MarkT  it would be helpful if you could help me with the closed Loop PID code for this case.
I guess the above code some how resembles a PID control loop 

dattasaurabh82

And My motor will oscillate - It doesn't have to stop.. And closed loop PID control does that. But I don't know how it will work here.. Help with the code logic at least here.. 

MarkT

What you have here is a very crude control loop:



volatile int go_to_target(volatile int target){
  int temp = 0;
  if(target < 3600 && target > 100){  // Boundary limit readings
    if(pos < target){
      moveForward();
      temp = 0;
    }
    else if(pos > target){
      moveBackward();
      temp = 0;
    }
    else temp = 1;
  }
  return temp;
}



void moveForward(){
  digitalWrite(motorPinA, HIGH);
  digitalWrite(motorPinB, LOW);
}

void moveBackward(){
  digitalWrite(motorPinA, LOW);
  digitalWrite(motorPinB, HIGH);
}


Its only binary in its behaviour, but it needs to be continuous, so a PID loop can
do its thing.  Crude binary control like this always oscillates, it has no option
but to do so.

You need to impement a function to drive the motor controlled by a signed integer,
perhaps +255 is full power forwards, -255 is full power backwards - its simple to
drive direction and PWM pins from that, something like:

Code: [Select]

void control_motor (int level)
{
  digitalWrite (direction_pin, level < 0) ;  // direction
  level = abs (level) ;        // drive level
  if (level > 255)  // enforce limit
    level = 255 ;
  analogWrite (PWM_pin, level) ;
}


Then you need to feed the position error into a PID loop that controls this motor
driver code, something like:
Code: [Select]

int target ;
void drive_pid (int t)
{
  target = constrain (t, 100, 3600) ;
}

#define PID_delay 5  // or whatever

void loop ()
{
  float error = target - pos ;
  control_motor ((int) PID_loop (error)) ;
  delay (PID_delay) ;
}



  Then find a pid library, or write one, and tune the PID.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

Go Up