Skip to content

Flash a LED light using PWM (Python)

toliver edited this page Jan 14, 2014 · 10 revisions

PWM (Pulse Width Modulation) refers to the concept of rapidly pulsing the digital signal of a wire to simulate a varying voltage on the wire. This method is commonly used for driving motors, heaters, or lights in varying intensities or speeds. This example demonstrates how to flash a LED light by varying duty cycle (i.e., the amount of time in the period that the pulse is active or high).

Note that General I/O (GIO) module consists of one GIO Node board and one GIO Peripheral board, and hence there are 12 General purpose digital I/O channels (a GIO Node board has 6 digital I/O channels, a GIO Peripheral board has also 6 digital I/O channels). PWM is on all 12 digital channels.

For this example, we assume that the LED light is connected to the first digital I/O channel (i.e., the light is connected to the GIO Node board).

The code

If you followed the standard installation process (deb package through apt-get install) the python code will be found by doing:

$ cd /opt/ros/hydro/lib/sr_ronex_examples

If you installed the package from source, change directories to your sr_ronex_examples package.

$ roscd sr_ronex_examples/
$ cd src
$ pwd

Python file sr_ronex_flash_LED_with_PWM.py is located inside the src directory.

#!/usr/bin/env python

# ####################################################################
# Copyright (c) 2013, Shadow Robot Company, All rights reserved.
# 
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
# ####################################################################

import roslib; roslib.load_manifest('sr_ronex_examples')
import rospy
from time import sleep
from sr_ronex_msgs.msg import PWM

#--------------------------------------------------------------------------------

# Flash a LED light with PWM.
def flashLED(topic):
    pwm_period = 320
    # Start with a 100% duty cycle.
    pwm_on_time_0 = pwm_period
    # The second output is not used.
    pwm_on_time_1 = 0

    pub = rospy.Publisher( topic, PWM )
    while not rospy.is_shutdown():
        # Flash the light...
        pwm_on_time_0 -= 10
        if pwm_on_time_0 < 0:
            pwm_on_time_0 = pwm_period

        # Set the PWM data.
        pwm = PWM()
        pwm.pwm_period    = pwm_period
        pwm.pwm_on_time_0 = pwm_on_time_0
        pwm.pwm_on_time_1 = pwm_on_time_1

        pub.publish( pwm )
        rospy.sleep( 0.01 )

#--------------------------------------------------------------------------------

"""
This class demonstrates how to find the General I/O module with the given ronex_id.
"""
class SrRonexFindGeneralIOModule(object):

    def __init__(self, ronex_id):
        self.ronex_id = ronex_id
        
    """
    Get the path of the General I/O module with the given ronex_id.
    Note that None is returned if the module is not found.
    """
    def get_path(self):
        """
        Find the ronexes present on the system.
        """
        # Wait until there's one ronex.
        while True:
            try:
                rospy.get_param("/ronex/devices/0/ronex_id")
                break
            except:
                rospy.loginfo("Waiting for the ronex to be loaded properly.")
                sleep(0.1)

        """
        Retrieve all the ronex parameter ids from the parameter server.
        If there are three General I/O modules, then ronex_param_ids is [0,1,2].
        Note that the id starts from zero. And the size of the returned variable
        is equal to the number of General I/O modules.
        """
        ronex_param_ids = rospy.get_param("/ronex/devices")
        for key in ronex_param_ids:
            if self.ronex_id == ronex_param_ids[key]["ronex_id"]:
                path = ronex_param_ids[key]["path"]
                return path

#--------------------------------------------------------------------------------

"""
Assume that your RoNeX consists of a Bridge (IN) module, and one or multiple General I/O module(s).
This example demonstrates how to flash a LED light with pulse-width modulation (PWM). 
"""
if __name__ == "__main__":
    rospy.init_node('sr_ronex_flash_LED_with_PWM')
    
    # Note that you may have to set the value of ronex_id,
    # depending on which General I/O board the LED is connected to.
    ronex_id = raw_input( "Please enter the ronex id: " )
    findModule = SrRonexFindGeneralIOModule( str(ronex_id) )
    path = findModule.get_path()
     
    if path != None:
        # Always use the first digital I/O channel to flash the LED light.
        # For example "/ronex/general_io/1" + "/command/pwm/0".
        topic = path + "/command/pwm/0"
        rospy.loginfo("topic = %s.", topic)
        try:
            flashLED(topic)
        except rospy.ROSInterruptException:
            pass
    else:
        rospy.loginfo("Failed to find the General I/O module with the given ronex_id %s.", ronex_id)

