Master’s Project – Neil's Log Book https://nrqm.ca What could possibly go wrong? Wed, 23 Jan 2013 01:23:53 +0000 en-US hourly 1 https://wordpress.org/?v=5.4.1 AVR Code for the MS5541C Pressure Sensor https://nrqm.ca/2013/01/avr-code-for-the-ms5541c-pressure-sensor/ Wed, 23 Jan 2013 00:22:56 +0000 https://nrqm.ca/?p=903 I originally wrote code for the MS5541C pressure sensor using bit banging, and described the result of my investigation in the previous log entry.  At the end I mentioned that the sensor’s weird digital interface is kind of like SPI, and looks SPI-compatible, with several differences that could be worked around.  The most notable difference was that reading and writing data happen out of phase with respect to the serial clock edge. It wound up being a little squirrellier than that, but it worked out.  Below is some code for accessing the MS5541C with SPI. It runs on a Seeeduino Mega (which is compatible with the Arduino Mega and uses the ATmega1280 microcontroller), but I’ve eschewed the Arduino libraries for the sake of more control (and, for the ol’ bit-banging, better timing resolution). Some of the code, particularly the SPI stuff, could be replaced with Arduino library calls.

Let’s start, as always, with some definitions:

#define SPI_DDR DDRB	// DDR of SPI port
#define SPI_PORT PORTB	// SPI port
#define SPI_MOSI PB2	// MOSI pin (Master out, Slave in)
#define SPI_MISO PB3	// MISO pin (Master in, Slave out)
#define SPI_SCK PB1	// SCK pin (SPI clock)
#define SPI_SS PB0	// SS pin (Slave Select (not used))
// wait for an SPI read/write operation to complete
#define SPI_WAIT()              while ((SPSR & _BV(SPIF)) == 0);

// bit patterns for reading calib words and sensor data, and for sending reset
#define READ_W1 0b0001110101010000
#define READ_W2 0b0001110101100000
#define READ_W3 0b0001110110010000
#define READ_W4 0b0001110110100000

#define READ_D1 0b0000111101000000	// pressure read command
#define READ_D2 0b0000111100100000	// temperature read command

#define RESET   0b1010101010101010

The only thing worthy of comment is the read commands, which I had to pad out. The commands are only 10 or 12 bits, but we can left-pad them with zeros since the MS5541C will ignore anything it gets before the three-bit start sequence (111). When reading the four calibration words I also had to add an extra 0 after the three-bit stop sequence (000), because the sensor waits for one extra clock cycle before it starts sending back the calibration data. Same story with the commands to initiate pressure and temperature readings, except they wait for two cycles before starting the conversion so I padded them with two extra 0s after the stop sequence.

Function to send commands:

void send_bytes(uint16_t data)
{
	SPCR &= ~_BV(CPOL);
	SPDR = data >> 8;
	SPI_WAIT();
	SPDR = data & 0xFF;
	SPI_WAIT();
}

The commands are all 16 bits, just split them up and send them one byte at a time. SPI’s default is big-endian bit and byte ordering.  The first line of the function clears the SPI CPOL bit, which sets the polarity so that data are read (by the sensor) on the rising clock edge.

Function to read data back:

uint16_t get_bytes()
{
	uint16_t data;
	SPCR |= _BV(CPOL);
	SPDR = 0;
	SPI_WAIT();
	data = SPDR;
	data <<= 8;
	SPDR = 0;
	SPI_WAIT();
	data |= SPDR;
	return data;
}

Every piece of data is a 16-bit integer, so the program never has to read anything other than two bytes. Writing a 0 value to SPDR will initiate a transfer where the SPI module will send 0 on MOSI and read whatever the sensor sends back on MISO. Once SPI_WAIT() finishes, the incoming data will have replaced the outgoing data in SPDR and can be read back out. The second line of the function sets the CPOL bit so that data are read (by the microcontroller) on the falling clock edge.

Function to get calibration coefficients:

typedef struct {
	uint16_t SENST1;
	uint16_t OFFT1;
	uint16_t TCS;
	uint16_t TCO;
	uint16_t Tref;
	uint16_t TEMPSENS;
} coeff_t;

void get_coeffs(coeff_t* coefficients)
{
	uint16_t w1, w2, w3, w4;

	send_bytes(READ_W1);
	w1 = get_bytes();
	send_bytes(READ_W2);
	w2 = get_bytes();
	send_bytes(READ_W3);
	w3 = get_bytes();
	send_bytes(READ_W4);
	w4 = get_bytes();

	coefficients->SENST1 = w1 >> 3;
	coefficients->OFFT1 = (w1 & 0b111) << 10;
 	coefficients->OFFT1 |= w2 >> 6;
	coefficients->TCS = w3 >> 6;
	coefficients->TCO = w4 >> 7;
	coefficients->Tref = (w2 & 0b111111) << 6;
	coefficients->Tref |= w3 & 0b111111;
	coefficients->TEMPSENS = w4 & 0b1111111;
}

