You are here

Finishing Up the Barc Makeover | Cypress Semiconductor

Finishing Up the Barc Makeover

Do you know the rule about the last 10% of a project taking 90% of the time? I think I fell foul of it with Barc's makeover. I thought I had plenty of time before Embedded World (February 27 to March 1) and knew how to finish the project back in November. Time to relax - oh wait - it's February! Let's re-cap where things stand.

Barc is a mobile (wheeled) dog, driven by PWMs on the PSoC 4200M and the Adafruit TB6612 1.2A DC/Stepper Motor Driver Breakout Board. He has two CapSense proximity-sensing loops (aka wires and copper tape) wrapped around either side of his head, which shall be used to detect the presence of a hand to the left right or overhead. And he has an Adafruit GP2Y0A21YK0F IR distance sensor which I set up in my previous post, Barc Assembled to detect a hand in front of the dog.

With all the hardware assembled I just need to write the C program to make him behave the way I need. Here is a summary of the program.

  1. Initialize the hardware (before the main loop)
  2. Generate a heart-beat on the LED
  3. Read the sensors and detect the presence of a hand
  4. State machine to choose the appropriate movement

Initialize the Hardware

I turn on the hardware in a specific order, with the motors starting last, so that Barc doesn't get frisky before the the sensors are ready to tell him what to do. In the following code I start the CapSense and the I2C (which is only used for tuning) first. Then I power up the IR sensor (ground low and power high), allow a little time for it to settle (100ms is long enough for the sensor output to be reliable) and then turn on PSoC ADC so i can read it. Lastly I turn on the motors and set their speed to "STOP" using the motor() function.

/*
Infrared sensor and ADC defines
*/
#define IR_POWER_ON             (1)
#define IR_POWER_OFF            (0)
#define IR_SENSOR_SETTLE_TIME   (100)
#define IR_ADC_CHANNEL          (0)
#define IR_ADC_THRESHOLD_MV     (1800)
/*
H-Bridge and PWM Motor defines
*/
#define HBRIDGE_POWER_ON        (1)
#define HBRIDGE_POWER_OFF       (0)
     /*
    Turn on the CapSense proximity detection (with I2C tuning)
    */
    EZI2C_Start();                                      // Turn on I2C (over kitprog bridge)
    EZI2C_EzI2CSetBuffer1( sizeof( CapSense_dsRam ),    // set up I2C buffer for tuning
                           sizeof( CapSense_dsRam ), 
                           (uint8 *)&CapSense_dsRam );
    CapSense_Start();                                   // Turn on CapSense
    CapSense_ScanAllWidgets();                          // Start scanning
    
    /*
    Turn on the IR sensor and ADC
    */
    Pin_IR_GND_Write( IR_POWER_OFF );                   // Make sure ground is low
    Pin_IR_Power_Write( IR_POWER_ON );                  // Turn on the sensor
    CyDelay( IR_SENSOR_SETTLE_TIME );                   // Allow time for sensor output to be valid
    
    ADC_IR_Sensor_Start();                              // Turn on the ADC
    ADC_IR_Sensor_StartConvert();                       // Start sampling (free running)
       
    /*
    Turn on the motors
    */
    PWM_Motor_Start();                                  // Turn on the 2-channel PWM
    motor( SPEED_STOP, SPEED_STOP );                    // Hold output high (no motion)
    Pin_SLP_Write( HBRIDGE_POWER_ON );                  // Turn on the H-bridge for the motors
    CyDelay( 1 );                                       // Allow time for FLT to go low (380us)

 

The motor() function is very important. It is how I adjust Barc's speed and direction from this part of the schematic.

Barc PSoC motor control

It's worth diving into this function a little bit because it makes the rest of the program so easy to write. Here's the code.

#define SPEED_ILLEGAL           (-1000)

void motor( int left, int right )
{
    static int last_left = SPEED_ILLEGAL;               // Remember the previous speeds
    static int last_right = SPEED_ILLEGAL;
    
    /* Pack the directions into a 2-bit register */
    int dir = ( right >= 0 );
    dir <<= 1;
    dir |= ( left >= 0 );
    Reg_Direction_Write( dir );                         // Set the direction (control the OR gates)
    
    if( left != last_left )
        PWM_Motor_WriteCompare1( abs( left ) );         // Left motor speed
        
    if( right != last_right )
        PWM_Motor_WriteCompare2( abs( right ) );        // Right motor speed
        
    last_left = left;                                   // Remember for next call
    last_right = right;
}

The function accepts two signed speed values, one each for the left and right motors. If the speed is negative then the motor has to run backwards. So a local variable, dir, is set according to the sign of the arguments and is written into the control register (Reg_Direction). Next it sets the PWM compare values to control the duty cycle and, as a result, the motor speeds. The abs() C run-time function just returns the absolute (positive) value of a signed argument. I use static local variables to remember the values of the speeds from the previous function call so that I only write to the PWM if I need to change the speed. This is what makes my main loop easy to write - I can call motor() as often as a like and it only touches the hardware if an actual speed change is required. You'll soon see how my state machine code is really simple and easy to read.

Generate a Heart Beat

Now that Barc has an on/off button I need to know when he is awake. I am using the blue LED on the kit to tell me when he is active. Rather than simply turning on the LED I decided to blink it periodically within the main loop so I know my code is still running. Bad code happens. Especially late at night on hobby projects! I wanted the proof of life test to be written in firmware - it is easy to generate a heart beat from a PWM but that keeps running even when I write really broken software. So I added a simple loop_count variable and update the LED based on its value - it's a software PWM.

