The following sample is written for for the AVR BASCOM compiler and the ARC 1.1 board.  Although the ARC and Atmel AVR processor are used, the techniques illustrated below should work for any modern microcontroller that can set the direction of individual I/O pins and perform analog readings with those pins.  Even if that isn't possible, then using two I/O pins should work: one to drive the sensor and one to do the analog input function.

How to interface with LEGO Mindstorm sensors.

The ARC board was designed to physically mate with the LEGO technique/Dacta hardware.  With one additional component and some software the ARC board can control and read the Mindstorms sensors as well.  All LEGO sensors are two wire devices.  The controller applies power across the terminals (direction does not matter) for a few milliseconds, then disconnects the power and reads an analog voltage across the terminals.  This all happens in a few hundred microseconds.  The process is repeated as long as you are reading the sensor.  Here is a good page explaining sensor operation.

The majority of the time spent reading the sensor is the ADC conversion.  The output signal of the LEGO sensors settles down within 5 us after the power is turned off.  The LEGO sensors need a 10k ohm load resistor to generate the voltage which the controller reads.  Here is a schematic of the circuit I used:

I left out the details of how I hooked it to the ARC board.  Just note, that the board, as shipped, has a variety of components installed that will interfere with simply connecting the sensor.  Please refer to the Users Guide for information about accessing un-used I/O ports, or, reconfiguring the existing ports for direct connections to sensors.  PORTA doubles as the analog inputs to the microprocessor, that is why I use the A port bits in the examples.

The basic algorithm I used in my code was to switch the particular I/O port to an input with the internal pull-up resister disabled; then wait 5 us for the output to settle (determined with an oscilloscope), then do a fast ADC conversion, then switch the I/O pin back to a high level output.  Then, by interpreting the analog value the code could determine what was happening.

ADC input

The ARC analog inputs are read with a 10 bit Analog to Digital Converter (ADC).  This means the values range from 0 to 1023 (0 - +5v).  The output of each LEGO sensor will sink current through the 10k load resistor to give different values from the ADC.  For the sensors, below, I list the rough values read, rather than the voltages.  In setting up the AVR ADC, I chose a very low pre-scale value and clocked the ADC at 1 Mhz.  This is eight times faster than Atmel recommends, but appears to work just fine.  Don't expect to get stable 10 bit conversion at this rate.  But it is good enough for LEGO sensors.  I did this mainly to keep the period of time that the power was off to a minimum (~50 us).


The LEGO switch has an 800 ohm resistor in series, so it can be connected directly across the ARC I/O pin.  When the pin is an output, it can easily drive +5v across the resistor, when it is an input, the resister easily pulls the pin near ground.  For just reading a switch, it is sufficient to keep the I/O line an input and simply read the value.  However, often, switches are put across Light Sensors since a switch closure looks like 100% light, something that is impossible to get with the light sensor.  That way you can use two sensors on one port.

The code is very simple and only added for completeness.  The Light Sensor code, below, could be used to read the switch input.  This code insures that the port is configured as an input before returning the state of the input.

Function Switch() As Byte
  Ddra.7 = 0
  Waitus 5
  Switch = Pina.7
End Function

Light Sensor

Light Sensor read a lower value with increasing light.  Very bright light will cause the lowest reading.  With a sample of two sensors I determined that the output is between ~600 and ~900 counts.  Connecting a switch across the sensor gives a reading of ~10-19.  The sample code inverts the signal and shifts it down ~600 counts so that bright light (indirect sunlight) gives a value of ~200 and black foam (dark) gives a value of ~35.  The code also clips signals outside of the byte range so that very bright light will read 255 and switch closures always read 0.

The code, below, first makes portA bit 7 an input, then it waits 10 us (5 is sufficient), samples the input and then decodes the signal.  Afterwards it makes the port an output driven high to supply power to the sensor.

Function Light() As Byte
  Ddra.7 = 0
  Porta.7 = 0
  Waitus 10
  Light_tmp = Getadc(7)
  If Light_tmp < 600 Then
    Light = 0
  Elseif Light_tmp > 855 Then
    Light = 255
    Light = 875 - Light_tmp
  End If
  Porta.7 = 1
  Ddra.7 = 1
End Function

Rotation Sensor

The rotation sensor is a classic quadrature encoder with four segments.  It gives 16 counts per revolution.  The way the sensor communicates this is a series of four different voltages.  Again, I experimentally determined that 350, 450, 700 and 800 were good  thresholds for the four possible states.  Then I assigned values to each state such that I could write a simple decoder algorithm.  The diagram, on the left, below, shows the varying output voltage as the shaft rotates.  I didn't show the timing for reading a LEGO sensor, just the resulting values read.

The state diagram, on the right, shows the transitions of the output as the shaft rotates left or right.  A simple IF/THEN or look up table using the previous state and the current state will tell you whether to increment or decrement the encoder value.  Note, my hand drawing, has different state labels: I started with the highest value, whereas the code, below, starts with the lowest.

Sub Doencoder()
  Ddra.0 = 0
  Porta.0 = 0
  Waitus 5
  Select Case Getadc(0)
    Case Is > 850 :
      Enc_tmp = 2
      If Enc_prev = 1 Then Goto Inc_encoder
      If Enc_prev = 3 Then Goto Dec_encoder
    Case Is > 700 :
      Enc_tmp = 1
      If Enc_prev = 0 Then Goto Inc_encoder
      If Enc_prev = 2 Then Goto Dec_encoder
    Case Is > 450 :
      Enc_tmp = 3
      If Enc_prev = 2 Then Goto Inc_encoder
      If Enc_prev = 0 Then Goto Dec_encoder
    Case Else :
      Enc_tmp = 0
      If Enc_prev = 3 Then Goto Inc_encoder
      If Enc_prev = 1 Then Goto Dec_encoder
    End Select
  Goto No_encoder
  Enc_val = Enc_val + 2
  Enc_val = Enc_val - 1
  Porta.0 = 1
  Ddra.0 = 1
  Enc_prev = Enc_tmp
End Sub


What Next?

The next thing to do is to re-write the sample code in assembly and to pass the port bit value.  That way each bit can be assigned to it's own input and the same code can be used for multiple inputs.  BASCOM is pretty fast and easy, but it isn't particularly efficient in generating code.  The sample code runs about 700 bytes which is easily twice as big as it should be.

Last  update 16-Mar-2004