├── .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 I2C communication with 11 | a Raspberry Pi, with the Arduino acting as the I2C 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 I2C library, Wire.h: 26 | 27 | * Wire.h just gives you access to raw I2C 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 | I2C 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 I2C 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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Buttons
LEDs
35 | 36 |

37 | 38 | 39 |

40 | 41 | 42 | 43 |
Battery mV
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
Analog
55 | 56 | 57 | 58 | 59 | 60 | 61 |
Encoders (Romi only)
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 | --------------------------------------------------------------------------------