This function just reads the four 16-bit factory calibration words, and then unpacks them into the coefficients in the manner defined in the datasheet. The datasheet often refers to the coefficients as C1 through C6, they're in that order in the structure.

Reading the temperature:

int16_t get_temp_diff(coeff_t* coeffs)
{
	uint16_t T;
	uint16_t UT1;
	int16_t dT;
	int32_t dT_sq;
	send_bytes(READ_D2);
	_delay_ms(33);
	T = get_bytes();
	// find reference temperature
	UT1 = 8 * coeffs->Tref + 10000;	// maximum value is 42760
	dT = T - UT1;		// find difference between temperature reading and reference
	// calculate second-order temperature differential
	dT_sq = dT;
	dT_sq *= dT;	// dT squared
	dT_sq /= 128;
	dT_sq /= 128;	// looking for (dT/128) * (dT/128)
	if (dT >= 0) dT_sq /= 8;
	else dT_sq /= 2;
	// correct dT using second-order differential
	return dT - dT_sq;
	return 0;
}

int16_t get_temperature(int16_t dT, coeff_t* coeffs)
{
	int32_t acc = dT;
	acc *= (coeffs->TEMPSENS + 100);
	acc /= 2048;
	acc += 200;
	return (uint16_t)acc;
}

Temperature is obtained in two steps. First we get the temperature differential, which is the difference between the actual temperature and the reference temperature, Tref. Second we use that along with the TEMPSENS constant to calculate the actual temperature in units of 0.1°C (that is, a result of 200 corresponds to a temperature of 20.0°C).

I'm doing the second-order temperature differential correction as the datasheet suggests. At room temperature the second order differential is pretty close to 0.

Only the temperature differential is used to calculate the pressure. The actual temperature value isn't needed except for display and otherwise doesn't need to be calculated.

Calculating the pressure, in millibars:

uint16_t get_pressure(int16_t dT, coeff_t* coeffs)
{
	int16_t off;	// offset at temperature
	int32_t sens;	// sensitivity at temperature
	uint16_t P;		// pressure value
	int32_t pressure;	// actual pressure in mbar

	off = coeffs->TCO - 250;
	off *= dT;		// this shouldn't overflow, but watch out.
	off /= 4096;
	off += coeffs->OFFT1 + 10000;

	sens = coeffs->TCS + 200;
	sens *= dT;
	sens /= 8192;
	sens += coeffs->SENST1 / 2 + 3000;
	send_bytes(READ_D1);
	_delay_ms(33);
	P =  get_bytes();
	pressure = P;
	pressure -= off;
	pressure *= sens;
	pressure /= 2048;
	pressure += 1000;
	return (uint16_t)pressure;
}

This function just goes through the flow diagram in the datasheet. First it calculates the ADC offset and sensitivity, based on the temperature differential. Then it measures the actual pressure, and does math to it to convert the ADC units into millibars.

The datasheet specifies that each ADC conversion takes 33 ms to complete (I measured it to be around 31.3 ms). It actually outputs a trigger on the MISO pin whose falling edge indicates that the conversion is done. Unfortunately the SPI pins can't seem to be read without disabling the SPI module. One solution would be to tie the MISO line to an separate pin, and poll that while the conversion runs. The separate pin could also be an external interrupt that waits for a falling edge.  That's probably the right way to do it, but I just stuck in a busy wait.

The last software issue that needs to be considered is clock frequencies. The MS5541C has two clock inputs: MCLK, the master clock, and SCLK, the serial clock. The latter is generated by the SPI module, and can be at most 500 kHz. The former is intended to hook up to a 32,768 Hz clock source. It's unbuffered, so the clock voltage swing has to be the full TTL-compatible range. You could hook up a buffered watch crystal, I just generated a close-enough clock from the microcontroller as a PWM signal.

I've omitted the configuration code for the SPI, UART printing, and PWM signals. You can download the complete program here. The datasheet recommends averaging 8 samples to get an accurate result; I did not do so. The code assumes the following pin mapping to the digital pins on a Seeeduino Mega (AVR pin identifiers are noted in brackets):

MS5541C -> Seeeduino (AVR)
SCK -> D52 (PB1)
DOUT -> D50 (PB3, MISO)
DIN -> D51 (PB2, MOSI)
MCK -> D11 (PB5)
Vcc -> 3.3 V
GND -> GND

N.b. that the MS5541C is a 3.3 V device. The Seeeduino uses 5 V I/O, so the signal lines that go from the Seeeduino to the MS5541C (MOSI, SCLK, and MCLK) need to have voltage dividers or level shifters to step the voltage from 5 V down to 3.3 V. I used voltage dividers with resistor values of R1 = 22 kΩ and R2 = 43 kΩ.

]]>
Interfacing AVR/Arduino with the MS5541C pressure sensor https://nrqm.ca/2012/12/interfacing-avrarduino-with-the-ms5541c-pressure-sensor/ Mon, 31 Dec 2012 03:51:28 +0000 https://nrqm.ca/?p=833 The MS5541C is kind of an oddball sensor, BUUUUUUUT it’s small, it can be used underwater (with appropriate waterproofing), it’s cheapish (~$30), and Digikey sells it. It’s a pain to get running though, because of its hardware interface (50 mil pitch, surface mount (and not the good kind of surface mount either)) and its data interface (which doesn’t implement any particular standard protocol).

