Skip to content
Alexander Crain edited this page Sep 9, 2023 · 9 revisions

Overview

This Wiki entry will cover the Python script that is currently (as of RC.4) being used to control the thrusters on all three platforms. The script is broken into distinct components:

  • Importing the requisite libraries.
  • Setting up the GPIO pins.
  • Setting up the UDP receive functionality.
  • Initializing parameters and variables for the PWM.
  • Receiving and converting the desired duty cycle into a PWM signal at the desired frequency.
  • Shutting down the GPIOs and UDP when the script is terminated or when it fails due to an error.

Importing Libraries

The Python script relies on the use of either the Jetson.GPIO or RPi.GPIO libraries (for our purposes they are equivalent). The import section of the code is as follows:

import Jetson.GPIO as GPIO
import socket
import struct
import time
import signal

The various libraries provide the following functionalities:

  • Jetson.GPIO: This library was developed by NVIDIA and can be used for GPIO control. It also has hardware PWM capabilities for boards that support it, though the NVIDIA Jetson Xavier NX only has 2 hardware PWM available - which is not enough for 8 thrusters. The library is installed by default and can be found here.

  • socket: This library handles network communication.

  • struct: This library is used for packing and unpacking binary data.

  • signal: This library is to handle signals (i.e. what to do when the code completes or fails).

    Define the 8 pins you want to use.

    PINS = [7, 12, 13, 15, 16, 18, 22, 23]

    Set up the GPIO library

    GPIO.setmode(GPIO.BOARD) GPIO.setwarnings(False)

    for pin in PINS: GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.LOW)

    UDP setup

    IP_ADDRESS = '127.0.0.1' PORT = 48291 NUM_DOUBLES = 10

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_address = (IP_ADDRESS, PORT) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(server_address) server_socket.setblocking(0)

    data = b''

    def signal_handler(sig, frame): raise KeyboardInterrupt

    signal.signal(signal.SIGTERM, signal_handler)

    SAFETY_BIT = 568471 duty_cycles = [0] * len(PINS) pwm_frequency = 50 # Default to 50Hz

    Initialize timestamps and states for each pin

    next_toggle_time = [0] * len(PINS) pin_states = [GPIO.LOW] * len(PINS)

    try: while True: current_time = time.time()

          for i, pin in enumerate(PINS):
              if current_time >= next_toggle_time[i]:
                  if pin_states[i] == GPIO.LOW:
                      pin_states[i] = GPIO.HIGH
                      next_toggle_time[i] = current_time + (duty_cycles[i] / 100) * (1 / pwm_frequency)
                  else:
                      pin_states[i] = GPIO.LOW
                      next_toggle_time[i] = current_time + (1 - duty_cycles[i] / 100) * (1 / pwm_frequency)
                  
                  GPIO.output(pin, pin_states[i])
    
          # Check for new data
          try:
              more_data, client_address = server_socket.recvfrom(NUM_DOUBLES * 8 - len(data))
              if more_data:
                  data += more_data
              if len(data) == NUM_DOUBLES * 8:
                  doubles = struct.unpack('d' * NUM_DOUBLES, data)
                  if int(doubles[0]) == SAFETY_BIT:
                      pwm_frequency = doubles[1]
                      duty_cycles = doubles[2:10]
                  data = b''
          except BlockingIOError:
              pass
    

    except KeyboardInterrupt: GPIO.output(PINS[0],GPIO.LOW) GPIO.output(PINS[1],GPIO.LOW) GPIO.output(PINS[2],GPIO.LOW) GPIO.output(PINS[3],GPIO.LOW) GPIO.output(PINS[4],GPIO.LOW) GPIO.output(PINS[5],GPIO.LOW) GPIO.output(PINS[6],GPIO.LOW) GPIO.output(PINS[7],GPIO.LOW) GPIO.cleanup() server_socket.close() print("\nExiting...")

    finally: GPIO.output(PINS[0],GPIO.LOW) GPIO.output(PINS[1],GPIO.LOW) GPIO.output(PINS[2],GPIO.LOW) GPIO.output(PINS[3],GPIO.LOW) GPIO.output(PINS[4],GPIO.LOW) GPIO.output(PINS[5],GPIO.LOW) GPIO.output(PINS[6],GPIO.LOW) GPIO.output(PINS[7],GPIO.LOW) GPIO.cleanup() server_socket.close()

Key Components: RPi.GPIO: A library to control the Raspberry Pi's GPIO pins. socket: A library to handle network communication. struct: A library used for packing and unpacking binary data. signal: A library to handle signals. Details: GPIO Setup: The GPIO pins defined in the PINS list are initialized for output. All pins are set to a LOW state initially. UDP Setup: The UDP server is set up on IP address 127.0.0.1 and port 48291. The server is configured to reuse the same address and set to non-blocking mode. Signal Handling: A signal handler is set up to catch the termination signal (SIGTERM). This ensures a safe cleanup in case the script is terminated externally. Main Loop: The script continuously checks the current time and compares it to the next toggle time for each GPIO pin. Depending on the duty cycle, it toggles the GPIO pin's state. The script listens for incoming UDP packets. When a packet is received, it checks for a safety bit to ensure the data's validity. If the safety bit matches, the script updates the PWM frequency and duty cycles for the pins. Cleanup: On receiving a keyboard interrupt (Ctrl+C), or at the end of the script execution, all GPIO pins are set to a LOW state, and the GPIO library and server socket are cleaned up. Usage: This script can be run on a Raspberry Pi to set up a UDP server that listens for commands to control the GPIO pins. External devices or software can send UDP packets with the appropriate structure to control the state of the GPIO pins and set PWM frequencies and duty cycles.

Note: Before deploying this script in a production environment, consider adding error handling and validation mechanisms to ensure the safety and reliability of the system.

Clone this wiki locally