From 74a517645425ef76e97846e8ff7f3ba5228176eb Mon Sep 17 00:00:00 2001 From: Sylwester Date: Sun, 13 Oct 2024 13:26:53 +0200 Subject: [PATCH] Add in-progress (not working) PSRAM implementation. --- CMakeLists.txt | 38 ++++----- default.ld.in | 153 +++++++++++++++++++++++++++++------ include/picoadk_hw.h | 1 + src/main.cpp | 184 +++++++++++++++++-------------------------- src/picoadk_hw.cpp | 6 +- tools/simplesub.py | 29 +++++++ 6 files changed, 256 insertions(+), 155 deletions(-) create mode 100644 tools/simplesub.py diff --git a/CMakeLists.txt b/CMakeLists.txt index f576994..7c27d4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,12 +18,12 @@ else() endif() # Define values for the placeholders -set(FLASH_LENGTH 4M) # Example value for FLASH length -set(PSRAM_LENGTH 8M) # Example value for PSRAM length -set(RAM_LENGTH 520K) # Example value for RAM length -set(EEPROM_START 0x1FFF000) # Example EEPROM start address -set(FS_START 0x08080000) # Example file system start address -set(FS_END 0x080C0000) # Example file system end address +set(PICO_FLASH_LENGTH "4194304") # 4MB +set(PICO_EEPROM_START "272621568") +set(FS_START "272621568") +set(FS_END "272621568") +set(RAM_LENGTH "520K") # Example value for RAM length +set(PSRAM_LENGTH "8388608") # 8MB set(DSP_SAMPLE_RATE 48000) @@ -68,7 +68,7 @@ add_executable(main src/picoadk_hw.cpp src/midi_input_usb.cpp src/get_serial.c - src/psram.cpp + #src/psram.cpp lib/FreeRTOS-Kernel/portable/MemMang/heap_3.c ) @@ -140,6 +140,7 @@ target_compile_definitions(main PRIVATE USE_USB_MIDI_HOST=0 SAMPLE_RATE=${DSP_SAMPLE_RATE} RP2350_PSRAM_CS=0 + ) target_compile_definitions(Audio PRIVATE @@ -147,10 +148,10 @@ target_compile_definitions(Audio PRIVATE ) # Configure the linker script with actual values -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/default.ld.in # Input template file - ${CMAKE_CURRENT_BINARY_DIR}/default.ld # Output linker script file - @ONLY # Use only @var@ style placeholders +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/default.ld + COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/tools ${CMAKE_COMMAND} -E env python3 ${CMAKE_CURRENT_SOURCE_DIR}/tools/simplesub.py ${CMAKE_CURRENT_SOURCE_DIR}/default.ld.in ${CMAKE_CURRENT_BINARY_DIR}/default.ld __FLASH_LENGTH__ ${FLASH_LENGTH} __EEPROM_START__ ${EEPROM_START} __FS_START__ ${FS_START} __FS_END__ ${FS_END} __RAM_LENGTH__ ${RAM_LENGTH} __PSRAM_LENGTH__ ${PSRAM_LENGTH} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/default.ld.in ${CMAKE_CURRENT_SOURCE_DIR}/tools/simplesub.py ) # Set the linker script to use the custom default.ld @@ -169,12 +170,13 @@ endforeach() add_compile_options("-Wall" "-Wredundant-decls") -if (PICO_PLATFORM STREQUAL "rp2040") - pico_define_boot_stage2(slower_boot2 ${PICO_DEFAULT_BOOT_STAGE2_FILE}) - target_compile_definitions(slower_boot2 PRIVATE PICO_FLASH_SPI_CLKDIV=4) - pico_set_boot_stage2(main slower_boot2) -else() - message("Skipping slower_boot2 as PICO_PLATFORM is not rp2040") -endif() + +#if (PICO_PLATFORM STREQUAL "rp2040") +# pico_define_boot_stage2(slower_boot2 ${PICO_DEFAULT_BOOT_STAGE2_FILE}) +# target_compile_definitions(slower_boot2 PRIVATE PICO_FLASH_SPI_CLKDIV=4) +# pico_set_boot_stage2(main slower_boot2) +#else() +# message("Skipping slower_boot2 as PICO_PLATFORM is not rp2040") +#endif() pico_add_extra_outputs(main) \ No newline at end of file diff --git a/default.ld.in b/default.ld.in index 83b5bd2..abf993a 100644 --- a/default.ld.in +++ b/default.ld.in @@ -1,15 +1,41 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + + + + MEMORY { - FLASH(rx) : ORIGIN = 0x10000000, LENGTH = @FLASH_LENGTH@ - PSRAM(rwx) : ORIGIN = 0x11000000, LENGTH = @PSRAM_LENGTH@ - RAM(rwx) : ORIGIN = 0x20000000, LENGTH = @RAM_LENGTH@ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = __FLASH_LENGTH__ + PSRAM(rwx) : ORIGIN = 0x11000000, LENGTH = __PSRAM_LENGTH__ + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = __RAM_LENGTH__ SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k } -PROVIDE ( _EEPROM_start = @EEPROM_START@ ); -PROVIDE ( _FS_start = @FS_START@ ); -PROVIDE ( _FS_end = @FS_END@ ); +PROVIDE ( _EEPROM_start = __EEPROM_START__ ); +PROVIDE ( _FS_start = __FS_START__ ); +PROVIDE ( _FS_end = __FS_END__ ); ENTRY(_entry_point) @@ -20,18 +46,29 @@ SECTIONS } > FLASH .ota : { + /* Start image with OTA */ KEEP (*(.OTA)) *ota.o } > FLASH .partition : { . = __flash_binary_start + 0x2ff0; - LONG(@FS_START@) - LONG(@FS_END@) - LONG(@EEPROM_START@) - LONG(@FLASH_LENGTH@) + LONG(__FS_START__) + LONG(__FS_END__) + LONG(__EEPROM_START__) + LONG(__FLASH_LENGTH__) } > FLASH + /* The bootrom will enter the image at the point indicated in your + IMAGE_DEF, which is usually the reset handler of your vector table. + + The debugger will use the ELF entry point, which is the _entry_point + symbol, and in our case is *different from the bootrom's entry point.* + This is used to go back through the bootrom on debugger launches only, + to perform the same initial flash setup that would be performed on a + cold boot. + */ + .text : { KEEP (*(.vectors)) __logical_binary_start = .; @@ -40,39 +77,72 @@ SECTIONS KEEP (*(.embedded_block)) __embedded_block_end = .; KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ *(.init) *libgcc.a:cmse_nonsecure_call.o *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) *(.fini) + /* Pull all c'tors into .text */ *crtbegin.o(.ctors) *crtbegin?.o(.ctors) *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) *(SORT(.ctors.*)) *(.ctors) + /* Followed by destructors */ *crtbegin.o(.dtors) *crtbegin?.o(.dtors) *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) *(SORT(.dtors.*)) *(.dtors) + . = ALIGN(4); + /* preinit data */ PROVIDE_HIDDEN (__preinit_array_start = .); KEEP(*(SORT(.preinit_array.*))) KEEP(*(.preinit_array)) PROVIDE_HIDDEN (__preinit_array_end = .); + . = ALIGN(4); + /* init data */ PROVIDE_HIDDEN (__init_array_start = .); KEEP(*(SORT(.init_array.*))) KEEP(*(.init_array)) PROVIDE_HIDDEN (__init_array_end = .); + . = ALIGN(4); + /* finit data */ PROVIDE_HIDDEN (__fini_array_start = .); *(SORT(.fini_array.*)) *(.fini_array) PROVIDE_HIDDEN (__fini_array_end = .); + *(.eh_frame*) . = ALIGN(4); } > FLASH + /* Note the boot2 section is optional, and should be discarded if there is + no reference to it *inside* the binary, as it is not called by the + bootrom. (The bootrom performs a simple best-effort XIP setup and + leaves it to the binary to do anything more sophisticated.) However + there is still a size limit of 256 bytes, to ensure the boot2 can be + stored in boot RAM. + + Really this is a "XIP setup function" -- the name boot2 is historic and + refers to its dual-purpose on RP2040, where it also handled vectoring + from the bootrom into the user image. + */ +/* + .boot2 : { + __boot2_start__ = .; + *(.boot2) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ <= 256, + "ERROR: Pico second stage bootloader must be no more than 256 bytes in size") +*/ .rodata : { *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) *(.srodata*) @@ -81,30 +151,34 @@ SECTIONS . = ALIGN(4); } > FLASH - .ARM.extab : { + .ARM.extab : + { *(.ARM.extab* .gnu.linkonce.armextab.*) } > FLASH __exidx_start = .; - .ARM.exidx : { + .ARM.exidx : + { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } > FLASH __exidx_end = .; + /* Machine inspectable binary information */ . = ALIGN(4); __binary_info_start = .; - .binary_info : { + .binary_info : + { KEEP(*(.binary_info.keep.*)) *(.binary_info.*) } > FLASH __binary_info_end = .; . = ALIGN(4); - .ram_vector_table (NOLOAD) : { + .ram_vector_table (NOLOAD): { *(.ram_vector_table) } > RAM - .uninitialized_data (NOLOAD) : { + .uninitialized_data (NOLOAD): { . = ALIGN(4); *(.uninitialized_data*) } > RAM @@ -112,30 +186,40 @@ SECTIONS .data : { __data_start__ = .; *(vtable) + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ *(.text*) . = ALIGN(4); *(.rodata*) . = ALIGN(4); + *(.data*) *(.sdata*) + . = ALIGN(4); *(.after_data.*) . = ALIGN(4); + /* preinit data */ PROVIDE_HIDDEN (__mutex_array_start = .); KEEP(*(SORT(.mutex_array.*))) KEEP(*(.mutex_array)) PROVIDE_HIDDEN (__mutex_array_end = .); + *(.jcr) . = ALIGN(4); } > RAM AT> FLASH .tdata : { . = ALIGN(4); - *(.tdata .tdata.* .gnu.linkonce.td.*) + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ __tdata_end = .; } > RAM AT> FLASH PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ __etext = LOADADDR(.data); .tbss (NOLOAD) : { @@ -144,12 +228,14 @@ SECTIONS __tls_base = .; *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) + __tls_end = .; } > RAM .bss (NOLOAD) : { . = ALIGN(4); __tbss_end = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) *(COMMON) PROVIDE(__global_pointer$ = . + 2K); @@ -158,14 +244,18 @@ SECTIONS __bss_end__ = .; } > RAM - .heap (NOLOAD) : { + .heap (NOLOAD): + { __end__ = .; end = __end__; KEEP(*(.heap*)) - . = ALIGN(4); + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + . = ORIGIN(RAM) + LENGTH(RAM); __HeapLimit = .; } > RAM + /* Start and end symbols must be word-aligned */ .scratch_x : { __scratch_x_start__ = .; *(.scratch_x.*) @@ -182,11 +272,21 @@ SECTIONS } > SCRATCH_Y AT > FLASH __scratch_y_source__ = LOADADDR(.scratch_y); - .stack1_dummy (NOLOAD) : { + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { *(.stack1*) } > SCRATCH_X - - .stack_dummy (NOLOAD) : { + .stack_dummy (NOLOAD): + { KEEP(*(.stack*)) } > SCRATCH_Y @@ -202,6 +302,7 @@ SECTIONS __psram_heap_start__ = .; } > PSRAM + /* stack limit is poorly named, but historically is maximum heap ptr */ __StackLimit = ORIGIN(RAM) + LENGTH(RAM); __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); @@ -209,19 +310,27 @@ SECTIONS __StackBottom = __StackTop - SIZEOF(.stack_dummy); PROVIDE(__stack = __StackTop); + /* picolibc and LLVM */ PROVIDE (__heap_start = __end__); PROVIDE (__heap_end = __HeapLimit); PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + /* TLSF */ PROVIDE (__psram_start = __psram_start__); PROVIDE (__psram_heap_start = __psram_heap_start__); + /* llvm-libc */ PROVIDE (_end = __end__); PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + /* Check if data + heap + stack exceeds RAM limit */ ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary") ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary") -} \ No newline at end of file + + /* todo assert on extra code */ +} + diff --git a/include/picoadk_hw.h b/include/picoadk_hw.h index 111de35..9ea7e2f 100644 --- a/include/picoadk_hw.h +++ b/include/picoadk_hw.h @@ -16,6 +16,7 @@ #include "hardware/vreg.h" #include "hardware/structs/rosc.h" #include "helper.h" +#include "psram.h" #if __has_include("bsp/board_api.h") #include "bsp/board_api.h" diff --git a/src/main.cpp b/src/main.cpp index 9daeaf6..0b5fd37 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,7 @@ #include #include "project_config.h" +#include "hardware/uart.h" +#include "pico/stdlib.h" // Reverse compatibility with old PicoSDK. #if __has_include("bsp/board_api.h") @@ -12,14 +14,12 @@ #include "audio_subsystem.h" #include "picoadk_hw.h" - #include "FreeRTOS.h" #include #include #include "arduino_compat.h" -// Audio Buffer (Size is set in lib/audio/include/audio_subsystem.h) audio_buffer_pool_t *audio_pool; #include "Heavy_prog.hpp" @@ -27,7 +27,6 @@ Heavy_prog pd_prog(SAMPLE_RATE); float smp[2]; - #if (USE_USB_MIDI_HOST == 1) static uint8_t midi_dev_addr = 0; #endif @@ -59,135 +58,96 @@ MIDIInputUSB usbMIDI; #define MIDI_RT_ACTIVESENSE 0xFE #define MIDI_RT_RESET 0xFF -// TODO - implement MIDI CLOCK -// - Pitch bend -// - Aftertouch -// - Program Change - #ifdef __cplusplus extern "C" { #endif - /** - * Task to handle USB MIDI input processing. - * - * @param pvParameters Unused task parameters - */ - void usb_midi_task(void *pvParameters) - { - // Setup MIDI Callbacks using lambdas - usbMIDI.setCCCallback([](uint8_t cc, uint8_t value, uint8_t channel) { - // Handle Control Change (CC) event - pd_prog.sendMessageToReceiverV(HV_HASH_CTLIN, 0, "fff", (float)value, (float)cc, (float)channel); - }); - - usbMIDI.setNoteOnCallback([](uint8_t note, uint8_t velocity, uint8_t channel) { - if (velocity > 0) - { - pd_prog.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", (float)note, (float)velocity, (float)channel); - // Handle Note On event - } - else - { - pd_prog.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", (float)note, (float)velocity, (float)channel); - // Treat zero velocity as Note Off - } - }); - - usbMIDI.setNoteOffCallback([](uint8_t note, uint8_t velocity, uint8_t channel) { +void usb_midi_task(void *pvParameters) { + usbMIDI.setCCCallback([](uint8_t cc, uint8_t value, uint8_t channel) { + pd_prog.sendMessageToReceiverV(HV_HASH_CTLIN, 0, "fff", (float)value, (float)cc, (float)channel); + }); + + usbMIDI.setNoteOnCallback([](uint8_t note, uint8_t velocity, uint8_t channel) { + if (velocity > 0) { + pd_prog.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", (float)note, (float)velocity, (float)channel); + } else { pd_prog.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", (float)note, (float)velocity, (float)channel); - // Handle Note Off event - }); - - while (1) - { - // TinyUSB Device Task - #if (USE_USB_MIDI_HOST == 1) - tuh_task(); - #else - tud_task(); - #endif - usbMIDI.process(); } + }); + + usbMIDI.setNoteOffCallback([](uint8_t note, uint8_t velocity, uint8_t channel) { + pd_prog.sendMessageToReceiverV(HV_HASH_NOTEIN, 0, "fff", (float)note, (float)velocity, (float)channel); + }); + + while (1) { + #if (USE_USB_MIDI_HOST == 1) + tuh_task(); + #else + tud_task(); + #endif + usbMIDI.process(); } +} - /** - * Task to blink an LED on GPIO pin 2. - * - * @param pvParameters Unused task parameters - */ - void blinker_task(void *pvParameters) - { - gpio_init(2); - gpio_set_dir(2, GPIO_OUT); - - while (1) - { - gpio_put(2, 1); - vTaskDelay(pdMS_TO_TICKS(100)); // Delay for 100ms - gpio_put(2, 0); - vTaskDelay(pdMS_TO_TICKS(100)); // Delay for 100ms - } - } +void blinker_task(void *pvParameters) { + gpio_init(2); + gpio_set_dir(2, GPIO_OUT); - /** - * Main entry point. - */ - int main(void) - { - // Initialize hardware - picoadk_init(); + while (1) { + gpio_put(2, 1); + vTaskDelay(pdMS_TO_TICKS(100)); // Delay for 100ms + gpio_put(2, 0); + vTaskDelay(pdMS_TO_TICKS(100)); // Delay for 100ms + } +} - // Initialize DSP engine (if needed) - +int main(void) { + // Initialize hardware + picoadk_init(); - // Initialize the audio subsystem - audio_pool = init_audio(); + // Initialize UART0 + //uart_init(uart0, 115200); + //gpio_set_function(12, GPIO_FUNC_UART); + //gpio_set_function(13, GPIO_FUNC_UART); - // Create FreeRTOS tasks for MIDI handling and LED blinking - xTaskCreate(usb_midi_task, "USB_MIDI_Task", 4096, NULL, configMAX_PRIORITIES, NULL); - xTaskCreate(blinker_task, "Blinker_Task", 128, NULL, configMAX_PRIORITIES - 1, NULL); + //stdio_init_all(); - // Start the FreeRTOS scheduler - vTaskStartScheduler(); + // Initialize DSP engine (if needed) + + // Initialize the audio subsystem + //audio_pool = init_audio(); - // Idle loop (this is fine for Cortex-M33) - while (1) - { - __wfi(); - } - } + // Create FreeRTOS tasks for MIDI handling and LED blinking + //xTaskCreate(usb_midi_task, "USB_MIDI_Task", 4096, NULL, configMAX_PRIORITIES, NULL); + //xTaskCreate(blinker_task, "Blinker_Task", 128, NULL, configMAX_PRIORITIES - 1, NULL); - /** - * I2S audio callback for filling the audio buffer with samples. - * - * This function is called at a fixed rate determined by the audio subsystem - * and must return within the interval between calls to avoid audio glitches. - */ - void __not_in_flash_func(i2s_callback_func()) - { - audio_buffer_t *buffer = take_audio_buffer(audio_pool, false); - if (buffer == NULL) - { - return; - } + // Start the FreeRTOS scheduler + //vTaskStartScheduler(); - int32_t *samples = (int32_t *)buffer->buffer->bytes; + // Idle loop (this is fine for Cortex-M33) + while (1) { + // __wfi(); + } +} - // Fill buffer with 32-bit samples (stereo, 2 channels) - for (uint i = 0; i < buffer->max_sample_count; i++) - { - pd_prog.processInlineInterleaved(smp, smp, 1); +void __not_in_flash_func(i2s_callback_func()) { + audio_buffer_t *buffer = take_audio_buffer(audio_pool, false); + if (buffer == NULL) { + return; + } - samples[i * 2 + 0] = float_to_int32(smp[0]); // Left channel sample - samples[i * 2 + 1] = float_to_int32(smp[1]); // Right channel sample - // Use your DSP function here for generating the audio samples - } + int32_t *samples = (int32_t *)buffer->buffer->bytes; - buffer->sample_count = buffer->max_sample_count; - give_audio_buffer(audio_pool, buffer); + for (uint i = 0; i < buffer->max_sample_count; i++) { + pd_prog.processInlineInterleaved(smp, smp, 1); + samples[i * 2 + 0] = float_to_int32(smp[0]); + samples[i * 2 + 1] = float_to_int32(smp[1]); } + buffer->sample_count = buffer->max_sample_count; + give_audio_buffer(audio_pool, buffer); +} + #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/src/picoadk_hw.cpp b/src/picoadk_hw.cpp index b55492c..4b6f5c4 100644 --- a/src/picoadk_hw.cpp +++ b/src/picoadk_hw.cpp @@ -3,9 +3,9 @@ void picoadk_init() { - vreg_set_voltage(VREG_VOLTAGE_1_30); - sleep_ms(1); - set_sys_clock_khz(300000, true); + //vreg_set_voltage(VREG_VOLTAGE_1_30); + //sleep_ms(1); + //set_sys_clock_khz(300000, true); // Initialize TinyUSB board_init(); diff --git a/tools/simplesub.py b/tools/simplesub.py new file mode 100644 index 0000000..9fd18d1 --- /dev/null +++ b/tools/simplesub.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse +import time + +def main(): + parser = argparse.ArgumentParser(description='Simple text substitution') + parser.add_argument('-i', '--input', action='store', required=True, help='Path to the source file') + parser.add_argument('-o', '--out', action='store', required=True, help='Path to the output file') + parser.add_argument('-s', '--sub', action='append', nargs=2, metavar=('find', 'replace'), required=True, help='Substition') + args = parser.parse_args() + + with open(args.input, "r") as fin: + data = fin.read() + + for f, r in args.sub: + data = re.sub(f, r, data) + + with open(args.out, "w") as fout: + fout.write(data) + + +main() +