I tried carving out an interface board using some copper-clad PCB and a rotary tool, but I didn’t do a good job and it was really hard to get the 50 mil pads to line up with the messily-carved copper traces.  I used a heat gun to solder the sensor onto the PCB, and it almost worked—but two of the pins were shorted.  And of course a copper pad got torn off of the sensor when I tried to remove it from the PCB.  I bought another one and connected it to a 100 mil header via some ribbon cable.  That’s the wrong thing to do.  The datasheet specifies that the sensor should be securely fastened to a circuit board to prevent it from flexing, although it hints that might be just to prevent stress on the solder pads (which are, as previously noted, not incredibly strong) and not necessary to improve sensor performance.  The spec also says that a 47 μF tantalum decoupling capacitor should be placed as closely as possible to the sensor.  That is a noise reduction requirement, but whatever, there’s one on the Arduino that I have the sensor hooked up to and that’s good enough for now.  In any case, eucatastrophically, it works.

The data interface is a little easier to work out.  Like I said before, it doesn’t use a standard protocol, presumably to keep the internal circuitry as uncomplicated (and small and low-power) as possible.  The thankfully decent datasheet defines the protocol using electrical timing diagrams, and fortunately it’s simple enough once you work through it:

Example timing diagram for MS5541C.

Example timing diagram for reading W1 and W3 calibration words from the MS5541C internal ROM, copied from the datasheet.

There are six 16-bit data words that can be read over the MS5541C’s data interface (there are no writable words).  Four calibration words hold six constant values awkwardly packed together.  The other two words are to obtain the temperature and pressure measurements.  The diagram above shows the waveform diagram for reading two of the data calibration words, W1 and W3.  The waveform for the W2 and W4 words is identical except the rising edge that precedes the data signal on DOUT happens 1 clock cycle sooner.  The waveform for triggering the temperature and pressure measurements is also similar, and has a 33 ms conversion delay whose completion is signaled as a falling edge on DOUT.  You can look at the datasheet for more details on the protocol’s specifics.

You might look at that diagram and notice it’s a lot like an 8-bit SPI protocol, with a few subtle differences.  Well observed, hypothetical reader!  I think the protocol should be implementable using SPI, with these caveats:

  • The command pattern on DIN is 12 bits (10 bits for the temperature/pressure read commands, and 16 for the reset command).  This isn’t a problem, the command word can be padded out to 16 bits and sent as two bytes.
  • The sensor doesn’t send anything back on commands, and sends back data when receiving 0 bits.  No problem, just send 0 bytes to read the slave output and ignore the output when writing commands.
  • There’s no select pin, so you can’t put the sensor on an SPI bus unless you turn the sensor on and off at its power source.  This is fine as long as there’s nothing else on the SPI bus.
  • The DIN and DOUT pins are read 180° out of phase.  This is potentially a problem.

To elaborate on the fourth point, the sensor reads command bits from DIN (viz MOSI) on rising clock edges and returns data bits to DOUT (viz MISO) on falling clock edges.  In the AVR’s version of SPI data in either direction are always valid on the same clock edge: the rising edge by default, or, if the SPI module’s clock polarity bit is reversed, on the falling edge.  Hey, that suggests a solution to the 4th item:

reverse_polarity

You ought to be able to toggle the SPI module’s polarity bit between transmitting and receiving, so that the master transmits data on the rising edges and receives data on the falling edges.

I’ve implemented the code by bit banging because I didn’t notice the SPI similarity until I was done doing it manually.  It’s a mess.  When I’ve got the SPI version working I’ll put up some code.

]]>
Waterproofing 3D Prints (and also making them look super-cool) with Epoxy Clay https://nrqm.ca/2012/11/waterproofing-3d-prints-epoxy-cla/ Thu, 15 Nov 2012 07:29:51 +0000 https://nrqm.ca/?p=807 It’s pretty hard to get a watertight object out of our Makerbot Thing-O-Matic.  The walls of printed objects are pretty solid, but unexpectedly porous; even a thick block printed with 100% infill will allow water to penetrate it due to errors around the edges and imperfectly fused strands of plastic.  If you want to make a hollow object waterproof you’re going to have to do some post-processing.

It gets worse when the object is a curved surface, as is my AUV hull.  I’ve read that objects can be made watertight by adding outer shells.  That may be true for some objects, but on objects that curve along the z-axis the number of shells exposed to the surface grows as the tangent plane gets closer to parallel to the printer’s build platform—and big holes start to form.

My hull doesn’t actually have to be watertight, it’s a wet hull.  But it has to be airtight in order to hold the bubble of gas that controls the robot’s buoyancy.  Here’s what the airtight hull looks like:

Picture of Finished AUV Hull

Finished (‽) AUV Hull.