#define WAIT_DEBOUNCE_MS        (50)
#define HEARTBEAT_PERIOD        (20)
#define HEARTBEAT_DUTY_CYCLE    (1)

    /* Create an inverse heart beat (mostly on, briefly off) with the LED */
    Pin_Status_Write( ( loop_count > HEARTBEAT_DUTY_CYCLE ) ? LED_OFF : LED_ON );
        
    loop_count++;
    if( loop_count > HEARTBEAT_PERIOD )
    {
        loop_count = 0;                             // Reset the loop counter
    }
    CyDelay( WAIT_DEBOUNCE_MS );                    // Let the motors run for a while to prevent "jitter"

 

Read Sensors

This is a three-step process that calculates the state variable, used in the state machine below. I start by setting the default state to WAIT so that Barc will stay put if no hand is detected. Next I read the ADC to determine if there is a hand out in front and either leave the state alone or alter it to CHASE. The last step is to read the proximity sensors and, if they are active, set the state to LEFT, RIGHT or REVERSE. Note that there is an inherent priority here - if both the ADC and CapSense sensors detect a hand, the CapSense wins. If I were to swap the order of the code then the priority would be reversed. It all depends on whether you want a dog that chases a little more than it shies away, or the other way around.

typedef enum { WAIT, CHASE, REVERSE, LEFT, RIGHT } state_t;

state_t state;                                      // Result of sensor scans
int16 range = 0;                                    // ADC value

    /*
    Start sensing - default state is WAIT (do nothing)
    */
    state = WAIT;
        
    /* Get an ADC value from the distance sensor and convert it to millivolts */
    if( ADC_IR_Sensor_IsEndConversion( ADC_IR_Sensor_RETURN_STATUS ) )
    {
        range = ADC_IR_Sensor_GetResult16( IR_ADC_CHANNEL );
        range = ADC_IR_Sensor_CountsTo_mVolts( IR_ADC_CHANNEL, range );
        
        /* Change the state if above the proximity threshold */
        if( range > IR_ADC_THRESHOLD_MV )
        {
            state = CHASE;
        }
    }
        
    /*
    CapSense - detect a hand to the left, right or overhead
    */
    if( CapSense_NOT_BUSY == CapSense_IsBusy() )
    {
        int left, right;
            
        CapSense_ProcessAllWidgets();               // Get the scan results
        CapSense_RunTuner();                        // Tuning across EZI2C 
           
        /* Read the scan values */
        left = CapSense_IsWidgetActive( CapSense_LEFT_WDGT_ID );
        right = CapSense_IsWidgetActive( CapSense_RIGHT_WDGT_ID );
            
        /* Set the state if a hand is detected */
        if( left && right )
        {
            state = REVERSE;                        // Back up
        }
        else if( left )
        {
            state = RIGHT;                          // Turn away
        }
        else if( right )
        {
            state = LEFT;                           // Turn away
        }
      
        CapSense_ScanAllWidgets();                  // Start the next scan (non blocking call)
    }

 

State Machine

Once I have the state from the sensors it is really easy to control behavior. It's a simple switch statement that calls motor() to make Barc run, turn or back off. I toyed with the idea of running a buzzer in the REVERSE state to sound like a growl. But it turns out the only buzzer I had in the ever-filling shoe box of random electronic parts I am inevitably collecting wasn't louder than the running motors... so I'll shelve that idea for a while!

        /*
        State machine - act on the sensed state of the robot
        */
        switch( (int)state )
        {
            case RIGHT:
                /* reverse right motor, forward left */
                motor( SPEED_WALK_LEFT, -SPEED_WALK_RIGHT );
            break;
            
            case LEFT:
                /* reverse left motor, forward right */
                motor( -SPEED_WALK_LEFT, SPEED_WALK_RIGHT );
            break;
            
            case REVERSE:
                /* reverse both motors */
                motor( -SPEED_WALK_LEFT, -SPEED_WALK_RIGHT );
            break;
            
            case CHASE:
                /* forward both motors, fast */             
                motor( SPEED_RUN_LEFT, SPEED_RUN_RIGHT );
            break;
            
            case WAIT:
            default:
                /* stop both motors */
                motor( SPEED_STOP, SPEED_STOP );
            break;            
        }

Barc lives again! The whole program is less than 300 lines of quite thoroughly documented code (I will attach the project in my next blog) so I am quite pleased with the implementation. I think there are plenty of other enhancements I could make but I think the sensible thing to do is to finalize the CapSense proximity tuning so I can be sure he'll be ready to go to Embedded World.

 

Comments

catherinebarrett525_3079256's picture

These are wonderful! Thank you so much for sharing! :)custom writings 

ALL CONTENT AND MATERIALS ON THIS SITE ARE PROVIDED "AS IS". CYPRESS SEMICONDUCTOR AND ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY OF THESE MATERIALS FOR ANY PURPOSE AND DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THESE MATERIALS, INCLUDING BUT NOT LIMITED TO, ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT OF ANY THIRD PARTY INTELLECTUAL PROPERTY RIGHT. NO LICENSE, EITHER EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, IS GRANTED BY CYPRESS SEMICONDUCTOR. USE OF THE INFORMATION ON THIS SITE MAY REQUIRE A LICENSE FROM A THIRD PARTY, OR A LICENSE FROM CYPRESS SEMICONDUCTOR.

Content on this site may contain or be subject to specific guidelines or limitations on use. All postings and use of the content on this site are subject to the Terms and Conditions of the site; third parties using this content agree to abide by any limitations or guidelines and to comply with the Terms and Conditions of this site. Cypress Semiconductor and its suppliers reserve the right to make corrections, deletions, modifications, enhancements, improvements and other changes to the content and materials, its products, programs and services at any time or to move or discontinue any content, products, programs, or services without notice.