I’ve been working on a Giant Lego Calculator using Lego Mindstorms EV3s to drive a large mechanical display. Here you’ll see some of the progress I’ve made in designing a working digit. I’m excited to show a much improved version of my previous design that is more readable, and has a nice cover made of technic parts.

These pictures show you a single digit unit. The final calculator is composed of 8 of these units running in parallel. Each digit is controlled by a single EV3, and the segments are driven by 4 EV3 medium motors.

Let’s take a look inside

Watch it in action

The first video shows the segments moving without a cover plate in place. This allows you to see the gearing internally.

 

The second video shows the final design working with the cover plate in place.

 

How does it work?

Now obviously 4 motors are not enough to drive 7 segments, so what I’ve done is grouped segments into linked pairs, each pair driven by a single motor. If you look at the digit the two segments on the left are linked. As they rotate you’ll see they cycle through a simple binary pattern of on-off every 90 degree turn. A pair of segments can represent any of four states simply by turning the axle 90 degrees at a time.

Part of the challenge I had was how to represent the states of the digit so that it was simple to move from value to value in the display. As always the key is designing a good data structure! The state of each pair of segments is represented as a value from 0 to 3. Moving from state 0 to state 1 requires a 90 degree rotation, from state 0 to state 2 requires 180 degrees, and so forth. At any point in time the overall value of the digit can be stored as a quadruple of state values: [S1, S2, S3, S4].

How is a digit represented?

I ported my earlier Java code to python running on ev3dev.

At the core of the code is the state-table which represents the current state of the digit.

Segments 0 and 1 are on Port A
Segment 6 is on Port C
Segments 2 and 3 are on Port D
Segments 4 and 5 are on Port B
        5
      -----
     |     |
   1 |  4  | 3
      -----
     |     |
   0 |     | 2
      -----
        6
     DP = bit 7
     
Initial state is all segments are off. Segments are constructed so that they are 90 degrees
out of phase with each other. 
     
Each segment pair (other than segment 6) is represented as being in one of 4 states as follows
Read from top to bottom, so state 1 is topmost segment 'off' and bottom segment 'on'
State	Top  Bottom
 0	OFF  OFF
 1	OFF  ON
 2	ON   OFF
 3  	ON   ON

# This array defines what state each segment pair needs to be in for the corresponding digit
# to be displayed. 
states = [
    [1, 2, 3, 3],		# 0
    [0, 0, 3, 0],		# 1
    [1, 3, 2, 1],		# 2
    [1, 3, 3, 0],		# 3
    [0, 1, 3, 2],		# 4
    [1, 3, 1, 2],		# 5
    [1, 3, 1, 3],		# 6
    [0, 2, 3, 0],		# 7
    [1, 3, 3, 3],		# 8
    [0, 3, 3, 2],		# 9
    [1, 3, 0, 3],		# E
    [0, 0, 0, 0]		# BLANK
]

Here the states[] array holds the state values for each of the possible values that can be displayed on the digit. These state values are computed from the binary representation of how the segments should be set, as this picture shows:

For example, to display the digit 0 segments 0, 1, 5, 3, 2 and 6 must be ‘on’ and segment 4 is ‘off’. The corresponding binary values are converted to a state value from 0 to 3, and stored in the states[] array.

The SegmentController class

This class represents a single segment pair and provides methods to reset the segment back to the initial state, and then to set it to a state from 0 to 3. Resetting the segment rotates it until it hits a physical hard-stop. I built the digit so that the hard-stop means that the segment is showing black faces. The picture below shows the red piece mounted on the axle at the lower left hitting the hard-stop.

The key function in this class is setState(). This rotates the segment to a state 0 to 3, which corresponds to a rotation amount of 0 to 270 degrees. A scaling factor is applied to take into account the gearing attached to the motor between the motor output and the segments.