So that’s kind of neat.  I did the job using the following procedure:

  1. Clean the surface of each semidemihemisphere with isopropanol or something else that dries clear.
  2. Assemble each hemisphere with big wads of epoxy clay filling in the gaps between the four semidemihemispheres.  Then slather the assembled hemisphere in more epoxy clay so that it’s fully covered.  Then leave it until the clay cures.
  3. Sand down the epoxy clay until the plastic shows through (I used a 120-220-400-800 grit progression of sandpaper).
  4. Coat with lacquer to make it shiny.  I used shellac at first, then switched to polyurethane as it was easier to use (shellac has a very short work time and also discolours in water) and food-safety wasn’t a concern.

I actually did step 2 in reverse because I’m a terrible engineer and didn’t think to seal the gaps between the semidemihemispheres with clay until after I’d done the outer surfaces.

Epoxy clay turned out to be an effective material for smoothing and sealing 3D prints.  I bought 2 ounces of the reddish-brown (“flesh” coloured) stuff on eBay and later supplemented it with a 1 lb. white batch of the unsettlingly spelled Apoxie Sculpt from Sculpture Supply Canada (n.b. what I discovered regarding the difference between Apoxie Clay and Apoxie Sculpt).  The clay doesn’t adhere well to ABS as you’re applying it, I had to keep my fingers lubricated with water so that I could press the clay into the object’s filament ridges without the clay sticking to me.  (The water also made it easier to smooth the clay out so that I could squish it into a thin layer that was quick to sand down.)  Once the clay cures it’s really fricking hard to get it off of whatever it’s on, but it sands and drills cleanly.

The clay can also be used for filling holes and errant curves (e.g. due to the bottom of a print curling up as it cools) and for making good-looking, smooth, heat-resistant surfaces (I made a totally sweet coaster).  It also takes acrylic paint a little better than ABS plastic does, which isn’t saying much.

Another miraculous substance that I’ve discovered is Plumbing Goop.  It’s clear, creates a thin layer, is very easy to apply, and sticks really well to ABS.  It doesn’t peel off like silicone rubber.  It also provides a rubber-like grip, something that bare ABS is sorely lacking.  The downsides: because it’s applied in a thin, clear layer, it’s harder to get a thorough seal with Plumbing Goop than with epoxy clay; it doesn’t make spherical prints look like Jupiter; it doesn’t smooth out the filament ridges; and it’s flexible (comparable to silicone rubber), which might not actually be a downside depending on what you’re doing.

]]>
Underwater Solenoid Valve Remake https://nrqm.ca/2012/04/underwater-solenoid-valve-remake/ Sun, 22 Apr 2012 19:08:14 +0000 https://nrqm.ca/?p=767 I tried a solenoid I scavenged from underwater valves on my 3D printed valve system and it didn’t work (duh).  It was just too weak.  The original valve spreads the force from the high-pressure side across a larger area, so I guess the spring return can be comparatively weak.  My design didn’t do that.  I also didn’t cut my compression spring down very much, but I qualitatively determined that the solenoid wasn’t generating a useful amount of force by holding it on the magnetic core while turning the power on and off a bunch of times.  I am a terrible engineer.

So I went back to square 2 and decided to remake the original brass valve body in lighter ABS plastic using our Makerbot 3D printer.  The beta version looked like this:

Picture of a remade underwater solenoid valve with 3D-printed structure.

Remade underwater solenoid valve with 3D-printed structure.

I re-used the gasket, screws, core, and shaft from the original valve (where the solenoid sits), and replaced the big brass parts with light ABS plastic that is customized to fit onto my hull.  The steel shaft press-fits onto the shaft holder at the top of the photo.  The shaft holder screws into the connector piece, which implements the same idea as the brass valve piece, but instead of the input being a pipe fitting the input is open to the inside of the robot hull.  Note that in the photo I’m holding the valve upside-down, the solenoid shaft will hang down from the top of the robot hull, and the other end of the valve will bolt onto the hull’s top using the existing screw holes, pressing the base of the conical shaft against the hull’s top hole (apparently I’m a terrible photographer too).  When the valve opens, the air inside the hull will flow through the conical shaft out of the hull’s top hole and will be replaced by water flowing in through the bottom hole.

The new valve did in fact work.  The solenoid successfully pulled back the gasket to open the valve when activated, and it almost sealed when the power was disconnected.  The seal wasn’t perfect because it printed over a bump in the 3D printer’s build platform.  The plastic was 277 grams less massive than the original brass, and with a tweak or two the new valve will bolt onto the hull efficiently.

The valve had a few problems.  The steel shaft’s wide screw part was too wide for the hole it was supposed to screw into, so I had to file the hole out until I could press-fit the shaft in.  The support structures on the interface piece are too spindly, as you can see in the photo.  Also the square plate on the interface piece was a little thin, and it sat a little too short.  The corners  of the square hit other structures inside the robot hull, and it needed to be about 1 mm farther away from the nut holders.  In the new version I have tried to rectify these issues:

Model of a new version of the valve connector structure.

Updated valve connector structure.

