Jul 19, 2014

A chart recorder printing text

Chart recorders were pretty neat devices to graph time curves of analog values, such as temperature changes. My dad had still a pair of MFE 4144 thermal line recorders in his cabinet of replacement parts. However, the device using those modules is discontinued for now about two decades and he asked me whether I could make use of them.
Luckily, my dad found the schematics and the pin mapping of the recorder, so attaching it to a microcontroller was pretty easy. The supply voltage is 12 V; I power it from a bench power supply. It takes 5 parallel TTL signals as a forward speed selection (a microcontroller drives a stepper motor), and a +/-2 V analog signal for the 'pen' position. The 'pen' is actually a needle that is being heated by a resistor. The thermopaper stains in a nice blue color when having contact to that hot metal.



To control the line recorder, I used the PWM signal of an ATmega32u4 on a Pro Micro Arduino-like board, which can be programmed just like the Arduino Leonardo. There are two pins for the needle postion, both independent of the supply voltage. The positive input is hooked up to a PWM output of the microcontroller, the negative input is wired to the wiper of a voltage divider (in this case a potentiometer) between GND and +5V to avoid having to deal with negative voltages. However, the 1 kHz PWM frequency is so slow that it creates a non-linearity between the value inputted to the device and the actual position of the needle: In the middle the resolution is way higher than near the maximum displacement. Only an extremely large RC low-pass filter (R = 10k, C = 10µF) gets rid of that problem but also makes the needle movement incredibly slow. Another way of solving the problem is raising the PWM frequence by setting the timer prescalers of the AVR differently - and that's what I eventually did. Running the PWM at 65 kHz lets you even remove the low-pass filter completely - the result is therefore an extremely simple circuit only consisting of the power supply, the microcontroller, a potentiometer and the line writer itself.
But what can the line recorder do? You guessed right, drawing a line on a piece of paper. For example these beautiful sine waves:

Quite boring, right? Well, to draw more complicated things you could buy a real thermal printer. But as I love the challenge....
You would either need to be able to move the paper in both directions (the stepper only pulls it off the reel) or the possibility to discontinue the line. The first is unfeasible without enormous effort in modifying the mechanical part of the machine. The latter can be done by either moving the pen really fast that it doesn't leave a trace on the paper (if the needle arrangement wasn't that inert), turning the heater off and back on (which takes about half a minute - definitely too long) or by lifting the needle off the paper (what I did).

You could use a servo to do that job, but since it is only needed to lift the needle less than a millimeter to discontinue the line, a solenoid is probably the faster solution. But where can you get small solenoids which are easy to use? Right, from relays. Those where lying around in the basement:


So I cut them open and soldered a little wire frame to the pallet of the relays which goes just right below the needle and lifts it up when 12 V are applied. The BD649 NPN darlington transistor is way overpowered to operate the relays, but the TO-220 package makes a nice convinient way of mounting the perf board to the recorder. Another transistor in that kind of package is placed on the other side for secure fixation. In the front, spacing bolts and 3 mm screws make it easy to adjust the height of the wire frame later for perfect performance. The other components are a 1 k resistor at the base of the transistor and a flyback diode for the solenoids.

Now just upload bit of code to the microcontroller and your line recorder can print text :D


Currently I only have it print text in a 8x6 font, but it should be pretty easy to adjust it for larger fonts and even bitmap images.

Video of it in action:




#define FORWARD 25   //ms
#define WIDTH 4      //lines; Times 2
#define PIXELWIDTH 2 //mm
#define DOWNTIME 50  //ms spring takes to move it back down
#define UPTIME   20  //ms relays take to lift
#define DRAWTIME 4   //ms on paper  per step
#define MOVETIME 1   //ms lifted up per mm

//pin definitions
const int drive = 5;
const int pos = 3;
const int up  = 10;

