An exciting update on my Giant Lego Calculator project; I’ve succeeded in making a 7 segment display unit count upwards and also attached keys that change the value on the digit when you press them. This is a huge step forward after a lot of failed prototypes!

Watch it in action

In this first video watch it count from 0 to 9:

In this video I press keys from 0 to 3 to ddisplay that value on the digit.

Some pictures of the digits and keys

Here you see my current prototype with 4 keys attached to a single EV3.

4 keys attached to an EV3 and a digit

There is a touch sensor in each key unit that responds to the key being pressed. Key faces can be swapped and changed.

Key unit

Key unit

Design inspiration

How did I come up with this design? I can’t claim this as an original idea. I was asking some friends on Facebook for advice and one of them forwarded this sketch to me which he had done some time ago. His idea was deceptive simple, as all the best ideas are! Connect two segments to each motor, and have them be out of phase with each other. Rotating the segments cycles through a sequence of on-off for both top and bottom. This means that each motor can drive two segments with one rotation and set them to one of four states. 4 motors can control 7 segments plus a decimal point, which means one EV3 can control a single digit. Perfect!

Encoding the segments

After a little work I came up with this design. It uses a 1×8 yellow or black tile for the faces of the segment. The interior of the segment is simple 1×2 technic bricks, with 1×1 bricks with a hole at either end.

Then I built this working prototype. 3 of the motors control the left, top two middle and right pairs of segments, with the bottom segment controlled by a motor on its own.

Prototype digit

Code

Now that the building work is done the interesting question is how to control a single 7 segment digit? Like most programming problems this place boils down to coming up with an efficient data representation. Once I figured out how to efficiently represent the states of the digit and its segments then the code to rotate the motors was trivial!

Each segment pair is represented as a pair of bits, stored in a 2D array. There is a simple function to compute the degrees of rotation between two states, and another array maps each rotation onto a motor. In all the code required to run this is about 100 lines total. A lot of pre-work in how the segments are constructed and thinking about the code leads to a very efficient implementation.

Each pair of segments can be in one of four states: off-off, on-off, off-on, on-on. This lends itself perfectly to a binary representation using two bits. But rather than represent the state of the digit in a single byte I used an array of four integers to hold the state for each motor. A digit is encoded as a sequence of state values. The challenges then boils down to: what is the minimal number of motor rotations to move from state to state?

As you can see from my notebook below this again was easily solved using a little maths. Given the design I made all you need to do is rotate that motor by a positive or negative multiple of 90 degrees to move between states.

Notebook with state table

This results in compact and efficient code. It assumes a blank initial state and then counts up from 0..9 and displays E (for error) and then reverts back to blank to begin counting again. The changeState() function is where the magic happens; it takes the current state (from 0 to 3) and the desired state (from 0 to 3) of a segment pair and then computes the minimal number of rotations needed for each segment pair to move between those states. The rotationDegrees() function computes the rotation amount needed to move between states.

All of the state information is stored in the two-dimensional states[][] array. It defines the states for each possible display value. To change between two values the changeState() function looks up an array in the states table, and then calls the rotationDegrees() function to determine how much to rotate the motor by. The motorMap[] array is a convenience so that I don’t have to hard-code motor names into the code.