The square plate is now half again as thick, and the nut holders (the bits sticking out near the top of the conical shaft) are 1 mm farther away from the plate.  The supports are a lot thicker, too: they’re 4 mm wide instead of 2 mm, and the six supports that don’t connect to the nut holders flare outward stylishly.

There’s one other issue that I foresee.  The heavy copper solenoid and the steel shaft/magnetic core sit pretty high in the hull, and raise the robot’s centre of mass.  This is a potential problem because if the centre of mass is too high the robot will roll too much in the water.  I can add extra ballast to lower the centre of mass, which may or may not be fine, depending on what kind of buoyancy the rest of the hull has.  I might actually have to do some engineering to figure that out.  Or, I can try it and see what happens.

]]>
Cravenly resorting to commercial solutions in underwater solenoid valves https://nrqm.ca/2011/12/cravenly-resorting-to-commercial-solutions-in-underwater-solenoid-valves/ Sat, 24 Dec 2011 06:16:13 +0000 https://nrqm.ca/?p=683 I’m having trouble with the upper valve that will allow air to be released from the hull (thus decreasing buoyancy and giving some downward thrust).  I mean, I’m having trouble with everything, but that’s why I’m doing this, right?  Anyway, I decided to order a couple underwater solenoid valves in the hope that I could stick one in the robot and have it work (hahahahaha).  I found several suppliers in China listed on this site alibaba.com, and settled on Nuoling Pneumatic.  Most of the other options either weren’t waterproof, were too big, didn’t support a 12 V power supply, or had a large minimum order size. The two I bought were $12.35 each, which is less than I was expecting (although the shipping was $55 for two units).

Underwater solenoid valve from Nuoling Pneumatic

Underwater solenoid valve from Nuoling Pneumatic

The valves were a lot bigger and heavier than I expected.  You can see it’s basically a big chunk of brass connected to the waterproof electrical components.  Fortunately it’s easy to take apart:

Inner workings of a solenoid valve.

Inner workings of this solenoid valve.

The valve assembly consists of three parts: the brass base; a rubber gasket connected to the spring-mounted iron solenoid core; and a metal sheath that encloses the core, bonded to the upper brass component.  (The solenoid is mounted on the metal sheath.)

Inside the solenoid valve's brass base.

Inside the solenoid valve's brass base.

The above photo shows the structure inside the brass base.  Notice the arrow indicating the expected direction of flow through the valve.  The rubber gasket seals the inner aperture, and the pressure of the incoming fluid is dispersed around the perimeter of the large outer chamber so that it doesn’t push the gasket up.  When the solenoid is activated it pulls the iron core up, which pulls the gasket away from the inner aperture.  Fluid flows under the lifted gasket into the aperture until the solenoid is de-energized, at which time the iron core is released and the spring return pushes the gasket against the aperture’s lip to seal it back up.

Solenoid valve specification.

Solenoid valve specification.

This label shows the valve’s specification, including the desired 12 V power supply.  In fact, I tested with a fully charged 7.2 V Li-ion battery and it still actuated in air.  The operating pressure label is a little disconcerting, as the datasheet the supplier sent me specified a working pressure of up to 500 kPa.  A 0.10 kgf/cm^2 pressure corresponds to less than 10 kPa.

The valve's waterproof solenoid.

The valve's waterproof solenoid.

The solenoid is made of a copper winding connected to some nice, solid leads.  The winding is coated in an encapsulant, which is a pretty thin layer of some kind of urethane-like material, and wrapped in a fabric mesh to hold everything together tightly.  I haven’t tried it underwater yet, but this part (housed in the black casing in the photo at the top of this post) is exposed to the environment so I assume it’s actually waterproof.

This may turn out to be the most useful part of the valve.  I don’t think I can justify putting all that brass and steel in my underwater robot, it’s just too heavy.  By a happy coincidence, the valve I made previously fits perfectly inside this structure, so hopefully I can just mount this solenoid on my existing valve (or maybe an elongated version of the current valve.  I tried, and my spring return is too strong–the compression spring on the solenoid’s iron core is surprisingly febrile–but this gives me hope that I can cut it down further and reduce the amount of force needed to pull the valve open.

One problem I foresee is that in my configuration, the transvalve pressure will be pushing the valve closed, so the deeper the robot, presumably the more force is needed to open the valve.  It’s not a big deal, it will just limit the robot’s depth, which is better than making the robot unrecoverable.  I’ll be happy if the stupid valve opens at all.

]]>
Un-improving range on the infrared channel https://nrqm.ca/2011/10/un-improving-range-on-the-infrared-channel/ Tue, 04 Oct 2011 03:51:46 +0000 https://nrqm.ca/?p=668 It turned out doing software UART was a terrible idea.  The processor is way too slow to support a reasonable baud rate.  I did figure out how to use a comparator though: the key phrase I was missing was “rail-to-rail.”  That means that inputs can be in the full voltage range from ground to Vcc.  Another handy phrase is “push-pull,” which means that the comparator can output 0 and 1; in contrast, an “open collector” comparator can only output 0, and needs an external resistor to pull the output to 1.

