SEM digital image acquisition with ADC microcontroller

After almost one year from the start of this project, I’ve finally succeeded in recording my first digital images from my vintage analog SEM using a Teensy 3.2 microcontroller and a simple computer script.

The whole project has been inspired by Ben Krasnow who shared his precious project, computer side code and Teensy sketch which he realized for a similar SEM, a Jeol JSM T200. You can find his Youtube video here. In his Youtube channel, Applied science, you can also find many other interesting and inspiring videos.

 

Old analog image recording

In order to record one single, high quality, slow scan image, many old electron microscopes required the exposure of a polaroid film or, later, a camera directly to the CRT monitor while the scan line covers the whole frame height. Nowadays, this practice is a bit outdated and many old analog SEMs have been upgraded with digital image systems. Unfortunately the commercial ones are kind of expensive. Some other options require computers with expensive frame grabbers.

As soon as my SEM passed through my garage door, I started to think how to add a similar upgrade to it. Recording an image right from your computer is indeed a lot more handy than just taking a picture of the monitor with a camera.

So far, I’ve been using a Canon 600D camera, synchronized with the vsync signal from the SEM by a simple transistor/relais circuit, but image quality and the need of frequent diaphragm adjusting are not the best.
In this project, inspired by Ben, I show how to deal with an analog composite video signal and how to send it to a microcontroller for the ADC conversion and subsequent image acquisition on a computer with a very cheap investment.

 

The analog video signals

First of all, I would like to point out that I’m basically a total novice to electronics and getting to understand the basics of signals, operational amplifiers and how to use devices like an oscilloscope or a function generator, took me a while as those are topics very far from my biology studies background.

The first step into this project was to look for the right signals needed for a frame reconstruction:

– video data signal;
– horizontal sync pulse (end/start of a single horizontal line);
– vertical sync pulse (start/end of a frame).

With the use of an oscilloscope and with the help of the schematics, I could find the right signals.
DSC010112

The composite video signal contains both the image data and the hsync:

P1050063

The video data ranges from 0V to around -4.5V.

P1050075

The duration of one hsync pulse (end/star of a new line) lasts 25ms and one line data signal lasts 35ms. This makes a total of 60ms for each line and it perfectly fits the 1000 lines per frame in one single 60 seconds slow scan. The rest of the signal, let’s say lower than -5V, is hsync pulse. The signal goes as low as -13.6V during a hsync pulse.

 

P1050072

In the image above you can see on the bottom the composite video signal which ends when the vsync pulse ends, upper signal. The vsync pulse is a “high” state signal which stays at 3.8V for the whole duration of the frame.

 

At this point, the signals are clear. Now it’s time to build a circuit to feed them into the input pins of the Teensy 3.2 microcontroller. This controller, like many others, only allows a voltage of 3.3V at his input pins. However, this version of Teensy is “5V tolerant” which gives me a bit more safety in case a signal goes over the standard 3.3V. This might happen at the beginning of a frame, when a “weird” spiked pulse in the composite video signal occurs:

P1050071

As you can see here, on channel 2, the first pulse has a couple of high peaks which can go pretty high over 0V and I’m sure those will go over the 3.3V limit, but probably stay under the 5V tolerance after being processed through the circuit.

 

The circuit

At this point, the development of a circuit capable of translating the signals in the positive range and of limiting them into the 0-3.3V range is required.

The two video signals need to be processed by two different circuits.
The composite video signal will need to be split into just video and just hsync signal and those signals need to be sent to two different pins of the microcontroller.
The vsync signal will just need a limiting circuit to have a 0-3.3V output.

P1050087

In this drawing you can see the composite video signal circuit, which gives as output a video signal and a hsync pulse in the range of 3.3V. All opamps (TL082) are +/-15V power supplied straight from the SEM boards through a short cable.

The composite video signal is first inverted and its offset regulated by a trimmer. Then a second opamp reverses it back and suppresses the negative part of the signal (hsync). The 1MOhm resistor on the input is to rise input impedance even more. This way, a current lower than 0.01 mA is flowing, thus avoiding the risk of overcharging the circuit from the SEM.

The second part of the circuit is reversing and translating the hsync pulse over 0, regulating its gain (the feedback here is working as a limiter) and the last opamp reverses the signal back and the last trimmer finely regulates the output voltage to 3.3V.

P10500862

The vsync signal is processed through the circuit above. The use of the opamp is due to the need of de-coupling the signal from the SEM with a high impedance input. The signal is then attenuated with a simple voltage divider and fed to the microcontroller.

The circuit was then tested with a function generator to set the right outputs by regulating the various trimmers.

 

50784625_2303389019893281_2676395199621496832_n

The circuit. On the top, the composite video and vsync input RCA connectors and the 3 pin connector for +/-15V power supply. On bottom right, the Teensy (LC version) microcontroller.

 

Teensy sketch and computer script