const byte font[]=
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      // Code for char  
        0x00, 0x00, 0x6F, 0x00, 0x00, 0x00,      // Code for char !
        0x00, 0x07, 0x00, 0x07, 0x00, 0x00,      // Code for char "
        0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00,      // Code for char #
        0x24, 0x2A, 0x6B, 0x2A, 0x12, 0x00,      // Code for char $
        0x23, 0x13, 0x08, 0x64, 0x62, 0x00,      // Code for char %
        0x36, 0x49, 0x55, 0x22, 0x50, 0x00,      // Code for char &
        0x00, 0x00, 0x07, 0x00, 0x00, 0x00,      // Code for char '
        0x00, 0x1C, 0x22, 0x41, 0x00, 0x00,      // Code for char (
        0x00, 0x41, 0x22, 0x1C, 0x00, 0x00,      // Code for char )
        0x14, 0x08, 0x3E, 0x08, 0x14, 0x00,      // Code for char *
        0x08, 0x08, 0x3E, 0x08, 0x08, 0x00,      // Code for char +
        0x00, 0xB0, 0x70, 0x00, 0x00, 0x00,      // Code for char ,
        0x08, 0x08, 0x08, 0x08, 0x08, 0x00,      // Code for char -
        0x00, 0x60, 0x60, 0x00, 0x00, 0x00,      // Code for char .
        0x20, 0x10, 0x08, 0x04, 0x02, 0x00,      // Code for char /
        0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00,      // Code for char 0
        0x00, 0x42, 0x7F, 0x40, 0x00, 0x00,      // Code for char 1
        0x42, 0x61, 0x51, 0x49, 0x46, 0x00,      // Code for char 2
        0x22, 0x41, 0x49, 0x49, 0x36, 0x00,      // Code for char 3
        0x18, 0x14, 0x12, 0x7F, 0x10, 0x00,      // Code for char 4
        0x27, 0x45, 0x45, 0x45, 0x39, 0x00,      // Code for char 5
        0x3E, 0x49, 0x49, 0x49, 0x32, 0x00,      // Code for char 6
        0x03, 0x01, 0x71, 0x09, 0x07, 0x00,      // Code for char 7
        0x36, 0x49, 0x49, 0x49, 0x36, 0x00,      // Code for char 8
        0x26, 0x49, 0x49, 0x49, 0x3E, 0x00,      // Code for char 9
        0x00, 0x36, 0x36, 0x00, 0x00, 0x00,      // Code for char :
        0x00, 0xB6, 0x76, 0x00, 0x00, 0x00,      // Code for char ;
        0x08, 0x14, 0x14, 0x22, 0x22, 0x00,      // Code for char <
        0x14, 0x14, 0x14, 0x14, 0x14, 0x00,      // Code for char =
        0x22, 0x22, 0x14, 0x14, 0x08, 0x00,      // Code for char >
        0x02, 0x01, 0x51, 0x09, 0x06, 0x00,      // Code for char ?
        0x3E, 0x41, 0x5D, 0x51, 0x4E, 0x00,      // Code for char @
        0x7E, 0x09, 0x09, 0x09, 0x7E, 0x00,      // Code for char A
        0x7F, 0x49, 0x49, 0x49, 0x36, 0x00,      // Code for char B
        0x3E, 0x41, 0x41, 0x41, 0x22, 0x00,      // Code for char C
        0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00,      // Code for char D
        0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,      // Code for char E
        0x7F, 0x09, 0x09, 0x09, 0x01, 0x00,      // Code for char F
        0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00,      // Code for char G
        0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00,      // Code for char H
        0x00, 0x41, 0x7F, 0x41, 0x00, 0x00,      // Code for char I
        0x31, 0x41, 0x41, 0x41, 0x3F, 0x00,      // Code for char J
        0x7F, 0x08, 0x14, 0x22, 0x41, 0x00,      // Code for char K
        0x7F, 0x40, 0x40, 0x40, 0x40, 0x00,      // Code for char L
        0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00,      // Code for char M
        0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00,      // Code for char N
        0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00,      // Code for char O
        0x7F, 0x09, 0x09, 0x09, 0x06, 0x00,      // Code for char P
        0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00,      // Code for char Q
        0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,      // Code for char R
        0x26, 0x49, 0x49, 0x49, 0x32, 0x00,      // Code for char S
        0x01, 0x01, 0x7F, 0x01, 0x01, 0x00,      // Code for char T
        0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00,      // Code for char U
        0x0F, 0x30, 0x40, 0x30, 0x0F, 0x00,      // Code for char V
        0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00,      // Code for char W
        0x63, 0x14, 0x08, 0x14, 0x63, 0x00,      // Code for char X
        0x07, 0x08, 0x70, 0x08, 0x07, 0x00,      // Code for char Y
        0x61, 0x51, 0x49, 0x45, 0x43, 0x00,      // Code for char Z
        0x00, 0x00, 0x7F, 0x41, 0x00, 0x00,      // Code for char [
        0x02, 0x04, 0x08, 0x10, 0x20, 0x00,      // Code for char BackSlash
        0x00, 0x41, 0x7F, 0x00, 0x00, 0x00,      // Code for char ]
        0x04, 0x02, 0x01, 0x02, 0x04, 0x00,      // Code for char ^
        0x40, 0x40, 0x40, 0x40, 0x40, 0x00,      // Code for char _
        0x00, 0x01, 0x02, 0x04, 0x00, 0x00,      // Code for char `
        0x20, 0x54, 0x54, 0x54, 0x78, 0x00,      // Code for char a
        0x7F, 0x44, 0x44, 0x44, 0x38, 0x00,      // Code for char b
        0x38, 0x44, 0x44, 0x44, 0x28, 0x00,      // Code for char c
        0x38, 0x44, 0x44, 0x44, 0x7F, 0x00,      // Code for char d
        0x38, 0x54, 0x54, 0x54, 0x58, 0x00,      // Code for char e
        0x00, 0x08, 0xFE, 0x09, 0x00, 0x00,      // Code for char f
        0x18, 0xA4, 0xA4, 0xA4, 0x78, 0x00,      // Code for char g
        0x7F, 0x04, 0x04, 0x04, 0x78, 0x00,      // Code for char h
        0x00, 0x44, 0x7D, 0x40, 0x00, 0x00,      // Code for char i
        0x00, 0x40, 0x84, 0x7D, 0x00, 0x00,      // Code for char j
        0x7F, 0x10, 0x28, 0x44, 0x00, 0x00,      // Code for char k
        0x00, 0x41, 0x7F, 0x40, 0x00, 0x00,      // Code for char l
        0x7C, 0x04, 0x18, 0x04, 0x78, 0x00,      // Code for char m
        0x7C, 0x08, 0x04, 0x04, 0x78, 0x00,      // Code for char n
        0x38, 0x44, 0x44, 0x44, 0x38, 0x00,      // Code for char o
        0xFC, 0x24, 0x24, 0x24, 0x18, 0x00,      // Code for char p
        0x18, 0x24, 0x24, 0x24, 0xFC, 0x00,      // Code for char q
        0x7C, 0x08, 0x04, 0x04, 0x08, 0x00,      // Code for char r
        0x48, 0x54, 0x54, 0x54, 0x24, 0x00,      // Code for char s
        0x04, 0x3F, 0x44, 0x44, 0x00, 0x00,      // Code for char t
        0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00,      // Code for char u
        0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00,      // Code for char v
        0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00,      // Code for char w
        0x44, 0x28, 0x10, 0x28, 0x44, 0x00,      // Code for char x
        0x1C, 0xA0, 0xA0, 0xA0, 0x7C, 0x00,      // Code for char y
        0x44, 0x64, 0x54, 0x4C, 0x44, 0x00,      // Code for char z
        0x00, 0x08, 0x77, 0x41, 0x41, 0x00,      // Code for char {
        0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,      // Code for char |
        0x41, 0x41, 0x77, 0x08, 0x00, 0x00,      // Code for char }
        0x08, 0x04, 0x08, 0x10, 0x08, 0x00,      // Code for char ~
        0x00, 0x7F, 0x41, 0x7F, 0x00, 0x00       // Code for char Block
        };
        