I bought a rail-to-rail push-pull comparator, the MCP6541, and tried it with the receiver circuit, and sure enough it increased the maximum range significantly.  Unfortunately it also increased the minimum range significantly.

Here’s the circuit diagram for the modified receiver circuit:

Infrared receiver with comparator for amplification.

Infrared receiver with comparator for amplification.

The 1 kΩ and 10 kΩ resistors are in a voltage divider configuration to generate a 3.0 V reference against which the input is compared.  If the input is higher than 3.0 V, the comparator outputs 3.3 V, and otherwise the comparator outputs 0 V.

I guess there are a bunch of transistors inside the comparator, and putting them in a chain with the pair of external transistors messes things up.  I don’t know why, but it might be a gain-bandwidth thing (too much gain, lowers the maximum frequency the circuit can operate at).  This is what the output looks like at about 6.5 cm:

'U' character sent over infrared with infrared amplifier.

'U' character sent at 19200 bps over infrared with comparator amplifier.

The jagged bits aren’t there without the comparator.  I used the U character to test because its binary pattern is 01010101.  As you can see, the pattern is there but it’s kind of messed up and it’s dangerously close to 3.0 V.  (Now that I look at this again I see maybe reducing the 3.0 V threshold would improve the minimum range.)

I figured I could put an OPA2134 op-amp voltage follower in between the external transistors and the comparator.  This would de-couple the two sets of transistors, so the weird transistor chaining effect would disappear.  This worked perfectly, once, for no apparent reason at all.  Eventually, after spending a day trying to reproduce my success, I read the op-amp’s datasheet more closely and discovered that it’s not rail-to-rail and should never have worked (it can’t output the full 3.3 V signal, so the comparator’s input was never reaching 3.0 V).

I switched to a JFET op-amp that had rail-to-rail output, and it was able to transmit the ‘U’ character at a good set of ranges, both small and large.  Unfortunately this is the signal it was outputting, at 7.5 cm and at 2 cm:

'U' sent at 19200 bps over infrared with JFET voltage follower at 7.5 cm.

'U' sent at 19200 bps over infrared with JFET voltage follower at 7.5 cm.

'U' sent over infraret with JFET voltage follower at 2 cm.

'U' sent at 19200 bps over infrared with JFET voltage follower at 2 cm.

The observant among you, my imaginary audience, will notice two strange things from this screenshot:

  1. The signal is peaking at almost 4 V.
  2. It’s sending 9 bits at 7.5 cm and 19 bits at 2 cm.

Hoo boy, it didn’t work at all, and it was pure luck that the ‘U’ transmitted correctly at short range (followed, presumably, by a framing error that my system silently ignored).  No other character worked.

I found out that you can use an op-amp as a comparator, so I tried that with my OPA2134:

'U' sent at 19200 bps over infrared with op-amp comparator.

'U' sent at 19200 bps over infrared with op-amp comparator.

Okay!  You can see what it means that the amplifier is not rail-to-rail, it’s only going up to 2.6 V (and that only briefly), but if I remember correctly it received.  Unfortunately the high bits got narrower for some reason as the range decreased, so it didn’t actually do anything to solve the range problem.

I tried using the JFET amp as a comparator, but it didn’t output anything at all so I gave up and ignored the project for a few weeks.  My current solution is to pretend there isn’t a problem, and if I run into the  minimum range issue then I can just bend the receiver askew so that the signal is damped enough to receive.  In the meantime, the transmitter now has a wider field of view, which ought to make it easier to place several receivers in range of the control unit.

]]>
Improving range on the infrared channel https://nrqm.ca/2011/09/improving-range-on-the-infrared-channel/ Sat, 03 Sep 2011 19:15:55 +0000 https://nrqm.ca/?p=663 The range on the infrared channel, which I discussed in the last entry, is probably enough; but I’d like to increase it a bit.  With more range I can space modules farther apart if needed, and hopefully be able to have a wider angle between the transmitter and receiver.

Fortunately the signal output by the Darlington transistor pair on the receiver is a pretty clean digital signal.  At full power it ranges from (a little above) 0 V to (a little below) 3.3 V.  As the transmitter gets farther away the digital signal remains but the low voltage increases beyond the UART receiver’s ability to read a 0.  For example, at a large distance the UART signal might range from 2.5 V (logical 0) to 3.3 V (logical 1).

There are several options I’ve considered:

The handy old Schmitt trigger is the most straightforward, but it’s touchy to design and it will add a bunch of parts (an op-amp and a bundle of resistors).  I tried using a comparator chip, which is like the Schmitt trigger but simpler, but I found that it had a big limitation.  It worked, but the inputs can’t exceed Vcc – 1.5 V (the “common mode input voltage range” on the datasheet).  It can’t read a digital signal ranging between 1.8 (or more) and 3.3 V, so it would provide some amplification but not a lot.  I’m targeting 90% of Vcc as my threshold, so it should read anything below 3.0 V as low.  I also considered using another PNP transistor to amplify the difference between Vcc and the input voltage, but: that much amplification is risky noisewise, an extra transistor reduces the bandwidth too much, and the PNP transistor will produce a logically inverted signal.

