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)

   /* Create an inverse heart beat (mostly on, briefly off) with the LED */
   Pin_Status_Write( ( loop_count > HEARTBEAT_DUTY_CYCLE ) ?LED_OFF :LED_ON );
   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 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 );
           case LEFT:
               /* reverse left motor, forward right */
               motor( -SPEED_WALK_LEFT, SPEED_WALK_RIGHT );
           case REVERSE:
               /* reverse both motors */
               motor( -SPEED_WALK_LEFT, -SPEED_WALK_RIGHT );
           case CHASE:
               /* forward both motors, fast */             
               motor( SPEED_RUN_LEFT, SPEED_RUN_RIGHT );
           case WAIT:
               /* stop both motors */
               motor( SPEED_STOP, SPEED_STOP );

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.



catherinebarrett525_3079256's picture

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

このサイトに掲示されているすべてのコンテンツと資料は、「そのままの状態」で提供されます。サイプレス セミコンダクタとその関連サプライヤは、これらの資料について、いかなる目的への適合性をも表明することはありません。また、これらの資料について、すべての保証や条件を放棄します。これには、暗示的な保証および条件、商用性、特定の目的への適合性、すべてのサードパーティの知的財産権に対する権利と非侵害などが含まれますが、これらに制限されることはありません。サイプレス セミコンダクタにより、明示または暗示にかかわらず、禁反言などによるライセンスは、付与されないものとします。このサイトに掲示されている情報の使用には、サードパーティまたはサイプレス セミコンダクタからのライセンスが必要となる場合があります。

このサイトのコンテンツには、特定のガイドラインや使用制限が含まれている場合があります。このサイトにおけるすべての掲示やコンテンツの使用は、サイトの利用規約に準じて行われるものとします。このコンテンツを使用するサードパーティは、制限やガイドラインに従い、このサイトの利用規約を遵守するものとします。サイプレス セミコンダクタとそのサプライヤは、コンテンツや資料、その製品、プログラム、サービスに対し、いつでも修正、削除、変更、改善、向上、その他の変更を加える権利を有します。また、いかなるコンテンツ、製品、プログラム、サービスを予告なく変更または閉鎖する権利を有します。