#--------------------------------------------------------------------------------

The Code Explained

    # Note that you may have to set the value of ronex_id,
    # depending on which General I/O board the LED is connected to.
    ronex_id = raw_input( "Please enter the ronex id: " )
    findModule = SrRonexFindGeneralIOModule( str(ronex_id) )
    path = findModule.get_path()
     
    if path != None:
        # Always use the first digital I/O channel to flash the LED light.
        # For example "/ronex/general_io/1" + "/command/pwm/0".
        topic = path + "/command/pwm/0"
        rospy.loginfo("topic = %s.", topic)
        try:
            flashLED(topic)
        except rospy.ROSInterruptException:
            pass
    else:
        rospy.loginfo("Failed to find the General I/O module with the given ronex_id %s.", ronex_id)

Before we flash the LED light, we have to get the ronex id of the General I/O module that is connected to the light. Follow simply tutorial Parse Parameter Server (Python) to retrieve the ronex id.

Note that ronex id is defined as a string. Here we assume that an alias name has not been given to it. Simply input the ronex id as an integer on the command line, and it will be converted to a string.

class SrRonexFindGeneralIOModule(object):

    def __init__(self, ronex_id):
        self.ronex_id = ronex_id
        
    """
    Get the path of the General I/O module with the given ronex_id.
    Note that None is returned if the module is not found.
    """
    def get_path(self):
        """
        Find the ronexes present on the system.
        """
        # Wait until there's one ronex.
        while True:
            try:
                rospy.get_param("/ronex/devices/0/ronex_id")
                break
            except:
                rospy.loginfo("Waiting for the ronex to be loaded properly.")
                sleep(0.1)

        """
        Retrieve all the ronex parameter ids from the parameter server.
        If there are three General I/O modules, then ronex_param_ids is [0,1,2].
        Note that the id starts from zero. And the size of the returned variable
        is equal to the number of General I/O modules.
        """
        ronex_param_ids = rospy.get_param("/ronex/devices")
        for key in ronex_param_ids:
            if self.ronex_id == ronex_param_ids[key]["ronex_id"]:
                path = ronex_param_ids[key]["path"]
                return path

The attempt to find the General I/O module with that ronex id is made. If it fails, method get_path returns None. Otherwise, a path to the module (e.g., /ronex/general_io/1, where the number is the value of the ronex id) is set.

Note that in the main function, the topic of messages that we will send to the LED light in order to flash it is set by

        # Always use the first digital I/O channel to flash the LED light.
        # For example "/ronex/general_io/1" + "/command/pwm/0".
        topic = path + "/command/pwm/0"
# Flash a LED light with PWM.
def flashLED(topic):
    pwm_period = 320
    # Start with a 100% duty cycle.
    pwm_on_time_0 = pwm_period
    # The second output is not used.
    pwm_on_time_1 = 0

    pub = rospy.Publisher( topic, PWM )
    while not rospy.is_shutdown():
        # Flash the light...
        pwm_on_time_0 -= 10
        if pwm_on_time_0 < 0:
            pwm_on_time_0 = pwm_period

        # Set the PWM data.
        pwm = PWM()
        pwm.pwm_period    = pwm_period
        pwm.pwm_on_time_0 = pwm_on_time_0
        pwm.pwm_on_time_1 = pwm_on_time_1

        pub.publish( pwm )
        rospy.sleep( 0.01 )

Here we flash the LED light by varying the duty cycle (more specifically pwm_on_time_0). Note that pwm_on_time_1 is for the second digital I/O channel, and it is NOT used.

Running the code

Make sure that a roscore is up and running:

$ roscore

If you are using catkin, make sure you have sourced your workspace's setup.sh file after calling catkin_make but before trying to use your applications:

# In your catkin workspace
$ source ./devel/setup.bash

Then run the driver (see Launch driver ).

Let's run it:

$ rosrun sr_ronex_examples sr_ronex_flash_LED_with_PWM.py

Note that all digital I/O channels are inputs by default. Therefore, in order to flash the LED light, we have to make sure that it is used as an output instead. The easiest way is to use GUI and set input_mode_0 to false.

$ rosrun rqt_reconfigure rqt_reconfigure

Configuration parameters of a ronex module can also be set from a user-defined launchfile. For example, the following code turns on the output mode for the digital I/O pin number 0 on the General I/O module with ronex id 2.

<launch>
...
<node pkg="dynamic_reconfigure" type="dynparam" name="dynparam_i2" args="set /ronex/general_io/2 input_mode_0 false" />
...
</launch>

Now you should be able to see the flashing light.