Another option is to amplify the signal using crazy op-amp magic, but aaaaaaah no.

So I tried thinking like I’m supposed to for once, and I’m going to try a software solution.  AVR processors include an analog comparator, which I can (hopefully) use to amplify the digital signal coming from the infrared optotransistor.  The downside to doing it this way is that it bypasses the UART module, which means that the application needs to receive data manually (I can still use UART to transmit the signal).  Receiving data will tie up the processor, but as long as there’s enough time to run the receiver code it should be okay, I don’t foresee much data traffic.  Here’s a potential flow chart for the receive procedure:

Software receiver flow chart.

Software receiver flow chart.

Some notes:

  • The wait process is a euphemism for “disable the timer, store the data, blah blah blah, then go back to the program until the next start bit.”  This process takes place during the stop bit, so if there’s another byte incoming then it should finish in time to receive the next start bit.
  • Kevin Rosenberg’s awesome tool AVRCalc calculates that the 8-bit timer running at 8 MHz can be used to generate a 57554 Hz interrupt rate using an OCR value of 0x8A.
  • I might need to mess around with the timing so that the AC reads the right bit, but probably not.  There should be plenty of interrupt overhead delay, and the sampling rate is a little slow and will drift toward the end of the bit.
  • I’m not sure how long the analog comparator takes to produce a result but I’m guessing it’s pretty fast because it doesn’t have to do much.  The AVR datasheet doesn’t have much information on it.  I also don’t know if the comparator has a large common mode input range.

Failing that, I can look for an external comparator with a large common mode input range, or just suck it up and use an op-amp.

]]>
Transmitting UART serial over infrared https://nrqm.ca/2011/08/transmitting-uart-serial-over-infrared/ Sat, 20 Aug 2011 04:50:34 +0000 https://nrqm.ca/?p=658 Here’s how I’d like to communicate between modules in the AUV:

UART-over-IR transmitter (left) and receiver (right).

This is how it works:

For the transmitter I’m using a TSAL6100 infrared emitter.  The LED is rated to operate at 100 mA, which the 15 Ω resistor generates (I actually calculated a 22 Ω resistance and bought 22 Ω resistors; two of those in series, in parallel with another one results in around 15 Ω).  I will actually use a 10 Ω resistor to get a bit more power out of the LED.  This is safe because the LED will only be turned on for brief periods.

The transmitter uses a PNP transistor.  When the Tx-o pin is low, current will flow from the emitter (the side with the arrow) into the base (the Tx-o pin) and the transistor will open, sending current to the LED.  When the Tx-o pin is high current will not flow from the emitter and the transistor will be closed.  This effectively inverts the UART signal, which is good because the Tx-o line spends its idle time high and only goes low when sending data.  The LED only turns on when the Tx-o pin is sending a 0.

The receiver circuit is based around a TEFT4300 phototransistor.  The current generated by the phototransistor is amplified by another NPN transistor (i.e. a Darlington pair configuration).  The resistor is a pull-up resistor, keeping the Rx-i line high while idle.  When the phototransistor is activated by the transmitter LED (a 0 bit is transmitted), the signal is amplified by the NPN transistor and the Rx-i line is pulled to slightly above ground.

I found that the pull-up resistor in the receiver circuit is a little sensitive.  With a 10 kΩ resistor the receiver is not responsive enough to generate a square wave matching the transmitted signal.  With a 10 Ω resistor the NPN transistor saturates and never reaches the low threshold.  I tried the 1 kΩ resistor and a 390 Ω resistor, and qualitatively decided that the 1 kΩ gave a little more range.

The transistor switching frequencies determine the maximum speed of the UART link.  The NPN and PNP transistors both have bandwidths exceeding 1 MHz under the conditions presented above.  The phototransistor is limited to 180 kHz, which is plenty for this application.  It is sufficient to program an Arduino (57600 bps) or Netduino (115200 bps) wirelessly.  More bandwidth could be obtained using photodiodes, which are very fast but a little more expensive than phototransistors.

I haven’t measured the range of this system yet, but eyeballing it it’s around 15 cm with the 10 Ω resistor on the transmitter.  I could improve this by amplifying the signal at the receiver (e.g. with a Schmitt trigger), but 15 cm is probably enough for my application, as long as it works underwater.

]]>
Finished 3D-printed AUV hull https://nrqm.ca/2011/08/finished-3d-printed-auv-hull/ Thu, 18 Aug 2011 02:24:22 +0000 https://nrqm.ca/?p=650 The hull is done for now.  As I mentioned before, I have some improvements in mind (mainly to get rid of the outer bolts), but it took about 34 hours to print all eight pieces and I’m not eager to do it again.  If I ever get around to putting motors on this thing then I will have to re-print at least four of the semidemihemispheres, as the current ones don’t have any mounting points for motor attachments.

Pictures and video follow:

