├── ros_arduino_python
├── src
│ └── ros_arduino_python
│ │ ├── __init__.py
│ │ ├── arduino_sensors.py
│ │ ├── base_controller.py
│ │ └── arduino_driver.py
├── launch
│ └── arduino.launch
├── setup.py
├── CMakeLists.txt
├── package.xml
├── config
│ ├── my_arduino_params.yaml
│ └── arduino_params.yaml
└── nodes
│ └── arduino_node.py
├── ros_arduino_msgs
├── srv
│ ├── DigitalRead.srv
│ ├── ServoRead.srv
│ ├── AnalogRead.srv
│ ├── AnalogWrite.srv
│ ├── DigitalWrite.srv
│ ├── ServoWrite.srv
│ └── DigitalSetDirection.srv
├── msg
│ ├── SensorState.msg
│ ├── Digital.msg
│ ├── Analog.msg
│ ├── AnalogFloat.msg
│ └── ArduinoConstants.msg
├── package.xml
└── CMakeLists.txt
├── ros_arduino_bridge
├── CMakeLists.txt
└── package.xml
├── ros_arduino_firmware
├── CMakeLists.txt
├── src
│ └── libraries
│ │ ├── MegaRobogaiaPololu
│ │ ├── servos.h
│ │ ├── commands.h
│ │ ├── sensors.h
│ │ ├── diff_controller.h
│ │ └── MegaRobogaiaPololu.ino
│ │ └── ROSArduinoBridge
│ │ ├── motor_driver.h
│ │ ├── commands.h
│ │ ├── encoder_driver.h
│ │ ├── sensors.h
│ │ ├── servos.h
│ │ ├── servos.ino
│ │ ├── motor_driver.ino
│ │ ├── diff_controller.h
│ │ ├── encoder_driver.ino
│ │ └── ROSArduinoBridge.ino
└── package.xml
├── .gitignore
└── README.md
/ros_arduino_python/src/ros_arduino_python/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/DigitalRead.srv:
--------------------------------------------------------------------------------
1 | uint8 pin
2 | ---
3 | bool value
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/ServoRead.srv:
--------------------------------------------------------------------------------
1 | uint8 id
2 | ---
3 | float32 value
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/AnalogRead.srv:
--------------------------------------------------------------------------------
1 | uint8 pin
2 | ---
3 | uint16 value
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/AnalogWrite.srv:
--------------------------------------------------------------------------------
1 | uint8 pin
2 | uint16 value
3 | ---
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/DigitalWrite.srv:
--------------------------------------------------------------------------------
1 | uint8 pin
2 | bool value
3 | ---
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/ServoWrite.srv:
--------------------------------------------------------------------------------
1 | uint8 id
2 | float32 value
3 | ---
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/srv/DigitalSetDirection.srv:
--------------------------------------------------------------------------------
1 | uint8 pin
2 | bool direction
3 | ---
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/msg/SensorState.msg:
--------------------------------------------------------------------------------
1 | Header header
2 |
3 | string[] name
4 | float32[] value
5 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/msg/Digital.msg:
--------------------------------------------------------------------------------
1 | # Reading on a digital pin
2 | Header header
3 | uint8 value
4 |
5 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/msg/Analog.msg:
--------------------------------------------------------------------------------
1 | # Reading from a single analog IO pin.
2 | Header header
3 | uint16 value
4 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/msg/AnalogFloat.msg:
--------------------------------------------------------------------------------
1 | # Float message from a single analog IO pin.
2 | Header header
3 | float64 value
--------------------------------------------------------------------------------
/ros_arduino_msgs/msg/ArduinoConstants.msg:
--------------------------------------------------------------------------------
1 | # Arduino-style constants
2 | uint8 LOW=0
3 | uint8 HIGH=1
4 | uint8 INPUT=0
5 | uint8 OUTPUT=1
6 |
--------------------------------------------------------------------------------
/ros_arduino_bridge/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(ros_arduino_bridge)
3 | find_package(catkin REQUIRED)
4 | catkin_metapackage()
5 |
--------------------------------------------------------------------------------
/ros_arduino_python/launch/arduino.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(ros_arduino_firmware)
3 |
4 | find_package(catkin REQUIRED)
5 | catkin_package(DEPENDS)
6 |
7 | install(DIRECTORY src
8 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
9 | )
10 |
--------------------------------------------------------------------------------
/ros_arduino_python/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from distutils.core import setup
4 | from catkin_pkg.python_setup import generate_distutils_setup
5 |
6 | d = generate_distutils_setup(
7 | packages=['ros_arduino_python'],
8 | package_dir={'': 'src'},
9 | )
10 |
11 | setup(**d)
12 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/MegaRobogaiaPololu/servos.h:
--------------------------------------------------------------------------------
1 | /* Define the attachment of any servos here.
2 | The example shows two servos attached on pins 3 and 5.
3 | */
4 |
5 | #define N_SERVOS 2
6 |
7 | Servo servos [N_SERVOS];
8 | byte servoPins [N_SERVOS] = {3, 5};
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/package.xml:
--------------------------------------------------------------------------------
1 |
2 | ros_arduino_firmware
3 | 0.2.0
4 |
5 | ROS Arduino Firmware.
6 |
7 | Patrick Goebel
8 | Patrick Goebel
9 | BSD
10 | http://ros.org/wiki/ros_arduino_firmware
11 |
12 | catkin
13 |
14 |
--------------------------------------------------------------------------------
/ros_arduino_python/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(ros_arduino_python)
3 |
4 | find_package(catkin REQUIRED)
5 | catkin_package(DEPENDS)
6 | catkin_python_setup()
7 |
8 | install(DIRECTORY config
9 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
10 | )
11 |
12 | install(DIRECTORY launch
13 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
14 | )
15 |
16 | install(DIRECTORY nodes
17 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
18 | )
19 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/MegaRobogaiaPololu/commands.h:
--------------------------------------------------------------------------------
1 | /* Define single-letter commands that will be sent by the PC over the
2 | serial link.
3 | */
4 |
5 | #define ANALOG_READ 'a'
6 | #define GET_BAUDRATE 'b'
7 | #define PIN_MODE 'c'
8 | #define DIGITAL_READ 'd'
9 | #define READ_ENCODERS 'e'
10 | #define MOTOR_SPEEDS 'm'
11 | #define PING 'p'
12 | #define RESET_ENCODERS 'r'
13 | #define SERVO_WRITE 's'
14 | #define SERVO_READ 't'
15 | #define UPDATE_PID 'u'
16 | #define DIGITAL_WRITE 'w'
17 | #define ANALOG_WRITE 'x'
18 | #define LEFT 0
19 | #define RIGHT 1
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/package.xml:
--------------------------------------------------------------------------------
1 |
2 | ros_arduino_msgs
3 | 0.2.0
4 |
5 | ROS Arduino Messages.
6 |
7 | Patrick Goebel
8 | Patrick Goebel
9 | BSD
10 | http://ros.org/wiki/ros_arduino_msgs
11 |
12 | catkin
13 |
14 | message_generation
15 | std_msgs
16 |
17 | message_runtime
18 | std_msgs
19 |
20 |
21 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/motor_driver.h:
--------------------------------------------------------------------------------
1 | /***********************************************************************
2 | Motor driver function definitions - by James Nugen and Chaoyang Liu
3 | ***********************************************************************/
4 |
5 | void initMotorController();
6 | void setMotorSpeed(int i, int spd);
7 | void setMotorSpeeds(int leftSpeed, int rightSpeed);
8 | #ifdef L298N_DUAL_HBRIDGE
9 | // motor one
10 | #define ENA 5
11 | #define IN1 7
12 | #define IN2 8
13 | // motor two
14 | #define ENB 6
15 | #define IN3 9
16 | #define IN4 10
17 | #endif
18 |
--------------------------------------------------------------------------------
/ros_arduino_bridge/package.xml:
--------------------------------------------------------------------------------
1 |
2 | ros_arduino_bridge
3 | 0.2.0
4 |
5 | Metapackage for ros_arduino_bridge.
6 |
7 | Patrick Goebel
8 | Patrick Goebel
9 | BSD
10 | http://ros.org/wiki/ros_arduino_bridge
11 |
12 | catkin
13 |
14 | ros_arduino_firmware
15 | ros_arduino_msgs
16 | ros_arduino_python
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/ros_arduino_python/package.xml:
--------------------------------------------------------------------------------
1 |
2 | ros_arduino_python
3 | 0.2.0
4 |
5 | ROS Arduino Python.
6 |
7 | Patrick Goebel
8 | Patrick Goebel
9 | BSD
10 | http://ros.org/wiki/ros_arduino_python
11 |
12 | catkin
13 |
14 | rospy
15 | std_msgs
16 | sensor_msgs
17 | geometry_msgs
18 | nav_msgs
19 | tf
20 | ros_arduino_msgs
21 | python-serial
22 |
23 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/commands.h:
--------------------------------------------------------------------------------
1 | /* Define single-letter commands that will be sent by the PC over the
2 | serial link.
3 | */
4 |
5 | #ifndef COMMANDS_H
6 | #define COMMANDS_H
7 |
8 | #define ANALOG_READ 'a'
9 | #define GET_BAUDRATE 'b'
10 | #define PIN_MODE 'c'
11 | #define DIGITAL_READ 'd'
12 | #define READ_ENCODERS 'e'
13 | #define MOTOR_SPEEDS 'm'
14 | #define PING 'p'
15 | #define RESET_ENCODERS 'r'
16 | #define SERVO_WRITE 's'
17 | #define SERVO_READ 't'
18 | #define UPDATE_PID 'u'
19 | #define DIGITAL_WRITE 'w'
20 | #define ANALOG_WRITE 'x'
21 | #define LEFT 0
22 | #define RIGHT 1
23 | #define FORWARDS true
24 | #define BACKWARDS false
25 |
26 | #endif
27 |
--------------------------------------------------------------------------------
/ros_arduino_msgs/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(ros_arduino_msgs)
3 |
4 | find_package(catkin REQUIRED COMPONENTS std_msgs message_generation)
5 |
6 | add_message_files(FILES
7 | AnalogFloat.msg
8 | Analog.msg
9 | ArduinoConstants.msg
10 | Digital.msg
11 | SensorState.msg
12 | )
13 |
14 | add_service_files(FILES
15 | DigitalSetDirection.srv
16 | DigitalWrite.srv
17 | DigitalRead.srv
18 | ServoRead.srv
19 | ServoWrite.srv
20 | AnalogWrite.srv
21 | AnalogRead.srv
22 | )
23 |
24 | generate_messages(
25 | DEPENDENCIES
26 | std_msgs
27 | )
28 |
29 | catkin_package(CATKIN_DEPENDS message_runtime std_msgs)
30 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/encoder_driver.h:
--------------------------------------------------------------------------------
1 | /* *************************************************************
2 | Encoder driver function definitions - by James Nugen
3 | ************************************************************ */
4 |
5 |
6 | #ifdef ARDUINO_ENC_COUNTER
7 | //below can be changed, but should be PORTD pins;
8 | //otherwise additional changes in the code are required
9 | #define LEFT_ENC_PIN_A PD2 //pin 2
10 | #define LEFT_ENC_PIN_B PD3 //pin 3
11 |
12 | //below can be changed, but should be PORTC pins
13 | #define RIGHT_ENC_PIN_A PC4 //pin A4
14 | #define RIGHT_ENC_PIN_B PC5 //pin A5
15 | #endif
16 |
17 | #ifdef ARDUINO_MY_COUNTER
18 | #define LEFT_ENC_A 2
19 | #define LEFT_ENC_B 22
20 | #define RIGHT_ENC_A 21
21 | #define RIGHT_ENC_B 24
22 | void initEncoders();
23 | void leftEncoderEvent();
24 | void rightEncoderEvent();
25 | #endif
26 |
27 | long readEncoder(int i);
28 | void resetEncoder(int i);
29 | void resetEncoders();
30 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/sensors.h:
--------------------------------------------------------------------------------
1 | /* Functions for various sensor types */
2 |
3 | float microsecondsToCm(long microseconds)
4 | {
5 | // The speed of sound is 340 m/s or 29 microseconds per cm.
6 | // The ping travels out and back, so to find the distance of the
7 | // object we take half of the distance travelled.
8 | return microseconds / 29 / 2;
9 | }
10 |
11 | long Ping(int pin) {
12 | long duration, range;
13 |
14 | // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
15 | // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
16 | pinMode(pin, OUTPUT);
17 | digitalWrite(pin, LOW);
18 | delayMicroseconds(2);
19 | digitalWrite(pin, HIGH);
20 | delayMicroseconds(5);
21 | digitalWrite(pin, LOW);
22 |
23 | // The same pin is used to read the signal from the PING))): a HIGH
24 | // pulse whose duration is the time (in microseconds) from the sending
25 | // of the ping to the reception of its echo off of an object.
26 | pinMode(pin, INPUT);
27 | duration = pulseIn(pin, HIGH);
28 |
29 | // convert the time into meters
30 | range = microsecondsToCm(duration);
31 |
32 | return(range);
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/MegaRobogaiaPololu/sensors.h:
--------------------------------------------------------------------------------
1 | /* Functions for various sensor types */
2 |
3 | float microsecondsToCm(long microseconds)
4 | {
5 | // The speed of sound is 340 m/s or 29 microseconds per cm.
6 | // The ping travels out and back, so to find the distance of the
7 | // object we take half of the distance travelled.
8 | return microseconds / 29 / 2;
9 | }
10 |
11 | long Ping(int pin) {
12 | long duration, range;
13 |
14 | // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
15 | // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
16 | pinMode(pin, OUTPUT);
17 | digitalWrite(pin, LOW);
18 | delayMicroseconds(2);
19 | digitalWrite(pin, HIGH);
20 | delayMicroseconds(5);
21 | digitalWrite(pin, LOW);
22 |
23 | // The same pin is used to read the signal from the PING))): a HIGH
24 | // pulse whose duration is the time (in microseconds) from the sending
25 | // of the ping to the reception of its echo off of an object.
26 | pinMode(pin, INPUT);
27 | duration = pulseIn(pin, HIGH);
28 |
29 | // convert the time into meters
30 | range = microsecondsToCm(duration);
31 |
32 | return(range);
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/servos.h:
--------------------------------------------------------------------------------
1 | #ifndef SERVOS_H
2 | #define SERVOS_H
3 |
4 |
5 | #define N_SERVOS 2
6 |
7 | // This delay in milliseconds determines the pause
8 | // between each one degree step the servo travels. Increasing
9 | // this number will make the servo sweep more slowly.
10 | // Decreasing this number will make the servo sweep more quickly.
11 | // Zero is the default number and will make the servos spin at
12 | // full speed. 150 ms makes them spin very slowly.
13 | int stepDelay [N_SERVOS] = { 0, 0 }; // ms
14 |
15 | // Pins
16 | byte servoPins [N_SERVOS] = { 3, 4 };
17 |
18 | // Initial Position
19 | byte servoInitPosition [N_SERVOS] = { 90, 90 }; // [0, 180] degrees
20 |
21 |
22 | class SweepServo
23 | {
24 | public:
25 | SweepServo();
26 | void initServo(
27 | int servoPin,
28 | int stepDelayMs,
29 | int initPosition);
30 | void doSweep();
31 | void setTargetPosition(int position);
32 | Servo getServo();
33 |
34 | private:
35 | Servo servo;
36 | int stepDelayMs;
37 | int currentPositionDegrees;
38 | int targetPositionDegrees;
39 | long lastSweepCommand;
40 | };
41 |
42 | SweepServo servos [N_SERVOS];
43 |
44 | #endif
45 |
--------------------------------------------------------------------------------
/ros_arduino_python/config/my_arduino_params.yaml:
--------------------------------------------------------------------------------
1 | # For a direct USB cable connection, the port name is typically
2 | # /dev/ttyACM# where is # is a number such as 0, 1, 2, etc
3 | # For a wireless connection like XBee, the port is typically
4 | # /dev/ttyUSB# where # is a number such as 0, 1, 2, etc.
5 |
6 | port: /dev/ttyACM0
7 | baud: 57600
8 | timeout: 0.1
9 |
10 | rate: 50
11 | sensorstate_rate: 10
12 |
13 | use_base_controller: True
14 | base_controller_rate: 10
15 |
16 | # For a robot that uses base_footprint, change base_frame to base_footprint
17 | base_frame: base_footprint
18 |
19 | # === Robot drivetrain parameters
20 | wheel_diameter: 0.065
21 | wheel_track: 0.145
22 | encoder_resolution: 11700
23 | gear_reduction: 0.033
24 | motors_reversed: true
25 |
26 | # === PID parameters
27 | Kp: 10
28 | Kd: 12
29 | Ki: 0
30 | Ko: 50
31 | accel_limit: 1.0
32 |
33 | # === Sensor definitions. Examples only - edit for your robot.
34 | # Sensor type can be one of the follow (case sensitive!):
35 | # * Ping
36 | # * GP2D12
37 | # * Analog
38 | # * Digital
39 | # * PololuMotorCurrent
40 | # * PhidgetsVoltage
41 | # * PhidgetsCurrent (20 Amp, DC)
42 |
43 |
44 |
45 | sensors: {
46 | motor_current_left: {pin: 0, type: PololuMotorCurrent, rate: 5},
47 | motor_current_right: {pin: 1, type: PololuMotorCurrent, rate: 5},
48 | #ir_front_center: {pin: 2, type: GP2D12, rate: 10},
49 | #sonar_front_center: {pin: 5, type: Ping, rate: 10},
50 | arduino_led: {pin: 13, type: Digital, rate: 5, direction: output}
51 | }
52 |
--------------------------------------------------------------------------------
/ros_arduino_python/config/arduino_params.yaml:
--------------------------------------------------------------------------------
1 | # For a direct USB cable connection, the port name is typically
2 | # /dev/ttyACM# where is # is a number such as 0, 1, 2, etc
3 | # For a wireless connection like XBee, the port is typically
4 | # /dev/ttyUSB# where # is a number such as 0, 1, 2, etc.
5 |
6 | port: /dev/ttyACM0
7 | baud: 57600
8 | timeout: 0.1
9 |
10 | rate: 50
11 | sensorstate_rate: 10
12 |
13 | use_base_controller: False
14 | base_controller_rate: 10
15 |
16 | # For a robot that uses base_footprint, change base_frame to base_footprint
17 | base_frame: base_link
18 |
19 | # === Robot drivetrain parameters
20 | wheel_diameter: 0.146
21 | wheel_track: 0.2969
22 | encoder_resolution: 8384 # from Pololu for 131:1 motors
23 | gear_reduction: 1.0
24 | motors_reversed: True
25 |
26 | # === PID parameters
27 | Kp: 10
28 | Kd: 12
29 | Ki: 0
30 | Ko: 50
31 | accel_limit: 1.0
32 |
33 | # === Sensor definitions. Examples only - edit for your robot.
34 | # Sensor type can be one of the follow (case sensitive!):
35 | # * Ping
36 | # * GP2D12
37 | # * Analog
38 | # * Digital
39 | # * PololuMotorCurrent
40 | # * PhidgetsVoltage
41 | # * PhidgetsCurrent (20 Amp, DC)
42 |
43 |
44 |
45 | sensors: {
46 | motor_current_left: {pin: 0, type: PololuMotorCurrent, rate: 5},
47 | motor_current_right: {pin: 1, type: PololuMotorCurrent, rate: 5},
48 | #ir_front_center: {pin: 2, type: GP2D12, rate: 10},
49 | #sonar_front_center: {pin: 5, type: Ping, rate: 10},
50 | arduino_led: {pin: 13, type: Digital, rate: 5, direction: output}
51 | }
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/python
3 |
4 | ### Python ###
5 | # temp file
6 | *~
7 |
8 | # Byte-compiled / optimized / DLL files
9 | __pycache__/
10 | *.py[cod]
11 | *$py.class
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | env/
19 | build/
20 | develop-eggs/
21 | dist/
22 | downloads/
23 | eggs/
24 | .eggs/
25 | lib/
26 | lib64/
27 | parts/
28 | sdist/
29 | var/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *,cover
53 | .hypothesis/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 |
63 | # Flask stuff:
64 | instance/
65 | .webassets-cache
66 |
67 | # Scrapy stuff:
68 | .scrapy
69 |
70 | # Sphinx documentation
71 | docs/_build/
72 |
73 | # PyBuilder
74 | target/
75 |
76 | # IPython Notebook
77 | .ipynb_checkpoints
78 |
79 | # pyenv
80 | .python-version
81 |
82 | # celery beat schedule file
83 | celerybeat-schedule
84 |
85 | # dotenv
86 | .env
87 |
88 | # virtualenv
89 | .venv/
90 | venv/
91 | ENV/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/MegaRobogaiaPololu/diff_controller.h:
--------------------------------------------------------------------------------
1 | /* Functions and type-defs for PID control.
2 |
3 | Taken mostly from Mike Ferguson's ArbotiX code which lives at:
4 |
5 | http://vanadium-ros-pkg.googlecode.com/svn/trunk/arbotix/
6 | */
7 |
8 | /* PID setpoint info For a Motor */
9 | typedef struct {
10 | double TargetTicksPerFrame; // target speed in ticks per frame
11 | long Encoder; // encoder count
12 | long PrevEnc; // last encoder count
13 | int PrevErr; // last error
14 | int Ierror; // integrated error
15 | int output; // last motor setting
16 | }
17 | SetPointInfo;
18 |
19 | SetPointInfo leftPID, rightPID;
20 |
21 | /* PID Parameters */
22 | int Kp = 20;
23 | int Kd = 12;
24 | int Ki = 0;
25 | int Ko = 50;
26 |
27 | unsigned char moving = 0; // is the base in motion?
28 |
29 | /* PID routine to compute the next motor commands */
30 | void doPID(SetPointInfo * p) {
31 | long Perror;
32 | long output;
33 |
34 | Perror = p->TargetTicksPerFrame - (p->Encoder - p->PrevEnc);
35 |
36 | // Derivative error is the delta Perror
37 | output = (Kp * Perror + Kd * (Perror - p->PrevErr) + Ki * p->Ierror) / Ko;
38 | p->PrevErr = Perror;
39 | p->PrevEnc = p->Encoder;
40 |
41 | output += p->output;
42 | // Accumulate Integral error *or* Limit output.
43 | // Stop accumulating when output saturates
44 | if (output >= MAX_PWM)
45 | output = MAX_PWM;
46 | else if (output <= -MAX_PWM)
47 | output = -MAX_PWM;
48 | else
49 | p->Ierror += Perror;
50 |
51 | p->output = output;
52 | }
53 |
54 | /* Read the encoder values and call the PID routine */
55 | void updatePID() {
56 | /* Read the encoders */
57 | leftPID.Encoder = readEncoder(0);
58 | rightPID.Encoder = readEncoder(1);
59 |
60 | /* If we're not moving there is nothing more to do */
61 | if (!moving)
62 | return;
63 |
64 | /* Compute PID update for each motor */
65 | doPID(&rightPID);
66 | doPID(&leftPID);
67 |
68 | /* Set the motor speeds accordingly */
69 | setMotorSpeeds(leftPID.output, rightPID.output);
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/servos.ino:
--------------------------------------------------------------------------------
1 | /***************************************************************
2 | Servo Sweep - by Nathaniel Gallinger
3 |
4 | Sweep servos one degree step at a time with a user defined
5 | delay in between steps. Supports changing direction
6 | mid-sweep. Important for applications such as robotic arms
7 | where the stock servo speed is too fast for the strength
8 | of your system.
9 |
10 | *************************************************************/
11 |
12 | #ifdef USE_SERVOS
13 |
14 |
15 | // Constructor
16 | SweepServo::SweepServo()
17 | {
18 | this->currentPositionDegrees = 0;
19 | this->targetPositionDegrees = 0;
20 | this->lastSweepCommand = 0;
21 | }
22 |
23 |
24 | // Init
25 | void SweepServo::initServo(
26 | int servoPin,
27 | int stepDelayMs,
28 | int initPosition)
29 | {
30 | this->servo.attach(servoPin);
31 | this->stepDelayMs = stepDelayMs;
32 | this->currentPositionDegrees = initPosition;
33 | this->targetPositionDegrees = initPosition;
34 | this->lastSweepCommand = millis();
35 | }
36 |
37 |
38 | // Perform Sweep
39 | void SweepServo::doSweep()
40 | {
41 |
42 | // Get ellapsed time
43 | int delta = millis() - this->lastSweepCommand;
44 |
45 | // Check if time for a step
46 | if (delta > this->stepDelayMs) {
47 | // Check step direction
48 | if (this->targetPositionDegrees > this->currentPositionDegrees) {
49 | this->currentPositionDegrees++;
50 | this->servo.write(this->currentPositionDegrees);
51 | }
52 | else if (this->targetPositionDegrees < this->currentPositionDegrees) {
53 | this->currentPositionDegrees--;
54 | this->servo.write(this->currentPositionDegrees);
55 | }
56 | // if target == current position, do nothing
57 |
58 | // reset timer
59 | this->lastSweepCommand = millis();
60 | }
61 | }
62 |
63 |
64 | // Set a new target position
65 | void SweepServo::setTargetPosition(int position)
66 | {
67 | this->targetPositionDegrees = position;
68 | }
69 |
70 |
71 | // Accessor for servo object
72 | Servo SweepServo::getServo()
73 | {
74 | return this->servo;
75 | }
76 |
77 |
78 | #endif
79 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/motor_driver.ino:
--------------------------------------------------------------------------------
1 | /***************************************************************
2 | Motor driver definitions
3 |
4 | Add a "#elif defined" block to this file to include support
5 | for a particular motor driver. Then add the appropriate
6 | #define near the top of the main ROSArduinoBridge.ino file.
7 |
8 | *************************************************************/
9 |
10 | #ifdef USE_BASE
11 |
12 | #ifdef POLOLU_VNH5019
13 | /* Include the Pololu library */
14 | #include "DualVNH5019MotorShield.h"
15 |
16 | /* Create the motor driver object */
17 | DualVNH5019MotorShield drive;
18 |
19 | /* Wrap the motor driver initialization */
20 | void initMotorController() {
21 | drive.init();
22 | }
23 |
24 | /* Wrap the drive motor set speed function */
25 | void setMotorSpeed(int i, int spd) {
26 | if (i == LEFT) drive.setM1Speed(spd);
27 | else drive.setM2Speed(spd);
28 | }
29 |
30 | // A convenience function for setting both motor speeds
31 | void setMotorSpeeds(int leftSpeed, int rightSpeed) {
32 | setMotorSpeed(LEFT, leftSpeed);
33 | setMotorSpeed(RIGHT, rightSpeed);
34 | }
35 | #elif defined POLOLU_MC33926
36 | /* Include the Pololu library */
37 | #include "DualMC33926MotorShield.h"
38 |
39 | /* Create the motor driver object */
40 | DualMC33926MotorShield drive;
41 |
42 | /* Wrap the motor driver initialization */
43 | void initMotorController() {
44 | drive.init();
45 | }
46 |
47 | /* Wrap the drive motor set speed function */
48 | void setMotorSpeed(int i, int spd) {
49 | if (i == LEFT) drive.setM1Speed(spd);
50 | else drive.setM2Speed(spd);
51 | }
52 |
53 | // A convenience function for setting both motor speeds
54 | void setMotorSpeeds(int leftSpeed, int rightSpeed) {
55 | setMotorSpeed(LEFT, leftSpeed);
56 | setMotorSpeed(RIGHT, rightSpeed);
57 | }
58 |
59 | #elif defined L298N_DUAL_HBRIDGE
60 |
61 | boolean directionLeft = false;
62 | boolean directionRight = false;
63 |
64 | boolean direction(int i){
65 | if(i == LEFT){
66 | return directionLeft;
67 | }else{
68 | return directionRight;
69 | }
70 | }
71 | void initMotorController() {
72 | // set all the motor control pins to outputs
73 | pinMode(ENA, OUTPUT);
74 | pinMode(ENB, OUTPUT);
75 | pinMode(IN1, OUTPUT);
76 | pinMode(IN2, OUTPUT);
77 | pinMode(IN3, OUTPUT);
78 | pinMode(IN4, OUTPUT);
79 | }
80 |
81 | void setMotorSpeed(int i, int spd) {
82 | if(spd>MAX_PWM){
83 | spd=MAX_PWM;
84 | }
85 | if(spd<-MAX_PWM){
86 | spd=-1*MAX_PWM;
87 | }
88 | if (i == LEFT){
89 | if(spd>=0){
90 | directionLeft = FORWARDS;
91 | digitalWrite(IN2, HIGH);
92 | digitalWrite(IN1, LOW);
93 | analogWrite(ENA, spd);
94 | }else if(spd < 0){
95 | directionLeft = BACKWARDS;
96 | digitalWrite(IN1, HIGH);
97 | digitalWrite(IN2, LOW);
98 | analogWrite(ENA, -spd);
99 | }
100 | }
101 | else {
102 | if(spd>=0){
103 | directionRight = FORWARDS;
104 | digitalWrite(IN4, HIGH);
105 | digitalWrite(IN3, LOW);
106 | analogWrite(ENB, spd);
107 | }else if(spd<0){
108 | directionRight = BACKWARDS;
109 | digitalWrite(IN3, HIGH);
110 | digitalWrite(IN4, LOW);
111 | analogWrite(ENB, -spd);
112 | }
113 | }
114 | }
115 |
116 | void setMotorSpeeds(int leftSpeed, int rightSpeed) {
117 | setMotorSpeed(LEFT, leftSpeed);
118 | setMotorSpeed(RIGHT, rightSpeed);
119 | }
120 | #else
121 | #error A motor driver must be selected!
122 | #endif
123 |
124 | #endif
125 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/diff_controller.h:
--------------------------------------------------------------------------------
1 | /* Functions and type-defs for PID control.
2 |
3 | Taken mostly from Mike Ferguson's ArbotiX code which lives at:
4 |
5 | http://vanadium-ros-pkg.googlecode.com/svn/trunk/arbotix/
6 | */
7 |
8 | /* PID setpoint info For a Motor */
9 | typedef struct {
10 | double TargetTicksPerFrame; // target speed in ticks per frame
11 | long Encoder; // encoder count
12 | long PrevEnc; // last encoder count
13 |
14 | /*
15 | * Using previous input (PrevInput) instead of PrevError to avoid derivative kick,
16 | * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/
17 | */
18 | int PrevInput; // last input
19 | //int PrevErr; // last error
20 |
21 | /*
22 | * Using integrated term (ITerm) instead of integrated error (Ierror),
23 | * to allow tuning changes,
24 | * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
25 | */
26 | //int Ierror;
27 | int ITerm; //integrated term
28 |
29 | long output; // last motor setting
30 | }
31 | SetPointInfo;
32 |
33 | SetPointInfo leftPID, rightPID;
34 |
35 | /* PID Parameters */
36 | int Kp = 20;
37 | int Kd = 12;
38 | int Ki = 0;
39 | int Ko = 50;
40 |
41 | unsigned char moving = 0; // is the base in motion?
42 |
43 | /*
44 | * Initialize PID variables to zero to prevent startup spikes
45 | * when turning PID on to start moving
46 | * In particular, assign both Encoder and PrevEnc the current encoder value
47 | * See http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/
48 | * Note that the assumption here is that PID is only turned on
49 | * when going from stop to moving, that's why we can init everything on zero.
50 | */
51 | void resetPID(){
52 | leftPID.TargetTicksPerFrame = 0.0;
53 | leftPID.Encoder = readEncoder(LEFT);
54 | leftPID.PrevEnc = leftPID.Encoder;
55 | leftPID.output = 0;
56 | leftPID.PrevInput = 0;
57 | leftPID.ITerm = 0;
58 |
59 | rightPID.TargetTicksPerFrame = 0.0;
60 | rightPID.Encoder = readEncoder(RIGHT);
61 | rightPID.PrevEnc = rightPID.Encoder;
62 | rightPID.output = 0;
63 | rightPID.PrevInput = 0;
64 | rightPID.ITerm = 0;
65 | }
66 |
67 | /* PID routine to compute the next motor commands */
68 | void doPID(SetPointInfo * p) {
69 | long Perror;
70 | long output;
71 | int input;
72 |
73 | //Perror = p->TargetTicksPerFrame - (p->Encoder - p->PrevEnc);
74 | input = p->Encoder - p->PrevEnc;
75 | Perror = p->TargetTicksPerFrame - input;
76 |
77 |
78 | /*
79 | * Avoid derivative kick and allow tuning changes,
80 | * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/
81 | * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
82 | */
83 | //output = (Kp * Perror + Kd * (Perror - p->PrevErr) + Ki * p->Ierror) / Ko;
84 | // p->PrevErr = Perror;
85 | output = (Kp * Perror - Kd * (input - p->PrevInput) + p->ITerm) / Ko;
86 | p->PrevEnc = p->Encoder;
87 |
88 | output += p->output;
89 | // Accumulate Integral error *or* Limit output.
90 | // Stop accumulating when output saturates
91 | if (output >= MAX_PWM)
92 | output = MAX_PWM;
93 | else if (output <= -MAX_PWM)
94 | output = -MAX_PWM;
95 | else
96 | /*
97 | * allow turning changes, see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
98 | */
99 | p->ITerm += Ki * Perror;
100 |
101 | p->output = output;
102 | p->PrevInput = input;
103 | }
104 |
105 | /* Read the encoder values and call the PID routine */
106 | void updatePID() {
107 | /* Read the encoders */
108 | leftPID.Encoder = readEncoder(LEFT);
109 | rightPID.Encoder = readEncoder(RIGHT);
110 |
111 | /* If we're not moving there is nothing more to do */
112 | if (!moving){
113 | /*
114 | * Reset PIDs once, to prevent startup spikes,
115 | * see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/
116 | * PrevInput is considered a good proxy to detect
117 | * whether reset has already happened
118 | */
119 | if (leftPID.PrevInput != 0 || rightPID.PrevInput != 0) resetPID();
120 | return;
121 | }
122 |
123 | /* Compute PID update for each motor */
124 | doPID(&rightPID);
125 | doPID(&leftPID);
126 |
127 | /* Set the motor speeds accordingly */
128 | setMotorSpeeds(leftPID.output, rightPID.output);
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/encoder_driver.ino:
--------------------------------------------------------------------------------
1 | /* *************************************************************
2 | Encoder definitions
3 |
4 | Add an "#ifdef" block to this file to include support for
5 | a particular encoder board or library. Then add the appropriate
6 | #define near the top of the main ROSArduinoBridge.ino file.
7 |
8 | ************************************************************ */
9 |
10 | #ifdef USE_BASE
11 |
12 | #ifdef ROBOGAIA
13 | /* The Robogaia Mega Encoder shield */
14 | #include "MegaEncoderCounter.h"
15 |
16 | /* Create the encoder shield object */
17 | MegaEncoderCounter encoders = MegaEncoderCounter(4); // Initializes the Mega Encoder Counter in the 4X Count mode
18 |
19 | /* Wrap the encoder reading function */
20 | long readEncoder(int i) {
21 | if (i == LEFT) return encoders.YAxisGetCount();
22 | else return encoders.XAxisGetCount();
23 | }
24 |
25 | /* Wrap the encoder reset function */
26 | void resetEncoder(int i) {
27 | if (i == LEFT) return encoders.YAxisReset();
28 | else return encoders.XAxisReset();
29 | }
30 | #elif defined(ARDUINO_ENC_COUNTER)
31 | volatile long left_enc_pos = 0L;
32 | volatile long right_enc_pos = 0L;
33 | static const int8_t ENC_STATES [] = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0}; //encoder lookup table
34 |
35 | /* Interrupt routine for LEFT encoder, taking care of actual counting */
36 | ISR (PCINT2_vect){
37 | static uint8_t enc_last=0;
38 |
39 | enc_last <<=2; //shift previous state two places
40 | enc_last |= (PIND & (3 << 2)) >> 2; //read the current state into lowest 2 bits
41 |
42 | left_enc_pos += ENC_STATES[(enc_last & 0x0f)];
43 | }
44 |
45 | /* Interrupt routine for RIGHT encoder, taking care of actual counting */
46 | ISR (PCINT1_vect){
47 | static uint8_t enc_last=0;
48 |
49 | enc_last <<=2; //shift previous state two places
50 | enc_last |= (PINC & (3 << 4)) >> 4; //read the current state into lowest 2 bits
51 |
52 | right_enc_pos += ENC_STATES[(enc_last & 0x0f)];
53 | }
54 |
55 | /* Wrap the encoder reading function */
56 | long readEncoder(int i) {
57 | if (i == LEFT) return left_enc_pos;
58 | else return right_enc_pos;
59 | }
60 |
61 | /* Wrap the encoder reset function */
62 | void resetEncoder(int i) {
63 | if (i == LEFT){
64 | left_enc_pos=0L;
65 | return;
66 | } else {
67 | right_enc_pos=0L;
68 | return;
69 | }
70 | }
71 | #elif defined(ARDUINO_MY_COUNTER)
72 | volatile long left_enc_pos = 0L;
73 | volatile long right_enc_pos = 0L;
74 |
75 | void initEncoders(){
76 | pinMode(LEFT_ENC_A, INPUT);
77 | pinMode(LEFT_ENC_B, INPUT);
78 | pinMode(RIGHT_ENC_A, INPUT);
79 | pinMode(RIGHT_ENC_B, INPUT);
80 |
81 | attachInterrupt(0, leftEncoderEvent, CHANGE);
82 | attachInterrupt(2, rightEncoderEvent, CHANGE);
83 | }
84 |
85 | // encoder event for the interrupt call
86 | void leftEncoderEvent() {
87 | if (digitalRead(LEFT_ENC_A) == HIGH) {
88 | if (digitalRead(LEFT_ENC_B) == LOW) {
89 | left_enc_pos++;
90 | }
91 | else {
92 | left_enc_pos--;
93 | }
94 | }
95 | else {
96 | if (digitalRead(LEFT_ENC_B) == LOW) {
97 | left_enc_pos--;
98 | }
99 | else {
100 | left_enc_pos++;
101 | }
102 | }
103 | }
104 |
105 | // encoder event for the interrupt call
106 | void rightEncoderEvent() {
107 | if (digitalRead(RIGHT_ENC_A) == HIGH) {
108 | if (digitalRead(RIGHT_ENC_B) == LOW) {
109 | right_enc_pos++;
110 | }
111 | else {
112 | right_enc_pos--;
113 | }
114 | }
115 | else {
116 | if (digitalRead(RIGHT_ENC_B) == LOW) {
117 | right_enc_pos--;
118 | }
119 | else {
120 | right_enc_pos++;
121 | }
122 | }
123 | }
124 |
125 | /* Wrap the encoder reading function */
126 | long readEncoder(int i) {
127 | if (i == LEFT) return left_enc_pos;
128 | else return -right_enc_pos; // It's just because my right encoder get reverse value so if yours is normal, don't add "-"
129 | }
130 |
131 | /* Wrap the encoder reset function */
132 | void resetEncoder(int i) {
133 | if (i == LEFT){
134 | left_enc_pos=0L;
135 | return;
136 | } else {
137 | right_enc_pos=0L;
138 | return;
139 | }
140 | }
141 | #else
142 | #error A encoder driver must be selected!
143 | #endif
144 |
145 | /* Wrap the encoder reset function */
146 | void resetEncoders() {
147 | resetEncoder(LEFT);
148 | resetEncoder(RIGHT);
149 | }
150 |
151 | #endif
152 |
--------------------------------------------------------------------------------
/ros_arduino_python/nodes/arduino_node.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | A ROS Node for the Arduino microcontroller
5 |
6 | Created for the Pi Robot Project: http://www.pirobot.org
7 | Copyright (c) 2012 Patrick Goebel. All rights reserved.
8 |
9 | This program is free software; you can redistribute it and/or modify
10 | it under the terms of the GNU General Public License as published by
11 | the Free Software Foundation; either version 2 of the License, or
12 | (at your option) any later version.
13 |
14 | This program is distributed in the hope that it will be useful,
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | GNU General Public License for more details at:
18 |
19 | http://www.gnu.org/licenses/gpl.html
20 | """
21 |
22 | import rospy
23 | from ros_arduino_python.arduino_driver import Arduino
24 | from ros_arduino_python.arduino_sensors import *
25 | from ros_arduino_msgs.srv import *
26 | from ros_arduino_python.base_controller import BaseController
27 | from geometry_msgs.msg import Twist
28 | import os, time
29 | import thread
30 |
31 | class ArduinoROS():
32 | def __init__(self):
33 | rospy.init_node('Arduino', log_level=rospy.DEBUG)
34 |
35 | # Cleanup when termniating the node
36 | rospy.on_shutdown(self.shutdown)
37 |
38 | self.port = rospy.get_param("~port", "/dev/ttyACM0")
39 | self.baud = int(rospy.get_param("~baud", 57600))
40 | self.timeout = rospy.get_param("~timeout", 0.5)
41 | self.base_frame = rospy.get_param("~base_frame", 'base_link')
42 |
43 | # Overall loop rate: should be faster than fastest sensor rate
44 | self.rate = int(rospy.get_param("~rate", 50))
45 | r = rospy.Rate(self.rate)
46 |
47 | # Rate at which summary SensorState message is published. Individual sensors publish
48 | # at their own rates.
49 | self.sensorstate_rate = int(rospy.get_param("~sensorstate_rate", 10))
50 |
51 | self.use_base_controller = rospy.get_param("~use_base_controller", False)
52 |
53 | # Set up the time for publishing the next SensorState message
54 | now = rospy.Time.now()
55 | self.t_delta_sensors = rospy.Duration(1.0 / self.sensorstate_rate)
56 | self.t_next_sensors = now + self.t_delta_sensors
57 |
58 | # Initialize a Twist message
59 | self.cmd_vel = Twist()
60 |
61 | # A cmd_vel publisher so we can stop the robot when shutting down
62 | self.cmd_vel_pub = rospy.Publisher('cmd_vel', Twist, queue_size=5)
63 |
64 | # The SensorState publisher periodically publishes the values of all sensors on
65 | # a single topic.
66 | self.sensorStatePub = rospy.Publisher('~sensor_state', SensorState, queue_size=5)
67 |
68 | # A service to position a PWM servo
69 | rospy.Service('~servo_write', ServoWrite, self.ServoWriteHandler)
70 |
71 | # A service to read the position of a PWM servo
72 | rospy.Service('~servo_read', ServoRead, self.ServoReadHandler)
73 |
74 | # A service to turn set the direction of a digital pin (0 = input, 1 = output)
75 | rospy.Service('~digital_set_direction', DigitalSetDirection, self.DigitalSetDirectionHandler)
76 |
77 | # A service to turn a digital sensor on or off
78 | rospy.Service('~digital_write', DigitalWrite, self.DigitalWriteHandler)
79 |
80 | # A service to read the value of a digital sensor
81 | rospy.Service('~digital_read', DigitalRead, self.DigitalReadHandler)
82 |
83 | # A service to set pwm values for the pins
84 | rospy.Service('~analog_write', AnalogWrite, self.AnalogWriteHandler)
85 |
86 | # A service to read the value of an analog sensor
87 | rospy.Service('~analog_read', AnalogRead, self.AnalogReadHandler)
88 |
89 | # Initialize the controlller
90 | self.controller = Arduino(self.port, self.baud, self.timeout)
91 |
92 | # Make the connection
93 | self.controller.connect()
94 |
95 | rospy.loginfo("Connected to Arduino on port " + self.port + " at " + str(self.baud) + " baud")
96 |
97 | # Reserve a thread lock
98 | mutex = thread.allocate_lock()
99 |
100 | # Initialize any sensors
101 | self.mySensors = list()
102 |
103 | sensor_params = rospy.get_param("~sensors", dict({}))
104 |
105 | for name, params in sensor_params.iteritems():
106 | # Set the direction to input if not specified
107 | try:
108 | params['direction']
109 | except:
110 | params['direction'] = 'input'
111 |
112 | if params['type'] == "Ping":
113 | sensor = Ping(self.controller, name, params['pin'], params['rate'], self.base_frame)
114 | elif params['type'] == "GP2D12":
115 | sensor = GP2D12(self.controller, name, params['pin'], params['rate'], self.base_frame)
116 | elif params['type'] == 'Digital':
117 | sensor = DigitalSensor(self.controller, name, params['pin'], params['rate'], self.base_frame, direction=params['direction'])
118 | elif params['type'] == 'Analog':
119 | sensor = AnalogSensor(self.controller, name, params['pin'], params['rate'], self.base_frame, direction=params['direction'])
120 | elif params['type'] == 'PololuMotorCurrent':
121 | sensor = PololuMotorCurrent(self.controller, name, params['pin'], params['rate'], self.base_frame)
122 | elif params['type'] == 'PhidgetsVoltage':
123 | sensor = PhidgetsVoltage(self.controller, name, params['pin'], params['rate'], self.base_frame)
124 | elif params['type'] == 'PhidgetsCurrent':
125 | sensor = PhidgetsCurrent(self.controller, name, params['pin'], params['rate'], self.base_frame)
126 |
127 | # if params['type'] == "MaxEZ1":
128 | # self.sensors[len(self.sensors)]['trigger_pin'] = params['trigger_pin']
129 | # self.sensors[len(self.sensors)]['output_pin'] = params['output_pin']
130 |
131 | self.mySensors.append(sensor)
132 | rospy.loginfo(name + " " + str(params) + " published on topic " + rospy.get_name() + "/sensor/" + name)
133 |
134 | # Initialize the base controller if used
135 | if self.use_base_controller:
136 | self.myBaseController = BaseController(self.controller, self.base_frame)
137 |
138 | # Start polling the sensors and base controller
139 | while not rospy.is_shutdown():
140 | for sensor in self.mySensors:
141 | mutex.acquire()
142 | sensor.poll()
143 | mutex.release()
144 |
145 | if self.use_base_controller:
146 | mutex.acquire()
147 | self.myBaseController.poll()
148 | mutex.release()
149 |
150 | # Publish all sensor values on a single topic for convenience
151 | now = rospy.Time.now()
152 |
153 | if now > self.t_next_sensors:
154 | msg = SensorState()
155 | msg.header.frame_id = self.base_frame
156 | msg.header.stamp = now
157 | for i in range(len(self.mySensors)):
158 | msg.name.append(self.mySensors[i].name)
159 | msg.value.append(self.mySensors[i].value)
160 | try:
161 | self.sensorStatePub.publish(msg)
162 | except:
163 | pass
164 |
165 | self.t_next_sensors = now + self.t_delta_sensors
166 |
167 | r.sleep()
168 |
169 | # Service callback functions
170 | def ServoWriteHandler(self, req):
171 | self.controller.servo_write(req.id, req.value)
172 | return ServoWriteResponse()
173 |
174 | def ServoReadHandler(self, req):
175 | pos = self.controller.servo_read(req.id)
176 | return ServoReadResponse(pos)
177 |
178 | def DigitalSetDirectionHandler(self, req):
179 | self.controller.pin_mode(req.pin, req.direction)
180 | return DigitalSetDirectionResponse()
181 |
182 | def DigitalWriteHandler(self, req):
183 | self.controller.digital_write(req.pin, req.value)
184 | return DigitalWriteResponse()
185 |
186 | def DigitalReadHandler(self, req):
187 | value = self.controller.digital_read(req.pin)
188 | return DigitalReadResponse(value)
189 |
190 | def AnalogWriteHandler(self, req):
191 | self.controller.analog_write(req.pin, req.value)
192 | return AnalogWriteResponse()
193 |
194 | def AnalogReadHandler(self, req):
195 | value = self.controller.analog_read(req.pin)
196 | return AnalogReadResponse(value)
197 |
198 | def shutdown(self):
199 | # Stop the robot
200 | try:
201 | rospy.loginfo("Stopping the robot...")
202 | self.cmd_vel_pub.Publish(Twist())
203 | rospy.sleep(2)
204 | except:
205 | pass
206 | rospy.loginfo("Shutting down Arduino Node...")
207 |
208 | if __name__ == '__main__':
209 | myArduino = ArduinoROS()
210 |
--------------------------------------------------------------------------------
/ros_arduino_python/src/ros_arduino_python/arduino_sensors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | Sensor class for the arudino_python package
5 |
6 | Created for the Pi Robot Project: http://www.pirobot.org
7 | Copyright (c) 2012 Patrick Goebel. All rights reserved.
8 |
9 | This program is free software; you can redistribute it and/or modify
10 | it under the terms of the GNU General Public License as published by
11 | the Free Software Foundation; either version 2 of the License, or
12 | (at your option) any later version.
13 |
14 | This program is distributed in the hope that it will be useful,
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | GNU General Public License for more details at:
18 |
19 | http://www.gnu.org/licenses/gpl.html
20 | """
21 |
22 | import roslib; roslib.load_manifest('ros_arduino_python')
23 | import rospy
24 | from sensor_msgs.msg import Range
25 | from ros_arduino_msgs.msg import *
26 |
27 | LOW = 0
28 | HIGH = 1
29 |
30 | INPUT = 0
31 | OUTPUT = 1
32 |
33 | class MessageType:
34 | ANALOG = 0
35 | DIGITAL = 1
36 | RANGE = 2
37 | FLOAT = 3
38 | INT = 4
39 | BOOL = 5
40 |
41 | class Sensor(object):
42 | def __init__(self, controller, name, pin, rate, frame_id, direction="input", **kwargs):
43 | self.controller = controller
44 | self.name = name
45 | self.pin = pin
46 | self.rate = rate
47 | self.direction = direction
48 |
49 | self.frame_id = frame_id
50 | self.value = None
51 |
52 | self.t_delta = rospy.Duration(1.0 / self.rate)
53 | self.t_next = rospy.Time.now() + self.t_delta
54 |
55 | def poll(self):
56 | now = rospy.Time.now()
57 | if now > self.t_next:
58 | if self.direction == "input":
59 | try:
60 | self.value = self.read_value()
61 | except:
62 | return
63 | else:
64 | try:
65 | self.ack = self.write_value()
66 | except:
67 | return
68 |
69 | # For range sensors, assign the value to the range message field
70 | if self.message_type == MessageType.RANGE:
71 | self.msg.range = self.value
72 | else:
73 | self.msg.value = self.value
74 |
75 | # Add a timestamp and publish the message
76 | self.msg.header.stamp = rospy.Time.now()
77 | self.pub.publish(self.msg)
78 |
79 | self.t_next = now + self.t_delta
80 |
81 | class AnalogSensor(Sensor):
82 | def __init__(self, *args, **kwargs):
83 | super(AnalogSensor, self).__init__(*args, **kwargs)
84 |
85 | self.message_type = MessageType.ANALOG
86 |
87 | self.msg = Analog()
88 | self.msg.header.frame_id = self.frame_id
89 |
90 | self.pub = rospy.Publisher("~sensor/" + self.name, Analog, queue_size=5)
91 |
92 | if self.direction == "output":
93 | self.controller.pin_mode(self.pin, OUTPUT)
94 | else:
95 | self.controller.pin_mode(self.pin, INPUT)
96 |
97 | self.value = LOW
98 |
99 | def read_value(self):
100 | return self.controller.analog_read(self.pin)
101 |
102 | def write_value(self, value):
103 | return self.controller.analog_write(self.pin, value)
104 |
105 | class AnalogFloatSensor(Sensor):
106 | def __init__(self, *args, **kwargs):
107 | super(AnalogFloatSensor, self).__init__(*args, **kwargs)
108 |
109 | self.message_type = MessageType.ANALOG
110 |
111 | self.msg = AnalogFloat()
112 | self.msg.header.frame_id = self.frame_id
113 |
114 | self.pub = rospy.Publisher("~sensor/" + self.name, AnalogFloat, queue_size=5)
115 |
116 | if self.direction == "output":
117 | self.controller.pin_mode(self.pin, OUTPUT)
118 | else:
119 | self.controller.pin_mode(self.pin, INPUT)
120 |
121 | self.value = LOW
122 |
123 | def read_value(self):
124 | return self.controller.analog_read(self.pin)
125 |
126 | def write_value(self, value):
127 | return self.controller.analog_write(self.pin, value)
128 |
129 |
130 | class DigitalSensor(Sensor):
131 | def __init__(self, *args, **kwargs):
132 | super(DigitalSensor, self).__init__(*args, **kwargs)
133 |
134 | self.message_type = MessageType.BOOL
135 |
136 | self.msg = Digital()
137 | self.msg.header.frame_id = self.frame_id
138 |
139 | self.pub = rospy.Publisher("~sensor/" + self.name, Digital, queue_size=5)
140 |
141 | if self.direction == "output":
142 | self.controller.pin_mode(self.pin, OUTPUT)
143 | else:
144 | self.controller.pin_mode(self.pin, INPUT)
145 |
146 | self.value = LOW
147 |
148 | def read_value(self):
149 | return self.controller.digital_read(self.pin)
150 |
151 | def write_value(self):
152 | # Alternate HIGH/LOW when writing at a fixed rate
153 | self.value = not self.value
154 | return self.controller.digital_write(self.pin, self.value)
155 |
156 |
157 | class RangeSensor(Sensor):
158 | def __init__(self, *args, **kwargs):
159 | super(RangeSensor, self).__init__(*args, **kwargs)
160 |
161 | self.message_type = MessageType.RANGE
162 |
163 | self.msg = Range()
164 | self.msg.header.frame_id = self.frame_id
165 |
166 | self.pub = rospy.Publisher("~sensor/" + self.name, Range, queue_size=5)
167 |
168 | def read_value(self):
169 | self.msg.header.stamp = rospy.Time.now()
170 |
171 |
172 | class SonarSensor(RangeSensor):
173 | def __init__(self, *args, **kwargs):
174 | super(SonarSensor, self).__init__(*args, **kwargs)
175 |
176 | self.msg.radiation_type = Range.ULTRASOUND
177 |
178 |
179 | class IRSensor(RangeSensor):
180 | def __init__(self, *args, **kwargs):
181 | super(IRSensor, self).__init__(*args, **kwargs)
182 |
183 | self.msg.radiation_type = Range.INFRARED
184 |
185 | class Ping(SonarSensor):
186 | def __init__(self,*args, **kwargs):
187 | super(Ping, self).__init__(*args, **kwargs)
188 |
189 | self.msg.field_of_view = 0.785398163
190 | self.msg.min_range = 0.02
191 | self.msg.max_range = 3.0
192 |
193 | def read_value(self):
194 | # The Arduino Ping code returns the distance in centimeters
195 | cm = self.controller.ping(self.pin)
196 |
197 | # Convert it to meters for ROS
198 | distance = cm / 100.0
199 |
200 | return distance
201 |
202 |
203 | class GP2D12(IRSensor):
204 | def __init__(self, *args, **kwargs):
205 | super(GP2D12, self).__init__(*args, **kwargs)
206 |
207 | self.msg.field_of_view = 0.001
208 | self.msg.min_range = 0.10
209 | self.msg.max_range = 0.80
210 |
211 | def read_value(self):
212 | value = self.controller.analog_read(self.pin)
213 |
214 | if value <= 3.0:
215 | return self.msg.max_range
216 |
217 | try:
218 | distance = (6787.0 / (float(value) - 3.0)) - 4.0
219 | except:
220 | return self.msg.max_range
221 |
222 | # Convert to meters
223 | distance /= 100.0
224 |
225 | # If we get a spurious reading, set it to the max_range
226 | if distance > self.msg.max_range: distance = self.msg.max_range
227 | if distance < self.msg.min_range: distance = self.msg.max_range
228 |
229 | return distance
230 |
231 | class PololuMotorCurrent(AnalogFloatSensor):
232 | def __init__(self, *args, **kwargs):
233 | super(PololuMotorCurrent, self).__init__(*args, **kwargs)
234 |
235 | def read_value(self):
236 | # From the Pololu source code
237 | milliamps = self.controller.analog_read(self.pin) * 34
238 | return milliamps / 1000.0
239 |
240 | class PhidgetsVoltage(AnalogFloatSensor):
241 | def __init__(self, *args, **kwargs):
242 | super(PhidgetsVoltage, self).__init__(*args, **kwargs)
243 |
244 | def read_value(self):
245 | # From the Phidgets documentation
246 | voltage = 0.06 * (self.controller.analog_read(self.pin) - 500.)
247 | return voltage
248 |
249 | class PhidgetsCurrent(AnalogFloatSensor):
250 | def __init__(self, *args, **kwargs):
251 | super(PhidgetsCurrent, self).__init__(*args, **kwargs)
252 |
253 | def read_value(self):
254 | # From the Phidgets documentation for the 20 amp DC sensor
255 | current = 0.05 * (self.controller.analog_read(self.pin) - 500.)
256 | return current
257 |
258 | class MaxEZ1Sensor(SonarSensor):
259 | def __init__(self, *args, **kwargs):
260 | super(MaxEZ1Sensor, self).__init__(*args, **kwargs)
261 |
262 | self.trigger_pin = kwargs['trigger_pin']
263 | self.output_pin = kwargs['output_pin']
264 |
265 | self.msg.field_of_view = 0.785398163
266 | self.msg.min_range = 0.02
267 | self.msg.max_range = 3.0
268 |
269 | def read_value(self):
270 | return self.controller.get_MaxEZ1(self.trigger_pin, self.output_pin)
271 |
272 |
273 | if __name__ == '__main__':
274 | myController = Controller()
275 | mySensor = SonarSensor(myController, "My Sonar", type=Type.PING, pin=0, rate=10)
276 |
--------------------------------------------------------------------------------
/ros_arduino_python/src/ros_arduino_python/base_controller.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | A base controller class for the Arduino microcontroller
5 |
6 | Borrowed heavily from Mike Feguson's ArbotiX base_controller.py code.
7 |
8 | Created for the Pi Robot Project: http://www.pirobot.org
9 | Copyright (c) 2010 Patrick Goebel. All rights reserved.
10 |
11 | This program is free software; you can redistribute it and/or modify
12 | it under the terms of the GNU General Public License as published by
13 | the Free Software Foundation; either version 2 of the License, or
14 | (at your option) any later version.
15 |
16 | This program is distributed in the hope that it will be useful,
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | GNU General Public License for more details at:
20 |
21 | http://www.gnu.org/licenses
22 | """
23 | import roslib; roslib.load_manifest('ros_arduino_python')
24 | import rospy
25 | import os
26 |
27 | from math import sin, cos, pi
28 | from geometry_msgs.msg import Quaternion, Twist, Pose
29 | from nav_msgs.msg import Odometry
30 | from tf.broadcaster import TransformBroadcaster
31 |
32 | """ Class to receive Twist commands and publish Odometry data """
33 | class BaseController:
34 | def __init__(self, arduino, base_frame):
35 | self.arduino = arduino
36 | self.base_frame = base_frame
37 | self.rate = float(rospy.get_param("~base_controller_rate", 10))
38 | self.timeout = rospy.get_param("~base_controller_timeout", 1.0)
39 | self.stopped = False
40 |
41 | pid_params = dict()
42 | pid_params['wheel_diameter'] = rospy.get_param("~wheel_diameter", "")
43 | pid_params['wheel_track'] = rospy.get_param("~wheel_track", "")
44 | pid_params['encoder_resolution'] = rospy.get_param("~encoder_resolution", "")
45 | pid_params['gear_reduction'] = rospy.get_param("~gear_reduction", 1.0)
46 | pid_params['Kp'] = rospy.get_param("~Kp", 20)
47 | pid_params['Kd'] = rospy.get_param("~Kd", 12)
48 | pid_params['Ki'] = rospy.get_param("~Ki", 0)
49 | pid_params['Ko'] = rospy.get_param("~Ko", 50)
50 |
51 | self.accel_limit = rospy.get_param('~accel_limit', 0.1)
52 | self.motors_reversed = rospy.get_param("~motors_reversed", False)
53 |
54 | # Set up PID parameters and check for missing values
55 | self.setup_pid(pid_params)
56 |
57 | # How many encoder ticks are there per meter?
58 | self.ticks_per_meter = self.encoder_resolution * self.gear_reduction / (self.wheel_diameter * pi)
59 |
60 | # What is the maximum acceleration we will tolerate when changing wheel speeds?
61 | self.max_accel = self.accel_limit * self.ticks_per_meter / self.rate
62 |
63 | # Track how often we get a bad encoder count (if any)
64 | self.bad_encoder_count = 0
65 |
66 | now = rospy.Time.now()
67 | self.then = now # time for determining dx/dy
68 | self.t_delta = rospy.Duration(1.0 / self.rate)
69 | self.t_next = now + self.t_delta
70 |
71 | # Internal data
72 | self.enc_left = None # encoder readings
73 | self.enc_right = None
74 | self.x = 0 # position in xy plane
75 | self.y = 0
76 | self.th = 0 # rotation in radians
77 | self.v_left = 0
78 | self.v_right = 0
79 | self.v_des_left = 0 # cmd_vel setpoint
80 | self.v_des_right = 0
81 | self.last_cmd_vel = now
82 |
83 | # Subscriptions
84 | rospy.Subscriber("cmd_vel", Twist, self.cmdVelCallback)
85 |
86 | # Clear any old odometry info
87 | self.arduino.reset_encoders()
88 |
89 | # Set up the odometry broadcaster
90 | self.odomPub = rospy.Publisher('odom', Odometry, queue_size=5)
91 | self.odomBroadcaster = TransformBroadcaster()
92 |
93 | rospy.loginfo("Started base controller for a base of " + str(self.wheel_track) + "m wide with " + str(self.encoder_resolution) + " ticks per rev")
94 | rospy.loginfo("Publishing odometry data at: " + str(self.rate) + " Hz using " + str(self.base_frame) + " as base frame")
95 |
96 | def setup_pid(self, pid_params):
97 | # Check to see if any PID parameters are missing
98 | missing_params = False
99 | for param in pid_params:
100 | if pid_params[param] == "":
101 | print("*** PID Parameter " + param + " is missing. ***")
102 | missing_params = True
103 |
104 | if missing_params:
105 | os._exit(1)
106 |
107 | self.wheel_diameter = pid_params['wheel_diameter']
108 | self.wheel_track = pid_params['wheel_track']
109 | self.encoder_resolution = pid_params['encoder_resolution']
110 | self.gear_reduction = pid_params['gear_reduction']
111 |
112 | self.Kp = pid_params['Kp']
113 | self.Kd = pid_params['Kd']
114 | self.Ki = pid_params['Ki']
115 | self.Ko = pid_params['Ko']
116 |
117 | self.arduino.update_pid(self.Kp, self.Kd, self.Ki, self.Ko)
118 |
119 | def poll(self):
120 | now = rospy.Time.now()
121 | if now > self.t_next:
122 | # Read the encoders
123 | try:
124 | left_enc, right_enc = self.arduino.get_encoder_counts()
125 | except:
126 | self.bad_encoder_count += 1
127 | rospy.logerr("Encoder exception count: " + str(self.bad_encoder_count))
128 | return
129 |
130 | dt = now - self.then
131 | self.then = now
132 | dt = dt.to_sec()
133 |
134 | # Calculate odometry
135 | if self.enc_left == None:
136 | dright = 0
137 | dleft = 0
138 | else:
139 | dright = (right_enc - self.enc_right) / self.ticks_per_meter
140 | dleft = (left_enc - self.enc_left) / self.ticks_per_meter
141 |
142 | self.enc_right = right_enc
143 | self.enc_left = left_enc
144 |
145 | dxy_ave = (dright + dleft) / 2.0
146 | dth = (dright - dleft) / self.wheel_track
147 | vxy = dxy_ave / dt
148 | vth = dth / dt
149 |
150 | if (dxy_ave != 0):
151 | dx = cos(dth) * dxy_ave
152 | dy = -sin(dth) * dxy_ave
153 | self.x += (cos(self.th) * dx - sin(self.th) * dy)
154 | self.y += (sin(self.th) * dx + cos(self.th) * dy)
155 |
156 | if (dth != 0):
157 | self.th += dth
158 |
159 | quaternion = Quaternion()
160 | quaternion.x = 0.0
161 | quaternion.y = 0.0
162 | quaternion.z = sin(self.th / 2.0)
163 | quaternion.w = cos(self.th / 2.0)
164 |
165 | # Create the odometry transform frame broadcaster.
166 | self.odomBroadcaster.sendTransform(
167 | (self.x, self.y, 0),
168 | (quaternion.x, quaternion.y, quaternion.z, quaternion.w),
169 | rospy.Time.now(),
170 | self.base_frame,
171 | "odom"
172 | )
173 |
174 | odom = Odometry()
175 | odom.header.frame_id = "odom"
176 | odom.child_frame_id = self.base_frame
177 | odom.header.stamp = now
178 | odom.pose.pose.position.x = self.x
179 | odom.pose.pose.position.y = self.y
180 | odom.pose.pose.position.z = 0
181 | odom.pose.pose.orientation = quaternion
182 | odom.twist.twist.linear.x = vxy
183 | odom.twist.twist.linear.y = 0
184 | odom.twist.twist.angular.z = vth
185 |
186 | self.odomPub.publish(odom)
187 |
188 | if now > (self.last_cmd_vel + rospy.Duration(self.timeout)):
189 | self.v_des_left = 0
190 | self.v_des_right = 0
191 |
192 | if self.v_left < self.v_des_left:
193 | self.v_left += self.max_accel
194 | if self.v_left > self.v_des_left:
195 | self.v_left = self.v_des_left
196 | else:
197 | self.v_left -= self.max_accel
198 | if self.v_left < self.v_des_left:
199 | self.v_left = self.v_des_left
200 |
201 | if self.v_right < self.v_des_right:
202 | self.v_right += self.max_accel
203 | if self.v_right > self.v_des_right:
204 | self.v_right = self.v_des_right
205 | else:
206 | self.v_right -= self.max_accel
207 | if self.v_right < self.v_des_right:
208 | self.v_right = self.v_des_right
209 |
210 | # Set motor speeds in encoder ticks per PID loop
211 | if not self.stopped:
212 | self.arduino.drive(self.v_left, self.v_right)
213 |
214 | self.t_next = now + self.t_delta
215 |
216 | def stop(self):
217 | self.stopped = True
218 | self.arduino.drive(0, 0)
219 |
220 | def cmdVelCallback(self, req):
221 | # Handle velocity-based movement requests
222 | self.last_cmd_vel = rospy.Time.now()
223 |
224 | x = req.linear.x # m/s
225 | th = req.angular.z # rad/s
226 |
227 | if x == 0:
228 | # Turn in place
229 | right = th * self.wheel_track * self.gear_reduction / 2.0
230 | left = -right
231 | elif th == 0:
232 | # Pure forward/backward motion
233 | left = right = x
234 | else:
235 | # Rotation about a point in space
236 | left = x - th * self.wheel_track * self.gear_reduction / 2.0
237 | right = x + th * self.wheel_track * self.gear_reduction / 2.0
238 |
239 | self.v_des_left = int(left * self.ticks_per_meter / self.arduino.PID_RATE)
240 | self.v_des_right = int(right * self.ticks_per_meter / self.arduino.PID_RATE)
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/MegaRobogaiaPololu/MegaRobogaiaPololu.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * ROSArduinoBridge
3 |
4 | A set of simple serial commands to control a differential drive
5 | robot and receive back sensor and odometry data. Default
6 | configuration assumes use of an Arduino Mega + Pololu motor
7 | controller shield + Robogaia Mega Encoder shield. Edit the
8 | readEncoder() and setMotorSpeed() wrapper functions if using
9 | different motor controller or encoder method.
10 |
11 | Created for the Pi Robot Project: http://www.pirobot.org
12 |
13 | Inspired and modeled after the ArbotiX driver by Michael Ferguson
14 |
15 | Software License Agreement (BSD License)
16 |
17 | Copyright (c) 2012, Patrick Goebel.
18 | All rights reserved.
19 |
20 | Redistribution and use in source and binary forms, with or without
21 | modification, are permitted provided that the following conditions
22 | are met:
23 |
24 | * Redistributions of source code must retain the above copyright
25 | notice, this list of conditions and the following disclaimer.
26 | * Redistributions in binary form must reproduce the above
27 | copyright notice, this list of conditions and the following
28 | disclaimer in the documentation and/or other materials provided
29 | with the distribution.
30 |
31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
34 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
35 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
36 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
37 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
40 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
41 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42 | * POSSIBILITY OF SUCH DAMAGE.
43 | *********************************************************************/
44 |
45 | #define USE_BASE // Enable the base controller code
46 | //#undef USE_BASE // Disable the base controller code
47 |
48 | //#define USE_SERVOS // Enable use of PWM servos as defined in servos.h
49 | #undef USE_SERVOS // Disable use of PWM servos
50 |
51 | /* Serial port baud rate */
52 | #define BAUDRATE 57600
53 |
54 | /* Maximum PWM signal */
55 | #define MAX_PWM 255
56 |
57 | #if defined(ARDUINO) && ARDUINO >= 100
58 | #include "Arduino.h"
59 | #else
60 | #include "WProgram.h"
61 | #endif
62 |
63 | /* Include definition of serial commands */
64 | #include "commands.h"
65 |
66 | /* Sensor functions */
67 | #include "sensors.h"
68 |
69 | /* Include servo support if required */
70 | #ifdef USE_SERVOS
71 | #include
72 | #include "servos.h"
73 | #endif
74 |
75 | #ifdef USE_BASE
76 | /* The Pololu motor driver shield */
77 | #include "DualVNH5019MotorShield.h"
78 |
79 | /* The Robogaia Mega Encoder shield */
80 | #include "MegaEncoderCounter.h"
81 |
82 | /* Create the motor driver object */
83 | DualVNH5019MotorShield drive;
84 |
85 | /* Create the encoder shield object */
86 | MegaEncoderCounter encoders(4); // Initializes the Mega Encoder Counter in the 4X Count mode
87 |
88 | /* PID parameters and functions */
89 | #include "diff_controller.h"
90 |
91 | /* Run the PID loop at 30 times per second */
92 | #define PID_RATE 30 // Hz
93 |
94 | /* Convert the rate into an interval */
95 | const int PID_INTERVAL = 1000 / PID_RATE;
96 |
97 | /* Track the next time we make a PID calculation */
98 | unsigned long nextPID = PID_INTERVAL;
99 |
100 | /* Stop the robot if it hasn't received a movement command
101 | in this number of milliseconds */
102 | #define AUTO_STOP_INTERVAL 2000
103 | long lastMotorCommand = AUTO_STOP_INTERVAL;
104 |
105 | /* Wrap the encoder reading function */
106 | long readEncoder(int i) {
107 | if (i == LEFT) return encoders.YAxisGetCount();
108 | else return encoders.XAxisGetCount();
109 | }
110 |
111 | /* Wrap the encoder reset function */
112 | void resetEncoder(int i) {
113 | if (i == LEFT) return encoders.YAxisReset();
114 | else return encoders.XAxisReset();
115 | }
116 |
117 | /* Wrap the encoder reset function */
118 | void resetEncoders() {
119 | resetEncoder(LEFT);
120 | resetEncoder(RIGHT);
121 | }
122 |
123 | /* Wrap the motor driver initialization */
124 | void initMotorController() {
125 | drive.init();
126 | }
127 |
128 | /* Wrap the drive motor set speed function */
129 | void setMotorSpeed(int i, int spd) {
130 | if (i == LEFT) drive.setM1Speed(spd);
131 | else drive.setM2Speed(spd);
132 | }
133 |
134 | // A convenience function for setting both motor speeds
135 | void setMotorSpeeds(int leftSpeed, int rightSpeed) {
136 | setMotorSpeed(LEFT, leftSpeed);
137 | setMotorSpeed(RIGHT, rightSpeed);
138 | }
139 | #endif
140 |
141 | /* Variable initialization */
142 |
143 | // A pair of varibles to help parse serial commands (thanks Fergs)
144 | int arg = 0;
145 | int index = 0;
146 |
147 | // Variable to hold an input character
148 | char chr;
149 |
150 | // Variable to hold the current single-character command
151 | char cmd;
152 |
153 | // Character arrays to hold the first and second arguments
154 | char argv1[16];
155 | char argv2[16];
156 |
157 | // The arguments converted to integers
158 | long arg1;
159 | long arg2;
160 |
161 | /* Clear the current command parameters */
162 | void resetCommand() {
163 | cmd = NULL;
164 | memset(argv1, 0, sizeof(argv1));
165 | memset(argv2, 0, sizeof(argv2));
166 | arg1 = 0;
167 | arg2 = 0;
168 | arg = 0;
169 | index = 0;
170 | }
171 |
172 | /* Run a command. Commands are defined in commands.h */
173 | int runCommand() {
174 | int i = 0;
175 | char *p = argv1;
176 | char *str;
177 | int pid_args[4];
178 | arg1 = atoi(argv1);
179 | arg2 = atoi(argv2);
180 |
181 | switch(cmd) {
182 | case GET_BAUDRATE:
183 | Serial.println(BAUDRATE);
184 | break;
185 | case ANALOG_READ:
186 | Serial.println(analogRead(arg1));
187 | break;
188 | case DIGITAL_READ:
189 | Serial.println(digitalRead(arg1));
190 | break;
191 | case ANALOG_WRITE:
192 | analogWrite(arg1, arg2);
193 | Serial.println("OK");
194 | break;
195 | case DIGITAL_WRITE:
196 | if (arg2 == 0) digitalWrite(arg1, LOW);
197 | else if (arg2 == 1) digitalWrite(arg1, HIGH);
198 | Serial.println("OK");
199 | break;
200 | case PIN_MODE:
201 | if (arg2 == 0) pinMode(arg1, INPUT);
202 | else if (arg2 == 1) pinMode(arg1, OUTPUT);
203 | Serial.println("OK");
204 | break;
205 | case PING:
206 | Serial.println(Ping(arg1));
207 | break;
208 | #ifdef USE_SERVOS
209 | case SERVO_WRITE:
210 | servos[arg1].write(arg2);
211 | Serial.println("OK");
212 | break;
213 | case SERVO_READ:
214 | Serial.println(servos[arg1].read());
215 | break;
216 | #endif
217 |
218 | #ifdef USE_BASE
219 | case READ_ENCODERS:
220 | Serial.print(readEncoder(LEFT));
221 | Serial.print(" ");
222 | Serial.println(readEncoder(RIGHT));
223 | break;
224 | case RESET_ENCODERS:
225 | resetEncoders();
226 | Serial.println("OK");
227 | break;
228 | case MOTOR_SPEEDS:
229 | /* Reset the auto stop timer */
230 | lastMotorCommand = millis();
231 | if (arg1 == 0 && arg2 == 0) {
232 | setMotorSpeeds(0, 0);
233 | moving = 0;
234 | }
235 | else moving = 1;
236 | leftPID.TargetTicksPerFrame = arg1;
237 | rightPID.TargetTicksPerFrame = arg2;
238 | Serial.println("OK");
239 | break;
240 | case UPDATE_PID:
241 | while ((str = strtok_r(p, ":", &p)) != '\0') {
242 | pid_args[i] = atoi(str);
243 | i++;
244 | }
245 | Kp = pid_args[0];
246 | Kd = pid_args[1];
247 | Ki = pid_args[2];
248 | Ko = pid_args[3];
249 | Serial.println("OK");
250 | break;
251 | #endif
252 | default:
253 | Serial.println("Invalid Command");
254 | break;
255 | }
256 | }
257 |
258 | /* Setup function--runs once at startup. */
259 | void setup() {
260 | Serial.begin(BAUDRATE);
261 |
262 | // Initialize the motor controller if used */
263 | #ifdef USE_BASE
264 | initMotorController();
265 | #endif
266 |
267 | /* Attach servos if used */
268 | #ifdef USE_SERVOS
269 | int i;
270 | for (i = 0; i < N_SERVOS; i++) {
271 | servos[i].attach(servoPins[i]);
272 | }
273 | #endif
274 | }
275 |
276 | /* Enter the main loop. Read and parse input from the serial port
277 | and run any valid commands. Run a PID calculation at the target
278 | interval and check for auto-stop conditions.
279 | */
280 | void loop() {
281 | while (Serial.available() > 0) {
282 |
283 | // Read the next character
284 | chr = Serial.read();
285 |
286 | // Terminate a command with a CR
287 | if (chr == 13) {
288 | if (arg == 1) argv1[index] = NULL;
289 | else if (arg == 2) argv2[index] = NULL;
290 | runCommand();
291 | resetCommand();
292 | }
293 | // Use spaces to delimit parts of the command
294 | else if (chr == ' ') {
295 | // Step through the arguments
296 | if (arg == 0) arg = 1;
297 | else if (arg == 1) {
298 | argv1[index] = NULL;
299 | arg = 2;
300 | index = 0;
301 | }
302 | continue;
303 | }
304 | else {
305 | if (arg == 0) {
306 | // The first arg is the single-letter command
307 | cmd = chr;
308 | }
309 | else if (arg == 1) {
310 | // Subsequent arguments can be more than one character
311 | argv1[index] = chr;
312 | index++;
313 | }
314 | else if (arg == 2) {
315 | argv2[index] = chr;
316 | index++;
317 | }
318 | }
319 | }
320 |
321 | // If we are using base control, run a PID calculation at the appropriate intervals
322 | #ifdef USE_BASE
323 | if (millis() > nextPID) {
324 | updatePID();
325 | nextPID += PID_INTERVAL;
326 | }
327 |
328 | // Check to see if we have exceeded the auto-stop interval
329 | if ((millis() - lastMotorCommand) > AUTO_STOP_INTERVAL) {;
330 | setMotorSpeeds(0, 0);
331 | moving = 0;
332 | }
333 |
334 | #endif
335 | }
336 |
337 |
338 |
339 |
340 |
341 |
342 |
--------------------------------------------------------------------------------
/ros_arduino_firmware/src/libraries/ROSArduinoBridge/ROSArduinoBridge.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * ROSArduinoBridge
3 |
4 | A set of simple serial commands to control a differential drive
5 | robot and receive back sensor and odometry data. Default
6 | configuration assumes use of an Arduino Mega + Pololu motor
7 | controller shield + Robogaia Mega Encoder shield. Edit the
8 | readEncoder() and setMotorSpeed() wrapper functions if using
9 | different motor controller or encoder method.
10 |
11 | Created for the Pi Robot Project: http://www.pirobot.org
12 | and the Home Brew Robotics Club (HBRC): http://hbrobotics.org
13 |
14 | Authors: Patrick Goebel, James Nugen
15 |
16 | Inspired and modeled after the ArbotiX driver by Michael Ferguson
17 |
18 | Software License Agreement (BSD License)
19 |
20 | Copyright (c) 2012, Patrick Goebel.
21 | All rights reserved.
22 |
23 | Redistribution and use in source and binary forms, with or without
24 | modification, are permitted provided that the following conditions
25 | are met:
26 |
27 | * Redistributions of source code must retain the above copyright
28 | notice, this list of conditions and the following disclaimer.
29 | * Redistributions in binary form must reproduce the above
30 | copyright notice, this list of conditions and the following
31 | disclaimer in the documentation and/or other materials provided
32 | with the distribution.
33 |
34 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
35 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
36 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
37 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
38 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
39 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
40 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
41 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
42 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
43 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
44 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
45 | * POSSIBILITY OF SUCH DAMAGE.
46 | *********************************************************************/
47 |
48 | #define USE_BASE // Enable the base controller code
49 | //#undef USE_BASE // Disable the base controller code
50 |
51 | /* Define the motor controller and encoder library you are using */
52 | #ifdef USE_BASE
53 | /* The Pololu VNH5019 dual motor driver shield */
54 | //#define POLOLU_VNH5019
55 |
56 | /* The Pololu MC33926 dual motor driver shield */
57 | //#define POLOLU_MC33926
58 |
59 | /* The RoboGaia encoder shield */
60 | //#define ROBOGAIA
61 |
62 | /* L298N Dual H-Bridge Motor Controller */
63 | #define L298N_DUAL_HBRIDGE
64 |
65 | /* Encoders directly attached to Arduino board */
66 | //#define ARDUINO_ENC_COUNTER
67 | /* My customized encoders */
68 | #define ARDUINO_MY_COUNTER
69 | #endif
70 |
71 | #define USE_SERVOS // Enable use of PWM servos as defined in servos.h
72 | //#undef USE_SERVOS // Disable use of PWM servos
73 |
74 | /* Serial port baud rate */
75 | #define BAUDRATE 57600
76 |
77 | /* Maximum PWM signal */
78 | #define MAX_PWM 255
79 |
80 | #if defined(ARDUINO) && ARDUINO >= 100
81 | #include "Arduino.h"
82 | #else
83 | #include "WProgram.h"
84 | #endif
85 |
86 | /* Include definition of serial commands */
87 | #include "commands.h"
88 |
89 | /* Sensor functions */
90 | #include "sensors.h"
91 |
92 | /* Include servo support if required */
93 | #ifdef USE_SERVOS
94 | #include
95 | #include "servos.h"
96 | #endif
97 |
98 | #ifdef USE_BASE
99 | /* Motor driver function definitions */
100 | #include "motor_driver.h"
101 |
102 | /* Encoder driver function definitions */
103 | #include "encoder_driver.h"
104 |
105 | /* PID parameters and functions */
106 | #include "diff_controller.h"
107 |
108 | /* Run the PID loop at 30 times per second */
109 | #define PID_RATE 30 // Hz
110 |
111 | /* Convert the rate into an interval */
112 | const int PID_INTERVAL = 1000 / PID_RATE;
113 |
114 | /* Track the next time we make a PID calculation */
115 | unsigned long nextPID = PID_INTERVAL;
116 |
117 | /* Stop the robot if it hasn't received a movement command
118 | in this number of milliseconds */
119 | #define AUTO_STOP_INTERVAL 2000
120 | long lastMotorCommand = AUTO_STOP_INTERVAL;
121 | #endif
122 |
123 | /* Variable initialization */
124 |
125 | // A pair of varibles to help parse serial commands (thanks Fergs)
126 | int arg = 0;
127 | int index = 0;
128 |
129 | // Variable to hold an input character
130 | char chr;
131 |
132 | // Variable to hold the current single-character command
133 | char cmd;
134 |
135 | // Character arrays to hold the first and second arguments
136 | char argv1[16];
137 | char argv2[16];
138 |
139 | // The arguments converted to integers
140 | long arg1;
141 | long arg2;
142 |
143 | /* Clear the current command parameters */
144 | void resetCommand() {
145 | cmd = NULL;
146 | memset(argv1, 0, sizeof(argv1));
147 | memset(argv2, 0, sizeof(argv2));
148 | arg1 = 0;
149 | arg2 = 0;
150 | arg = 0;
151 | index = 0;
152 | }
153 |
154 | /* Run a command. Commands are defined in commands.h */
155 | int runCommand() {
156 | int i = 0;
157 | char *p = argv1;
158 | char *str;
159 | int pid_args[4];
160 | arg1 = atoi(argv1);
161 | arg2 = atoi(argv2);
162 |
163 | switch(cmd) {
164 | case GET_BAUDRATE:
165 | Serial.println(BAUDRATE);
166 | break;
167 | case ANALOG_READ:
168 | Serial.println(analogRead(arg1));
169 | break;
170 | case DIGITAL_READ:
171 | Serial.println(digitalRead(arg1));
172 | break;
173 | case ANALOG_WRITE:
174 | analogWrite(arg1, arg2);
175 | Serial.println("OK");
176 | break;
177 | case DIGITAL_WRITE:
178 | if (arg2 == 0) digitalWrite(arg1, LOW);
179 | else if (arg2 == 1) digitalWrite(arg1, HIGH);
180 | Serial.println("OK");
181 | break;
182 | case PIN_MODE:
183 | if (arg2 == 0) pinMode(arg1, INPUT);
184 | else if (arg2 == 1) pinMode(arg1, OUTPUT);
185 | Serial.println("OK");
186 | break;
187 | case PING:
188 | Serial.println(Ping(arg1));
189 | break;
190 | #ifdef USE_SERVOS
191 | case SERVO_WRITE:
192 | servos[arg1].setTargetPosition(arg2);
193 | Serial.println("OK");
194 | break;
195 | case SERVO_READ:
196 | Serial.println(servos[arg1].getServo().read());
197 | break;
198 | #endif
199 |
200 | #ifdef USE_BASE
201 | case READ_ENCODERS:
202 | Serial.print(readEncoder(LEFT));
203 | Serial.print(" ");
204 | Serial.println(readEncoder(RIGHT));
205 | break;
206 | case RESET_ENCODERS:
207 | resetEncoders();
208 | resetPID();
209 | Serial.println("OK");
210 | break;
211 | case MOTOR_SPEEDS:
212 | /* Reset the auto stop timer */
213 | lastMotorCommand = millis();
214 | if (arg1 == 0 && arg2 == 0) {
215 | setMotorSpeeds(0, 0);
216 | resetPID();
217 | moving = 0;
218 | }
219 | else moving = 1;
220 | leftPID.TargetTicksPerFrame = arg1;
221 | rightPID.TargetTicksPerFrame = arg2;
222 | Serial.println("OK");
223 | break;
224 | case UPDATE_PID:
225 | while ((str = strtok_r(p, ":", &p)) != '\0') {
226 | pid_args[i] = atoi(str);
227 | i++;
228 | }
229 | Kp = pid_args[0];
230 | Kd = pid_args[1];
231 | Ki = pid_args[2];
232 | Ko = pid_args[3];
233 | Serial.println("OK");
234 | break;
235 | #endif
236 | default:
237 | Serial.println("Invalid Command");
238 | break;
239 | }
240 | }
241 |
242 | /* Setup function--runs once at startup. */
243 | void setup() {
244 | Serial.begin(BAUDRATE);
245 |
246 | // Initialize the motor controller if used */
247 | #ifdef USE_BASE
248 | #ifdef ARDUINO_ENC_COUNTER
249 | //set as inputs
250 | DDRD &= ~(1< 0) {
292 |
293 | // Read the next character
294 | chr = Serial.read();
295 |
296 | // Terminate a command with a CR
297 | if (chr == 13) {
298 | if (arg == 1) argv1[index] = NULL;
299 | else if (arg == 2) argv2[index] = NULL;
300 | runCommand();
301 | resetCommand();
302 | }
303 | // Use spaces to delimit parts of the command
304 | else if (chr == ' ') {
305 | // Step through the arguments
306 | if (arg == 0) arg = 1;
307 | else if (arg == 1) {
308 | argv1[index] = NULL;
309 | arg = 2;
310 | index = 0;
311 | }
312 | continue;
313 | }
314 | else {
315 | if (arg == 0) {
316 | // The first arg is the single-letter command
317 | cmd = chr;
318 | }
319 | else if (arg == 1) {
320 | // Subsequent arguments can be more than one character
321 | argv1[index] = chr;
322 | index++;
323 | }
324 | else if (arg == 2) {
325 | argv2[index] = chr;
326 | index++;
327 | }
328 | }
329 | }
330 |
331 | // If we are using base control, run a PID calculation at the appropriate intervals
332 | #ifdef USE_BASE
333 | if (millis() > nextPID) {
334 | updatePID();
335 | nextPID += PID_INTERVAL;
336 | }
337 |
338 | // Check to see if we have exceeded the auto-stop interval
339 | if ((millis() - lastMotorCommand) > AUTO_STOP_INTERVAL) {
340 | setMotorSpeeds(0, 0);
341 | moving = 0;
342 | }
343 | #endif
344 |
345 | // Sweep servos
346 | #ifdef USE_SERVOS
347 | int i;
348 | for (i = 0; i < N_SERVOS; i++) {
349 | servos[i].doSweep();
350 | }
351 | #endif
352 | }
353 |
--------------------------------------------------------------------------------
/ros_arduino_python/src/ros_arduino_python/arduino_driver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | A Python driver for the Arduino microcontroller running the
5 | ROSArduinoBridge firmware.
6 |
7 | Created for the Pi Robot Project: http://www.pirobot.org
8 | Copyright (c) 2012 Patrick Goebel. All rights reserved.
9 |
10 | This program is free software; you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation; either version 2 of the License, or
13 | (at your option) any later version.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details at:
19 |
20 | http://www.gnu.org/licenses/gpl.html
21 |
22 | """
23 |
24 | import thread
25 | from math import pi as PI, degrees, radians
26 | import os, time, sys, traceback
27 | from serial.serialutil import SerialException, SerialTimeoutException
28 | from serial import Serial
29 | import rospy
30 |
31 | SERVO_MAX = 180
32 | SERVO_MIN = 0
33 |
34 | class Arduino:
35 | ''' Configuration Parameters
36 | '''
37 | N_ANALOG_PORTS = 6
38 | N_DIGITAL_PORTS = 12
39 |
40 | def __init__(self, port="/dev/ttyUSB0", baudrate=57600, timeout=0.5):
41 |
42 | self.PID_RATE = 30 # Do not change this! It is a fixed property of the Arduino PID controller.
43 | self.PID_INTERVAL = 1000 / 30
44 |
45 | self.port = port
46 | self.baudrate = baudrate
47 | self.timeout = timeout
48 | self.encoder_count = 0
49 | self.writeTimeout = timeout
50 | self.interCharTimeout = timeout / 30.
51 | self.motors_reversed = rospy.get_param("~motors_reversed", False)
52 | # Keep things thread safe
53 | self.mutex = thread.allocate_lock()
54 |
55 | # An array to cache analog sensor readings
56 | self.analog_sensor_cache = [None] * self.N_ANALOG_PORTS
57 |
58 | # An array to cache digital sensor readings
59 | self.digital_sensor_cache = [None] * self.N_DIGITAL_PORTS
60 |
61 | def connect(self):
62 | try:
63 | print "Connecting to Arduino on port", self.port, "..."
64 | self.port = Serial(port=self.port, baudrate=self.baudrate, timeout=self.timeout, writeTimeout=self.writeTimeout)
65 | # The next line is necessary to give the firmware time to wake up.
66 | time.sleep(1)
67 | test = self.get_baud()
68 | if test != self.baudrate:
69 | time.sleep(1)
70 | test = self.get_baud()
71 | if test != self.baudrate:
72 | raise SerialException
73 | print "Connected at", self.baudrate
74 | print "Arduino is ready."
75 |
76 | except SerialException:
77 | print "Serial Exception:"
78 | print sys.exc_info()
79 | print "Traceback follows:"
80 | traceback.print_exc(file=sys.stdout)
81 | print "Cannot connect to Arduino!"
82 | os._exit(1)
83 |
84 | def open(self):
85 | ''' Open the serial port.
86 | '''
87 | self.port.open()
88 |
89 | def close(self):
90 | ''' Close the serial port.
91 | '''
92 | self.port.close()
93 |
94 | def send(self, cmd):
95 | ''' This command should not be used on its own: it is called by the execute commands
96 | below in a thread safe manner.
97 | '''
98 | self.port.write(cmd + '\r')
99 |
100 | def recv(self, timeout=0.5):
101 | timeout = min(timeout, self.timeout)
102 | ''' This command should not be used on its own: it is called by the execute commands
103 | below in a thread safe manner. Note: we use read() instead of readline() since
104 | readline() tends to return garbage characters from the Arduino
105 | '''
106 | c = ''
107 | value = ''
108 | attempts = 0
109 | while c != '\r':
110 | c = self.port.read(1)
111 | value += c
112 | attempts += 1
113 | if attempts * self.interCharTimeout > timeout:
114 | return None
115 |
116 | value = value.strip('\r')
117 |
118 | return value
119 |
120 | def recv_ack(self):
121 | ''' This command should not be used on its own: it is called by the execute commands
122 | below in a thread safe manner.
123 | '''
124 | ack = self.recv(self.timeout)
125 | return ack == 'OK'
126 |
127 | def recv_int(self):
128 | ''' This command should not be used on its own: it is called by the execute commands
129 | below in a thread safe manner.
130 | '''
131 | value = self.recv(self.timeout)
132 | try:
133 | return int(value)
134 | except:
135 | return None
136 |
137 | def recv_array(self):
138 | ''' This command should not be used on its own: it is called by the execute commands
139 | below in a thread safe manner.
140 | '''
141 | try:
142 | values = self.recv(self.timeout * self.N_ANALOG_PORTS).split()
143 | return map(int, values)
144 | except:
145 | return []
146 |
147 | def execute(self, cmd):
148 | ''' Thread safe execution of "cmd" on the Arduino returning a single integer value.
149 | '''
150 | self.mutex.acquire()
151 |
152 | try:
153 | self.port.flushInput()
154 | except:
155 | pass
156 |
157 | ntries = 1
158 | attempts = 0
159 |
160 | try:
161 | self.port.write(cmd + '\r')
162 | value = self.recv(self.timeout)
163 | while attempts < ntries and (value == '' or value == 'Invalid Command' or value == None):
164 | try:
165 | self.port.flushInput()
166 | self.port.write(cmd + '\r')
167 | value = self.recv(self.timeout)
168 | except:
169 | print "Exception executing command: " + cmd
170 | attempts += 1
171 | except:
172 | self.mutex.release()
173 | print "Exception executing command: " + cmd
174 | value = None
175 |
176 | self.mutex.release()
177 | return int(value)
178 |
179 | def execute_array(self, cmd):
180 | ''' Thread safe execution of "cmd" on the Arduino returning an array.
181 | '''
182 | self.mutex.acquire()
183 |
184 | try:
185 | self.port.flushInput()
186 | except:
187 | pass
188 |
189 | ntries = 1
190 | attempts = 0
191 |
192 | try:
193 | self.port.write(cmd + '\r')
194 | values = self.recv_array()
195 | while attempts < ntries and (values == '' or values == 'Invalid Command' or values == [] or values == None):
196 | try:
197 | self.port.flushInput()
198 | self.port.write(cmd + '\r')
199 | values = self.recv_array()
200 | except:
201 | print("Exception executing command: " + cmd)
202 | attempts += 1
203 | except:
204 | self.mutex.release()
205 | print "Exception executing command: " + cmd
206 | raise SerialException
207 | return []
208 |
209 | try:
210 | values = map(int, values)
211 | except:
212 | values = []
213 |
214 | self.mutex.release()
215 | return values
216 |
217 | def execute_ack(self, cmd):
218 | ''' Thread safe execution of "cmd" on the Arduino returning True if response is ACK.
219 | '''
220 | self.mutex.acquire()
221 |
222 | try:
223 | self.port.flushInput()
224 | except:
225 | pass
226 |
227 | ntries = 1
228 | attempts = 0
229 |
230 | try:
231 | self.port.write(cmd + '\r')
232 | ack = self.recv(self.timeout)
233 | while attempts < ntries and (ack == '' or ack == 'Invalid Command' or ack == None):
234 | try:
235 | self.port.flushInput()
236 | self.port.write(cmd + '\r')
237 | ack = self.recv(self.timeout)
238 | except:
239 | print "Exception executing command: " + cmd
240 | attempts += 1
241 | except:
242 | self.mutex.release()
243 | print "execute_ack exception when executing", cmd
244 | print sys.exc_info()
245 | return 0
246 |
247 | self.mutex.release()
248 | return ack == 'OK'
249 |
250 | def update_pid(self, Kp, Kd, Ki, Ko):
251 | ''' Set the PID parameters on the Arduino
252 | '''
253 | print "Updating PID parameters"
254 | cmd = 'u ' + str(Kp) + ':' + str(Kd) + ':' + str(Ki) + ':' + str(Ko)
255 | self.execute_ack(cmd)
256 |
257 | def get_baud(self):
258 | ''' Get the current baud rate on the serial port.
259 | '''
260 | try:
261 | return int(self.execute('b'));
262 | except:
263 | return None
264 |
265 | def get_encoder_counts(self):
266 | values = self.execute_array('e')
267 | if len(values) != 2:
268 | print "Encoder count was not 2"
269 | raise SerialException
270 | return None
271 | else:
272 | if self.motors_reversed:
273 | values[0], values[1] = -values[0], -values[1]
274 | return values
275 |
276 | def reset_encoders(self):
277 | ''' Reset the encoder counts to 0
278 | '''
279 | return self.execute_ack('r')
280 |
281 | def drive(self, right, left):
282 | ''' Speeds are given in encoder ticks per PID interval
283 | '''
284 | if self.motors_reversed:
285 | left, right = -left, -right
286 | return self.execute_ack('m %d %d' %(right, left))
287 |
288 | def drive_m_per_s(self, right, left):
289 | ''' Set the motor speeds in meters per second.
290 | '''
291 | left_revs_per_second = float(left) / (self.wheel_diameter * PI)
292 | right_revs_per_second = float(right) / (self.wheel_diameter * PI)
293 |
294 | left_ticks_per_loop = int(left_revs_per_second * self.encoder_resolution * self.PID_INTERVAL * self.gear_reduction)
295 | right_ticks_per_loop = int(right_revs_per_second * self.encoder_resolution * self.PID_INTERVAL * self.gear_reduction)
296 |
297 | self.drive(right_ticks_per_loop , left_ticks_per_loop )
298 |
299 | def stop(self):
300 | ''' Stop both motors.
301 | '''
302 | self.drive(0, 0)
303 |
304 | def analog_read(self, pin):
305 | return self.execute('a %d' %pin)
306 |
307 | def analog_write(self, pin, value):
308 | return self.execute_ack('x %d %d' %(pin, value))
309 |
310 | def digital_read(self, pin):
311 | return self.execute('d %d' %pin)
312 |
313 | def digital_write(self, pin, value):
314 | return self.execute_ack('w %d %d' %(pin, value))
315 |
316 | def pin_mode(self, pin, mode):
317 | return self.execute_ack('c %d %d' %(pin, mode))
318 |
319 | def servo_write(self, id, pos):
320 | ''' Usage: servo_write(id, pos)
321 | Position is given in radians and converted to degrees before sending
322 | '''
323 | return self.execute_ack('s %d %d' %(id, min(SERVO_MAX, max(SERVO_MIN, degrees(pos)))))
324 |
325 | def servo_read(self, id):
326 | ''' Usage: servo_read(id)
327 | The returned position is converted from degrees to radians
328 | '''
329 | return radians(self.execute('t %d' %id))
330 |
331 | def ping(self, pin):
332 | ''' The srf05/Ping command queries an SRF05/Ping sonar sensor
333 | connected to the General Purpose I/O line pinId for a distance,
334 | and returns the range in cm. Sonar distance resolution is integer based.
335 | '''
336 | return self.execute('p %d' %pin);
337 |
338 | # def get_maxez1(self, triggerPin, outputPin):
339 | # ''' The maxez1 command queries a Maxbotix MaxSonar-EZ1 sonar
340 | # sensor connected to the General Purpose I/O lines, triggerPin, and
341 | # outputPin, for a distance, and returns it in Centimeters. NOTE: MAKE
342 | # SURE there's nothing directly in front of the MaxSonar-EZ1 upon
343 | # power up, otherwise it wont range correctly for object less than 6
344 | # inches away! The sensor reading defaults to use English units
345 | # (inches). The sensor distance resolution is integer based. Also, the
346 | # maxsonar trigger pin is RX, and the echo pin is PW.
347 | # '''
348 | # return self.execute('z %d %d' %(triggerPin, outputPin))
349 |
350 | """ Basic test for connectivity """
351 | if __name__ == "__main__":
352 | if os.name == "posix":
353 | portName = "/dev/ttyACM0"
354 | else:
355 | portName = "COM43" # Windows style COM port.
356 |
357 | baudRate = 57600
358 |
359 | myArduino = Arduino(port=portName, baudrate=baudRate, timeout=0.5)
360 | myArduino.connect()
361 |
362 | print "Sleeping for 1 second..."
363 | time.sleep(1)
364 |
365 | print "Reading on analog port 0", myArduino.analog_read(0)
366 | print "Reading on digital port 0", myArduino.digital_read(0)
367 | print "Blinking the LED 3 times"
368 | for i in range(3):
369 | myArduino.digital_write(13, 1)
370 | time.sleep(1.0)
371 | #print "Current encoder counts", myArduino.encoders()
372 |
373 | print "Connection test successful.",
374 |
375 | myArduino.stop()
376 | myArduino.close()
377 |
378 | print "Shutting down Arduino."
379 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 写在前面
2 | -----
3 | 本文是根据ros\_arduino\_bridge功能包的官方文档翻译修改而来,目的是为了便于广大网友更方便的学习使用该功能包。本文主要针对indigo-devel版本进行介绍,也会捎带介绍一下其他的版本。文中存在我的一些主观看法,如您有其他观点可以与我交流,我的email:。这篇博文篇幅有些冗长,希望各位网友耐心阅读。
4 |
5 | # 概述
6 | ------
7 | 使用这个功能包时,注意选择相应的版本,因为不同的版本之间有些并不能兼容。indigo-devel branch是针对ROS Indigo以及更高版本的,它使用的是Catkin编译系统,它对于ROS Hydro也是兼容的;而master 分支目前还在测试中,可能在某些板子或电脑上无法正常运行,我曾今使用过这个分支,但是出了很多问题,如串口(serial)就无法与主机正常交流、波特率的大小设置等问题。本文主要针对indigo-devel版本进行介绍,也会捎带介绍一下其他的版本。
8 |
9 | 这个ROS功能包集包括了Arduino库(ROSArduinoBridge)和一系列用来控制基于Arduino的ROS功能包,它使用的是标准的ROS消息和服务。这个功能包集并不依赖于ROS串口。
10 | 这个功能包集的功能包括:
11 |
12 | - 可以直接支持ping声呐和Sharp红外线传感器
13 | - 也可以从通用的模拟和数字信号的传感器读取数据
14 | - 可以控制数字信号的输出
15 | - 支持PWM伺服机
16 | - 如果使用所要求的硬件的话,可以配置基本的控制
17 | - 如果你的Arduino编程基础好的话,并且具有python基础的话,你就可以很自由的改动代码来满足你的硬件要求
18 |
19 | 这个功能包集包括一个兼容不同驱动的机器人的基本控制器(`base controller`),它可以接收ROS Twist类型的消息,可以发布里程数据到个人电脑。这个控制器(`base controller`)要求使用一个电机控制器和编码器来读取里程数据。目前indigo-devel版本的功能包集支持下面的控制器硬件:
20 |
21 | - [Pololu VNH5019 dual motor controller shield](http://www.pololu.com/catalog/product/2502) or [Pololu MC33926 dual motor shield](http://www.pololu.com/catalog/product/2503).
22 | - [Robogaia Mega Encoder shield](http://www.robogaia.com/two-axis-encoder-counter-mega-shield-version-2.html) or on-board wheel encoder counters.
23 |
24 | **NOTE:** Robogaia Mega Encoder shield 仅适用于Arduino Mega,而板上编码计数器(ARDUINO_ENC_COUNTER)目前仅支持Arduino Uno,这些是官方文档上的说法。但是实际上只要你有一定的编程基础,你就可以随心所欲,想让它支持什么硬件就支持什么硬件。
25 |
26 | # 关于ROS官方文档
27 | -----------
28 | 在这个ROS wiki [ros\_arduino\_bridge](http://www.ros.org/wiki/ros_arduino_bridge)里可以找到一个标准的ROS风格的文档,但是貌似这个wiki里面的信息并不全面,应该说很不全面,不会有什么实质性的帮助。也许是因为在它的github仓库里有详细的说明,所以它才偷懒了吧!所以如果你想要了解关于该功能包的详细内容,请直接查看它的github仓库里的[README文件](https://github.com/hbrobotics/ros_arduino_bridge)。当然里面全是E文,所以如果你觉得有困难的话,可以委屈一下继续阅读我的拙文,当然这也是我写这篇博文的一个原因,我十分乐意为广大网友翻译英文资料,如有翻译不当的地方,欢迎各位批评指正,我的e-mail是。
29 |
30 | # 系统要求
31 | ---------
32 | Python 串口:使用下面的命令可以在Ubuntu下安装python-serial功能包
33 | ```bash
34 | $ sudo apt-get install python-serial
35 | ```
36 | 在非Ubuntu系统下,可以使用:
37 | ```bash
38 | $ sudo pip install --upgrade pyserial
39 | ```
40 | 或者
41 | ```bash
42 | $ sudo easy_install -U pyserial
43 | ```
44 |
45 | 这个功能包集可以在兼容Arduino的控制器上进行读取传感器上的数据,以及控制[PWM](https://en.wikipedia.org/wiki/Pulse-width_modulation)伺服机。但是你必须具备上面所说的被支持的硬件(电动机控制器和编码器),你才能使用这个功能包集中的基本控制器(`base controller`)。
46 |
47 | **NOTE**:如果你没有这些硬件设备,你仍然有办法使用这个功能包,当然这需要一些相应的编程基础,具体方法请查看本文档结尾的NOTES部分。
48 |
49 | 要使用这个基本控制器(`base controller`),你还得为你的电动机控制器和编码器安装合适的库。对于上面提到过的Pololu VNH5019 Dual Motor Shield,它的相应的库可以在这里找到:
50 |
51 |
52 |
53 | 对于Pololu MC33926 Dual Motor Shield,则在这里可以找到对应的库:
54 |
55 |
56 |
57 | Robogaia Mega Encoder shield的库可以在这里找到;
58 |
59 |
60 |
61 | 这些库应该被安装到你的标准Arduino sketchbook/libraries路径下面。最后,这个功能包集假设你使用的Arduino IDE的版本是1.0或以上,我使用的是1.6.8。
62 |
63 | # 在你的Linux下准备串口
64 | ------------
65 | 你的Arduino很可能是通过接口`/dev/ttyACM#` 或者 `/dev/ttyUSB#`来连接你的Linux系统的。这里的`#`可以是0,1,2等数字,当然这根据你连接的设备数量而定。得到这个数字`#`最简单的方式就是拔掉所有的USB设备,然后插上你的Arduino,然后运行下面这个命令:
66 |
67 | ```bash
68 | $ ls /dev/ttyACM*
69 | ```
70 |
71 | 或者
72 |
73 | ```bash
74 | $ ls /dev/ttyUSB*
75 | ```
76 |
77 | 但愿上面这两个命令的有一个可以返回你想要的结果(例如/dev/ttyACM0),而另一个可能会返回这个错误"No such file or directory"。反正只要有一个命令可以得到结果就行。
78 |
79 | 然后你需要确保你对这个接口有访问的权限。假设你的Arduino连接的是`/dev/ttyACM0`,那么就运行下面这个命令:
80 |
81 | ```bash
82 | $ ls -l /dev/ttyACM0
83 | ```
84 |
85 | 然后你就可以看到类似于下面的输出结果:
86 |
87 | > crw-rw---- 1 root dialout 166, 0 2013-02-24 08:31 /dev/ttyACM0
88 |
89 | 我们注意到在上面的结果中,只有root和"dialout"组才有读写权限。因此,你需要成为`dialout`(关于dialout你可以参考[这里](http://bbs.csdn.net/topics/391944286))组的一个成员。你仅仅需要马上加入这组中,并且它会对你之后插入的所有的USB设备都起作用。
90 |
91 | 你可以运行这个命令来加入dialout组:
92 |
93 | ```bash
94 | $ sudo usermod -a -G dialout your_user_name
95 | ```
96 |
97 | 在这个命令中`your_user_name`就是你在Linux下登录的用户名。之后你可能需要注销登录,然后再重新登录进去,或者就简单的重启一下电脑。
98 |
99 | 当你执行完上面的操作之后,你可以运行下面的命令查看一下:
100 |
101 | ```bash
102 | $ groups
103 | ```
104 |
105 | 然后如果你可以在列出的组中找到dialout,这就说明你已经加入到dialout中了。
106 |
107 |
108 | # 安装ros\_arduino\_bridge功能包集
109 | -----------
110 |
111 | ```bash
112 | $ cd ~/catkin_workspace/src
113 | $ git clone https://github.com/hbrobotics/ros_arduino_bridge.git
114 | $ cd ~/catkin_workspace
115 | $ catkin_make
116 | ```
117 |
118 | 这个被提供的Arduino库叫做ROSArduinoBridge(前面已经提到过),你可以在 ros\_arduino\_firmware功能包中找到。这个sketch(这是arduino对使用arduino IDE编辑出来的程序的专门叫法)是针对上面所要求的硬件,但是也可以被用于Arduino其他版本的板子(例如Uno),你可以不使用基础控制器(`base controller`),你可以在文档末尾的NOTES部分找到详细的说明。
119 |
120 | 你可以按照下面的步骤安装ROSArduinoBridge库:
121 |
122 | ```bash
123 | $ cd SKETCHBOOK_PATH
124 | ```
125 | 这里`SKETCHBOOK_PATH`是指你的Arduino sketchbook路径。
126 |
127 | ```bash
128 | $ cp -rp `rospack find ros_arduino_firmware`/src/libraries/ROSArduinoBridge ROSArduinoBridge
129 | ```
130 |
131 | 最后一个命令就是把ROSArduinoBridge sketch文件拷贝到你的sketchbook文件夹下面。如果你之前已经拷贝过这个sketch,那么下次拷贝的时候请先删除之前的sketch:
132 |
133 | cd SKETCHBOOK_PATH
134 | rm -R ROSArduinoBridge
135 |
136 | 下一节描述的是如何配置,编译并上传这个sketch。
137 |
138 | # 加载ROSArduinoBridge的Sketch
139 | -----------
140 |
141 | - 如果你正在使用基础控制器功能包(`base controller`),那么你必须确保你已经在Arduino sketchbook/libraries文件夹里面安装了合适的电动机控制器和编码器的相关库。
142 | - 启动ArduinoIDE(1.0及以上),然后载入ROSArduinoBridgesketch。你可以这样找到他们File->Sketchbook->ROSArduinoBridge
143 |
144 | **NOTE:**如果你还没有安装要求的基础控制硬件,但是你仍然想使用这些代码,那么你可以参考文档末尾的NOTES部分。
145 |
146 | 你可以通过去掉或保留相关的宏定义声明,来选择你想用的电动机控制器,同时你还要将其他电动机控制器的宏声明注释掉。默认选择的是 Pololu VNH5019 driver。
147 |
148 | 同样,你也可以用相同的方法选择你想使用的编码器。默认选择的是Pololu VNH5019 driver。
149 |
150 | 如果你想让你的基础控制器控制PWM伺服机的话,就把下面这两行:
151 |
152 | ```c
153 | //#define USE_SERVOS
154 | #undef USE_SERVOS
155 | ```
156 | 改成:
157 | ```c
158 | #define USE_SERVOS
159 | //#undef USE_SERVOS
160 | ```
161 | 之后你就必须修改头文件`servos.h`改变其中的N_SERVOS参数,另外你还需要根据你的伺服机所接的引脚修改相应的引脚数字。
162 |
163 | 一切都准备好后你就可以把这个sketch编译并上传到你的Arduino板子上上了。
164 |
165 | # 固件程序命令
166 | ----------
167 | 这个ROSArduino库接受单字母命令来轮询传感器、控制伺服机、驱动机器人以及读取编码器。这些命令可以通过串口接口来发送给Arduino,比如Arduino的串口监视器。
168 |
169 | **NOTE:**在尝试这些命令之前,把串口监视器的波特率设置为57600然后把行结束符设置为“Carriage return(回车)”和“Both NL & CR”(NL和CR)。这些设置选项在串口监视器右下角的两个下拉菜单中。如下图所示:
170 |
171 | 
172 |
173 | 命令列表可以在头文件commands.h中找到。目前的列表包括这些命令:
174 |
175 | ```c
176 | #define ANALOG_READ 'a'
177 | #define GET_BAUDRATE 'b'
178 | #define PIN_MODE 'c'
179 | #define DIGITAL_READ 'd'
180 | #define READ_ENCODERS 'e'
181 | #define MOTOR_SPEEDS 'm'
182 | #define PING 'p'
183 | #define RESET_ENCODERS 'r'
184 | #define SERVO_WRITE 's'
185 | #define SERVO_READ 't'
186 | #define UPDATE_PID 'u'
187 | #define DIGITAL_WRITE 'w'
188 | #define ANALOG_WRITE 'x'
189 | ```
190 |
191 | 例如如果你想从模拟引脚pin3读取数据,你就可以使用这个命令:
192 |
193 | > a 3
194 |
195 | 如果你想改变数字引脚pin3的模式为`OUTPUT`,就发送这个命令:
196 |
197 | > c 3 1
198 |
199 | 要得到目前编码器的计数,你可以用这个命令:
200 |
201 | > e
202 |
203 | 把机器人以每秒20个encoder ticks的速度向前移动,可以使用命令:
204 |
205 | > m 20 20
206 |
207 | **NOTE:**这些命令有些需要有对应的参数,比如`a 3`中`3`代表模拟引脚3,其实这些命令的参数很多都是不言自明的,如果有些命令比较隐晦(比如`m 20 20`中的两个参数究竟那个参数是左轮,那个是右轮并未说明),你可以通过查看源码了解相应参数的意义,这里也就不在一一赘述了。
208 |
209 | PS:**我在此强调一遍,务必要进行[这一节](#固件程序命令)以及[下一节](#测试你的电线连接)的测试,否则你不能保证python-serial(后面会说到)与主机交流正常。**
210 |
211 | # 测试你的电线连接
212 | 在一个差速轮式机器人上,电动机使用两个极性相反的接头来连接到电动机控制器上的。同样地,连接到编码器上的A/B端也是互斥的。然而,你仍然需要确保这样两个要求:
213 |
214 | - 当给一个正向的速度,轮子向前移动;
215 | - 而当轮子向前移动时,编码器计数增加。
216 |
217 | 把你的机器人放在一个小块儿上(**此处原文为:*placing your robot on blocks*,按我的理解是这样的:把机器人放在一个块儿上,使得它的轮子可以悬空,以便于测试**),你可以使用串口监视器来测试上述的两个要求。你可以使用`m`命令来启动电动机,使用`e`命令来获取编码器计数,以及使用`r`命令来将编码器重置为0。你要明白,在固件层,电动机的速度是按照编码器每秒的tick数来表示的,这样可以使编码器很容易解析(理解)它。假如轮子转速是每秒4000个编码器计数,那么命令`m 20 20`会以一个非常小的速度移动小车。(默认设置轮子仅仅移动2秒,你可以通过修改源码中的`AUTO_STOP_INTERVAL`改变这个值)。另外你还要知道,第一个参数是左轮的速度,另一个参数是右轮的速度。类似地,当使用`e`命令时,第一个被返回的数是左轮的编码器计数,第二个数是右轮的编码器计数。
218 |
219 | 最后,你可以先使用`r`命令把编码器计数清零,然后通过手动粗略地旋转轮子一整圈,再通过`e`命令来验证获取到的编码器计数是否为预期的结果。
220 |
221 | # 配置ros\_arduino\_python节点
222 |
223 | 既然你的Arduino已经可以按要求运行sketch了,那么现在你就可以在你的PC上把它配置为ROS节点。你可以在通过编辑ros\_arduino\_python/config路径下的YAML文件定义的机器人尺寸,PID参数,以及传感器配置信息。所以首先进入到这个路径中:
224 |
225 | ```bash
226 | $ roscd ros_arduino_python/config
227 | ```
228 |
229 | 你可以把这个提供的配置文件拷贝一份然后再修改(名字可以自定义):
230 |
231 | ```bash
232 | $ cp arduino_params.yaml my_arduino_params.yaml
233 | ```
234 |
235 | 现在你可以使用任何你所喜欢的编辑器来编辑这个配置文件的副本(my_arduino_params.yaml),打开之后应该是这个样子:
236 |
237 | ```YAML
238 | # For a direct USB cable connection, the port name is typically
239 | # /dev/ttyACM# where is # is a number such as 0, 1, 2, etc
240 | # For a wireless connection like XBee, the port is typically
241 | # /dev/ttyUSB# where # is a number such as 0, 1, 2, etc.
242 |
243 | port: /dev/ttyACM0
244 | baud: 57600
245 | timeout: 0.1
246 |
247 | rate: 50
248 | sensorstate_rate: 10
249 |
250 | use_base_controller: False
251 | base_controller_rate: 10
252 |
253 | # For a robot that uses base_footprint, change base_frame to base_footprint
254 | base_frame: base_link
255 |
256 | # === Robot drivetrain parameters
257 | #wheel_diameter: 0.146
258 | #wheel_track: 0.2969
259 | #encoder_resolution: 8384 # from Pololu for 131:1 motors
260 | #gear_reduction: 1.0
261 | #motors_reversed: True
262 |
263 | # === PID parameters
264 | #Kp: 10
265 | #Kd: 12
266 | #Ki: 0
267 | #Ko: 50
268 | #accel_limit: 1.0
269 |
270 | # === Sensor definitions. Examples only - edit for your robot.
271 | # Sensor type can be one of the following:
272 | # * Ping
273 | # * GP2D12
274 | # * Analog
275 | # * Digital
276 | # * PololuMotorCurrent
277 | # * PhidgetsVoltage
278 | # * PhidgetsCurrent (20 Amp, DC)
279 |
280 |
281 | sensors: {
282 | #motor_current_left: {pin: 0, type: PololuMotorCurrent, rate: 5},
283 | #motor_current_right: {pin: 1, type: PololuMotorCurrent, rate: 5},
284 | #ir_front_center: {pin: 2, type: GP2D12, rate: 10},
285 | #sonar_front_center: {pin: 5, type: Ping, rate: 10},
286 | onboard_led: {pin: 13, type: Digital, rate: 5, direction: output}
287 | }
288 |
289 | # Joint name and configuration is an example only
290 | joints: {
291 | head_pan_joint: {pin: 3, init_position: 0, init_speed: 90, neutral: 90, min_position: -90, max_position: 90, invert: False, continuous: False},
292 | head_tilt_joint: {pin: 5, init_position: 0, init_speed: 90, neutral: 90, min_position: -90, max_position: 90, invert: False, continuous: False}
293 | }
294 | ```
295 | **NOTE:**不要在你的.yaml文件里使用tab,否则的话这个语法解析器会无法加载它。如果你要缩进的话必须用空格代替tab键。
296 |
297 | **ALSO:**当定义你的传感器参数时,列表中的最后一个传感器的行尾(花括号后面)后没有逗号,但是其余的行尾必须有逗号。
298 |
299 | 现在让我们一起浏览一下这个文件的每一部分。
300 |
301 | ***接口设置***
302 |
303 | 这个接口要么是/dev/ttyACM0,要么是/dev/ttyUSB0,视情况而定,而且前面我们也已经提到过这个。
304 |
305 | 其中MegaRobogaiaPololu的Arudino sketch默认是以57600的波特率连接的。
306 |
307 | ***轮询速率***
308 |
309 | **跳出ROS loop运行的速率主要就取决于这个速率参数(默认为50Hz),这个默认值足以满足大多数情况。在任何情况下,它应该至少与你的传感器的最大速率(下面我们会说到)一样快才行。**
310 |
311 | *sensorstate_rate*决定了多久发布一个所有传感器的集合列表,每个传感器也以各自的速率在各自的主题上发布消息。
312 |
313 | *use_base_controller*参数默认为False。你可以把它设置为True(假设你有文中所要求的硬件设施)。下面你将必须设置PID参数。
314 |
315 |
316 | *base_controller_rate*参数决定了多久发布一次里程计读取信息。
317 |
318 | ***定义传感器***
319 |
320 | *sensors*参数按照定义了传感器的名字和参数的字典。(你可以给你的传感器起任何你喜欢的名字,但是你必须要知道传感器的名字也会成为那个传感器所对应主题的名字。
321 |
322 | 这四个最重要的参数分别是:*pin*,*type*,*rate*,*direction*。*rate*定义了你每秒想轮询一次那个传感器多少次。例如,一个电压传感器可能每秒仅仅被轮询一次(或者仅仅每两秒一次),而一个声呐传感器可能每秒被轮询20次。*type*必须是列表中被列出来的(**注意区分大小写!**)。*direction*默认是*input*,所以如果你想将它定义为*output*,就将这个direction单独设为*output*。在上面的例子中,Arduino LED(pin13)将会以每秒两次的速率被点亮或熄灭。
323 |
324 | ***设置Drivetrain(驱动系统)和PID参数***
325 |
326 | 为了使用基础控制器(base controller),你必须去掉它的注释并且设置机器人的drivetrain和PID参数。示例中drivetrain参数是直径6英寸的驱动轮,距离11.5英寸。注意在ROS中使用米作为距离单位,所以一定要换算单位。示例中的编码器的分辨率(每转的tick数)规格来自于Pololu 131:1电动机。为你的电动机/编码器组合设置合理的数值。如果你的轮子可以向后转,那么就把* motors_reversed*设置为true,否则的话就设置为False。
327 |
328 | PID参数比较难设置,你可以先按照示例中的值设置。但是在你第一次发送转弯命令的时候,一定要把你的机器人放在小块儿上。
329 |
330 | # 启动ros_arduino_python节点
331 |
332 | 你现在看一下路径ros\_arduino\_python/launch下的文件arduino.launch。正如你所看到的,它指向一个名叫my_arduino_params.yaml的文件(之前创建的)。
333 |
334 | ```xml
335 |
336 |
337 |
338 |
339 |
340 | ```
341 |
342 | 如果你的配置文件命名不同,那么就把这里的文件名参数(my_arduino_params.yaml)修改成你的配置文件的名字。
343 |
344 | 当你连接好Arduino并运行MegaRobogaiaPololu sketch时,使用下面的参数来启动 ros_arduino_python node节点:
345 |
346 | ```bash
347 | $ roslaunch ros_arduino_python arduino.launch
348 | ```
349 |
350 | **NOTE:**启动之前,千万不要打开Arduino IDE 的串口监视器,因为串口监视器会与该节点争夺串口资源。
351 |
352 | 你应该看到下面这样的输出:
353 |
354 | > process[arduino-1]: started with pid [6098]
355 | > Connecting to Arduino on port /dev/ttyUSB0 ...
356 | > Connected at 57600
357 | > Arduino is ready.
358 | > [INFO] [WallTime: 1355498525.954491] Connected to Arduino on port / > dev/ttyUSB0 at 57600 baud
359 | > [INFO] [WallTime: 1355498525.966825] motor\_current\_right {'rate': 5, > 'type': 'PololuMotorCurrent', 'pin': 1}
360 | > [INFO]
361 | > etc
362 |
363 | 如果你在你的机器人上装的有Ping声呐而且你也在配置文件里面定义了它们,它们就会闪一下来告诉你已经连接成功。
364 |
365 | # 查看传感器数据
366 |
367 | 你可以回显传感器状态主题来查看所有传感器的数据:
368 |
369 | ```bash
370 | $ rostopic echo /arduino/sensor_state
371 | ```
372 |
373 | 回显相应传感器的主题名字你就可以查看任何指定的传感器数据:
374 |
375 | ```bash
376 | rostopic echo /arduino/sensor/sensor_name
377 | ```
378 |
379 | 例如,你有一个叫做
380 | ir\_front\_center的传感器,那么你可以运行下面的命令来查看相应的数据:
381 |
382 | ```bash
383 | $ rostopic echo /arduino/sensor/ir_front_center
384 | ```
385 |
386 | 你也可以使用`rxqrt`来将这系列数据用图像的形式表示出来:
387 |
388 | ```bash
389 | $ rxplot -p 60 /arduino/sensor/ir_front_center/range
390 | ```
391 | # 发送Twist命令与查看里程计数据
392 |
393 | ros\_arduino\_python功能包也定义了一些ROS服务,如下所示:
394 |
395 | **digital\_set\_direction**-设置数字引脚的方向
396 |
397 | ```bash
398 | $ rosservice call /arduino/digital_set_direction pin direction
399 | ```
400 |
401 | 这里`pin`是引脚数字,`direction`为0代表输入,1代表输出。
402 |
403 | **digital\_write**-给数字引脚发送高低电平(LOW为0,HIGH为1)
404 |
405 | ```bash
406 | $ rosservice call /arduino/digital_write pin value
407 | ```
408 |
409 | 同样,这里`pin`是引脚数字,`value`是电平高低(LOW为0,HIGH为1)。
410 |
411 | **servo\_write**-设置伺服机位置
412 |
413 | ```bash
414 | $ rosservice call /arduino/servo_write id pos
415 | ```
416 |
417 | 这里id是伺服机的索引号(定义在Arduino sketch中的servos.h)并且`pos`是以弧度为单位(0-3.14),头文件servos.h中具体是这样写的:
418 |
419 | ```bash
420 | byte servoInitPosition [N_SERVOS] = { 90, 90 }; // [0, 180] degrees
421 | ```
422 | **servo_read **-读取伺服机的位置
423 |
424 | ```bash
425 | $ rosservice call /arduino/servo_read id
426 | ```
427 |
428 | # 使用板上编码器计数(仅支持ArduinoUno)
429 |
430 | 对于Arduino Uno,这个固件程序支持板上的编码器计数。这样的话,编码器就直接可以连接到Arduino板上,而不用借助任何额外的编码器设备(例如RoboGaia encoder shield)
431 |
432 | 对于速度,这个代码可以直接找到Atmega328p的接口和中断管脚,而这一功能的实现必须依赖Atmega328p(Arduino Uno)。(尽管它也可能兼容其他电路板或AVR单片机芯片)
433 |
434 | 为了使用这个板上编码器计数,按照下面的要求来将编码器连接到Arduino Uno:
435 |
436 | ```
437 | Left wheel encoder A output -- Arduino UNO pin 2
438 | Left wheel encoder B output -- Arduino UNO pin 3
439 |
440 | Right wheel encoder A output -- Arduino UNO pin A4
441 | Right wheel encoder B output -- Arduino UNO pin A5
442 | ```
443 | 这时你需要在Arduino sketch中做相应的修改,通过下面的方式来取消使用RoboGaia encoder shield的代码,启用板上编码器的代码。
444 |
445 | ```c
446 | /* The RoboGaia encoder shield */
447 | //#define ROBOGAIA
448 | /* Encoders directly attached to Arduino board */
449 | #define ARDUINO_ENC_COUNTER
450 | ```
451 |
452 | 这时你就可以编译并上传到Arduino上了。
453 |
454 | # 注意
455 | -------
456 | 如果你没有文档中所要求的硬件来运行这个基础控制器,但是你仍然想使用其他兼容Arduino的控制器来读取传感器以及控制PWM伺服机,那么请按照下面的步骤:
457 |
458 | 首先,你需要编辑你的ROSArduinoBridge sketch,在文件的最前面,将这两行:
459 |
460 | ```c
461 | #define USE_BASE
462 | //#undef USE_BASE
463 | ```
464 |
465 | 改成这样:
466 |
467 | ```c
468 | //#define USE_BASE
469 | #undef USE_BASE
470 | ```
471 |
472 | **NOTE**:你还需要将文件encoder_driver.ino中的这一行注释掉:
473 |
474 | ```c
475 | #include "MegaEncoderCounter.h"
476 | ```
477 | 这时你就可以编译并上传你的代码了。
478 |
479 | 然后,编辑你的 my\_arduino\_params.yaml文件,确保参数`use_base_controller`被设为False。这样你就完成了。
480 |
481 | # 可能遇到的错误
482 |
483 | 我在使用这个功能包的时候遇到了不少错误,因此我特意将这些错误及解决方法总结出来并与大家分享:[ros_arduino_bridge功能包集的使用错误及解决方法总结](http://blog.csdn.net/github_30605157/article/details/51621965)。
484 |
--------------------------------------------------------------------------------