class SegmentController(object):
    '''
    Controls a single segment or segment pair

    Attributes:
    motor: the  motor that drives the segment or segment pair
    polarity: the default polarity to rotate through segment states. True if normal
    scale: scaling factor. 1 means motor:segment is 1:1 ratio
    state: the current state of the segment 0..3
    '''
    def __init__(self, motorPort, polarity=True, scale=1):
        '''
        Create an instance of a segmentController
        motor: the Medium Motor for this segment
        polarity: boolean, true if motor rotates normally forward
        scale: scaling factor for rotations (default is 1:1)
        '''
        self.motor = ev3.MediumMotor(motorPort)
        self.polarity = polarity
        self.scale = scale
        self.state = BLANK

    def resetToInitialState(self):
        '''Reset the segment to its starting state'''
        if self.polarity:
            self.motor.polarity = 'normal'
        else:
            self.motor.polarity = 'inversed'
        self.motor.run_forever(speed_sp=100, stop_action='brake')
        self.motor.wait_until_not_moving()
        self.motor.stop()
        sleep(0.5)
        # slight compensation for over-rotating to stop position
        self.motor.run_to_rel_pos(speed_sp=100, position_sp=15)
        self.motor.reset()
        self.state = 0

    def setState(self, state):
        ''' Move the segment to the given state 0..3. '''
        if self.polarity:
            self.motor.polarity = 'inversed'
        else:
            self.motor.polarity = 'normal'
        self.motor.run_to_abs_pos(position_sp=round((state*90)*self.scale), speed_sp=600, stop_action='brake')
        self.motor.wait_while('running')

    def stop(self):
        '''Stop the segment rotating'''
        self.motor.stop()

 

The Digit class

The Digit class encapsulates the logic of a digit. It creates 4 instances of a segment controller to represent each of the segment pairs. The key function here is setValue() which simply takes the value and tells each segment controller to set itself to the state corresponding to the configuration of segments for that value.

class Digit:
    '''
    Represents a single digit composed of 4 segment controllers.
    Allows setting and retrieving the current digit value.
    Holds one instance of the SegmentController class per set of motors.
    '''
    def __init__(self):
        self.segmentControllers = []

        # Create the segment controllers
        for i in range(0,4):
            debug_print('Creating controller %d with port %s, polarity %s, scale %d' %
                (i, motorPorts[i], motorPolarity[i], motorRotationScaler[i]))
            x = SegmentController(motorPorts[i], motorPolarity[i], motorRotationScaler[i])
            self.segmentControllers.append(x)
            # Reset the segments
        for x in self.segmentControllers:
            x.resetToInitialState()
        self.value = BLANK

    def reset(self):
        '''
        Force all segments to reset position. Does a slow rotation to the hard-stop position.
        To blank the digit use the clear() method
        '''
        # Reset the segments
        for x in self.segmentControllers:
            x.resetToInitialState()

    def stop(self):
        '''Force Stop the motors'''
        for s in self.segmentControllers:
            s.stop()

    def setValue(self, val):
        '''Set the digit to this value and display it on the segments'''
        debug_print('Set value %d' % val)
        assert( (val >= 0) and (val <= ERROR))
        for i in range(0,4):
            self.segmentControllers[i].setState(states[val][i])

    def getValue(self):
        return self.value

    def error(self):
        '''Display the E for error symbol'''
        self.setValue(ERROR)

    def clear(self):
        '''Clear the digit to a blank display. To force a reset use reset() method'''
        self.setValue(BLANK)

The main loop

The main loop is about as simple as can be – create a digit and then iterate through the values 0 through 9!

########################################################
## Main loop
## Initialise the initial position of the segments
########################################################
def main():
    '''Main loop'''
    debug_print('7 segment digit started')

    digit = Digit()
    digit.reset()
    sleep(1)

    debug_print('Starting main loop...')
    value = 0
    while not buttons.backspace:
        digit.setValue(value)
        sleep(1)
        value = (value + 1) % 10
        debug_print('Value is now %d' % value)

    digit.stop()
    debug_print('All done - exiting')
    ev3.Sound.beep().wait()

if __name__ == '__main__':
    main()