A semidemihemisphere with overhang supports attached.

A semidemihemisphere with overhang supports attached (but not the walls that the cutaway rests on).

Cutting away an overhang support.

Cutting away an overhang support.

Full AUV hull.

The full AUV hull. Neat!

Here are two videos of a semidemihemisphere being printed. Part 1:

Part 2 (the cool part):

]]>
What I learnt making a 3D printable AUV hull (Part 2) https://nrqm.ca/2011/07/what-i-learnt-making-a-3d-printable-auv-hull-part-2/ Mon, 01 Aug 2011 03:09:05 +0000 https://nrqm.ca/?p=640 I printed four copies of the semidemihemisphere and refined it a bit as I went.  Here’s the whole thing, including the hull that I covered in my last log entry.  The only difference in the hull is I removed the top hole, as only two of the semidemihemispheres need it (as valve mounting holes).

Tweaked version of the AUV model.

Tweaked version of the AUV model.

I made a few interesting changes to the cutaway portion though:

The cutaway is needed to hold up the hull while it prints.  Without the cutaway, the plastic, heated to its melting point by the Makerbot, will droop and hang or fall.  The cutaway gives the hull something to sit on as the plastic cools.  Here is the cutaway portion, viewed from the side:

AUV hull cutaway pieces from the side.

AUV hull cutaway pieces from the side.

A – I added a bunch of little bits on the bottom layer of the model.  This is a hack to get the printer to print a larger raft.  (The raft is a thick layer of plastic that the model sits on as it prints, intended to let the model stick well to the build platform.)  I had to reduce the raft margin so that it would fit on the build platform, but with the smaller raft the pieces were susceptible to being knocked around by the toolhead as it goes.  These pieces get raft added around them, which merges with the raft of the actual model so that the real pieces get a nice stable raft.

B – This bit connects the cutaway material to the hull.  The hull is getting pretty tall at this point, so if it gets hit by the toolhead there’s a force applied that is strong enough to knock around the hull and perhaps misalign the layer.  This part (and its equivalent on the other side of the model) help hold the hull steady against the cutaway shells as they get close to being joined together.

C – This bar connects the cutaway shells together to keep them steady.  In a previous model (shown in the last log entry) I made this part too wide, and it took so long to print the horizontal bar that the hull cooled too much and split a little.  It wasn’t anything a little plastic welder couldn’t fix, but the supports don’t need to be very wide to fulfill their purpose.  After shrinking them to 1 mm wide the printer could print one bar layer with just two quick strands of plastic.

AUV hull cutaway pieces from the top.

AUV hull cutaway pieces from the top.

D – This is the support for the upper tab that hangs freely over the semidemihemisphere.  You can see it from the side at the top of the cutaway side image above.  This is a pretty cool piece that’s anchored vertically on the cutaway shell, and ends up a horizontal flat surface at the upper tab bottom.  It is topped by a 0.5 mm shell that the tab sits on (see E).  I did this piece with a loft from a rectangle on the vertical plane to a rectangle on the horizontal plane.

E – The lattice pattern holds up the hull’s overhang.  The lattice rests on a 2-layer thick platform.  The bottom layer of the platform is printed over open space, and droops badly.  The second layer of the platform is printed on top of that and is pretty flat.  Then the lattice is printed on the nice flat layer, and the actual hull is printed over the lattice.  The lattice is sparse enough that it can be separated from the hull easily with a sharp utility knife (same as the shell in D).  The squares in the lattice are 5 mm to a side (I originally used a 10 mm lattice, but it was too sparse and the hull overhang drooped too much) and 0.5 mm thick, i.e. the width of one strand of plastic.  Note that the top-most (innermost) hull overhang is narrow enough that it doesn’t need to be supported by a lattice.

F – The circular patterns support the circular holes in the hull.  Without these the Makerbot would try to print a circle over the lattice, and it would just collapse into the open spaces.  The square support on the upper lattice supports the receptacle that accepts the upper assembly tab.  Without it, the printer would again try to print the square over the lattice and the first layer would fall into the holes.  With the support the first hull overhang layer is a full surface, and the receptacle square sits on that full layer instead of the lattice.

Here is an old version of the cutaway that had a bunch of problems:

An early, problematic cutaway version.

An early, problematic cutaway version.

Problem 1: The bottom layer had these wedges to hold everything together and provide a solid base.  After I started using a raft these were no longer necessary; in fact, they were a detriment because they took a long time to print and the first couple hull layers cooled too much and came apart.

Problem 2: As stated before (C) the cutaway support bar is too wide and takes too long to print.

Problem 3: The lattice is radial instead of square.  The printer couldn’t print the off-angle 0.5 mm lines.  The lattice would be printed near the edge of the lattice pattern, but there wouldn’t be anything in the middle, and the hull overhang would not get its support.

Problem 4: The circle supports in the lattice are too thin (0.5 mm) and they don’t get printed properly.

Problem 5: The innermost cutaway support is thicker than the others.  Once again, there was no benefit to this and it took significantly longer to print than the thinner version in the later versions.

]]>