import lejos.hardware.Brick; import lejos.hardware.Button; import lejos.hardware.lcd.LCD; import lejos.hardware.motor.EV3LargeRegulatedMotor; import lejos.hardware.motor.EV3MediumRegulatedMotor; import lejos.hardware.port.MotorPort; import lejos.remote.ev3.RMIRegulatedMotor; import lejos.remote.ev3.RemoteEV3; import lejos.robotics.RegulatedMotor; import lejos.utility.Delay; /** * Counts upwards and loops around * Assumes segments are in the all-off position at start * @author mcrosbie * @date 8 October 2016 */ public class Counter { // Port A: middle segments // Port B: left segments // Port C: right segments // Port D: lower segment /** * Digit bit mappings. Not obviously numbers because this is how the digit is * physically constructed. * Segments 0 and 1 are on Port B * Segments 2 and 3 are on Port C * Segments 4 and 5 are on Port A * Segment 6 and DP are on Port D * * 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. Rotation map is * 0 OFF OFF * 1 OFF ON * 2 ON OFF * 3 ON ON */ final static int rot = 90; final static int ERROR = 10; final static int BLANK = 11; static int value = BLANK; static int[][] 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 }; static RegulatedMotor motorMap[]; /** * * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub RegulatedMotor A = new EV3MediumRegulatedMotor(MotorPort.A); RegulatedMotor B = new EV3MediumRegulatedMotor(MotorPort.B); RegulatedMotor C = new EV3MediumRegulatedMotor(MotorPort.C); RegulatedMotor D = new EV3MediumRegulatedMotor(MotorPort.D); A.setSpeed(600); B.setSpeed(600); C.setSpeed(600); D.setSpeed(600); motorMap = new RegulatedMotor[4]; motorMap[0] = D; motorMap[1] = A; motorMap[2] = C; motorMap[3] = B; LCD.clear(); A.resetTachoCount(); B.resetTachoCount(); C.resetTachoCount(); D.resetTachoCount(); try { LCD.drawString("Calculator", 0, 0); int newValue = value; while(Button.ENTER.isUp()) { value = newValue; LCD.clear(2); LCD.drawInt(value, 0, 2); Delay.msDelay(100); newValue = value + 1; if(newValue == states.length) newValue = 0; changeState(value, newValue); } } catch(Exception e) { System.out.println("Exception: " + e); } changeState(value, BLANK); } /** * Change the digit from one state to the next * @param fromState Current state to change from * @param toState Next state to move into * * Computes the number of moves needed to transition from one state to the next */ static void changeState(int fromState, int toState) { int froms[]; int tos[]; froms = states[fromState]; tos = states[toState]; for(int i=0; i < froms.length; i++) { int rotation = rotationDegrees(froms[i], tos[i]); motorMap[i].rotate(rotation, true); } } /** * Compute the rotation amount for moving between states 0..3 * * @param currentState Current state of the segment pairs from 0..3 * @param desiredState Desired state of the segment pairs from 0..3 * @return Degree rotation. May be positive or negative */ private static int rotationDegrees(int currentState, int desiredState) { assert( (currentState >=0) && (currentState <= 3)); assert( (desiredState >=0) && (desiredState <= 3)); return (desiredState - currentState) * 90; } }

What about if I want to change what is displayed on the digit based on a key being pressed? Again the code is remarkably simple. I connect a touch sensor to ports 1 through 4, and based on the touch sensor pressed change the digit displayed.

import lejos.hardware.Brick;
import lejos.hardware.Button;
import lejos.hardware.Sound;
import lejos.hardware.lcd.LCD;
import lejos.hardware.motor.EV3LargeRegulatedMotor;
import lejos.hardware.motor.EV3MediumRegulatedMotor;
import lejos.hardware.port.MotorPort;
import lejos.hardware.port.SensorPort;
import lejos.hardware.sensor.EV3TouchSensor;
import lejos.hardware.sensor.SensorMode;
import lejos.remote.ev3.RMIRegulatedMotor;
import lejos.remote.ev3.RemoteEV3;
import lejos.robotics.RegulatedMotor;
import lejos.utility.Delay;
import lejos.robotics.filter.*;

/**
 * Sets the digit to display based on the button pressed
 * Currently supports 1..4
 * 
 * @author mcrosbie
 * @date 8 October 2016
 */
public class Buttons1 {

    // Port A: middle segments
    // Port B: left segments
    // Port C: right segments
    // Port D: lower segment
    // Port 1..4: input buttons

    /**
     * Digit bit mappings. Not obviously numbers because this is how the digit is
     * physically constructed.
     * Segments 0 and 1 are on Port B
     * Segments 2 and 3 are on Port C
     * Segments 4 and 5 are on Port A
     * Segment 6 and DP are on Port D
     * 
     *      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. Rotation map is
     *   0  OFF OFF
     *   1  OFF ON
     *   2  ON  OFF
     *   3  ON  ON
     */

    final static int rot = 90;
    final static int ERROR = 10;
    final static int BLANK = 11;

    static int value = BLANK;

    static int[][] 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
    };

    // Map the motor wiring onto the segments. Allows more flexibility on wiring.
    static RegulatedMotor motorMap[];

    // Input buttons
    static SimpleTouch buttons[];

    /**
     * 
     * @param args
     */
    public static void main(String[] args) {

        RegulatedMotor A = new EV3MediumRegulatedMotor(MotorPort.A);
        RegulatedMotor B = new EV3MediumRegulatedMotor(MotorPort.B);
        RegulatedMotor C = new EV3MediumRegulatedMotor(MotorPort.C);
        RegulatedMotor D = new EV3MediumRegulatedMotor(MotorPort.D);

        A.setSpeed(600);
        B.setSpeed(600);
        C.setSpeed(600);
        D.setSpeed(600);

        motorMap = new RegulatedMotor[4];
        motorMap[0] = D;
        motorMap[1] = A;
        motorMap[2] = C;
        motorMap[3] = B;

        // Initialise the touch sensor providers
        buttons = new SimpleTouch[4];
        EV3TouchSensor t = new EV3TouchSensor(SensorPort.S1);
        buttons[0] = new SimpleTouch(t);        
        t = new EV3TouchSensor(SensorPort.S2);
        buttons[1] = new SimpleTouch(t);
        t = new EV3TouchSensor(SensorPort.S3);
        buttons[2] = new SimpleTouch(t);
        t = new EV3TouchSensor(SensorPort.S4);
        buttons[3] = new SimpleTouch(t);

        LCD.clear();

        A.resetTachoCount();
        B.resetTachoCount();
        C.resetTachoCount();
        D.resetTachoCount();

        try {

            LCD.drawString("BUTTONS", 0, 0);

            int newValue = value;

            while(Button.ENTER.isUp()) {        

                for(int i=0; i < buttons.length; i++) {
                    if(buttons[i].isPressed()) {
                        newValue = i;
                        Sound.beep();
                        break;
                    }
                }

                LCD.clear(2);
                LCD.drawInt(value, 0, 2);               

                changeState(value, newValue);
                value = newValue;
                Delay.msDelay(100); 
            }

            Sound.beep();
            changeState(value, BLANK);          
            Delay.msDelay(1000);

        } catch(Exception e) {
            System.out.println("Exception: " + e);
        }

        A.close();
        B.close();
        C.close();
        D.close();

    }

    /**
     * Change the digit from one state to the next
     * @param fromState Current state to change from
     * @param toState Next state to move into
     * 
     * Computes the number of moves needed to transition from one state to the next
     */
    static void changeState(int fromState, int toState) {

        int froms[];
        int tos[];

        froms = states[fromState];
        tos = states[toState];

        for(int i=0; i < froms.length; i++) {
            int rotation = rotationDegrees(froms[i], tos[i]);

            // if this motor is running, wait until it has stopped before we rotate it
            while(motorMap[i].isMoving()) ;
            motorMap[i].rotate(rotation, true);
        }
    }

    /**
     * Compute the rotation amount for moving between states 0..3
     * 
     * @param currentState Current state of the segment pairs from 0..3
     * @param desiredState Desired state of the segment pairs from 0..3
     * @return Degree rotation. May be positive or negative
     */
    private static int rotationDegrees(int currentState, int desiredState) {
        assert( (currentState >=0) && (currentState <= 3));
        assert( (desiredState >=0) && (desiredState <= 3));     
        return (desiredState - currentState) * 90;
    }

}
[Prototype16-winner!.jpg]: Prototype16-winner!.jpg width=1023px height=1365px

[Notebook.jpg]: Notebook.jpg width=1024px height=768px