Controller – 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.

]]>
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.

]]>
Poor man’s IrDA for intermodular communication https://nrqm.ca/2011/06/poor-mans-irda-for-intermodular-communication/ Fri, 24 Jun 2011 21:15:56 +0000 https://nrqm.ca/?p=614 If all goes extraordinarily well, the AUV will have the following modules sitting in the hull:

  • Acoustic modem
  • Motor control
  • Depth control (buoyancy control and pressure sensing)
  • Master controller

Rather than wiring everything together, I plan to give each module its own power supply and use an optical communication protocol to connect the modules to the master controller.  The inside of the hull will be cleaner and more solid than if everything was wired together, and waterproofing will be easier.  Here’s how it works:

I’m thinking about the depth controller at the moment, so take that as an example.  The master controller wants to send a depth set point to the depth controller.  In my current design, such as it is, the master controller sends a signal to a high-power infrared emitter via a transistor.  This could be e.g. a UART signal (inverted to be active high).  The infrared emitter lights up the inside of the hull with the signal sent by the master controller.  Everything receives this signal, so the frame will be addressed to the depth controller.  The infrared signal is transduced back to a TTL UART signal using an optical transistor and a pull-up resistor or filter, and the UART signal is fed to the depth controller’s UART receiver.  The depth controller transmits its detected depth back to the controller in a similar manner.  This is a very simple non-modulated (baseband) infrared communication protocol.

I can foresee several issues that demand exploration:

  • The emitter isn’t strong enough: I hope this won’t be an issue in a small, enclosed, dark sphere, but I might be able to arrange all the modules so that the IR components have line of sight.
  • Infrared absorption and reflection: the module casings will need to be transparent to infrared.  I currently plan to have most or all of the casings be made out of clear acrylic, and I will need to test the infrared transmission through air, acrylic and water.
  • Data rate: the two kinds of optotransistors I bought have rise/fall times of 10 and 15 microseconds, leading to a theoretical maximum throughput of 33 to 50 kbps.  I don’t need high-bandwidth communications though, so the data rate can be lowered easily to reduce interference.
  • Grim shadows: I am not designing the AUV to be sea-worthy, in part so I don’t have to deal with crud building up and blocking the optical signal.  The module casings should stay clean, and with only a couple wires inside the hull this is unlikely to be a problem.
  • Collisions: The master controller will initiate all communications, and all communications will be synchronous.  This is effectively a one-wire bus protocol, kind of like I2C with an asynchronous clock.
  • Other UART errors: Specifically framing errors.  Likely each module will have a crystal.  Even without a crystal, so few data will be transmitted at a time that framing errors should not be a problem, but I will probably keep a sanity check on the UART’s frame error detect bit just in case.

My original idea associated each module with a colour instead of an address, so a module would only receive data transmitted on the correct wavelength.  As a friend once said to me, “Rube Goldberg would be proud.”

]]>