PX4-Autopilot/Tools/module_config/output_groups_from_timer_config.py
Beat Küng 916447e804 params: add script to parse timer_config.cpp for timer group info
This allows to generate pwm params in the form of:
module_name: PWM Output
actuator_output:
  output_groups:
    - generator: pwm
      param_prefix: PWM_FMU
      channel_labels: ['PWM Main', 'PWM Capture']
      standard_params:
        disarmed: { min: 800, max: 2200, default: 900 }
        min: { min: 800, max: 1400, default: 1000 }
        max: { min: 1600, max: 2200, default: 2000 }
        failsafe: { min: 800, max: 2200 }
      extra_function_groups: [ pwm_fmu ]
      pwm_timer_param:
        description:
            short: Output Protocol Configuration for ${label}
            long: |
                Select which Output Protocol to use for outputs ${label}.

                Custom PWM rates can be used by directly setting any value >0.
        type: enum
        default: 400
        values:
            -5: DShot150
            -4: DShot300
            -3: DShot600
            -2: DShot1200
            -1: OneShot
            50: PWM50
            100: PWM100
            200: PWM200
            400: PWM400
        reboot_required: true
2021-10-18 18:45:19 -04:00

203 lines
7.7 KiB
Python
Executable File

#!/usr/bin/env python3
""" Script to parse board-specific timer_config.cpp and print the output groups
and timer config params to stdout
"""
import argparse
import os
import sys
import re
from itertools import groupby
from copy import deepcopy
def find_matching_brackets(brackets, s, verbose):
idx = 0
opening = 0
first_open = -1
while idx < len(s):
if s[idx] == brackets[0]:
opening += 1
if first_open == -1:
first_open = idx
if s[idx] == brackets[1]:
opening -= 1
if opening == 0:
if verbose: print(first_open, idx, s[first_open:idx+1])
return first_open+1, idx
idx += 1
raise Exception('Failed to find opening/closing brackets in {:}'.format(s))
def extract_timer(line):
# Try format: initIOTimer(Timer::Timer5, DMA{DMA::Index1, DMA::Stream0, DMA::Channel6}),
search = re.search('Timer::([0-9a-zA-Z_]+)[,\)]', line, re.IGNORECASE)
if search:
return search.group(1)
# nxp rt1062 format: initIOPWM(PWM::FlexPWM2),
search = re.search('PWM::Flex([0-9a-zA-Z_]+)[,\)]', line, re.IGNORECASE)
if search:
return search.group(1)
return None
def extract_timer_from_channel(line):
# Try format: initIOTimerChannel(io_timers, {Timer::Timer5, Timer::Channel1}, {GPIO::PortA, GPIO::Pin0}),
search = re.search('Timer::([0-9a-zA-Z_]+), ', line, re.IGNORECASE)
if search:
return search.group(1)
# nxp rt1062 format: initIOTimerChannel(io_timers, {PWM::PWM2_PWM_A, PWM::Submodule0}, IOMUX::Pad::GPIO_B0_06),
search = re.search('PWM::(PWM[0-9]+)[_,\)]', line, re.IGNORECASE)
if search:
return search.group(1)
return None
def get_timer_groups(timer_config_file, verbose=False):
with open(timer_config_file, 'r') as f:
timer_config = f.read()
# timers
dshot_support = {} # key: timer
timers_start_marker = 'io_timers_t io_timers'
timers_start = timer_config.find(timers_start_marker)
if timers_start == -1:
raise Exception('"{:}" not found in {:}'.format(timers_start_marker, timer_config_file))
timer_config = timer_config[timers_start:]
open_idx, close_idx = find_matching_brackets(('{', '}'), timer_config, verbose)
timers = timer_config[open_idx:close_idx]
for line in timers.splitlines():
line = line.strip()
if len(line) == 0 or line.startswith('//'):
continue
timer = extract_timer(line)
if timer:
if verbose: print('found timer def: {:}'.format(timer))
dshot_support[timer] = 'DMA' in line
else:
# Make sure we don't miss anything (e.g. for different syntax) or misparse (e.g. multi-line comments)
raise Exception('Unparsed timer in line: {:}'.format(line))
# channels
channels_start_marker = 'timer_io_channels_t timer_io_channels'
channels_start = timer_config.find(channels_start_marker)
if channels_start == -1:
raise Exception('"{:}" not found in {:}'.format(channels_start_marker, timer_config_file))
timer_config = timer_config[channels_start:]
open_idx, close_idx = find_matching_brackets(('{', '}'), timer_config, verbose)
channels = timer_config[open_idx:close_idx]
channel_timers = []
channel_types = []
for line in channels.splitlines():
line = line.strip()
if len(line) == 0 or line.startswith('//'):
continue
if verbose: print('--'+line+'--')
timer = extract_timer_from_channel(line)
if timer:
if verbose: print('Found timer: {:} in channel line {:}'.format(timer, line))
channel_types.append('cap' if 'capture' in line.lower() else 'pwm')
channel_timers.append(timer)
else:
# Make sure we don't miss anything (e.g. for different syntax) or misparse (e.g. multi-line comments)
raise Exception('Unparsed channel in line: {:}'.format(line))
if len(channel_timers) == 0:
raise Exception('No channels found in "{:}"'.format(channels))
groups = [(len(list(g)), dshot_support[k]) for k, g in groupby(channel_timers)]
outputs = {
'types': channel_types,
'groups': groups
}
return outputs
def get_output_groups(timer_groups, param_prefix="PWM_MAIN",
channel_labels=["PWM Main", "PWM Capture"],
standard_params=[],
extra_function_groups=[], pwm_timer_param=None,
verbose=False):
""" convert the timer groups into an output_groups section of module.yaml
and extra timer params
"""
instance_start = 1
output_groups = []
timer_params = {}
timer_index = 0
instance_start_label = [ 1, 1 ]
for group_count, dshot_support in timer_groups['groups']:
# check for capture vs normal pins for the label
types = timer_groups['types'][instance_start-1:instance_start+group_count-1]
if not all(types[0] == t for t in types):
# Should this ever be needed, we can extend this script to handle that
raise Exception('Implementation requires all channel types for a timer to be equal (types: {:})'.format(types))
if types[0] == 'pwm':
channel_type_idx = 0
elif types[0] == 'cap':
channel_type_idx = 1
else:
raise Exception('unsupported channel type: {:}'.format(types[0]))
channel_label = channel_labels[channel_type_idx]
channel_type_instance = instance_start_label[channel_type_idx]
group = {
'param_prefix': param_prefix,
'channel_label': channel_label,
'instance_start': instance_start,
'instance_start_label': channel_type_instance,
'extra_function_groups': deepcopy(extra_function_groups),
'num_channels': group_count,
'standard_params': deepcopy(standard_params),
}
output_groups.append(group)
if pwm_timer_param is not None:
timer_channels_label = channel_label + ' ' + str(channel_type_instance)
if group_count > 1:
timer_channels_label += '-' + str(channel_type_instance+group_count-1)
pwm_timer_param_cp = deepcopy(pwm_timer_param)
if not dshot_support:
# remove dshot entries if no dshot support
values = pwm_timer_param_cp['values']
for key in list(values.keys()):
if 'dshot' in values[key].lower():
del values[key]
for descr_type in ['short', 'long']:
descr = pwm_timer_param_cp['description'][descr_type]
pwm_timer_param_cp['description'][descr_type] = \
descr.replace('${label}', timer_channels_label)
timer_params[param_prefix+'_TIM'+str(timer_index)] = pwm_timer_param_cp
instance_start += group_count
instance_start_label[channel_type_idx] += group_count
timer_index += 1
return (output_groups, timer_params)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Extract output groups from timer_config.cpp')
parser.add_argument('--timer-config', type=str, action='store',
help='timer_config.cpp file', required=True)
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
help='Verbose Output')
args = parser.parse_args()
verbose = args.verbose
timer_groups = get_timer_groups(args.timer_config, verbose)
print('timer groups: {:}'.format(timer_groups))
output_groups, timer_params = get_output_groups(timer_groups, verbose=verbose)
print('output groups: {:}'.format(output_groups))
print('timer params: {:}'.format(timer_params))