byte posx = 0; //remembers where the pen is

void setup(){
  pinMode(drive,OUTPUT);
  digitalWrite(drive,HIGH);  //Stop motor
  pinMode(pos,OUTPUT);
  pinMode(up,OUTPUT);
  
  TCCR0B = (TCCR0B & 0xF8) | 1;  //set pwm to 65 khz; millis run 64 times as fast
  
  penUp();
  setmm(20);
  
  Serial.begin(9600);
}


void loop(){
  if (Serial.available()>0){
    printChar(Serial.read());  //Just print the incoming ascii characters
  }
  
}

void printChar(unsigned char c){  //print one char
  uint16_t address = (c-32)*6;
  for (byte i = 0; i<6 data-blogger-escaped-0="" data-blogger-escaped-40="" data-blogger-escaped-8="" data-blogger-escaped-a="" data-blogger-escaped-address="" data-blogger-escaped-and="" data-blogger-escaped-b--="" data-blogger-escaped-b-back="" data-blogger-escaped-b="" data-blogger-escaped-back="" data-blogger-escaped-between="" data-blogger-escaped-bit="" data-blogger-escaped-bitread="" data-blogger-escaped-bits="" data-blogger-escaped-bool="" data-blogger-escaped-break="" data-blogger-escaped-byte="" data-blogger-escaped-calculate="" data-blogger-escaped-char="" data-blogger-escaped-column="" data-blogger-escaped-data="" data-blogger-escaped-delay="" data-blogger-escaped-dest="" data-blogger-escaped-destination="" data-blogger-escaped-digitalwrite="" data-blogger-escaped-direction="" data-blogger-escaped-do="" data-blogger-escaped-draw="" data-blogger-escaped-drawto="" data-blogger-escaped-drive="" data-blogger-escaped-drivep="" data-blogger-escaped-else="" data-blogger-escaped-endpos="" data-blogger-escaped-first="" data-blogger-escaped-font="" data-blogger-escaped-for="" data-blogger-escaped-frommm="" data-blogger-escaped-handle="" data-blogger-escaped-i="" data-blogger-escaped-if="" data-blogger-escaped-int="" data-blogger-escaped-is="map(frommm," data-blogger-escaped-last="" data-blogger-escaped-lastbit="false;" data-blogger-escaped-line="" data-blogger-escaped-low="" data-blogger-escaped-mm="" data-blogger-escaped-move="" data-blogger-escaped-nothing="" data-blogger-escaped-paper="" data-blogger-escaped-parse="" data-blogger-escaped-pendown="" data-blogger-escaped-penup="" data-blogger-escaped-pixel="" data-blogger-escaped-positions="" data-blogger-escaped-print="" data-blogger-escaped-printchar="" data-blogger-escaped-pwm="" data-blogger-escaped-row="" data-blogger-escaped-set="" data-blogger-escaped-setmm="" data-blogger-escaped-single="" data-blogger-escaped-start="" data-blogger-escaped-startpos="" data-blogger-escaped-string="" data-blogger-escaped-that="" data-blogger-escaped-the="" data-blogger-escaped-to="" data-blogger-escaped-tomm="" data-blogger-escaped-uint8_t="" data-blogger-escaped-up="" data-blogger-escaped-value="" data-blogger-escaped-void="" data-blogger-escaped-while="" data-blogger-escaped-width="" data-blogger-escaped-write8bit="" data-blogger-escaped-write="" data-blogger-escaped-writing="" data-blogger-escaped-xff=""> is) {
    while (is <= dest){
      analogWrite(pos,is++);
      delay(DRAWTIME*64);
    }
  }
  else  {
    while (is >= dest){
      analogWrite(pos,is--);
      delay(DRAWTIME*64);
    }
  }
  posx = tomm;   //remember position
}
void setmm(int mm){  //move pen to mm position
  byte x = map(mm, 0, 40,200,57); //calculate pwm value
  analogWrite(pos,x);
  delay(abs(mm-posx)*MOVETIME*64L);
  posx = mm;    //remember position
}
- Marv

No comments:

Post a Comment