As Ben had a different configuration of the signals, having a single input composite signal containing video data, hsync and vsync, I had to figure how to adapt his code to my needs. Thanks to his tips, I ended up by adding an interrupt when the vsync pulse occurs on the vsync pin.
His code used to discriminate a vsync from a hsync on the same input pin by counting the duration of a “blanking” pulse. I decied to leave this part of the code untouched and just put a ridiculously high value in microseconds as threshold, so a vsync pulse is never going to be registered on the composite video signal, but the interrupt I added will do the job instead.

Here is the code I came up with. You can download the original one from Ben’s video description.

/* Analog sampler for Teensy LC microcontroller
Copyright (c) 2015 Ben Krasnow ben.krasnow@gmail.com

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
“Software”), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

1. The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// This program samples a slow-scan analog video signal and sync signal with the ADC, and sends the data over USB serial
#include <ADC.h>

#define DEBUG_PINS // Comment this to disable hardware debug pins

// Pin I/O
const int PIN_SEM_VIDEO = A8; // Pin connected to video signal from SEM
const int PIN_SEM_SYNC = 15; // Pin connected to sync signal from SEM. HIGH = beam blanked during retrace
const int PIN_SEM_VSYNC = 2;

#ifdef DEBUG_PINS
const int PIN_DEBUG1 = 3; //HIGH during ADC ISR
const int PIN_DEBUG2 = 4; //HIGH during main loop
const int PIN_DEBUG3 = 5; //HIGH during sync ISR
#endif
//SEM video sign-al parameters
const int SYNC_DUR_THRESH = 28000; // Duration in microseconds that differentiates between a horizontal and vertical sync pulse.
//USB data parameters
const byte HSYNC_VAL = 0; //Value sent via USB that indicates a horizontal sync pulse
const byte VSYNC_VAL = 1; //Value sent via USB that indicates a vertical sync pulse
//Values 2-255 are image data
//Internal tuning
const int MAIN_BUF_LENGTH = 128; // How large is each ping-pong buffer
const int BUF_TX_TRIG = 100; // When the buffer has this many bytes in it, switch to the other buffer, and transmit now.
//Global variables

byte bufferA[MAIN_BUF_LENGTH];
byte bufferB[MAIN_BUF_LENGTH];
int bufptrA, bufptrB, usebufA; // usebufA is used as a boolean

elapsedMicros syncPulseDur; // This is a special Teensy variable type that increments in time automatically

ADC *adc = new ADC(); // adc object

void setup()
{
int i;
usebufA = 1;
bufptrA = 0;
bufptrB = 0;

for (i = 0; i < NVIC_NUM_INTERRUPTS; i++) NVIC_SET_PRIORITY(i, 128); //The Teensy LC only uses interrupt priorities of 0, 64, 128, 192, 255
NVIC_SET_PRIORITY(IRQ_ADC0, 64); // The ADC ISR must have a higher priority (lower number) than the USB ISR
NVIC_SET_PRIORITY(IRQ_PORTC, 0); // The sync pulse ISR needs to have the highest priority of all |||| Had to change from NVIC_SET_PRIORITY(IRQ_PORTC, 0); compiler returned error for “IRC_PORTCD” had to change it into “IRQ_PORTC”

Serial.begin(9600); // USB is always 12 Mbit/sec

pinMode(PIN_SEM_VIDEO, INPUT);
pinMode(PIN_SEM_SYNC, INPUT);
pinMode(PIN_SEM_VSYNC, INPUT);

// Debug pins
#ifdef DEBUG_PINS
pinMode(PIN_DEBUG1, OUTPUT);
pinMode(PIN_DEBUG2, OUTPUT);
pinMode(PIN_DEBUG3, OUTPUT);
#endif

attachInterrupt(PIN_SEM_SYNC, syncfallingISR, FALLING);
// attachInterrupt(PIN_SEM_VSYNC, vsyncrisingISR, RISING);
attachInterrupt(PIN_SEM_VSYNC, vsyncfallingISR, FALLING);

///// ADC0 ////
// reference can be ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT.
adc->setReference(ADC_REFERENCE::REF_3V3, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

adc->setAveraging(4); // set number of averages
adc->setResolution(8); // set bits of resolution

// it can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
// see the documentation for more information
// additionally the conversion speed can also be ADC_ADACK_2_4, ADC_ADACK_4_0, ADC_ADACK_5_2 and ADC_ADACK_6_2,
// where the numbers are the frequency of the ADC clock in MHz and are independent on the bus speed.
adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED);

// it can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed

// always call the compare functions after changing the resolution!
//adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V
// adc->enableCompareRange(1.0*adc->getMaxValue(ADC_0)/3.3, 2.0*adc->getMaxValue(ADC_0)/3.3, 0, 1, ADC_0); // ready if value lies out of [1.0,2.0] V
// In this case, we only want the raw byte vales from the ADC. No need for scaling.

adc->startContinuous(PIN_SEM_VIDEO, ADC_0);
adc->enableInterrupts(ADC_0);
}
void loop()
{
digitalRead(PIN_SEM_VSYNC);
//Serial.println(digitalRead(PIN_SEM_VSYNC));
#ifdef DEBUG_PINS
digitalWriteFast(PIN_DEBUG2, HIGH);
#endif

if (bufptrA > BUF_TX_TRIG && usebufA == 1)
{
__disable_irq();
usebufA = 0;
bufptrB = 0;
__enable_irq();
Serial.write(bufferA, bufptrA);
}
else if (bufptrB > BUF_TX_TRIG && usebufA == 0)
{
__disable_irq();
usebufA = 1;
bufptrA = 0;
__enable_irq()
Serial.write(bufferB, bufptrB);
}
#ifdef DEBUG_PINS
digitalWriteFast(PIN_DEBUG2, LOW);
#endif

}
void adc0_isr(void) {
int tempval;
__disable_irq();
#ifdef DEBUG_PINS
digitalWriteFast(PIN_DEBUG1, HIGH);
#endif
if (digitalRead(PIN_SEM_VSYNC) == LOW)
{
adc->stopContinuous();
}
if (digitalRead(PIN_SEM_SYNC) == HIGH)
{
syncPulseDur = 0; //We detected the start of a sync pulse. Set the auto-incrementing value to 0 so we can measure its length.
adc->stopContinuous(); // Stop the ADC so that we can start it exactly at the end of the sync pulse
}
else
{
if (usebufA == 1 && bufptrA < MAIN_BUF_LENGTH)
{
tempval = adc->analogReadContinuous(ADC_0) + 2; // Values 0 and 1 are used for sync, so 2-255 are data
bufferA[bufptrA++] = (tempval < 255) ? tempval : 255;
}
if (usebufA == 0 && bufptrB < MAIN_BUF_LENGTH)
{
tempval = adc->analogReadContinuous(ADC_0) + 2;
bufferB[bufptrB++] = (tempval < 255) ? tempval : 255;
}
}
#ifdef DEBUG_PINS
digitalWriteFast(PIN_DEBUG1, LOW);
#endif
__enable_irq();
}

void syncfallingISR(void) {
__disable_irq();
#ifdef DEBUG_PINS
digitalWriteFast(PIN_DEBUG3, HIGH);
#endif
if (usebufA == 1 && bufptrA < MAIN_BUF_LENGTH)
{
if (syncPulseDur < SYNC_DUR_THRESH)
{
bufferA[bufptrA++] = HSYNC_VAL;
}
else
{
bufferA[bufptrA++] = VSYNC_VAL;
}

}
if (usebufA == 0 && bufptrB < MAIN_BUF_LENGTH)
{
if (syncPulseDur < SYNC_DUR_THRESH)
{
bufferB[bufptrB++] = HSYNC_VAL;
}
else
{
bufferB[bufptrB++] = VSYNC_VAL;
}
}
adc->startContinuous(PIN_SEM_VIDEO, ADC_0);
#ifdef DEBUG_PINS
digitalWriteFast(PIN_DEBUG3, LOW);
#endif
__enable_irq();
}

void vsyncfallingISR(void)
{
__disable_irq();
if (usebufA == 1 && bufptrA < MAIN_BUF_LENGTH)
{
bufferA[bufptrA++] = VSYNC_VAL;
}
if (usebufA == 0 && bufptrB < MAIN_BUF_LENGTH)
{
bufferB[bufptrB++] = VSYNC_VAL;
}

__enable_irq();
}

 

This sketch can be uploaded into the Teensy with the classic Arduino IDE, by installing first the TeensyDuino drivers.

 

Testing the circuit and the software

 

The freshly made circuit has been tested with a sine wave and the resulting image recorded on the computer screen reflected the trend of it: a black to white to black column on the left of the frame:

50428356_2302246513340865_7544117743415459840_o

The code on the computer side was made by Ben and has been left unchanged (download under the same youtube video). In order to run it, you need to install a software, “Processing“. Once past-copied there, you just need to press “run” and the frame window will appear. Pictures can be saved by just pressing the “s” button on the keyboard.

 

Installation on the SEM and first images

 

Before installation, I decided to add some probing wires in order to make it easier to probe the whole inputs/outputs of the circuit once mounted on the SEM.

51286842_2306085119623671_1672380679318405120_n

 

Everything was set so to minimize the chances of flipping wires and shorts.

51089088_2306338326265017_6859802678612983808_n

It was pretty easy to finely adjust the signals while watching them on the oscilloscope.
The acquisition from the computer needed a bit of adjusting of the main ADC parameters such as sampling and conversion speeds as long as number of averages and resolution bits. So far it has been tested with 8 bit resolution only and the results are really cool!

Next the first pictures acquired with the new system.

capture000capture001capture002capture003capture004capture005capture006capture007capture008capture009capture010

 

Pretty satisfying result indeed! Over the best of my expectations for sure!
Maybe there is still a chance to make them better by rising the bit depth, so I will do some tests in the future.

At last, I think my SEM setup is (almost) definitive as I think there is not much more left to improve at this point!

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s