Now that I have a very basic transmitter I can now make it more useful. With the PPM output fixed at 1.5mS centering and end points at 1.0mS and 2mS I would have to be really lucky if it works with my robots. I really need to be able to adjust the end points so I can adjust the throws on servos and max throttle levels on motors. It is also important to adjust the centering to be able to trim the channels. While I am at it I will also add channel reversing and expo.
To make all these values easy to edit they are all added as constants. The math to adjust the channel output is done on the PyGame Joystick values so are based on values from -1 to 1. This is easier to work on compared to the 1mS to 2mS values of the PPM function. Because negative values need to be handled differently than positive values to calculate the range of movement and expo an if function is used to determine how to handle the joystick value.
To also make things easier to setup in the future I have also added channel mapping as a set of nested lists at the top of the code. Each 2 value list includes a value to determine if the channel is a joystick axis, button or hat as well as the number of that axis, button or hat. The program will look up this list when reading the joystick values to assign the correct axis to the channel.
import pygame import signal import threading import pigpio import ppm ppm_output = [] ppm_output_pin = 18 #Output pin for transmitter module reverse = [1, 1, 1, 1, 1, 1, 1, 1] #set to -1 if each channel is reversed center = [0, 0, 0, 0, 0, 0, 0, 0] #center value for each channel lower_end_point = [-1, -1, -1, -1, -1, -1, -1, -1] #lower end point upper_end_point = [1, 1, 1, 1, 1, 1, 1, 1] #upper end point expo = [1, 1, 1, 1, 1, 1, 1, 1] #expo value. should be between 0.5 and 1 for negative expo and 1 to 2 for positive expo #Channel assignment nested list. There are 8 pairs of values corresponding to each of the 8 channels. #The first value is either an "a" for an axis, "b" for a button or a "h" for a hat. The second value corresponds to which axis, button or hat is selected. channel_assignment = [[a, 3], [a, 4], [a, 1], [a, 0], [a, 5], [a, 2], [b, 0], [b, 2]] pi = pigpio.pi() pi.set_mode(ppm_output_pin, pigpio.OUTPUT) # Define some colors BLACK = ( 0, 0, 0) WHITE = ( 255, 255, 255) if not pi.connected: exit(0) pi.wave_tx_stop() # Start with a clean slate. ppm = ppm.X(pi, ppm_output_pin, frame_ms=20) # This is a simple class that will help us print to the screen # It has nothing to do with the joysticks, just outputting the # information. class TextPrint: def __init__(self): self.reset() self.font = pygame.font.Font(None, 20) def print(self, screen, textString): textBitmap = self.font.render(textString, True, BLACK) screen.blit(textBitmap, [self.x, self.y]) self.y += self.line_height def reset(self): self.x = 10 self.y = 10 self.line_height = 15 def indent(self): self.x += 10 def unindent(self): self.x -= 10 def second_column(self): self.x = 510 self.y = 10 self.line_height = 15 pygame.init() # Set the width and height of the screen [width,height] size = [1000, 700] screen = pygame.display.set_mode(size) pygame.display.set_caption("RC Controller") #Loop until the user clicks the close button. done = False # Used to manage how fast the screen updates clock = pygame.time.Clock() # Initialize the joysticks pygame.joystick.init() # Get ready to print textPrint = TextPrint() # -------- Main Program Loop ----------- while done==False: ppm_joystick_values = [0, 0, 0, 0, 0, 0, 0, 0] #Raw values for the PyGame Joystick function ranging from -1 to 1 ppm_chanels_us = [0, 0, 0, 0, 0, 0, 0, 0] #Processed Joystick values converted to micro-second value to be outputted to the PPM function scaled_channel_value = [0, 0, 0, 0, 0, 0, 0, 0] #A Joystick value that has been scalled according to the reversing, centering, end-point and expo values # EVENT PROCESSING STEP for event in pygame.event.get(): # User did something if event.type == pygame.QUIT: # If user clicked close done=True # Flag that we are done so we exit this loop # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION if event.type == pygame.JOYBUTTONDOWN: print("Joystick button pressed.") if event.type == pygame.JOYBUTTONUP: print("Joystick button released.") # DRAWING STEP # First, clear the screen to white. Don't put other drawing commands # above this, or they will be erased with this command. screen.fill(WHITE) textPrint.reset() # Get count of joysticks joystick_count = pygame.joystick.get_count() textPrint.print(screen, "Number of joysticks: {}".format(joystick_count) ) textPrint.indent() # For each joystick: for i in range(joystick_count): joystick = pygame.joystick.Joystick(i) joystick.init() textPrint.print(screen, "Joystick {}".format(i) ) textPrint.indent() # Get the name from the OS for the controller/joystick name = joystick.get_name() textPrint.print(screen, "Joystick name: {}".format(name) ) # Usually axis run in pairs, up/down for one, and left/right for # the other. axes = joystick.get_numaxes() textPrint.print(screen, "Number of axes: {}".format(axes) ) textPrint.indent() for i in range( axes ): axis = joystick.get_axis( i ) textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) ) textPrint.unindent() buttons = joystick.get_numbuttons() textPrint.print(screen, "Number of buttons: {}".format(buttons) ) textPrint.indent() for i in range( buttons ): button = joystick.get_button( i ) textPrint.print(screen, "Button {:>2} value: {}".format(i,button) ) textPrint.unindent() # Hat switch. All or nothing for direction, not like joysticks. # Value comes back in an array. hats = joystick.get_numhats() textPrint.print(screen, "Number of hats: {}".format(hats) ) textPrint.indent() for i in range( hats ): hat = joystick.get_hat( i ) textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)) ) textPrint.unindent() textPrint.unindent() #Read Joystick values based on the channel mapping for i in range(ppm_joystick_values) if channel_assignment[i][0] = 'a' ppm_joystick_values[i] = joystick.get_axis(channel_assignment[i][1]) if channel_assignment[i][0] = 'b' ppm_joystick_values[i] = joystick.get_button(channel_assignment[i][1]) if channel_assignment[i][0] = 'h' ppm_joystick_values[i] = joystick.get_hat(channel_assignment[i][1]) textPrint.second_column() textPrint.print(screen, "PPM values") textPrint.indent() for i in range(8): textPrint.print(screen, "Channel {}: {}".format(i+1, ppm_joystick_values[i])) textPrint.unindent() textPrint.print(screen, "PPM channels in uS") textPrint.indent() for i in range(8): ppm_joystick_values[i] = ppm_joystick_values[i] * reverse[i] #reverse the channel if needed if ppm_joystick_values[i] < 0: scaled_channel_value[i] = -abs(ppm_joystick_values[i]**expo[i]) * abs(lower_end_point[i] - center[i]) + center[i] #Processes the negative joystick values if ppm_joystick_values[i] > 0: scaled_channel_values[i] = ppm_joystick_value[i]**expo[i] * (upper_end_point[i] - center[i]) + center[i] #Processes the positive joystick values ppm_chanels_us[i] = int(round(1100 + (500 * scaled_channel_value[i]))) textPrint.print(screen, "Chanel {} in uS: {}".format(i+1, ppm_chanels_us[i])) textPrint.unindent() ppm.update_channels(ppm_chanels_us) # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT # Go ahead and update the screen with what we've drawn. pygame.display.flip() # Limit to 20 frames per second clock.tick(20) # Close the window and quit. # If you forget this line, the program will 'hang' # on exit if running from IDLE. pygame.quit ()