├── .arduino-ci.yaml
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report-or-feature-request.md
│ └── config.yml
└── workflows
│ └── ci.yaml
├── .gitignore
├── .gitlab-ci.yml
├── LICENSE.txt
├── README.md
├── examples
├── AStarRPiSlaveDemo
│ └── AStarRPiSlaveDemo.ino
└── RomiRPiSlaveDemo
│ └── RomiRPiSlaveDemo.ino
├── keywords.txt
├── library.properties
├── pi
├── .gitignore
├── a_star.py
├── a_star_slave.sh
├── a_star_slave_heartbeat.sh
├── beep.py
├── benchmark.py
├── blink.py
├── heartbeat.py
├── server.py
├── static
│ ├── main.css
│ └── script.js
└── templates
│ └── index.html
└── src
├── PololuRPiSlave.h
├── PololuTWISlave.cpp
└── PololuTWISlave.h
/.arduino-ci.yaml:
--------------------------------------------------------------------------------
1 | only_boards:
2 | - arduino:avr:leonardo
3 | - arduino:avr:micro
4 | - arduino:avr:yun
5 | library_archives:
6 | - AStar32U4=https://github.com/pololu/a-star-32u4-arduino-library/archive/1.1.1.tar.gz=1zbjki39jqs77kz7vdd42v1ydz3yvbidccn1j4qbwij6fgyjvgyn
7 | - Romi32U4=https://github.com/pololu/romi-32u4-arduino-library/archive/1.0.2.tar.gz=19d06h6lnv3k8yyxqn2qfh7jcc1gqzipddrg6jvdlxil1sji9q1s
8 | - Servo=https://github.com/arduino-libraries/Servo/archive/1.1.7.tar.gz=1z7396n16f3pg314sg06cgbi1zqd0qq6w7jr89f2qwmpg9hj7w4x
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report-or-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report or feature request
3 | about: Did you find a specific bug in the code for this project? Do you want to request
4 | a new feature? Please open an issue!
5 | title: ''
6 | labels: ''
7 | assignees: ''
8 |
9 | ---
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Pololu Forum
4 | url: https://forum.pololu.com/
5 | about: Do you need help getting started? Can't get this code to work at all? Having problems with electronics? Please post on our forum!
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: "CI"
2 | on:
3 | pull_request:
4 | push:
5 | jobs:
6 | ci:
7 | runs-on: ubuntu-20.04
8 | steps:
9 | - name: Checkout this repository
10 | uses: actions/checkout@v2.3.4
11 | - name: Cache for arduino-ci
12 | uses: actions/cache@v2.1.3
13 | with:
14 | path: |
15 | ~/.arduino15
16 | key: ${{ runner.os }}-arduino
17 | - name: Install nix
18 | uses: cachix/install-nix-action@v12
19 | - run: nix-shell -I nixpkgs=channel:nixpkgs-unstable -p arduino-ci --run "arduino-ci"
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /docs/
2 | /out/
3 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: $CI_REGISTRY_IMAGE/nixos/nix:2.3.6
2 |
3 | stages:
4 | - ci
5 |
6 | ci:
7 | stage: ci
8 | tags:
9 | - nix
10 | script:
11 | - nix-shell -I nixpkgs=channel:nixpkgs-unstable -p arduino-ci --run "arduino-ci"
12 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2020 Pololu Corporation (www.pololu.com)
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Raspberry Pi slave library for Arduino
2 |
3 | Version: 2.0.0
4 | Release date: 2017 March 31
5 | [www.pololu.com](https://www.pololu.com/)
6 |
7 | Summary
8 | -------
9 |
10 | This is an Arduino library that helps establish I2 C communication with
11 | a Raspberry Pi, with the Arduino acting as the I2 C slave. It should
12 | work with most Arduino-compatible boards, but we designed it for use
13 | with these Pololu products:
14 |
15 | * [A-Star 32U4 Robot Controller LV](https://www.pololu.com/product/3117) or [SV](https://www.pololu.com/product/3119)
16 | * [Romi 32U4 Control Board](https://www.pololu.com/product/3544)
17 |
18 | These boards are designed to connect conveniently to the Pi's GPIO header. The
19 | idea is that the Raspberry Pi can take care of high-level tasks like video
20 | processing or network communication, while the AVR microcontroller takes care of
21 | actuator control, sensor inputs, and other low-level tasks that the Pi is
22 | incapable of.
23 |
24 | There are a few reasons we made a library for this instead of
25 | just recommending the standard Arduino I2 C library, Wire.h:
26 |
27 | * Wire.h just gives you access to raw I2 C packets; you have to decide
28 | how they should relate to your data, and that's not trivial. This
29 | library implements a specific protocol allowing bidirectional access
30 | to a buffer, with atomic reads and writes.
31 | * The processor on the Raspberry Pi has
32 | [a major bug](http://www.advamation.com/knowhow/raspberrypi/rpi-i2c-bug.html)
33 | that corrupts data except at very low speeds. The standard Arduino
34 | I2 C library, Wire.h, is not flexible enough to allow us to follow
35 | the suggested workarounds (delays inserted at key points).
36 |
37 | We have included example Arduino code for the A-Star or Romi and Python code
38 | for the Raspberry Pi. Together, the examples set up a web server on
39 | the Raspberry Pi that will let you remotely control and monitor a
40 | robot from your smartphone or computer.
41 |
42 | Getting started
43 | ---------------
44 |
45 | See [this blog post](https://www.pololu.com/blog/577/building-a-raspberry-pi-robot-with-the-a-star-32u4-robot-controller)
46 | for a complete tutorial including step-by-step build instructions for
47 | an example robot.
48 |
49 | Benchmarking
50 | ------------
51 |
52 | The included script `benchmark.py` times reads and writes of 8 bytes,
53 | to give you an idea of how quickly data can be transferred between the
54 | devices. Because of a limitation in the AVR's I2 C module,
55 | we have slowed down reads significantly.
56 |
57 | | Bus speed | Reads | Writes |
58 | | --------- | --------- | ---------- |
59 | | 100 kHz | 21 kbit/s | 53 kbit/s |
60 | | 400 kHz | 43 kbit/s | 140 kbit/s |
61 |
62 | Version history
63 | ---------------
64 |
65 | * 2.0.0 (2017 Mar 31): Added support for encoder counts and slave sketch for the Romi 32U4 robot. Updated Raspberry Pi scripts to use Python 3 instead of Python 2.
66 | * 1.0.1 (2017 Jan 23): Added and adjusted delays necessary for reliable operation on the Pi 3.
67 | * 1.0.0 (2016 Feb 16): Original release.
68 |
--------------------------------------------------------------------------------
/examples/AStarRPiSlaveDemo/AStarRPiSlaveDemo.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | /* This example program shows how to make the A-Star 32U4 Robot
6 | * Controller into a Raspberry Pi I2C slave. The RPi and A-Star can
7 | * exchange data bidirectionally, allowing each device to do what it
8 | * does best: high-level programming can be handled in a language such
9 | * as Python on the RPi, while the A-Star takes charge of motor
10 | * control, analog inputs, and other low-level I/O.
11 | *
12 | * The example and libraries are available for download at:
13 | *
14 | * https://github.com/pololu/pololu-rpi-slave-arduino-library
15 | *
16 | * You will need the corresponding Raspberry Pi code, which is
17 | * available in that repository under the pi/ subfolder. The Pi code
18 | * sets up a simple Python-based web application as a control panel
19 | * for your Raspberry Pi robot.
20 | */
21 |
22 | // Custom data structure that we will use for interpreting the buffer.
23 | // We recommend keeping this under 64 bytes total. If you change the
24 | // data format, make sure to update the corresponding code in
25 | // a_star.py on the Raspberry Pi.
26 |
27 | struct Data
28 | {
29 | bool yellow, green, red;
30 | bool buttonA, buttonB, buttonC;
31 |
32 | int16_t leftMotor, rightMotor;
33 | uint16_t batteryMillivolts;
34 | uint16_t analog[6];
35 |
36 | bool playNotes;
37 | char notes[14];
38 |
39 | // Encoders are unused in this example.
40 | int16_t leftEncoder, rightEncoder;
41 | };
42 |
43 | PololuRPiSlave slave;
44 | PololuBuzzer buzzer;
45 | AStar32U4Motors motors;
46 | AStar32U4ButtonA buttonA;
47 | AStar32U4ButtonB buttonB;
48 | AStar32U4ButtonC buttonC;
49 |
50 | void setup()
51 | {
52 | // Set up the slave at I2C address 20.
53 | slave.init(20);
54 |
55 | // Play startup sound.
56 | buzzer.play("v10>>g16>>>c16");
57 | }
58 |
59 | void loop()
60 | {
61 | // Call updateBuffer() before using the buffer, to get the latest
62 | // data including recent master writes.
63 | slave.updateBuffer();
64 |
65 | // Write various values into the data structure.
66 | slave.buffer.buttonA = buttonA.isPressed();
67 | slave.buffer.buttonB = buttonB.isPressed();
68 | slave.buffer.buttonC = buttonC.isPressed();
69 |
70 | // Change this to readBatteryMillivoltsLV() for the LV model.
71 | slave.buffer.batteryMillivolts = readBatteryMillivoltsSV();
72 |
73 | for(uint8_t i=0; i<6; i++)
74 | {
75 | slave.buffer.analog[i] = analogRead(i);
76 | }
77 |
78 | // READING the buffer is allowed before or after finalizeWrites().
79 | ledYellow(slave.buffer.yellow);
80 | ledGreen(slave.buffer.green);
81 | ledRed(slave.buffer.red);
82 | motors.setSpeeds(slave.buffer.leftMotor, slave.buffer.rightMotor);
83 |
84 | // Playing music involves both reading and writing, since we only
85 | // want to do it once.
86 | static bool startedPlaying = false;
87 |
88 | if(slave.buffer.playNotes && !startedPlaying)
89 | {
90 | buzzer.play(slave.buffer.notes);
91 | startedPlaying = true;
92 | }
93 | else if (startedPlaying && !buzzer.isPlaying())
94 | {
95 | slave.buffer.playNotes = false;
96 | startedPlaying = false;
97 | }
98 |
99 | // When you are done WRITING, call finalizeWrites() to make modified
100 | // data available to I2C master.
101 | slave.finalizeWrites();
102 | }
103 |
--------------------------------------------------------------------------------
/examples/RomiRPiSlaveDemo/RomiRPiSlaveDemo.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | /* This example program shows how to make the Romi 32U4 Control Board
6 | * into a Raspberry Pi I2C slave. The RPi and Romi 32U4 Control Board can
7 | * exchange data bidirectionally, allowing each device to do what it
8 | * does best: high-level programming can be handled in a language such
9 | * as Python on the RPi, while the Romi 32U4 Control Board takes charge
10 | * of motor control, analog inputs, and other low-level I/O.
11 | *
12 | * The example and libraries are available for download at:
13 | *
14 | * https://github.com/pololu/pololu-rpi-slave-arduino-library
15 | *
16 | * You will need the corresponding Raspberry Pi code, which is
17 | * available in that repository under the pi/ subfolder. The Pi code
18 | * sets up a simple Python-based web application as a control panel
19 | * for your Raspberry Pi robot.
20 | */
21 |
22 | // Custom data structure that we will use for interpreting the buffer.
23 | // We recommend keeping this under 64 bytes total. If you change the
24 | // data format, make sure to update the corresponding code in
25 | // a_star.py on the Raspberry Pi.
26 |
27 | struct Data
28 | {
29 | bool yellow, green, red;
30 | bool buttonA, buttonB, buttonC;
31 |
32 | int16_t leftMotor, rightMotor;
33 | uint16_t batteryMillivolts;
34 | uint16_t analog[6];
35 |
36 | bool playNotes;
37 | char notes[14];
38 |
39 | int16_t leftEncoder, rightEncoder;
40 | };
41 |
42 | PololuRPiSlave slave;
43 | PololuBuzzer buzzer;
44 | Romi32U4Motors motors;
45 | Romi32U4ButtonA buttonA;
46 | Romi32U4ButtonB buttonB;
47 | Romi32U4ButtonC buttonC;
48 | Romi32U4Encoders encoders;
49 |
50 | void setup()
51 | {
52 | // Set up the slave at I2C address 20.
53 | slave.init(20);
54 |
55 | // Play startup sound.
56 | buzzer.play("v10>>g16>>>c16");
57 | }
58 |
59 | void loop()
60 | {
61 | // Call updateBuffer() before using the buffer, to get the latest
62 | // data including recent master writes.
63 | slave.updateBuffer();
64 |
65 | // Write various values into the data structure.
66 | slave.buffer.buttonA = buttonA.isPressed();
67 | slave.buffer.buttonB = buttonB.isPressed();
68 | slave.buffer.buttonC = buttonC.isPressed();
69 |
70 | // Change this to readBatteryMillivoltsLV() for the LV model.
71 | slave.buffer.batteryMillivolts = readBatteryMillivolts();
72 |
73 | for(uint8_t i=0; i<6; i++)
74 | {
75 | slave.buffer.analog[i] = analogRead(i);
76 | }
77 |
78 | // READING the buffer is allowed before or after finalizeWrites().
79 | ledYellow(slave.buffer.yellow);
80 | ledGreen(slave.buffer.green);
81 | ledRed(slave.buffer.red);
82 | motors.setSpeeds(slave.buffer.leftMotor, slave.buffer.rightMotor);
83 |
84 | // Playing music involves both reading and writing, since we only
85 | // want to do it once.
86 | static bool startedPlaying = false;
87 |
88 | if(slave.buffer.playNotes && !startedPlaying)
89 | {
90 | buzzer.play(slave.buffer.notes);
91 | startedPlaying = true;
92 | }
93 | else if (startedPlaying && !buzzer.isPlaying())
94 | {
95 | slave.buffer.playNotes = false;
96 | startedPlaying = false;
97 | }
98 |
99 | slave.buffer.leftEncoder = encoders.getCountsLeft();
100 | slave.buffer.rightEncoder = encoders.getCountsRight();
101 |
102 | // When you are done WRITING, call finalizeWrites() to make modified
103 | // data available to I2C master.
104 | slave.finalizeWrites();
105 | }
106 |
--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
1 | PololuRPiSlave KEYWORD1
2 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=PololuRPiSlave
2 | version=2.0.0
3 | author=Pololu
4 | maintainer=Pololu
5 | sentence=Pololu Raspberry Pi I2C Slave Arduino library
6 | paragraph=This library helps set up a Pololu A-Star or Romi as an I2C slave for use with the Raspberry Pi.
7 | category=Communication
8 | url=https://github.com/pololu/pololu-rpi-slave-arduino-library
9 | architectures=avr
10 |
--------------------------------------------------------------------------------
/pi/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | build
--------------------------------------------------------------------------------
/pi/a_star.py:
--------------------------------------------------------------------------------
1 | # Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 | import smbus
3 | import struct
4 | import time
5 |
6 | class AStar:
7 | def __init__(self):
8 | self.bus = smbus.SMBus(1)
9 |
10 | def read_unpack(self, address, size, format):
11 | # Ideally we could do this:
12 | # byte_list = self.bus.read_i2c_block_data(20, address, size)
13 | # But the AVR's TWI module can't handle a quick write->read transition,
14 | # since the STOP interrupt will occasionally happen after the START
15 | # condition, and the TWI module is disabled until the interrupt can
16 | # be processed.
17 | #
18 | # A delay of 0.0001 (100 us) after each write is enough to account
19 | # for the worst-case situation in our example code.
20 |
21 | self.bus.write_byte(20, address)
22 | time.sleep(0.0001)
23 | byte_list = [self.bus.read_byte(20) for _ in range(size)]
24 | return struct.unpack(format, bytes(byte_list))
25 |
26 | def write_pack(self, address, format, *data):
27 | data_array = list(struct.pack(format, *data))
28 | self.bus.write_i2c_block_data(20, address, data_array)
29 | time.sleep(0.0001)
30 |
31 | def leds(self, red, yellow, green):
32 | self.write_pack(0, 'BBB', red, yellow, green)
33 |
34 | def play_notes(self, notes):
35 | self.write_pack(24, 'B14s', 1, notes.encode("ascii"))
36 |
37 | def motors(self, left, right):
38 | self.write_pack(6, 'hh', left, right)
39 |
40 | def read_buttons(self):
41 | return self.read_unpack(3, 3, "???")
42 |
43 | def read_battery_millivolts(self):
44 | return self.read_unpack(10, 2, "H")
45 |
46 | def read_analog(self):
47 | return self.read_unpack(12, 12, "HHHHHH")
48 |
49 | def read_encoders(self):
50 | return self.read_unpack(39, 4, 'hh')
51 |
52 | def test_read8(self):
53 | self.read_unpack(0, 8, 'cccccccc')
54 |
55 | def test_write8(self):
56 | self.bus.write_i2c_block_data(20, 0, [0,0,0,0,0,0,0,0])
57 | time.sleep(0.0001)
58 |
--------------------------------------------------------------------------------
/pi/a_star_slave.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ### BEGIN INIT INFO
4 | # Provides: a_star_slave
5 | # Required-Start: $remote_fs $syslog
6 | # Required-Stop: $remote_fs $syslog
7 | # Default-Start: 2 3 4 5
8 | # Default-Stop: 0 1 6
9 | # Short-Description: Put a short description of the service here
10 | # Description: Put a long description of the service here
11 | ### END INIT INFO
12 |
13 | # Change the next 3 lines to suit where you install your script and what you want to call it
14 | DIR=/home/paul/gitprojects/pololu/pololu-rpi-slave-arduino-library/pi/
15 | DAEMON=$DIR/server.py
16 | DAEMON_NAME=a_star_slave
17 |
18 | # Add any command line options for your daemon here
19 | DAEMON_OPTS=""
20 |
21 | # This next line determines what user the script runs as.
22 | # Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
23 | DAEMON_USER=paul
24 |
25 | # The process ID of the script when it runs is stored here:
26 | PIDFILE=/var/run/$DAEMON_NAME.pid
27 |
28 | . /lib/lsb/init-functions
29 |
30 | do_start () {
31 | log_daemon_msg "Starting system $DAEMON_NAME daemon"
32 | start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
33 | log_end_msg $?
34 | }
35 | do_stop () {
36 | log_daemon_msg "Stopping system $DAEMON_NAME daemon"
37 | start-stop-daemon --stop --pidfile $PIDFILE --retry 10
38 | log_end_msg $?
39 | }
40 |
41 | case "$1" in
42 |
43 | start|stop)
44 | do_${1}
45 | ;;
46 |
47 | restart|reload|force-reload)
48 | do_stop
49 | do_start
50 | ;;
51 |
52 | status)
53 | status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
54 | ;;
55 |
56 | *)
57 | echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
58 | exit 1
59 | ;;
60 |
61 | esac
62 | exit 0
63 |
--------------------------------------------------------------------------------
/pi/a_star_slave_heartbeat.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ### BEGIN INIT INFO
4 | # Provides: a_star_slave_heartbeat
5 | # Required-Start: $remote_fs $syslog
6 | # Required-Stop: $remote_fs $syslog
7 | # Default-Start: 2 3 4 5
8 | # Default-Stop: 0 1 6
9 | # Short-Description: Put a short description of the service here
10 | # Description: Put a long description of the service here
11 | ### END INIT INFO
12 |
13 | # Change the next 3 lines to suit where you install your script and what you want to call it
14 | DIR=/home/paul/gitprojects/pololu/pololu-rpi-slave-arduino-library/pi/
15 | DAEMON=$DIR/heartbeat.py
16 | DAEMON_NAME=a_star_slave_heartbeat
17 |
18 | # Add any command line options for your daemon here
19 | DAEMON_OPTS=""
20 |
21 | # This next line determines what user the script runs as.
22 | # Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
23 | DAEMON_USER=paul
24 |
25 | # The process ID of the script when it runs is stored here:
26 | PIDFILE=/var/run/$DAEMON_NAME.pid
27 |
28 | . /lib/lsb/init-functions
29 |
30 | do_start () {
31 | log_daemon_msg "Starting system $DAEMON_NAME daemon"
32 | start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
33 | log_end_msg $?
34 | }
35 | do_stop () {
36 | log_daemon_msg "Stopping system $DAEMON_NAME daemon"
37 | start-stop-daemon --stop --pidfile $PIDFILE --retry 10
38 | log_end_msg $?
39 | }
40 |
41 | case "$1" in
42 |
43 | start|stop)
44 | do_${1}
45 | ;;
46 |
47 | restart|reload|force-reload)
48 | do_stop
49 | do_start
50 | ;;
51 |
52 | status)
53 | status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
54 | ;;
55 |
56 | *)
57 | echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
58 | exit 1
59 | ;;
60 |
61 | esac
62 | exit 0
63 |
--------------------------------------------------------------------------------
/pi/beep.py:
--------------------------------------------------------------------------------
1 | # Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 | from a_star import AStar
3 |
4 | a_star = AStar()
5 |
6 | a_star.play_notes("o4l16ceg>c8")
7 |
--------------------------------------------------------------------------------
/pi/benchmark.py:
--------------------------------------------------------------------------------
1 | # Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 | # This example tests the speed of reads and writes to the slave device.
3 |
4 | from a_star import AStar
5 | import time
6 |
7 | a_star = AStar()
8 |
9 | import timeit
10 |
11 | n = 500
12 | total_time = timeit.timeit(a_star.test_write8, number=n)
13 | write_kbits_per_second = 8 * n * 8 / total_time / 1000
14 |
15 | print("Writes of 8 bytes: "+'%.1f'%write_kbits_per_second+" kilobits/second")
16 |
17 | n = 500
18 | total_time = timeit.timeit(a_star.test_read8, number=n)
19 | read_kbits_per_second = 8 * n * 8 / total_time / 1000
20 |
21 | print("Reads of 8 bytes: "+'%.1f'%read_kbits_per_second+" kilobits/second")
22 |
--------------------------------------------------------------------------------
/pi/blink.py:
--------------------------------------------------------------------------------
1 | # Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 | from a_star import AStar
3 | import time
4 |
5 | a_star = AStar()
6 |
7 | while 1:
8 | a_star.leds(0,0,0)
9 | time.sleep(0.5)
10 | a_star.leds(1,1,1)
11 | time.sleep(0.5)
12 |
--------------------------------------------------------------------------------
/pi/heartbeat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright Pololu Corporation. For more information, see https://www.pololu.com/
4 |
5 | import urllib.request
6 | import time
7 |
8 | while True:
9 | try:
10 | time.sleep(1)
11 | urllib.request.urlopen("http://localhost:5000/heartbeat/1").read()
12 | time.sleep(0.01)
13 | urllib.request.urlopen("http://localhost:5000/heartbeat/0").read()
14 | except urllib.request.URLError:
15 | print("error")
16 |
--------------------------------------------------------------------------------
/pi/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright Pololu Corporation. For more information, see https://www.pololu.com/
4 | from flask import Flask
5 | from flask import render_template
6 | from flask import redirect
7 | from subprocess import call
8 | app = Flask(__name__)
9 | app.debug = True
10 |
11 | from a_star import AStar
12 | a_star = AStar()
13 |
14 | import json
15 |
16 | led0_state = False
17 | led1_state = False
18 | led2_state = False
19 |
20 | @app.route("/")
21 | def hello():
22 | return render_template("index.html")
23 |
24 | @app.route("/status.json")
25 | def status():
26 | buttons = a_star.read_buttons()
27 | analog = a_star.read_analog()
28 | battery_millivolts = a_star.read_battery_millivolts()
29 | encoders = a_star.read_encoders()
30 | data = {
31 | "buttons": buttons,
32 | "battery_millivolts": battery_millivolts,
33 | "analog": analog,
34 | "encoders": encoders
35 | }
36 | return json.dumps(data)
37 |
38 | @app.route("/motors/,")
39 | def motors(left, right):
40 | a_star.motors(int(left), int(right))
41 | return ""
42 |
43 | @app.route("/leds/,,")
44 | def leds(led0, led1, led2):
45 | a_star.leds(led0, led1, led2)
46 | global led0_state
47 | global led1_state
48 | global led2_state
49 | led0_state = led0
50 | led1_state = led1
51 | led2_state = led2
52 | return ""
53 |
54 | @app.route("/heartbeat/")
55 | def hearbeat(state):
56 | if state == 0:
57 | a_star.leds(led0_state, led1_state, led2_state)
58 | else:
59 | a_star.leds(not led0_state, not led1_state, not led2_state)
60 | return ""
61 |
62 | @app.route("/play_notes/")
63 | def play_notes(notes):
64 | a_star.play_notes(notes)
65 | return ""
66 |
67 | @app.route("/halt")
68 | def halt():
69 | call(["bash", "-c", "(sleep 2; sudo halt)&"])
70 | return redirect("/shutting-down")
71 |
72 | @app.route("/shutting-down")
73 | def shutting_down():
74 | return "Shutting down in 2 seconds! You can remove power when the green LED stops flashing."
75 |
76 | if __name__ == "__main__":
77 | app.run(host = "0.0.0.0")
78 |
--------------------------------------------------------------------------------
/pi/static/main.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: sans-serif;
3 | margin: 0;
4 | padding: 0;
5 | text-align: center;
6 | }
7 |
8 | body {
9 | margin: 0;
10 | padding: 0;
11 | }
12 |
13 | input, button {
14 | font-size: 1rem;
15 | }
16 |
17 | #container {
18 | display: inline-block;
19 | max-width: 600px;
20 | padding: 0.5em;
21 | }
22 |
23 | h1 {
24 | font-size: 1rem;
25 | font-weight: bold;
26 | text-align: center;
27 | margin: 0;
28 | margin-bottom: 0.5em;
29 | }
30 |
31 | td,th {
32 | width: 3em;
33 | text-align: right;
34 | }
35 |
36 | td.center {
37 | text-align: center;
38 | }
39 |
40 | td.wide {
41 | width: 5em;
42 | }
43 |
44 | table {
45 | border-collapse: collapse;
46 | margin-bottom: 1em;
47 | }
48 |
49 | table td, table th {
50 | border: 1px solid black;
51 | padding: 0.5em;
52 | }
53 |
54 | #joystick {
55 | border: 1px solid black;
56 | background-color: gray;
57 | width: 14em;
58 | height: 14em;
59 | margin-left: auto;
60 | margin-right: auto;
61 | }
62 |
--------------------------------------------------------------------------------
/pi/static/script.js:
--------------------------------------------------------------------------------
1 | // Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 | stop_motors = true
3 | block_set_motors = false
4 | mouse_dragging = false
5 |
6 | function init() {
7 | poll()
8 | $("#joystick").bind("touchstart",touchmove)
9 | $("#joystick").bind("touchmove",touchmove)
10 | $("#joystick").bind("touchend",touchend)
11 | $("#joystick").bind("mousedown",mousedown)
12 | $(document).bind("mousemove",mousemove)
13 | $(document).bind("mouseup",mouseup)
14 | }
15 |
16 | function poll() {
17 | $.ajax({url: "status.json"}).done(update_status)
18 | if(stop_motors && !block_set_motors)
19 | {
20 | setMotors(0,0);
21 | stop_motors = false
22 | }
23 | }
24 |
25 | function update_status(json) {
26 | s = JSON.parse(json)
27 | $("#button0").html(s["buttons"][0] ? '1' : '0')
28 | $("#button1").html(s["buttons"][1] ? '1' : '0')
29 | $("#button2").html(s["buttons"][2] ? '1' : '0')
30 |
31 | $("#battery_millivolts").html(s["battery_millivolts"])
32 |
33 | $("#analog0").html(s["analog"][0])
34 | $("#analog1").html(s["analog"][1])
35 | $("#analog2").html(s["analog"][2])
36 | $("#analog3").html(s["analog"][3])
37 | $("#analog4").html(s["analog"][4])
38 | $("#analog5").html(s["analog"][5])
39 |
40 | $("#encoders0").html(s["encoders"][0])
41 | $("#encoders1").html(s["encoders"][1])
42 |
43 | setTimeout(poll, 100)
44 | }
45 |
46 | function touchmove(e) {
47 | e.preventDefault()
48 | touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
49 | dragTo(touch.pageX, touch.pageY)
50 | }
51 |
52 | function mousedown(e) {
53 | e.preventDefault()
54 | mouse_dragging = true
55 | }
56 |
57 | function mouseup(e) {
58 | if(mouse_dragging)
59 | {
60 | e.preventDefault()
61 | mouse_dragging = false
62 | stop_motors = true
63 | }
64 | }
65 |
66 | function mousemove(e) {
67 | if(mouse_dragging)
68 | {
69 | e.preventDefault()
70 | dragTo(e.pageX, e.pageY)
71 | }
72 | }
73 |
74 | function dragTo(x, y) {
75 | elm = $('#joystick').offset();
76 | x = x - elm.left;
77 | y = y - elm.top;
78 | w = $('#joystick').width()
79 | h = $('#joystick').height()
80 |
81 | x = (x-w/2.0)/(w/2.0)
82 | y = (y-h/2.0)/(h/2.0)
83 |
84 | if(x < -1) x = -1
85 | if(x > 1) x = 1
86 | if(y < -1) y = -1
87 | if(y > 1) y = 1
88 |
89 | left_motor = Math.round(400*(-y+x))
90 | right_motor = Math.round(400*(-y-x))
91 |
92 | if(left_motor > 400) left_motor = 400
93 | if(left_motor < -400) left_motor = -400
94 |
95 | if(right_motor > 400) right_motor = 400
96 | if(right_motor < -400) right_motor = -400
97 |
98 | stop_motors = false
99 | setMotors(left_motor, right_motor)
100 | }
101 |
102 | function touchend(e) {
103 | e.preventDefault()
104 | stop_motors = true
105 | }
106 |
107 | function setMotors(left, right) {
108 | $("#joystick").html("Motors: " + left + " "+ right)
109 |
110 | if(block_set_motors) return
111 | block_set_motors = true
112 |
113 | $.ajax({url: "motors/"+left+","+right}).done(setMotorsDone)
114 | }
115 |
116 | function setMotorsDone() {
117 | block_set_motors = false
118 | }
119 |
120 | function setLeds() {
121 | led0 = $('#led0')[0].checked ? 1 : 0
122 | led1 = $('#led1')[0].checked ? 1 : 0
123 | led2 = $('#led2')[0].checked ? 1 : 0
124 | $.ajax({url: "leds/"+led0+","+led1+","+led2})
125 | }
126 |
127 | function playNotes() {
128 | notes = $('#notes').val()
129 | $.ajax({url: "play_notes/"+notes})
130 | }
131 |
132 | function shutdown() {
133 | if (confirm("Really shut down the Raspberry Pi?"))
134 | return true
135 | return false
136 | }
137 |
--------------------------------------------------------------------------------
/pi/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Raspberry Pi Robot Control Panel
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Raspberry Pi Robot Control Panel
18 |
19 |
Shutdown...
20 |
21 |
35 |
36 |
37 |
38 | Play
39 |
40 |
41 |
44 |
45 |
46 |
47 | Analog
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Encoders (Romi only)
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/PololuRPiSlave.h:
--------------------------------------------------------------------------------
1 | // Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 |
3 | #pragma once
4 | #include "PololuTWISlave.h"
5 |
6 | /* PololuRPiSlave is an extension of PololuTWISlave that slows down
7 | * communication where necessary to work around the RPi I2C clock
8 | * stretching bug described here:
9 | *
10 | * http://www.advamation.com/knowhow/raspberrypi/rpi-i2c-bug.html
11 | *
12 | * The second template parameter, pi_delay_us, specifies the delay.
13 | * We recommend a value of 10 for an I2C speed of 100 kHz or a value
14 | * of 0 for 400 kHz. However, on the Pi 3, CPU scaling will cause I2C
15 | * to run at half the speed; in this case we recommend values of 20 or
16 | * 5.
17 | *
18 | * Additionally, it implements a system of buffers allowing user code
19 | * and the I2C system to read and write asynchronously from the same
20 | * data, without dictating any particular protocol.
21 | *
22 | * The data size is determined by the template parameter BufferType.
23 | * As described below, we allocate four copies of the buffer. We
24 | * recommend keeping the buffer under 64 bytes.
25 | *
26 | * I2C writes are limited in the code to 16 bytes.
27 | *
28 | * You probably don't have to worry about the details below, since the
29 | * point of this buffering is to make it simple: all you need to do is
30 | * call updateBuffer() before using the buffer, do your writes and
31 | * reads, then call finalizeWrites() when you are done. The I2C
32 | * master can read and write to the same data at any time, and you
33 | * should never encounter inconsistent data unless both sides attempt
34 | * to write to the same region simultaneously.
35 | *
36 | * Buffering details:
37 | *
38 | * The point is that reads and writes involving I2C and user code are
39 | * asynchronous and slow, but we want these slow operations to be
40 | * effectively atomic, so the two sides have to avoid reading and
41 | * writing from the same buffer at the same time.
42 | *
43 | * There is a central buffer (staging_buffer) that is synchronized
44 | * with three other buffers (buffer, buffer_old, i2c_read_buffer) when
45 | * appropriate; I2C reads are done directly from i2c_read_buffer, and
46 | * user code can read and write to "buffer" as desired.
47 | *
48 | * There is also a 16-byte buffer i2c_write_buffer, which stores
49 | * incoming I2C writes until they can be applied.
50 | */
51 |
52 |
53 | template
54 | class PololuRPiSlave: public PololuTWISlave
55 | {
56 | private:
57 | uint8_t index;
58 | bool index_set = 0;
59 | uint8_t i2c_write_length = 0;
60 | uint8_t i2c_write_buffer[16];
61 |
62 | BufferType i2c_read_buffer;
63 | BufferType staging_buffer;
64 | BufferType buffer_old;
65 |
66 | void piDelay()
67 | {
68 | delayMicroseconds(pi_delay_us);
69 | }
70 |
71 | void updateI2CBuffer()
72 | {
73 | memcpy(&i2c_read_buffer, &staging_buffer, sizeof(BufferType));
74 | }
75 |
76 | void finalizeI2CWrites()
77 | {
78 | if(i2c_write_length == 0) return;
79 |
80 | for(uint8_t i=0; i < i2c_write_length; i++)
81 | {
82 | ((uint8_t *)&staging_buffer)[i+index] = i2c_write_buffer[i];
83 | }
84 | i2c_write_length = 0;
85 | }
86 |
87 | public:
88 |
89 | BufferType buffer;
90 |
91 | void updateBuffer()
92 | {
93 | cli();
94 | memcpy(&buffer, &staging_buffer, sizeof(BufferType));
95 | sei();
96 | memcpy(&buffer_old, &buffer, sizeof(BufferType));
97 | }
98 |
99 | void finalizeWrites()
100 | {
101 | uint8_t i;
102 | cli();
103 | for(i=0; i < sizeof(BufferType); i++)
104 | {
105 | if(((uint8_t *)&buffer_old)[i] != ((uint8_t *)&buffer)[i])
106 | ((uint8_t *)&staging_buffer)[i] = ((uint8_t *)&buffer)[i];
107 | }
108 | sei();
109 | }
110 |
111 | virtual void receive(uint8_t b)
112 | {
113 | piDelay();
114 | if(!index_set)
115 | {
116 | updateI2CBuffer();
117 | index = b;
118 | index_set = true;
119 | }
120 | else
121 | {
122 | // Wrap writes at the end of the buffer
123 | if(i2c_write_length > sizeof(i2c_write_buffer))
124 | i2c_write_length = 0;
125 |
126 | // Write the data to the buffer
127 | i2c_write_buffer[i2c_write_length] = b;
128 | i2c_write_length ++;
129 | }
130 | }
131 |
132 | virtual uint8_t transmit()
133 | {
134 | piDelay();
135 | return ((uint8_t *)&i2c_read_buffer)[index++];
136 | }
137 |
138 | virtual void start()
139 | {
140 | piDelay();
141 | index_set = false;
142 | }
143 |
144 | virtual void stop()
145 | {
146 | finalizeI2CWrites();
147 | }
148 |
149 | /* Initialize the slave on a given address. */
150 | void init(uint8_t address)
151 | {
152 | PololuTWISlave::init(address, *this);
153 | }
154 | };
155 |
--------------------------------------------------------------------------------
/src/PololuTWISlave.cpp:
--------------------------------------------------------------------------------
1 | // Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 |
3 | #include "Arduino.h"
4 | #include "PololuTWISlave.h"
5 | #include
6 |
7 | static PololuTWISlave *slave;
8 |
9 | void PololuTWISlave::init(uint8_t address, PololuTWISlave &my_slave)
10 | {
11 | slave = &my_slave;
12 | TWAR = address << 1;
13 | digitalWrite(SDA, 1);
14 | digitalWrite(SCL, 1);
15 | ack();
16 | }
17 |
18 | ISR(TWI_vect)
19 | {
20 | // Handle the TWI event. This function also uses the TWDR register
21 | // to send/receive data, and the TWCR register to handle a bus
22 | // error.
23 | PololuTWISlave::handleEvent(TWSR);
24 |
25 | // I don't think there is a reason to ever NACK unless the master is
26 | // doing something invalid, which is a situation that we don't need
27 | // to support. So, just ACK:
28 | PololuTWISlave::ack();
29 | }
30 |
31 | void PololuTWISlave::ack()
32 | {
33 | TWCR =
34 | (1<start();
63 | break;
64 | case TW_SR_DATA_ACK: // received a data byte and ACKed
65 | slave->receive(TWDR);
66 | break;
67 | case TW_SR_STOP: // A STOP or repeated START
68 | slave->stop();
69 | break;
70 | case TW_SR_DATA_NACK: // received data, NACKed
71 | break;
72 |
73 | /*** Slave transmitter mode ***/
74 | case TW_ST_SLA_ACK: // addressed and ACKed (get ready to transmit the first byte)
75 | case TW_ST_DATA_ACK: // transmitted a byte and got ACK (get ready to transmit the next byte)
76 | TWDR = slave->transmit();
77 | break;
78 | case TW_ST_DATA_NACK: // transmitted a byte and got NACK -> done sending
79 | case TW_ST_LAST_DATA: // transmitted a byte with TWEA=0 - shouldn't happen
80 | break;
81 |
82 | /*** Misc states ***/
83 | case TW_NO_INFO: // not sure how this can happen - TWINT=0 according to datasheet
84 | // ideally we would not change TWCR after this event, but our code will ACK
85 | break;
86 | case TW_BUS_ERROR: // error on the bus - set TWSTO and TWINT and ACK
87 | PololuTWISlave::clearBusError();
88 | break;
89 | }
90 |
91 | return 0; //what should we be returning?
92 | }
93 |
--------------------------------------------------------------------------------
/src/PololuTWISlave.h:
--------------------------------------------------------------------------------
1 | // Copyright Pololu Corporation. For more information, see https://www.pololu.com/
2 |
3 | #pragma once
4 | #include
5 |
6 | /* PololuTWISlave is a basic AVR I2C slave library that is lightweight
7 | * and fast. Unlike the standard Arduino library Wire.h, it does not
8 | * enforce a particular style of buffering the data - you get to
9 | * handle the bytes and events one at a time.
10 | *
11 | * To use this library, inherit from PololuTWISlave and implement the
12 | * four virtual functions that specify how to receive and transmit
13 | * bytes and how to handle the start and stop signals.
14 | *
15 | * The library does not support master mode, general calls, error
16 | * states, and possibly other features of I2C - it only does the
17 | * minimum required to establish communication with a master that we
18 | * control.
19 | */
20 |
21 | class PololuTWISlave
22 | {
23 | public:
24 | /* Methods for a slave to declare. These methods will be called
25 | * from the ISR, with clock stretching used to delay further bus
26 | * activity until they return. */
27 | virtual void receive(uint8_t b) = 0;
28 | virtual uint8_t transmit() = 0;
29 | virtual void start() = 0;
30 | virtual void stop() = 0;
31 |
32 | /* Initialize slave on a specific address; do not respond to general calls. */
33 | static void init(uint8_t address, PololuTWISlave &slave);
34 |
35 | /* Low-level static methods not meant to be called by users. */
36 | static uint8_t handleEvent(uint8_t event);
37 | static void ack();
38 | static void nack();
39 | static void clearBusError();
40 | };
41 |
--------------------------------------------------------------------------------