From 941e988ab018f98c9371f398c16236c5115cde4e Mon Sep 17 00:00:00 2001 From: loudnl Date: Thu, 19 Sep 2024 16:48:16 +0200 Subject: [PATCH] 0.0.1-ALPHA --- CMakeLists.txt | 135 +++++++++ src/asid.c | 97 +++++++ src/asid.h | 79 ++++++ src/clock.pio | 51 ++++ src/gpio.c | 180 ++++++++++++ src/gpio.h | 136 +++++++++ src/mcu.c | 61 +++++ src/mcu.h | 47 ++++ src/midi.c | 306 +++++++++++++++++++++ src/midi.h | 87 ++++++ src/sid.h | 213 +++++++++++++++ src/tusb_config.h | 132 +++++++++ src/usb_descriptors.c | 269 ++++++++++++++++++ src/usbsid.c | 623 ++++++++++++++++++++++++++++++++++++++++++ src/usbsid.h | 461 +++++++++++++++++++++++++++++++ src/ws2812.pio | 71 +++++ 16 files changed, 2948 insertions(+) create mode 100755 CMakeLists.txt create mode 100644 src/asid.c create mode 100644 src/asid.h create mode 100644 src/clock.pio create mode 100644 src/gpio.c create mode 100644 src/gpio.h create mode 100644 src/mcu.c create mode 100644 src/mcu.h create mode 100644 src/midi.c create mode 100644 src/midi.h create mode 100644 src/sid.h create mode 100644 src/tusb_config.h create mode 100644 src/usb_descriptors.c create mode 100644 src/usbsid.c create mode 100644 src/usbsid.h create mode 100644 src/ws2812.pio diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..2e6c3d8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,135 @@ +#### +# USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two +# MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, +# phone or ASID supporting player +# +# CMakeLists.txt +# This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) +# File author: LouD +# +# Copyright (c) 2024 LouD +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +#### +cmake_minimum_required(VERSION 3.17) + +set(PROJECT_NAME usbsidpico) # projectname +set(PROJECT_VERSION "0.0.1-ALPHA") +set(CMAKE_BUILD_TYPE Debug) # build with debug symbols +set(TINYUSB_PATH ${PICO_SDK_PATH}/lib/tinyusb) + +### Executables +list(APPEND FILENAMES + ${PROJECT_NAME}-singlesid + ${PROJECT_NAME}-singleskpico + ${PROJECT_NAME}-dualsid + ${PROJECT_NAME}-skpico-singlesid + ${PROJECT_NAME}-dualskpico + ${PROJECT_NAME}-dualskpico-mixed) +list(APPEND SIDTYPES 0 1 2 3 4 5) + +# default environment options +set(SOURCEFILES + ${CMAKE_CURRENT_LIST_DIR}/src/usbsid.c + ${CMAKE_CURRENT_LIST_DIR}/src/gpio.c + ${CMAKE_CURRENT_LIST_DIR}/src/midi.c + ${CMAKE_CURRENT_LIST_DIR}/src/asid.c + ${CMAKE_CURRENT_LIST_DIR}/src/mcu.c + ${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c) +# COMPILE_OPTS -Wall -Werror -Wno-maybe-uninitialized -save-temps -fverbose-asm) +set(COMPILE_OPTS PRIVATE -Wno-maybe-uninitialized -fverbose-asm) +set(TARGET_LL + pico_stdlib + pico_unique_id + pico_multicore + pico_stdio_usb + pico_usb_reset_interface + tinyusb_device + tinyusb_board + hardware_clocks + hardware_resets + hardware_uart + hardware_pwm + hardware_pio) +set(PIO ${CMAKE_CURRENT_LIST_DIR}/src/clock.pio) +set(PIO_RGB ${CMAKE_CURRENT_LIST_DIR}/src/ws2812.pio) +set(TARGET_INCLUDE_DIRS PRIVATE + . + src + $ENV{PICO_SDK_PATH}/lib/tinyusb/hw + $ENV{PICO_SDK_PATH}/lib/tinyusb/src) + +# rp2040 sdk +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) +include($ENV{PICO_EXTRAS_PATH}/external/pico_extras_import.cmake) + +# project +project(${PROJECT_NAME} C CXX ASM) + +# init sdk +pico_sdk_init() + +include_directories(".") + +foreach(FILENAME SIDTYPE IN ZIP_LISTS FILENAMES SIDTYPES) + # set filename + set(BUILD ${FILENAME}) + message(STATUS "Building ${FILENAME} with SIDTYPE=${SIDTYPE}") + # executable + add_executable(${BUILD} ${SOURCEFILES}) + # pio addition + pico_generate_pio_header(${BUILD} ${PIO}) + # source files to compile + target_sources(${BUILD} PUBLIC ${SOURCEFILES}) + target_compile_options(${BUILD} ${COMPILE_OPTS}) + target_compile_definitions(${BUILD} PRIVATE USBSID SIDTYPES=${SIDTYPE}) + # tell the linker what libraries to link + target_link_libraries(${BUILD} ${TARGET_LL}) + # target sid types + target_include_directories(${BUILD} ${TARGET_INCLUDE_DIRS}) + pico_set_program_name(${BUILD} "USBSIDPico") + pico_set_program_version(${BUILD} $PROJECT_VERSION) + # create map/bin/hex/uf2 file in addition to ELF. + pico_add_extra_outputs(${BUILD}) + # enable uart output, disable usb output + pico_enable_stdio_uart(${BUILD} 1) + pico_enable_stdio_usb(${BUILD} 0) +endforeach() + +foreach(FILENAME SIDTYPE IN ZIP_LISTS FILENAMES SIDTYPES) + # override filename for rgb compile + set(FILENAME ${FILENAME}-rgb) + # set filename + set(BUILD ${FILENAME}) + message(STATUS "Building ${FILENAME} with SIDTYPE=${SIDTYPE}") + # executable + add_executable(${BUILD} ${SOURCEFILES}) + # pio addition + pico_generate_pio_header(${BUILD} ${PIO}) + pico_generate_pio_header(${BUILD} ${PIO_RGB}) + # source files to compile + target_sources(${BUILD} PUBLIC ${SOURCEFILES}) + target_compile_options(${BUILD} ${COMPILE_OPTS}) + target_compile_definitions(${BUILD} PRIVATE USBSID SIDTYPES=${SIDTYPE} USE_RGB=1) + # tell the linker what libraries to link + target_link_libraries(${BUILD} ${TARGET_LL}) + # target sid types + target_include_directories(${BUILD} ${TARGET_INCLUDE_DIRS}) + pico_set_program_name(${BUILD} "USBSIDPico") + pico_set_program_version(${BUILD} $PROJECT_VERSION) + # create map/bin/hex/uf2 file in addition to ELF. + pico_add_extra_outputs(${BUILD}) + # enable uart output, disable usb output + pico_enable_stdio_uart(${BUILD} 1) + pico_enable_stdio_usb(${BUILD} 0) +endforeach() diff --git a/src/asid.c b/src/asid.c new file mode 100644 index 0000000..9c4f575 --- /dev/null +++ b/src/asid.c @@ -0,0 +1,97 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * asid.c + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * TherapSID by Twisted Electrons: https://github.com/twistedelectrons/TherapSID + * TeensyROM by Sensorium Embedded: https://github.com/SensoriumEmbedded/TeensyROM + * SID Factory II by Chordian: https://github.com/Chordian/sidfactory2 + * + * Any licensing conditions from either of the above named sources automatically + * apply to this code + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "midi.h" +#include "asid.h" +#include "usbsid.h" + + +void handle_asid_message(uint8_t sid ,uint8_t* buffer, int size) +{ + (void)size; /* unused at the moment */ + + unsigned int reg = 0; + for (uint8_t mask = 0; mask < 4; mask++) { /* no more then 4 masks */ + for (uint8_t bit = 0; bit < 7; bit++) { /* each packet has 7 bits ~ stoopid midi */ + if (buffer[mask + 3] & (1 << bit)) { /* 3 byte message, skip 3 each round and add the bit */ + uint8_t register_value = buffer[reg + 11]; /* get the value to write from the buffer */ + if(buffer[mask + 7] & (1 << bit)) { /* if anything higher then 0 */ + register_value |= 0x80; /* the register_value needs its 8th MSB bit */ + } + uint8_t address = asid_sid_registers[mask * 7 + bit]; + handle_asidbuffer_task((address |= sid), register_value); + reg++; + } + } + } +} + +void decode_asid_message(uint8_t* buffer, int size) +{ + switch(buffer[2]) { + case 0x4C: + /* Play start */ + midimachine.bus = CLAIMED; + break; + case 0x4D: + /* Play stop */ + midimachine.bus = FREE; + break; + case 0x4F: + /* Display characters */ + break; + case 0x4E: + handle_asid_message(0, buffer, size); + break; + case 0x50: + handle_asid_message(32, buffer, size); + break; + case 0x51: + handle_asid_message(64, buffer, size); + break; + case 0x60: + midimachine.fmopl = 1; + #if defined(SIDTYPE1) + /* Not implemented yet */ + #endif + default: + break; + } +} + +void process_sysex(uint8_t* buffer, int size) // NOTE: UNNESCESSARY OVERHEAD IF ASID ONLY +{ + if (buffer[1] == 0x2D) { /* 0x2D = ASID sysex message */ + decode_asid_message(buffer, size); + } // room for other sysex protocols +} diff --git a/src/asid.h b/src/asid.h new file mode 100644 index 0000000..9b2d3f5 --- /dev/null +++ b/src/asid.h @@ -0,0 +1,79 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * asid.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * TherapSID by Twisted Electrons: https://github.com/twistedelectrons/TherapSID + * TeensyROM by Sensorium Embedded: https://github.com/SensoriumEmbedded/TeensyROM + * SID Factory II by Chordian: https://github.com/Chordian/sidfactory2 + * + * Any licensing conditions from either of the above named sources automatically + * apply to this code + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASID_H_ +#define _ASID_H_ +#pragma once + +#include +#include +#include +#include + + +static const uint8_t asid_sid_registers[] = +{ + /* register, bit */ + 0x00, // 0 + 0x01, // 1 + 0x02, // 2 + 0x03, // 3 + 0x05, // 4 + 0x06, // 5 + 0x07, // 6 + 0x08, // 7 + 0x09, // 8 + 0x0a, // 9 + 0x0c, // 10 + 0x0d, // 11 + 0x0e, // 12 + 0x0f, // 13 + 0x10, // 14 + 0x11, // 15 + 0x13, // 16 + 0x14, // 17 + 0x15, // 18 + 0x16, // 19 + 0x17, // 20 + 0x18, // 21 + 0x04, // 22 + 0x0b, // 23 + 0x12, // 24 + 0x04, // 25 <= secondary for reg 04 + 0x0b, // 26 <= secondary for reg 11 + 0x12, // 27 <= secondary for reg 18 +}; + +void process_sysex(uint8_t *buffer, int size); + +#endif /* _ASID_H_ */ diff --git a/src/clock.pio b/src/clock.pio new file mode 100644 index 0000000..b8b3523 --- /dev/null +++ b/src/clock.pio @@ -0,0 +1,51 @@ +; +; USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two +; MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, +; phone or ASID supporting player +; +; clock.pio +; This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) +; File author: LouD +; +; Source: https://forums.raspberrypi.com/viewtopic.php?t=355595#p2131828 +; Source: https://parthssharma.github.io/Pico/PIOSquareWave.html +; +; Any licensing conditions from either of the above named sources automatically +; apply to this code +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, version 2. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +; Description: +; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is +; empty. Write the least significant bit to the OUT pin group. +; +.program clock + +.wrap_target ;Free 0 cycle unconditional jump +set pins, 1 ;Drive pin high, binary 1 <--- 250 ns execution time with 4MHz clock +set pins, 2 ;Drive pin low, binary 0 <--- 250 ns ;total 500 ns period +.wrap + + +%c-sdk{ + /* Program to initialize the PIO */ + static inline void clock_program_init(PIO pio, uint sm, uint offset, uint pin, float clk_div){ + pio_sm_config c = clock_program_get_default_config(offset); /* Get default configurations for the PIO state machine */ + sm_config_set_set_pins(&c, pin, 2); /* Set the state machine configurations on the given pin */ + sm_config_set_clkdiv(&c, clk_div); /* Set the state machine clock divider */ + pio_gpio_init(pio, pin); /* Setup the function select for a GPIO pin to use output from the given PIO instance */ + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); /* Use a state machine to set the pin direction for one pins for the PIO instance */ + pio_sm_init(pio, sm, offset, &c); /* Resets the state machine to a consistent state, and configures it */ + pio_sm_set_enabled(pio, sm, true); /* Enable or disable a PIO state machine */ + } +%} diff --git a/src/gpio.c b/src/gpio.c new file mode 100644 index 0000000..043505d --- /dev/null +++ b/src/gpio.c @@ -0,0 +1,180 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * gpio.c + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "usbsid.h" + +/* The following 2 lines (var naming changed) are copied from frenetic his SKPico code */ +register uint32_t b asm( "r10" ); +volatile const uint32_t *BUSState = &sio_hw->gpio_in; +/* https://github.com/frntc/SIDKick-pico */ + +void initPins(void) +{ + for ( int i = 0; i <= 21; i++ ) { /* init pin 0 -> 21 */ + if (i != TX || i != RX) /* do not init pins 16 & 17 because uart */ + goto init; + init: + gpio_set_dir(i, GPIO_OUT); + gpio_set_function(i, GPIO_FUNC_SIO); + gpio_put(i, 0); + } + + /* RES,CS & RW HIGH */ + gpio_put_masked(INIT_MASK, 0x3C0000); /* 0b111000000000000000000 */ + + /* PWM led */ + gpio_init( BUILTIN_LED ); + gpio_set_dir(BUILTIN_LED, GPIO_OUT); + gpio_set_function(BUILTIN_LED, GPIO_FUNC_PWM); + /* Init Vue */ + int led_pin_slice = pwm_gpio_to_slice_num( BUILTIN_LED ); + pwm_config configLED = pwm_get_default_config(); + pwm_config_set_clkdiv( &configLED, 1 ); + pwm_config_set_wrap( &configLED, 65535 ); /* LED max */ + pwm_init( led_pin_slice, &configLED, true ); + gpio_set_drive_strength( BUILTIN_LED, GPIO_DRIVE_STRENGTH_2MA ); + pwm_set_gpio_level( BUILTIN_LED, 0 ); /* turn off led */ + + // read_uS = to_us_since_boot(get_absolute_time()); + // write_uS = to_us_since_boot(get_absolute_time()); + + #if defined(USE_RGB) + /* Init RGB */ + gpio_init( 23 ); + // gpio_set_function(23, GPIO_FUNC_PWM); + gpio_set_dir(23, GPIO_OUT); + gpio_set_drive_strength( 23, GPIO_DRIVE_STRENGTH_2MA ); + #endif + +} + +void resetPins(void) +{ + sio_hw->gpio_clr = BUS_PINMASK; + for ( int i = 0; i < 21; i++ ) { + gpio_deinit( i ); + } +} + +void clearBus(void) +{ + gpio_clr_mask(ADDRPINS_PINMASK | DATAPINS_PINMASK); +} + +void pauseSID(void) +{ + gpio_put_masked(CS_MASK, (bPIN(CS) | bPIN(CS2))); +} + +void resetSID(void) +{ + // gpio_put(CS, 1); + // gpio_put(RES, 0); + // gpio_put_masked(CSRW_MASK, (bPIN(CS) | bPIN(CS2))); + + disableSID(); + // gpio_put(RES, 0); + clearBus(); + sleep_us(1); + enableSID(); + // gpio_put(RES, 1); + + // enableSID(); + // gpio_put(RES, 1); + // gpio_put(CS, 0); + // gpio_put_masked(bPIN(RW), bPIN(RW)); +} + +void enableSID(void) +{ + // gpio_put(CS, 0); + gpio_put(RES, 1); + // gpio_put_masked((CSRW_MASK | bPIN(RES)), (bPIN(RES) | bPIN(CS) | bPIN(CS2))); + for (int i = 0; i < NUMSIDS; i++) { + writeSID(0xD400 | ((0x20 * i) + 0x18), 0x0F); /* Volume to 15 */ + } +} + +void disableSID(void) +{ + for (int i = 0; i < NUMSIDS; i++) { + writeSID(0xD400 | ((0x20 * i) + 0x18), 0x0); /* Volume to 0 */ + } + gpio_put(CS, 1); + gpio_put(CS2, 1); + gpio_put(RES, 0); + // gpio_put_masked((CSRW_MASK | bPIN(RES)), (bPIN(CS) | bPIN(CS2))); +} + +uint8_t readSID(uint16_t addr) +{/* ISSUE: Reading the config tool from the SKPico misses data */ + uint32_t cs = ((addr & 0xFF) >= 0x40) /* addr in range $D440+ ? */ + ? bPIN(CS) /* SID1 disable, write CS1 1 HIGH */ + : bPIN(CS2); /* SID2 disable, write CS2 1 HIGH */ + + #if SIDTYPES == SIDTYPE0 + cs = 0; /* Override ChipSelect */ + #endif + + b = 0; /* empty g */ + gpio_set_dir_masked(DATAPINS_PINMASK, 0); /* all datapins to input */ + + addr = (addr & ADDR_MASK); + + WAIT_FOR_CPU_HALF_CYCLE /* QUESTION: DO WE ACTUALLY NEED THIS? ~ W/O THIS READING PRG DOESNT WORK PROPERLY */ + // gpio_put(RW, 1); /* write to high */ + uint16_t pinval = (addr << bADDR); /* addr pin value to write */ + // gpio_put_masked(ADDRPINS_PINMASK, pinval); /* 0b0111111100000000 */ + gpio_put_masked((CSRW_MASK | ADDRPINS_PINMASK), (bPIN(RW) | cs | pinval)); /* 0b0111111100000000 */ + // gpio_put(cs, 0); /* chipselect low */ + sleep_us(0.238); /* 238 nano seconds as per datasheet */ + b = *BUSState; /* read complete bus */ + gpio_put_masked((CSRW_MASK | ADDRPINS_PINMASK | DATAPINS_PINMASK), (bPIN(CS) | bPIN(CS2))); /* reset bus for writing */ + + gpio_set_dir_masked(DATAPINS_PINMASK, 0xFF); /* all datapins to output */ + + return (b & DATAPINS_PINMASK); +} + +void writeSID(uint16_t addr, uint8_t val) +{/* ISSUE: Notes are sometimes off !? */ + uint32_t cs = ((addr & 0xFF) >= 0x40) /* addr in range $D440+ ? */ + ? bPIN(CS) /* SID1 disable, write CS1 1 HIGH */ + : bPIN(CS2); /* SID2 disable, write CS2 1 HIGH */ + + #if SIDTYPES == SIDTYPE0 + cs = 0; /* Override ChipSelect */ + #endif + + addr = (addr & ADDR_MASK); /* create addr values */ + val = (val & DATA_MASK); /* create data values */ + + uint32_t pinval = ((addr << bADDR) | val | cs); /* create pico pin values */ + + WAIT_FOR_VIC_HALF_CYCLE /* QUESTION: DO WE ACTUALLY NEED THIS? */ + gpio_put_masked((CSRW_MASK | ADDRPINS_PINMASK | DATAPINS_PINMASK), pinval); /* write cs, rw, addr and data */ + sleep_us(0.360); /* Sleep for exactly 360 ns ~ write pulse width + hold time */ + gpio_put_masked(CSRW_MASK, (bPIN(RW) | bPIN(CS) | bPIN(CS2))); /* write cs and rw */ +} diff --git a/src/gpio.h b/src/gpio.h new file mode 100644 index 0000000..eca32f0 --- /dev/null +++ b/src/gpio.h @@ -0,0 +1,136 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * gpio.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _SIDGPIO_H_ +#define _SIDGPIO_H_ +#pragma once + + +#ifdef __cplusplus + extern "C" { +#endif + +#include +#include +#include + +#include "pico/stdlib.h" +#include "pico/types.h" +#include "hardware/structs/sio.h" /* Pico SIO structs */ +#include "hardware/pwm.h" /* Hardware Pulse Width Modulation (PWM) API */ + + +/* Uart */ +#define TX 16 /* uart 0 tx */ +#define RX 17 /* uart 0 rx */ + +/* IO pins */ +#define RES 18 /* Reset */ +#define RW 19 /* Read/ Write enable */ +#define CS 20 /* Chip Select for 1 or 1 & 2 with SKPico */ +#define CS2 21 /* Chip Select for 2 or 3 & 4 with SKPico */ +#define PHI 22 /* Pico 1Mhz PWM out ~ External Clock In */ + +/* Address pins ~ output only */ +#define A0 8 +#define A1 9 +#define A2 10 +#define A3 11 +#define A4 12 +#define A5 13 /* $D420+ or FM on SKPico */ +#define A8 14 /* $D500. $DE00, $DF00 or external CS on SKPico */ + +/* Data pins ~ output/input */ +#define D0 0 +#define D1 1 +#define D2 2 +#define D3 3 +#define D4 4 +#define D5 5 +#define D6 6 +#define D7 7 + +/* Other */ +#define BUILTIN_LED PICO_DEFAULT_LED_PIN /* 25 */ +#define WS2812_PIN 23 + +/* Unused */ +#define NIL1 15 +#define NIL2 26 +#define NIL3 27 +#define NIL4 28 + +#define bPIN(i) ( 1 << i ) +#define bADDR 8 +#define bDATA 0 + +#define BUS_PINMASK 0x3FFFFF /* 0b00000000001111111111111111111111 21 GPIO pins */ +#define ADDRPINS_PINMASK 0x7F00 /* 0b0111111100000000 */ +#define DATAPINS_PINMASK 0xFF /* 0b0000000011111111 */ +#define INIT_MASK (bPIN(RW) | bPIN(CS) | bPIN(CS2) | bPIN(RES)) +#define CS_MASK (bPIN(CS) | bPIN(CS2)) +#define RW_MASK bPIN(RW) +#define CSRW_MASK (bPIN(RW) | bPIN(CS) | bPIN(CS2)) + +#define A8_MASK 0x100 /* 0b100000000 */ +#define A5_MASK 0x20 /* 0b00100000 */ + +#define ADDR_MASK (0x1F | A5_MASK) /* 0b00111111 */ +#define DATA_MASK 0xFF /* 0b11111111 */ + +/* The following 4 lines are a direct copy (var naming changed) from frenetic his SKPico code */ +#define bPHI bPIN( PHI ) +#define VIC_HALF_CYCLE( b ) ( !( (b) & bPHI ) ) /* low */ +#define CPU_HALF_CYCLE( b ) ( ( (b) & bPHI ) ) /* high */ +#define WAIT_FOR_VIC_HALF_CYCLE { do { b = *BUSState; } while ( !( VIC_HALF_CYCLE( b ) ) ); } /* while !0 == Falling edge*/ +#define WAIT_FOR_CPU_HALF_CYCLE { do { b = *BUSState; } while ( !( CPU_HALF_CYCLE( b ) ) ); } /* while !1 == Rising edge*/ +/* https://github.com/frntc/SIDKick-pico */ + +/* Pins 0->20 (except TX and RX) to sio and output */ +void initPins(void); +/* Clears and de-inits all pins */ +void resetPins(void); +/* Sets the address and databus pins to LOW */ +void clearBus(void); +/* Pauses any operation by cetting CS high */ +void pauseSID(void); +/* Reset the SID and clears the bus */ +void resetSID(void); +/* Enable the SID by setting RES to HIGH */ +/* Enable the SID by setting RW to LOW and CS & RES to HIGH */ +void enableSID(void); +/* Disable the SID by setting RES to LOW */ +/* Disable the SID by setting RW & RES to LOW and CS to HIGH*/ +void disableSID(void); +/* Read val at SID addr hex value */ +uint8_t readSID(uint16_t addr); +/* Write val to sid at addr hex value */ +void writeSID(uint16_t addr, uint8_t val); + +#ifdef __cplusplus + } +#endif + +#endif /* _SIDGPIO_H_ */ diff --git a/src/mcu.c b/src/mcu.c new file mode 100644 index 0000000..4d64ff3 --- /dev/null +++ b/src/mcu.c @@ -0,0 +1,61 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * mcu.c + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * BusPirate5 by DangerousPrototypes: + * https://github.com/DangerousPrototypes/BusPirate5-firmware/blob/main/pirate/mcu.c + * + * Any licensing conditions from the above named source automatically apply to this code + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include "pico/stdlib.h" +#include "pico/unique_id.h" +#include "hardware/watchdog.h" +#include "pico/bootrom.h" +#include "mcu.h" + +uint64_t mcu_get_unique_id(void) +{ + static_assert(sizeof(pico_unique_board_id_t) == sizeof(uint64_t), "pico_unique_board_id_t is not 64 bits (but is cast to uint64_t)"); + pico_unique_board_id_t id; + pico_get_unique_board_id(&id); + return *((uint64_t*)(id.id)); +}; + +void mcu_reset(void) +{ + watchdog_enable(1, 1); + while(1); +} + +void mcu_jump_to_bootloader(void) +{ + /* \param usb_activity_gpio_pin_mask 0 No pins are used as per a cold boot. Otherwise a single bit set indicating which + * GPIO pin should be set to output and raised whenever there is mass storage activity + * from the host. + * \param disable_interface_mask value to control exposed interfaces + * - 0 To enable both interfaces (as per a cold boot) + * - 1 To disable the USB Mass Storage Interface + * - 2 To disable the USB PICOBOOT Interface + */ + reset_usb_boot(0x00,0x00); +} diff --git a/src/mcu.h b/src/mcu.h new file mode 100644 index 0000000..6855830 --- /dev/null +++ b/src/mcu.h @@ -0,0 +1,47 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * mcu.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * BusPirate5 by DangerousPrototypes: + * https://github.com/DangerousPrototypes/BusPirate5-firmware/blob/main/pirate/mcu.h + * + * Any licensing conditions from the above named source apply to this code + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef _MCU_H_ +#define _MCU_H_ +#pragma once +#include "pico/stdlib.h" + +/* https://forums.raspberrypi.com/viewtopic.php?p=1928868#p1928868 */ +/* Reset rp2040 register */ +#define AIRCR_Register (*((volatile uint32_t*)(PPB_BASE + 0x0ED0C))) +/* Reset rp2040 using the reset register */ +#define RESET_PICO() AIRCR_Register = 0x5FA0004; + +/* Retrieve the rp2040 mcu unique id */ +uint64_t mcu_get_unique_id(void); +/* Reset the rp2040 mcu */ +void mcu_reset(void); +/* Jump to rp2040 mcu bootloader */ +void mcu_jump_to_bootloader(void); + +#endif /* _MCU_H_ */ diff --git a/src/midi.c b/src/midi.c new file mode 100644 index 0000000..ebf7658 --- /dev/null +++ b/src/midi.c @@ -0,0 +1,306 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * midi.c + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * TherapSID by Twisted Electrons: https://github.com/twistedelectrons/TherapSID + * TeensyROM by Sensorium Embedded: https://github.com/SensoriumEmbedded/TeensyROM + * SID Factory II by Chordian: https://github.com/Chordian/sidfactory2 + * + * Any licensing conditions from either of the above named sources automatically + * apply to this code + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "midi.h" + + +clock_rates clock_rate = CLOCK_DEFAULT; +hertz_values hertz = HZ_50; + +void midi_init(void) +{ + /* Clear buffers once */ + memset(midimachine.readbuffer, 0, sizeof midimachine.readbuffer); + // memset(midimachine.packetbuffer, 0, sizeof midimachine.packetbuffer); + memset(midimachine.streambuffer, 0, sizeof midimachine.streambuffer); + memset(midimachine.sid_states, 0, sizeof midimachine.sid_states); + /* Initial state and index */ + midimachine.bus = FREE; + midimachine.state = IDLE; + midimachine.index = 0; + // writeSID((addr | 0x04), ((midimachine.sid_states[0][4] & 0xFE) | 0x1)); +} + +void process_midi(uint8_t* buffer, int size) +{ + uint8_t note_index = buffer[1]; + uint8_t note_velocity = buffer[2]; + uint16_t frequency = musical_scale_values[note_index]; + double a, b, c, d, e, f, g, h; + int freq, reg01; + uint16_t calculated_freq; + + a = buffer[1]; + b = a / 12; + // uint16_t calculated_freq = round(pow((16.35 * 2), (note_index / 12))); + c = pow(2, b); + d = 16.35 * c; + e = round(d); + freq = e; + f = pow(2, 24); + g = f / clock_rate; + h = freq * g; + reg01 = round(h); + calculated_freq = round(h); + + uint8_t Flo, Fhi, Flo1, Fhi1; + Flo = (frequency & 0xFF); + Fhi = ((frequency >> 8) >= 0xFF ? 0xFF : (frequency >> 8)); + Flo1 = (calculated_freq & 0xFF); + Fhi1 = ((calculated_freq >> 8) >= 0xFF ? 0xFF : (calculated_freq >> 8)); + + // writeSID(0xD418, 0xE); + int in, out, val, volume, add; + uint8_t mask, modevol_state, voice_state, keep_state; + uint16_t pulsewidth, pw_state; + add = (buffer[0] & 0xF); + addr = (0xD400 | (0x20 * add)); + + switch (buffer[0]) { + case 0x90 ... 0x93: // Note On ~ Voice 1 + midimachine.sid_states[0][0] = Flo; + midimachine.sid_states[0][1] = Fhi; + /* ADSR */ + // writeSID((addr | 0x05), 0x0); /* Attack / Decay */ + // writeSID((addr | 0x06), 0xF0); /* Sustain / Release */ + writeSID((addr | 0x18), midimachine.sid_states[0][24]); /* Volume */ + writeSID((addr | 0x05), midimachine.sid_states[0][5]); /* Attack / Decay */ + writeSID((addr | 0x06), midimachine.sid_states[0][6]); /* Sustain / Release */ + writeSID((addr | 0x02), midimachine.sid_states[0][2]); /* PW LO */ + writeSID((addr | 0x03), midimachine.sid_states[0][3]); /* PW HI */ + /* Control */ + midimachine.sid_states[0][4] = ((midimachine.sid_states[0][4] & 0xFE) | 0x1); + writeSID((addr | 0x04), midimachine.sid_states[0][4]); + /* writeSID((addr | 0x04), ((midimachine.sid_states[0][4] & 0xFE) | 0x1)); */ + /* Note Lo & Hi */ + writeSID((addr | 0x0), midimachine.sid_states[0][0]); + writeSID((addr | 0x01), midimachine.sid_states[0][1]); + break; + case 0x80 ... 0x83: // Note Off + /* Note Lo & Hi */ + // writeSID((addr | 0x0), 0); + // writeSID((addr | 0x01), 0); + // /* Control */ + // writeSID((addr | 0x04), 0x10); + midimachine.sid_states[0][4] = (midimachine.sid_states[0][4] & 0xFE); + writeSID((addr | 0x04), midimachine.sid_states[0][4]); + break; + case 0xB0 ... 0xB3: + switch (buffer[1]) { + case 0x07: /* Set Volume */ + in = buffer[2]; + volume = map(in, 0, 127, 0, 15); + mask = 0xF0; /* Inverted */ + modevol_state = midimachine.sid_states[0][24]; + keep_state = (modevol_state & mask); + val = (keep_state << 4) | volume; + midimachine.sid_states[0][24] = val; + addr |= 0x18; + // writeSID(addr, val); + break; + case 0x49: /* Attack */ + in = buffer[2]; + out = map(in, 0, 127, 0, 15); + mask = 0xF; /* Inverted */ + voice_state = midimachine.sid_states[0][5]; + keep_state = (voice_state & mask); + val = (out << 4) | keep_state; + midimachine.sid_states[0][5] = val; + addr |= 0x05; + // writeSID(addr, val); + break; + case 0x4B: /* Decay */ + in = buffer[2]; + out = map(in, 0, 127, 0, 15); + mask = 0xF0; /* Inverted */ + voice_state = midimachine.sid_states[0][5]; + keep_state = (voice_state & mask); + val = (keep_state | out); + midimachine.sid_states[0][5] = val; + // addr |= 0x05; + // writeSID(addr, val); + break; + case 0x40: /* Sustain */ + in = buffer[2]; + out = map(in, 0, 127, 0, 15); + mask = 0xF; /* Inverted */ + voice_state = midimachine.sid_states[0][6]; + keep_state = (voice_state & mask); + val = (out << 4) | keep_state; + midimachine.sid_states[0][6] = val; + addr |= 0x06; + // writeSID(addr, val); + break; + case 0x48: /* Release */ + in = buffer[2]; + out = map(in, 0, 127, 0, 15); + mask = 0xF0; /* Inverted */ + voice_state = midimachine.sid_states[0][6]; + keep_state = (voice_state & mask); + val = (keep_state | out); + midimachine.sid_states[0][6] = val; + addr |= 0x06; + // writeSID(addr, val); + break; + case 0x4C: /* Pulse Width */ + in = buffer[2]; + out = map(in, 0, 127, 0, 4095); + // mask = 0xF0; /* Inverted */ + pw_state = (midimachine.sid_states[0][3] << 8 | midimachine.sid_states[0][2]); + // keep_state = (voice_state & mask); + addr |= 0x06; + val = (out >> 8); + midimachine.sid_states[0][3] = val; + val = (out & 0xF); + midimachine.sid_states[0][2] = val; + // writeSID(addr, val); + break; + default: + break; + } + break; + case 0xC0 ... 0xC3: + switch (buffer[1]) { + case 0x00: + /* Noise */ + midimachine.sid_states[0][4] = ((midimachine.sid_states[0][4] & 0xF) | 0x80); + break; + case 0x01: + /* Pulse */ + midimachine.sid_states[0][4] = ((midimachine.sid_states[0][4] & 0xF) | 0x40); + break; + case 0x02: + /* Sawtooth */ + midimachine.sid_states[0][4] = ((midimachine.sid_states[0][4] & 0xF) | 0x20); + break; + case 0x03: + /* Triangle */ + midimachine.sid_states[0][4] = ((midimachine.sid_states[0][4] & 0xF) | 0x10); + break; + default: + break; + } + break; + default: + break; + } +} + +void process_buffer(uint8_t buffer) +{ + if (buffer & 0x80) { + switch (buffer) { + /* System Exclusive */ + case 0xF0: // System Exclusive Start + if (midimachine.bus != CLAIMED) { + midimachine.type = SYSEX; + midimachine.state = RECEIVING; + midimachine.bus = CLAIMED; + midimachine.index = 0; + midimachine.streambuffer[midimachine.index] = buffer; + midimachine.index++; + } + break; + case 0xF7: // System Exclusive End of SysEx (EOX) + midimachine.streambuffer[midimachine.index] = buffer; + process_sysex(midimachine.streambuffer, midimachine.index); + midimachine.index = 0; + midimachine.state = IDLE; + midimachine.bus = FREE; + midimachine.type = NONE; + break; + case 0xF1: // System Exclusive MIDI Time Code Qtr. Frame + case 0xF2: // System Exclusive Song Position Pointer + case 0xF3: // System Exclusive Song Select (Song #) + case 0xF4: // System Exclusive Undefined (Reserved) + case 0xF5: // System Exclusive Undefined (Reserved) + case 0xF6: // System Exclusive Tune request + case 0xF8: // System Exclusive Timing clock + case 0xF9: // System Exclusive Undefined (Reserved) + case 0xFA: // System Exclusive Start + case 0xFB: // System Exclusive Continue + case 0xFC: // System Exclusive Stop + case 0xFD: // System Exclusive Undefined (Reserved) + case 0xFE: // System Exclusive Active Sensing + case 0xFF: // System Exclusive System Reset + break; + + /* Midi */ + // case 0x0 ... 0x7F: // Data Byte + // break; + case 0x80 ... 0x8F: // Note Off Event + case 0x90 ... 0x9F: // Note On Event + case 0xA0 ... 0xAF: // Polyphonic Key Pressure (Aftertouch) + case 0xB0 ... 0xBF: // Control/Mode Change + case 0xC0 ... 0xCF: // Program Phange + case 0xD0 ... 0xDF: // Channel Pressure (After-touch) + case 0xE0 ... 0xEF: // Pitch Bend Change + if (midimachine.bus != CLAIMED && midimachine.type == NONE) { + midimachine.type = MIDI; + midimachine.state = RECEIVING; + midimachine.bus = CLAIMED; + midimachine.index = 0; + midimachine.streambuffer[midimachine.index] = buffer; + midimachine.index++; + } + break; + + default: + break; + } + } else { + if (midimachine.state == RECEIVING) { + if (midimachine.index < sizeof(midimachine.streambuffer) / sizeof(*midimachine.streambuffer)) { + /* Add midi data to the buffer ~ SysEx & Midi */ + midimachine.streambuffer[midimachine.index++] = buffer; + /* Handle 3 byte midi buffer */ + if (midimachine.type == MIDI) { + if (midimachine.streambuffer[0] >= 0x80 || midimachine.streambuffer[0] <= 0xEF) { + if (midimachine.index == 3) { + process_midi(midimachine.streambuffer, midimachine.index); + midimachine.index = 0; + midimachine.state = IDLE; + midimachine.bus = FREE; + midimachine.type = NONE; + } + } + } + } else { + // Buffer is full, just wait for this message to be finished + midimachine.state = WAITING_FOR_END; + } + } else if (midimachine.state == WAITING_FOR_END) { + // We are getting data even though we are just waiting for sysex end to come + // So just consume data and do nothing... + } + } +} diff --git a/src/midi.h b/src/midi.h new file mode 100644 index 0000000..5113191 --- /dev/null +++ b/src/midi.h @@ -0,0 +1,87 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * midi.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * TherapSID by Twisted Electrons: https://github.com/twistedelectrons/TherapSID + * TeensyROM by Sensorium Embedded: https://github.com/SensoriumEmbedded/TeensyROM + * SID Factory II by Chordian: https://github.com/Chordian/sidfactory2 + * + * Any licensing conditions from either of the above named sources automatically + * apply to this code + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef _MIDI_H_ +#define _MIDI_H_ +#pragma once + +#include +#include +#include +#include +#include +#include "usbsid.h" + +typedef enum { + CLAIMED, + FREE +} bus_state; + +typedef enum { + IDLE, + RECEIVING, + WAITING_FOR_END +} sysex_state; + +typedef enum { + NONE, + MIDI, + SYSEX, + ASID +} midi_type; + +typedef struct { + bus_state bus; + sysex_state state; + midi_type type; + uint8_t readbuffer[1]; + // uint8_t packetbuffer[4]; // 20240723 ~ disabled, unused + uint8_t streambuffer[64]; /* Normal speed max buffer for TinyUSB */ + uint8_t sid_states[4][29]; /* Stores states of each SID */ + uint8_t sidaddress; + int fmopl; + unsigned int index; +} midi_machine; + +extern midi_machine midimachine; + +/* Processes the 1 byte incoming midi buffer + * Figures out if we're receiving midi or sysex */ +void process_buffer(uint8_t buffer); + +/* Initialize the midi handlers */ +void midi_init(void); + +/* Processes the received Midi stream */ +// void process_stream(uint8_t* buffer); // 20240723 ~ disabled, unused + +#endif /* _MIDI_H_ */ diff --git a/src/sid.h b/src/sid.h new file mode 100644 index 0000000..06b7bd8 --- /dev/null +++ b/src/sid.h @@ -0,0 +1,213 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * sid.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once +#ifndef SID_H_ +#define SID_H_ + +#include + +typedef enum { + CLOCK_DEFAULT = 1000000, + CLOCK_PAL = 985248, + CLOCK_NTSC = 1022727, + CLOCK_DREAN = 1023440 +} clock_rates; + +typedef enum { + HZ_DEFAULT = 20000, // 50Hz ~ 20000 == 20us + HZ_50 = 19950, // 50Hz ~ 20000 == 20us / 50.125Hz ~ 19.950124688279us exact + HZ_60 = 16715 // 60Hz ~ 16667 == 16.67us / 59.826Hz ~ 16.715140574332 exact +} hertz_values; + +typedef enum { + DEFAULT = 0xFF, + L_NIBBLE = 0xF0, + R_NIBBLE = 0x0F, + VOICE_FREQLO = DEFAULT, + VOICE_FREQHI = DEFAULT, +} register_masks; + +/* + * Voice registers + * + * 0: Freq low + * 1: Freq high + * 2: Pulsewidth low + * 3: Pulsewidth high + * 4: Control registers + * 5: Attack/Decay + * 6: Sustain/Release + */ +static const uint8_t sid_registers[] = +{ + /* Voice 1 */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + /* Voice 2 */ + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + /* Voice 3 */ + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, + /* Filter low */ + 0x15, + /* Filter high */ + 0x16, + /* Resonance & filter channels */ + 0x17, + /* Volume */ + 0x18, + /* PotX/PotY */ + 0x19, 0x1A, + /* Oscillator Voice 3 */ + 0x1B, + /* Envelope Voice 3 */ + 0x1C +}; + +/* HEX DEC Attack(Time/Cycle) Decay(Time/Cycle) */ +static const uint8_t attack_decay[] = +{ + 0x0, // 0 2 mS 6 mS + 0x1, // 1 8 mS 24 mS + 0x2, // 2 16 mS 48 mS + 0x3, // 3 24 mS 72 mS + 0x4, // 4 38 mS 114 mS + 0x5, // 5 56 mS 168 mS + 0x6, // 6 68 mS 204 mS + 0x7, // 7 80 mS 240 mS + 0x8, // 8 100 mS 300 mS + 0x9, // 9 250 mS 750 mS + 0xA, // 10 500 mS 1.5 S + 0xB, // 11 800 mS 2.4 S + 0xC, // 12 1 S 3 S + 0xD, // 13 3 S 9 S + 0xE, // 14 5 S 15 S + 0xF, // 15 8 S 24 S +}; + +/* + * Osc Fn (Hex), No, Musical Note, Freq (Hz), Osc Fn (Decimal) + */ +static const uint32_t musical_scale_values[] = +{ + 0x0112, // 0 C0 16.35 274 + 0x0123, // 1 C0$ 17.32 291 + 0x0134, // 2 D0 18.35 308 + 0x0146, // 3 D0$ 19.44 326 + 0x015A, // 4 E0 20.60 346 + 0x016E, // 5 F0 21.83 366 + 0x0184, // 6 F0$ 23.12 388 + 0x018B, // 7 G0 24.50 411 + 0x01B3, // 8 G0$ 25.96 435 + 0x01CD, // 9 A0 27.50 461 + 0x01E9, // 10 A0$ 29.14 489 + 0x0206, // 11 B0 30.87 518 + 0x0225, // 12 C1 32.70 549 + 0x0245, // 13 C1$ 34.65 581 + 0x0268, // 14 D1 36.71 616 + 0x028C, // 15 D1$ 38.89 652 + 0x02B3, // 16 E1 41.20 691 + 0x02DC, // 17 F1 43.65 732 + 0x0308, // 18 F1$ 46.25 776 + 0x0336, // 19 G1 49.00 822 + 0x0367, // 20 G1$ 51.91 871 + 0x039B, // 21 A1 55.00 923 + 0x03D2, // 22 A1$ 58.27 978 + 0x040C, // 23 B1 61.74 1036 + 0x0449, // 24 C2 65.41 1097 + 0x048B, // 25 C2$ 69.30 1163 + 0x04D0, // 26 D2 73.42 1232 + 0x0519, // 27 D2$ 77.78 1305 + 0x0567, // 28 E2 82.41 1383 + 0x05B9, // 29 F2 87.31 1465 + 0x0610, // 30 F2$ 92.50 1552 + 0x066C, // 31 G2 98.00 1644 + 0x06CE, // 32 G2$ 103.83 1742 + 0x0735, // 33 A2 110.00 1845 + 0x07A3, // 34 A2$ 116.54 1955 + 0x0817, // 35 B2 123.47 2071 + 0x0893, // 36 C3 130.81 2195 + 0x0915, // 37 C3$ 138.59 2325 + 0x099F, // 38 D3 146.83 2463 + 0x0A32, // 39 D3$ 155.56 2610 + 0x0ACD, // 40 E3 164.81 2765 + 0x0B72, // 41 F3 174.61 2930 + 0x0C20, // 42 F3$ 185.00 3104 + 0x0CD8, // 43 G3 196.00 3288 + 0x0D9C, // 44 G3$ 207.65 3484 + 0x0E6B, // 45 A3 220.00 3691 + 0x0F46, // 46 A3$ 233.08 3910 + 0x102F, // 47 B3 246.94 4143 + 0x1125, // 48 C4 261.63 4389 + 0x122A, // 49 C4$ 277.18 4650 + 0x133F, // 50 D4 293.66 4927 + 0x1464, // 51 D4$ 311.13 5220 + 0x159A, // 52 E4 329.63 5530 + 0x16E3, // 53 F4 349.23 5859 + 0x183F, // 54 F4$ 370.00 6207 + 0x1981, // 55 G4 392.00 6577 + 0x1B38, // 56 G4$ 415.30 6968 + 0x1CD6, // 57 A4 440.00 7382 + 0x1E80, // 58 A4$ 466.16 7821 + 0x205E, // 59 B4 493.88 8286 + 0x224B, // 60 C5 523.25 8779 + 0x2455, // 61 C5$ 554.37 9301 + 0x267E, // 62 D5 587.33 9854 + 0x28C8, // 63 D5$ 622.25 10440 + 0x2B34, // 64 E5 659.25 11060 + 0x2DC6, // 65 F5 698.46 11718 + 0x307F, // 66 F5$ 740.00 12415 + 0x3361, // 67 G5 783.99 13153 + 0x366F, // 68 G5$ 830.61 13935 + 0x39AC, // 69 A5 880.00 14764 + 0x3D1A, // 70 A5$ 932.33 15642 + 0x40BC, // 71 B5 987.77 16572 + 0x4495, // 72 C6 1046.50 17557 + 0x48A9, // 73 C6$ 1108.73 18601 + 0x4CFC, // 74 D6 1174.66 19709 + 0x518F, // 75 D6$ 1244.51 20897 + 0x5669, // 76 E6 1381.51 22121 + 0x5B8C, // 77 F6 1396.91 23436 + 0x60FE, // 78 F6$ 1479.98 24830 + 0x6602, // 79 G6 1567.98 26306 + 0x6CDF, // 80 G6$ 1661.22 27871 + 0x7358, // 81 A6 1760.00 29528 + 0x7A34, // 82 A6$ 1864.65 31234 + 0x8178, // 83 B6 1975.53 33144 + 0x892B, // 84 C7 2093.00 35115 + 0x9153, // 85 C7$ 2217.46 37203 + 0x99F7, // 86 D7 2349.32 39415 + 0xA31F, // 87 D7$ 2489.01 41759 + 0xACD2, // 88 E7 2637.02 44242 + 0xB719, // 89 F7 2793.83 46873 + 0xC1FC, // 90 F7$ 2959.95 49660 + 0xCD85, // 91 G7 3135.96 52613 + 0xD9BD, // 92 G7$ 3322.44 55741 + 0xE6B0, // 93 A7 3520.00 59056 + 0xF467, // 94 A7$ 3729.31 62567 + 0x102F0, // 95 B7 3951.06 66288 +}; + +// static const uint16_t notes[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "H"}; + +#endif /* SID_H_ */ diff --git a/src/tusb_config.h b/src/tusb_config.h new file mode 100644 index 0000000..ffb5e59 --- /dev/null +++ b/src/tusb_config.h @@ -0,0 +1,132 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * tusb_config.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +#define CFG_TUSB_MCU OPT_MCU_RP2040 + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_FULL_SPEED +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_TUD_MAX_SPEED) /* High speed with TinyUSB is not available for rp2040*/ +#endif + +//-------------------------------------------------------------------- +// Common Configuration +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 1 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#define CFG_TUD_TASK_QUEUE_SZ 64 /* WHAT DOES THIS BUTTON DO!? */ + +//------------- CLASS -------------// +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 1 +#define CFG_TUD_VENDOR 1 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// CDC Endpoint transfer buffer size, more is faster +#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// Vendor FIFO size of TX and RX +// If not configured vendor endpoints will not be buffered +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c new file mode 100644 index 0000000..8ee7ce9 --- /dev/null +++ b/src/usb_descriptors.c @@ -0,0 +1,269 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * usb_descriptors.c + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include + +#include "pico/stdlib.h" +#include "pico/unique_id.h" +#include "tusb.h" +#include "mcu.h" + +#include "usbsid.h" + +#define USB_PID 0x4011 /* USBSID uses TinyUSB default ~ 1x CDC + 1x MIDI (Vendor not included) */ +#define USB_VID 0xCAFE /* USBSID uses TinyUSB default - Previous versions used 0x5553 */ +#define USB_BCD 0x0210 + + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, // at least 2.1 or 3.x for BOS + + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, /* Device release number in binary-coded decimal */ + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +enum +{ + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, /* This gets the descriptor of CDC */ + ITF_NUM_MIDI, + ITF_NUM_MIDI_STREAMING, /* This has to be here, even if not used! */ + ITF_NUM_VENDOR, + ITF_NUM_TOTAL +}; + +#define EPNUM_CDC_NOTIF 0x81 +#define EPNUM_CDC_OUT 0x02 +#define EPNUM_CDC_IN 0x82 +#define EPNUM_MIDI_OUT 0x03 +#define EPNUM_MIDI_IN 0x83 +#define EPNUM_VENDOR_OUT 0x04 +#define EPNUM_VENDOR_IN 0x84 + +#define USBD_CDC_CMD_MAX_SIZE 8 +#define USBD_CDC_IN_OUT_MAX_SIZE 64 +#define USBD_MIDI_IN_OUT_MAX_SIZE 64 +#define USBD_VENDOR_IN_OUT_MAX_SIZE 64 + + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MIDI_DESC_LEN + TUD_VENDOR_DESC_LEN) + +// full speed configuration +uint8_t const desc_fs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 500), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, USBD_CDC_CMD_MAX_SIZE, EPNUM_CDC_OUT, EPNUM_CDC_IN, USBD_CDC_IN_OUT_MAX_SIZE), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 5, EPNUM_MIDI_OUT, EPNUM_MIDI_IN, USBD_MIDI_IN_OUT_MAX_SIZE), + + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 6, EPNUM_VENDOR_OUT, EPNUM_VENDOR_IN, USBD_VENDOR_IN_OUT_MAX_SIZE), + +}; + + +#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) +#define MS_OS_20_DESC_LEN 0xB2 + +// BOS Descriptor is required for webUSB +uint8_t const desc_bos[] = +{ + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), + + // Vendor Code, iLandingPage + TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), + + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT) +}; + +uint8_t const * tud_descriptor_bos_cb(void) +{ + return desc_bos; +} + +uint8_t const desc_ms_os_20[] = +{ + // Set header: length, type, windows version, total length + U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), + + // Configuration subset header: length, type, configuration index, reserved, configuration total length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A), + + // Function Subset header: length, type, first interface, reserved, subset length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_NUM_VENDOR, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08), + + // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible + + // MS OS 2.0 Registry property descriptor: length, type + U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08-0x08-0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), + U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, + 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00, + U16_TO_U8S_LE(0x0050), // wPropertyDataLength + //bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”. + '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, 'D', 0x00, '9', 0x00, '-', 0x00, + '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, + '8', 0x00, 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00, + '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); + +extern uint8_t const desc_ms_os_20[]; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + return desc_fs_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + + +char const *string_desc_arr[] = +{ + (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) + "USBSID", // 1: Manufacturer + "USBSID-Pico", // 2: Product + "USES-MCU-ID", // 3: Serial, uses chip ID + "USBSID-Pico Data", // 4: CDC Interface + "USBSID-Pico Midi", // 5: Midi Interface + "USBSID-Pico WebUSB", // 6: WebUSB Vendor Interface +}; + +// automatically update if an additional string is later added to the table +#define STRING_DESC_ARR_ELEMENT_COUNT (sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) + +// temporary buffer returned from tud_descriptor_string_cb() +// relies on only a single outstanding call to this function +// until the buffer is no longer used by caller. +// "contents must exist long enough for transfer to complete" +// Safe because each string descriptor is a separate transfer, +// and only one is handled at a time. +// Use of "length" is ambiguous (byte count? element count?), +// so use more explicit "BYTE_COUNT" or "ELEMENT_COUNT" instead. +#define MAXIMUM_DESCRIPTOR_STRING_ELEMENT_COUNT 32 +static uint16_t _desc_str[MAXIMUM_DESCRIPTOR_STRING_ELEMENT_COUNT]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + } else if ( index == 3 ) { // special-case for USB Serial number + /* BOARD ID */ + // 1x uint16_t for length/type + // 16x uint16_t to encode 64-bit value as hex (16x nibbles) + static_assert(MAXIMUM_DESCRIPTOR_STRING_ELEMENT_COUNT >= 17); + uint64_t unique_id = mcu_get_unique_id(); + for ( uint_fast8_t t = 0; t < 16; t++ ) { + uint8_t nibble = (unique_id >> (t * 4u)) & 0xF; + _desc_str[16-t] = nibble < 10 ? '0' + nibble : 'A' + nibble - 10; + } + chr_count = 16; + } else if ( index >= STRING_DESC_ARR_ELEMENT_COUNT ) { // if not in table, return NULL + return NULL; + } else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = (uint8_t) strlen(str); + if ( chr_count > MAXIMUM_DESCRIPTOR_STRING_ELEMENT_COUNT-1 ) chr_count = MAXIMUM_DESCRIPTOR_STRING_ELEMENT_COUNT-1; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i. + * + */ + +#include "usbsid.h" + +/*-------------------------------------------------------------------- + * GLOBAL VARS / FUNCTION DECLARING + *--------------------------------------------------------------------*/ + +uint8_t memory[65536]; +uint16_t vue; +midi_machine midimachine; +extern void midi_init(void); + + +/*-------------------------------------------------------------------- + * MAIN LOOPS + *--------------------------------------------------------------------*/ + +void core1_main(void) +{ + if (detect_clock() == 0) { + gpio_deinit(PHI); + square_wave(); + } else { + /* Do nothing gpio is input pulled low */ + } + + #if defined(USE_RGB) + if (detect_smps() == 1) { + init_rgb(); + rgb_mode = true; + } else { + /* Do nothing gpio is input pulled low */ + rgb_mode = false; + } + #endif + + init_memory(); + + int x = 0; + while (1) + { + usbdata == 1 ? led_vuemeter_task() : led_breathe_task(); + if (vue == 0) x++; + if (x >= 10) { + x = 0; + usbdata = 0; + } + } +} + +int main() +{ + // set_sys_clock_pll(120 * MHZ,1,1); /* 120MHz ? */ + // set_sys_clock_pll( 1500000000, 6, 2 ); /* 125MHz */ + set_sys_clock_pll( 1500000000, 6, 2 ); /* 300MHz */ + memset(buffer, 0, sizeof buffer); /* clear USB read buffer */ + memset(data, 0, sizeof data); /* clear USB write buffer */ + + /* Init board */ + board_init(); + /* Init USB */ + tud_init(BOARD_TUD_RHPORT); + + /* Init SID */ + initPins(); + + /* Init logging */ + init_logging(); /* Initialize logging ~ placed after board_init as this resets something making the first print not print */ + + // multicore_reset_core1(); /* Reset core 1 before use */ + multicore_launch_core1(core1_main); /* Init core 1 */ + + sleep_us(100); /* wait for core1 init */ + resetSID(); /* reset the SID */ + sleep_us(100); /* Give the SID time to boot */ + + midi_init(); + + /* Loop IO tasks forever */ + while (1) { + if (tud_task_event_ready()) + { + tud_task(); // tinyusb device task + read_task(); // cdc read task + webserial_read_task(); // webusb read task + midi_task(); // midi read task + // sysex_task(); // sysex read task + /* midi_task(); */ // Disabled for now + } + } +} + + +/*-------------------------------------------------------------------- + * SETUP FUNCTIONS + *--------------------------------------------------------------------*/ + +void init_logging(void) +{ + #if defined(USBSID_UART) + uint baud_rate = 115200; + int tx_pin = TX; + int rx_pin = RX; + stdio_uart_init_full(uart0, + baud_rate, + tx_pin, + rx_pin); + sleep_ms(100); /* leave time for uart to settle */ + stdio_flush(); + #endif +} + +void init_memory(void) +{ + for (unsigned int i = 0; i < 65536; i++) { + memory[i] = 0x00; // fill with NOPs + } +} + +void square_wave(void) +{ + PIO pio = pio0; + uint offset = pio_add_program(pio, &clock_program); + uint sm = pio_claim_unused_sm(pio, true); + // clock_program_init(pio, sm, offset, PHI, 30.0f); // 2.08Mhz @ GPIO22 + // clock_program_init(pio, sm, offset, PHI, 60.0f); // 1.04Mhz with clock at 125Mhz and 300Mhz @ GPIO22 + clock_program_init(pio, sm, offset, PHI, 62.5f); // 1.00Mhz with clock at 125Mhz and 300Mhz @ GPIO22 +} + +int detect_clock(void) +{ + uint32_t b; + extern uint32_t *BUSState; + int c = 0, r = 0; + gpio_init(PHI); + gpio_set_pulls(PHI, false, true); + for (int i = 0; i < 20; i++) + { + extern uint32_t *BUSState; + b = *BUSState; /* read complete bus */ + r |= c = (b & bPIN(PHI)) >> PHI; + } + return r; /* 1 if clock detected */ +} + +#if defined(USE_RGB) +void init_rgb(void) +{ + // ws2812_sm = initProgramWS2812(); + // pio_sm_put(pio1, ws2812_sm, 0xffffff); + // pio_sm_put( pio1, ws2812_sm, 0 ); + r_ = g_ = b_ = 0; + + PIO pio = pio1; + ws2812_sm = pio_claim_unused_sm(pio, true); + uint offset = pio_add_program(pio, &ws2812_program); + ws2812_program_init(pio, ws2812_sm, offset, WS2812_PIN, 800000, IS_RGBW); + put_pixel(urgb_u32(0,0,0)); +} + +int detect_smps(void) +{ // TODO: FINISH! + rgb_mode = false; + return !(gpio_get(24)); +} +#endif + + +/*-------------------------------------------------------------------- + * BUFFER TASKS + *--------------------------------------------------------------------*/ + +void handle_buffer_task(unsigned char buff[4]) +{ + // pause = ((buff[0] & 0x80) >> 7); + // reset = ((buff[0] & 0x40) >> 6); + // rw = ((buff[0] & 0x10) >> 4); + addr = ((buff[1] << 8) | buff[2]); + val = (buff[3] & 0xFF); + memory[addr] = val; + + + /* Set address for SID no# */ + addr = sid_address(addr); + + + switch (buff[0]) + { + // if (pause == 1) + // { + case PAUSE: + pauseSID(); + break; + // } + // if (reset == 1) + // { /* ignore everything else if reset is true */ + case RESET_SID: + resetSID(); + break; + // } + // switch (rw) + // { + case RESET_USB: + RESET_PICO(); + break; + case READ: + memset(data, 0, sizeof data); /* clear write buffer */ + memset(rdata, 0, sizeof rdata); /* clear read buffer */ + rdata[0] = readSID(addr); /* read the SID datapins */ + for (int i = 0; i < sizeof(data); i++) + { + // data[i] = readSID(addr); /* assign the result to teach of the 4 bytes */ + data[i] = rdata[0]; /* assign the result to teach of the 4 bytes */ + } + /* write the result to the USB client */ + if (dtype == cdc) { + write_task(); + } else if (dtype == wusb) { + webserial_write_task(); + } + break; + case WRITE: + writeSID(addr, val); /* write the address and value to the SID */ + break; + default: + break; + } + return; +} + +void handle_asidbuffer_task(uint8_t a, uint8_t d) +{ + dtype = asid; + addr = (0xD400 | a); + val = d; + memory[addr] = val; + addr = sid_address(addr); + writeSID(addr, val); + return; +} + + +/*-------------------------------------------------------------------- + * IO TASKS + *--------------------------------------------------------------------*/ + +void read_task(void) +{ + if (tud_cdc_connected()) { + while (tud_cdc_available()) { /* NOTICE: If disabled this breaks the Pico USB availability */ + usbdata = 1; + read = tud_cdc_read(buffer, sizeof(buffer)); /* Read data from client */ + tud_cdc_read_flush(); + if (read == 4) { /* 4 as in exactly 4 bytes */ + dtype = cdc; + handle_buffer_task(buffer); + } + memset(buffer, 0, sizeof buffer); /* clear read buffer */ + } + } +} + +void write_task(void) +{ + if (tud_cdc_connected()) { + if (tud_cdc_write_available()) { /* NOTICE: Still testing */ // <- this uses IF instead of WHILE on purpose to avoid a write back loop + write = tud_cdc_write(data, sizeof(data)); /* write data to client */ + // write = tud_cdc_write(rxdata, sizeof(rxdata)); /* write data to client */ + tud_cdc_write_flush(); + memset(data, 0, sizeof data); /* clear write buffer */ + } + } +} + +void webserial_read_task(void) +{ + if ( web_serial_connected ) + { + while ( tud_vendor_available() ) { + usbdata = 1; + read = tud_vendor_read(buffer, sizeof(buffer)); + tud_vendor_read_flush(); + if (read == 4) { /* 4 as in exactly 4 bytes */ + // NOTE: This 4 byte minimum renders any webserial test useless + dtype = wusb; + handle_buffer_task(buffer); + } + memset(buffer, 0, sizeof buffer); /* clear read buffer */ + } + } +} + +void webserial_write_task(void) +{ + if (web_serial_connected) { + // if (tud_vendor_available()) { + if (tud_vendor_mounted()) { + write = tud_vendor_write(data, sizeof(data)); + tud_vendor_flush(); + memset(data, 0, sizeof data); /* clear write buffer */ + } + } +} + +void midi_task(void) +{ + while ( tud_midi_n_available(0, 0) ) { + usbdata = 1; + // tud_midi_packet_read(sysexMachine.packetbuffer); /* WORKS ~ PACKET IS 4 BYTES */ + // tud_midi_stream_read(midimachine.readbuffer, 1); /* WORKS ~ Handles 2 Byte at a time */ + // memset(midimachine.streambuffer, 0, sizeof midimachine.streambuffer); + + /* Working stream config */ + // int s = tud_midi_stream_read(midimachine.streambuffer, sizeof(midimachine.streambuffer)); /* WORKS ~ STREAM IS WHAT EVER THE MAX USB BUFFER SIZE IS! */ + // process_stream(midimachine.streambuffer); + + + /* Working config */ + int s = tud_midi_n_stream_read(0, 0, midimachine.readbuffer, 1); /* WORKS ~ Handles 1 Byte at a time */ + process_buffer(midimachine.readbuffer[0]); + } +} + +// void midi_task(void) +// { +// while ( tud_midi_n_available(1, 0) ) { +// usbdata = 1; +// /* Working packet config */ +// int p = tud_midi_n_packet_read(1, midimachine.packetbuffer); /* WORKS ~ PACKET IS 4 BYTES */ + +// } +// } + + +/*-------------------------------------------------------------------- + * LED TASKS + *--------------------------------------------------------------------*/ + +void led_vuemeter_task(void) +{ + uint8_t osc1, osc2, osc3, osc4, osc5, osc6; + osc1 = (memory[0xD400] * 0.596); /* Frequency in Hz of SID1 @ $D400 Oscillator 1 */ + osc2 = (memory[0xD407] * 0.596); /* Frequency in Hz of SID1 @ $D400 Oscillator 2 */ + osc3 = (memory[0xD40E] * 0.596); /* Frequency in Hz of SID1 @ $D400 Oscillator 3 */ + #if defined(USE_RGB) + osc4 = (memory[0xD420] * 0.596); /* Frequency in Hz of SID2 @ $D400 Oscillator 1 */ + osc5 = (memory[0xD427] * 0.596); /* Frequency in Hz of SID2 @ $D400 Oscillator 2 */ + osc6 = (memory[0xD42E] * 0.596); /* Frequency in Hz of SID2 @ $D400 Oscillator 3 */ + #endif + + vue = abs((osc1 + osc2 + osc3) / 3.0f); + vue = map(vue, 0, HZ_MAX, 0, VUE_MAX); + + pwm_set_gpio_level(BUILTIN_LED, vue); + + #if defined(USE_RGB) + if (rgb_mode) { // TODO: r_, g_ and b_ should be the modulo of the additions + r_ = 0, g_ = 0, b_ = 0, o1 = 0, o2 = 0, o3 = 0, o4 = 0, o5 = 0, o6 = 0; + o1 = map(osc1, 0, 255, 0, 43); + o2 = map(osc2, 0, 255, 0, 43); + o3 = map(osc3, 0, 255, 0, 43); + o4 = map(osc4, 0, 255, 0, 43); + o5 = map(osc5, 0, 255, 0, 43); + o6 = map(osc6, 0, 255, 0, 43); + r_ += color_LUT[o1][0][0] + color_LUT[o2][1][0] + color_LUT[o3][2][0] + color_LUT[o4][3][0] + color_LUT[o5][4][0] + color_LUT[o6][5][0] ; + g_ += color_LUT[o1][0][1] + color_LUT[o2][1][1] + color_LUT[o3][2][1] + color_LUT[o4][3][1] + color_LUT[o5][4][1] + color_LUT[o6][5][1] ; + b_ += color_LUT[o1][0][2] + color_LUT[o2][1][2] + color_LUT[o3][2][2] + color_LUT[o4][3][2] + color_LUT[o5][4][2] + color_LUT[o6][5][2] ; + put_pixel(urgb_u32(rgbb(r_), rgbb(g_), rgbb(b_))); + } + #endif +} + +void led_breathe_task(void) +{ + static uint32_t start_ms = 0; + + if ( board_millis() - start_ms < breathe_interval_ms) return; /* not enough time */ + start_ms += breathe_interval_ms; + + if (pwm_value >= VUE_MAX) + updown = 0; + if (pwm_value <= 0) + updown = 1; + + if (updown == 1 && pwm_value < VUE_MAX) + pwm_value += BREATHE_STEP; + + if (updown == 0 && pwm_value > 0) + pwm_value -= BREATHE_STEP; + pwm_set_gpio_level(BUILTIN_LED, pwm_value); + #if defined(USE_RGB) + if (rgb_mode) put_pixel(urgb_u32(0,0,0)); + #endif +} + + +/*-------------------------------------------------------------------- + * SUPPORTING FUNCTIOMS + *--------------------------------------------------------------------*/ + +uint16_t sid_address(uint16_t addr) +{ /* Set address for SID no# */ + /* D500, DE00 or DF00 is the second sid in SIDTYPE1, 3 & 4 */ + /* D500, DE00 or DF00 is the third sid in all other SIDTYPES */ + if (((addr) & SIDUMASK) != SID1ADDR) { /* Not in $D400 range but in D5, DE or DF */ + switch (SIDTYPES) { + case SIDTYPE1: + case SIDTYPE2: + addr = (SID2ADDR | (addr & SID1MASK)); + break; + case SIDTYPE3: + addr = (SID3ADDR | (addr & SID1MASK)); + break; + case SIDTYPE4: + addr = (SID3ADDR | (addr & SID2MASK)); + break; + case SIDTYPE5: /* UNTESTED */ + addr = (SID1ADDR | (addr & SIDUMASK)); + } + } else { /* $D400 */ + switch (SIDTYPES) { + case SIDTYPE1: + case SIDTYPE3: + case SIDTYPE4: + addr; /* $D400 -> $D479 */ + break; + case SIDTYPE2: + addr = ((addr & SIDLMASK) >= 0x20) ? (SID2ADDR | (addr & SID1MASK)) : addr ; /* */ + break; + case SIDTYPE5: + addr = + ((addr & SIDLMASK) >= 0x20) && ((addr & SIDLMASK) <= 0x39) /* $D420~$D439 -> $D440~$D459 */ + ? (addr + 0x20) + : ((addr & SIDLMASK) >= 0x40) && ((addr & SIDLMASK) <= 0x59) /* $D440~$D459 -> $D420~$D439 */ + ? (addr - 0x20) : addr; + break; + } + } + return addr; +} + +long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +#if defined(USE_RGB) +void put_pixel(uint32_t pixel_grb) { + pio_sm_put_blocking(pio1, ws2812_sm, pixel_grb << 8u); +} + +uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t) (r) << 8) | ((uint32_t) (g) << 16) | (uint32_t) (b); +} + +uint8_t rgbb(double inp) +{ + int r = abs((inp / 255) * BRIGHTNESS); + return (uint8_t)r; +}; +#endif + + +/*-------------------------------------------------------------------- + * DEVICE CALLBACKS + *--------------------------------------------------------------------*/ + +void tud_mount_cb(void) +{ + // NOTE: Disabled for now, causes dual volume write with a CLICK sound on PWM + // enableSID(); /* NOTICE: Testing if this is causing the random lockups */ +} + +void tud_umount_cb(void) +{ + // NOTE: Disabled for now, causes dual volume write with a CLICK sound on PWM + // disableSID(); /* NOTICE: Testing if this is causing the random lockups */ +} + +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + tud_mounted() ? enableSID() : disableSID(); /* NOTICE: Testing if this is causing the random lockups */ +} + +void tud_resume_cb(void) +{ + tud_mounted() ? enableSID() : disableSID(); /* NOTICE: Testing if this is causing the random lockups */ +} + + +/*-------------------------------------------------------------------- + * CDC CALLBACKS + *--------------------------------------------------------------------*/ + +void tud_cdc_rx_cb(uint8_t itf) +{ + (void)itf; +} + +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) +{ + (void)itf; + (void)wanted_char; +} + +void tud_cdc_tx_complete_cb(uint8_t itf) +{ + (void)itf; +} + +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) +{ + (void) itf; + (void) rts; + + if ( dtr ) { + /* Terminal connected */ + usbdata = 1; + } + else + { + /* Terminal disconnected */ + breathe_interval_ms = BREATHE_ON; + usbdata = 0; /* ISSUE: Led breather is not invoked again */ + } +} + +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding) +{ + (void)itf; + (void)p_line_coding; +} + +void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) +{ + (void)itf; + (void)duration_ms; +} + + +/*-------------------------------------------------------------------- + * WEBUSB VENDOR CLASS CALLBACKS + *--------------------------------------------------------------------*/ + +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // nothing to with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) return true; + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_VENDOR: + switch (request->bRequest) { + case VENDOR_REQUEST_WEBUSB: + // match vendor request in BOS descriptor + // Get landing page url + // return tud_control_xfer(rhport, request, (void*)(uintptr_t) &desc_url, desc_url.bLength); /* Do we want this? */ + return tud_control_status(rhport, request); + case VENDOR_REQUEST_MICROSOFT: + if ( request->wIndex == 7 ) { + // Get Microsoft OS 2.0 compatible descriptor + uint16_t total_len; + memcpy(&total_len, desc_ms_os_20+8, 2); + + return tud_control_xfer(rhport, request, (void*)(uintptr_t) desc_ms_os_20, total_len); + // return true; + } else { + return false; + } + default: break; + } + break; + case TUSB_REQ_TYPE_CLASS: + if (request->bRequest == 0x22) { + // Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to connect and disconnect. + web_serial_connected = (request->wValue != 0); + + // Always lit LED if connected + if (web_serial_connected) { + // board_led_write(true); + // blink_interval_ms = BLINK_ALWAYS_ON; + + /* tud_vendor_write_str("\r\nWebUSB interface connected\r\n"); */ + // tud_vendor_write_flush(); + usbdata = 1; + } else { + // blink_interval_ms = BLINK_MOUNTED; + } + // response with status OK + return tud_control_status(rhport, request); + } + break; + default: + break; + } + + // stall unknown request + return false; +} diff --git a/src/usbsid.h b/src/usbsid.h new file mode 100644 index 0000000..5517da5 --- /dev/null +++ b/src/usbsid.h @@ -0,0 +1,461 @@ +/* + * USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two + * MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, + * phone or ASID supporting player + * + * usbsid.h + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * Copyright (c) 2024 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _USBSID_H_ +#define _USBSID_H_ +#pragma once + + +#include +#include +#include +#include + +#include "pico/stdlib.h" +#include "pico/types.h" /* absolute_time_t */ +// #define __STDC_FORMAT_MACROS +// #include +#include "pico/multicore.h" /* Multicore */ +#include "hardware/pio.h" /* Programmable I/O (PIO) API */ +#include "hardware/clocks.h" +// #include "hardware/irq.h" /* Hardware interrupt handling */ +// #include "hardware/adc.h" /* Analog to Digital Converter (ADC) API */ + +#include "bsp/board.h" /* Tiny USB Boards Abstraction Layer */ +#include "tusb.h" /* Tiny USB stack */ +#include "tusb_config.h" /* Tiny USB configuration */ + +#include "clock.pio.h" /* Square wave generator */ +#if defined(USE_RGB) +#include "ws2812.pio.h" /* RGB led handler */ +#endif + +/* Debugging enabling overrides */ +/* #define USBSID_UART */ +/* #define MEM_DEBUG */ +/* #define USBSID_DEBUG */ +/* #define ADDR_DEBUG */ +/* #define USBIO_DEBUG */ +/* #define ASID_DEBUG */ +/* #define USBSIDGPIO_DEBUG */ + +#include "mcu.h" /* rp2040 mcu */ +#include "gpio.h" /* GPIO configuration */ +#include "midi.h" /* Midi */ +#include "asid.h" /* ASID */ +#include "sid.h" /* SID */ + +#ifdef __cplusplus + extern "C" { +#endif + + +/* SIDTYPES ~ Defaults to 0 + * + * 0: Single SID config + * SID socket 1 filled with either of + * - MOS6581/MOS6582/MOS8580 + * - SIDKickPico + * - SwinSID + * - any other single SID hardware emulator + * - NOTE: Will use the second SID socket simultaniously + * 1: Single SID config (dual optional with 1x SIDKickPico) + * SID socket 1 filled with either of + * - SIDKickPico (A5 connected to A5 for dual SID) + * - MOS6581/MOS6582/MOS8580 + * - SwinSID + * - any other single SID hardware emulator + * 2: Dual SID config + * SID sockets 1 & 2 filled with either of + * - MOS6581/MOS6582/MOS8580 + * - SIDKickPico + * - SwinSID + * - any other single SID hardware emulator + * 3: Triple SID config with 1x SIDKickPico and other + * SID socket 1 filled with a + * - SIDKickPico (A5 connected to A5 for dual SID) + * SID socket 2 filled with either of + * - MOS6581/MOS6582/MOS8580 + * - SIDKickPico + * - SwinSID + * - any other single SID hardware emulator + * 4: Quad SID config with 2x SIDKickPico + * SID socket 1 filled with a + * - SIDKickPico (A5 connected to A5 for dual SID) + * SID socket 2 filled with a + * - SIDKickPico (A5 connected to A5 for dual SID) + * 5: Quad SID mixed config with 2x SIDKickPico + * SID socket 1 filled with a + * - SIDKickPico (A5 connected to A5 for dual SID) + * - Acts as SID 1 & 3 + * SID socket 2 filled with a + * - SIDKickPico (A5 connected to A5 for dual SID) + * - Acts as SID 2 & 4 + * + */ +#define SIDTYPE0 0 +#define SIDTYPE1 1 +#define SIDTYPE2 2 +#define SIDTYPE3 3 +#define SIDTYPE4 4 +#define SIDTYPE5 5 + +/* Default config for SID type */ +#ifndef SIDTYPES +#define SIDTYPES SIDTYPE0 +#endif + +/* SID type masks for GPIO */ +#define SIDUMASK 0xFF00 +#define SIDLMASK 0xFF +#if SIDTYPES == SIDTYPE0 +#define NUMSIDS 1 +#define SID1ADDR 0xD400 /* SID 1 - socket 1 */ +#define SID1MASK 0x1F +#define SID2ADDR 0x0 +#define SID2MASK 0x0 +#define SID3ADDR 0x0 +#define SID3MASK 0x0 +#define SID4ADDR 0x0 +#define SID4MASK 0x0 +#elif SIDTYPES == SIDTYPE1 +#define NUMSIDS 2 +#define SID1ADDR 0xD400 /* SID 1 - socket 1 */ +#define SID1MASK 0x1F +#define SID2ADDR 0xD420 /* SID 2 - socket 1 ~ OPTIONAL */ +#define SID2MASK 0x3F +#define SID3ADDR 0x0 +#define SID3MASK 0x0 +#define SID4ADDR 0x0 +#define SID4MASK 0x0 +#elif SIDTYPES == SIDTYPE2 +#define NUMSIDS 2 +#define SID1ADDR 0xD400 /* SID 1 - socket 1 */ +#define SID1MASK 0x1F +#define SID2ADDR 0xD440 /* SID 2 - socket 2 */ +#define SID2MASK 0x5F +#define SID3ADDR 0x0 +#define SID3MASK 0x0 +#define SID4ADDR 0x0 +#define SID4MASK 0x0 +#elif SIDTYPES == SIDTYPE3 +#define NUMSIDS 3 +#define SID1ADDR 0xD400 /* SID 1 - socket 1 */ +#define SID1MASK 0x1F +#define SID2ADDR 0xD420 /* SID 2 - socket 1 */ +#define SID2MASK 0x3F +#define SID3ADDR 0xD440 /* SID 3 - socket 2 */ +#define SID3MASK 0x5F +#define SID4ADDR 0x0 +#define SID4MASK 0x0 +#elif SIDTYPES == SIDTYPE4 /* SKPico only */ +#define NUMSIDS 4 +#define SID1ADDR 0xD400 /* SID 1 - socket 1 */ +#define SID1MASK 0x1F +#define SID2ADDR 0xD420 /* SID 2 - socket 1 */ +#define SID2MASK 0x3F +#define SID3ADDR 0xD440 /* SID 3 - socket 2 */ +#define SID3MASK 0x5F +#define SID4ADDR 0xD460 /* SID 4 - socket 2 */ +#define SID4MASK 0x7F +#elif SIDTYPES == SIDTYPE5 /* SKPico only */ +#define NUMSIDS 4 +#define SID1ADDR 0xD400 /* SID 1 - socket 1 */ +#define SID1MASK 0x1F +#define SID2ADDR 0xD440 /* SID 2 - socket 2 */ +#define SID2MASK 0x5F +#define SID3ADDR 0xD420 /* SID 3 - socket 1 */ +#define SID3MASK 0x3F +#define SID4ADDR 0xD460 /* SID 4 - socket 2 */ +#define SID4MASK 0x7F +#endif + + +/* Global vars */ + +enum +{ + WRITE = 0, + READ, + PAUSE, + RESET_SID, + RESET_USB +}; + +enum /* LED breathe levels */ +{ + ALWAYS_OFF = 99999, + ALWAYS_ON = 0, + BREATHE_ON = 1, + BREATHE_STEP = 100, + VUE_MAX = 65534, + HZ_MAX = 40 +}; + +extern uint8_t memory[65536]; +extern uint16_t vue; +static uint32_t breathe_interval_ms = BREATHE_ON; +static uint32_t read = 0, write = 0; +static intptr_t usbdata = 0, pwm_value = 0, updown = 1; +static uintptr_t pause, reset, rw, addr, val; +static char dtype = 'E', cdc = 'C', asid = 'A', midi = 'M', wusb = 'W'; + +// static uint64_t read_uS, write_uS; + +#if defined(USE_RGB) /* RGB LED */ + +#define IS_RGBW false +#define BRIGHTNESS 127 /* Half of max brightness */ + +static bool rgb_mode; +static uint ws2812_sm; +static uint8_t r_ = 0, g_ = 0, b_ = 0; +static unsigned char o1 = 0, o2 = 0, o3 = 0, o4 = 0, o5 = 0, o6 = 0; +static const __not_in_flash( "mydata" ) unsigned char color_LUT[ 43 ][ 6 ][ 3 ] = +{ + /* Red Green Blue Yellow Cyan Purple*/ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + {{6, 0, 0}, {0, 6, 0}, {0, 0, 6}, {6, 6, 0}, {0, 6, 6}, {6, 0, 6}}, + {{12, 0, 0}, {0, 12, 0}, {0, 0, 12}, {12, 12, 0}, {0, 12, 12}, {12, 0, 12}}, + {{18, 0, 0}, {0, 18, 0}, {0, 0, 18}, {18, 18, 0}, {0, 18, 18}, {18, 0, 18}}, + {{24, 0, 0}, {0, 24, 0}, {0, 0, 24}, {24, 24, 0}, {0, 24, 24}, {24, 0, 24}}, + {{30, 0, 0}, {0, 30, 0}, {0, 0, 30}, {30, 30, 0}, {0, 30, 30}, {30, 0, 30}}, + {{36, 0, 0}, {0, 36, 0}, {0, 0, 36}, {36, 36, 0}, {0, 36, 36}, {36, 0, 36}}, + {{42, 0, 0}, {0, 42, 0}, {0, 0, 42}, {42, 42, 0}, {0, 42, 42}, {42, 0, 42}}, + {{48, 0, 0}, {0, 48, 0}, {0, 0, 48}, {48, 48, 0}, {0, 48, 48}, {48, 0, 48}}, + {{54, 0, 0}, {0, 54, 0}, {0, 0, 54}, {54, 54, 0}, {0, 54, 54}, {54, 0, 54}}, + {{60, 0, 0}, {0, 60, 0}, {0, 0, 60}, {60, 60, 0}, {0, 60, 60}, {60, 0, 60}}, + {{66, 0, 0}, {0, 66, 0}, {0, 0, 66}, {66, 66, 0}, {0, 66, 66}, {66, 0, 66}}, + {{72, 0, 0}, {0, 72, 0}, {0, 0, 72}, {72, 72, 0}, {0, 72, 72}, {72, 0, 72}}, + {{78, 0, 0}, {0, 78, 0}, {0, 0, 78}, {78, 78, 0}, {0, 78, 78}, {78, 0, 78}}, + {{84, 0, 0}, {0, 84, 0}, {0, 0, 84}, {84, 84, 0}, {0, 84, 84}, {84, 0, 84}}, + {{90, 0, 0}, {0, 90, 0}, {0, 0, 90}, {90, 90, 0}, {0, 90, 90}, {90, 0, 90}}, + {{96, 0, 0}, {0, 96, 0}, {0, 0, 96}, {96, 96, 0}, {0, 96, 96}, {96, 0, 96}}, + {{102, 0, 0}, {0, 102, 0}, {0, 0, 102}, {102, 102, 0}, {0, 102, 102}, {102, 0, 102}}, + {{108, 0, 0}, {0, 108, 0}, {0, 0, 108}, {108, 108, 0}, {0, 108, 108}, {108, 0, 108}}, + {{114, 0, 0}, {0, 114, 0}, {0, 0, 114}, {114, 114, 0}, {0, 114, 114}, {114, 0, 114}}, + {{120, 0, 0}, {0, 120, 0}, {0, 0, 120}, {120, 120, 0}, {0, 120, 120}, {120, 0, 120}}, + {{126, 0, 0}, {0, 126, 0}, {0, 0, 126}, {126, 126, 0}, {0, 126, 126}, {126, 0, 126}}, + {{132, 0, 0}, {0, 132, 0}, {0, 0, 132}, {132, 132, 0}, {0, 132, 132}, {132, 0, 132}}, + {{138, 0, 0}, {0, 138, 0}, {0, 0, 138}, {138, 138, 0}, {0, 138, 138}, {138, 0, 138}}, + {{144, 0, 0}, {0, 144, 0}, {0, 0, 144}, {144, 144, 0}, {0, 144, 144}, {144, 0, 144}}, + {{150, 0, 0}, {0, 150, 0}, {0, 0, 150}, {150, 150, 0}, {0, 150, 150}, {150, 0, 150}}, + {{156, 0, 0}, {0, 156, 0}, {0, 0, 156}, {156, 156, 0}, {0, 156, 156}, {156, 0, 156}}, + {{162, 0, 0}, {0, 162, 0}, {0, 0, 162}, {162, 162, 0}, {0, 162, 162}, {162, 0, 162}}, + {{168, 0, 0}, {0, 168, 0}, {0, 0, 168}, {168, 168, 0}, {0, 168, 168}, {168, 0, 168}}, + {{174, 0, 0}, {0, 174, 0}, {0, 0, 174}, {174, 174, 0}, {0, 174, 174}, {174, 0, 174}}, + {{180, 0, 0}, {0, 180, 0}, {0, 0, 180}, {180, 180, 0}, {0, 180, 180}, {180, 0, 180}}, + {{186, 0, 0}, {0, 186, 0}, {0, 0, 186}, {186, 186, 0}, {0, 186, 186}, {186, 0, 186}}, + {{192, 0, 0}, {0, 192, 0}, {0, 0, 192}, {192, 192, 0}, {0, 192, 192}, {192, 0, 192}}, + {{198, 0, 0}, {0, 198, 0}, {0, 0, 198}, {198, 198, 0}, {0, 198, 198}, {198, 0, 198}}, + {{204, 0, 0}, {0, 204, 0}, {0, 0, 204}, {204, 204, 0}, {0, 204, 204}, {204, 0, 204}}, + {{210, 0, 0}, {0, 210, 0}, {0, 0, 210}, {210, 210, 0}, {0, 210, 210}, {210, 0, 210}}, + {{216, 0, 0}, {0, 216, 0}, {0, 0, 216}, {216, 216, 0}, {0, 216, 216}, {216, 0, 216}}, + {{222, 0, 0}, {0, 222, 0}, {0, 0, 222}, {222, 222, 0}, {0, 222, 222}, {222, 0, 222}}, + {{228, 0, 0}, {0, 228, 0}, {0, 0, 228}, {228, 228, 0}, {0, 228, 228}, {228, 0, 228}}, + {{234, 0, 0}, {0, 234, 0}, {0, 0, 234}, {234, 234, 0}, {0, 234, 234}, {234, 0, 234}}, + {{240, 0, 0}, {0, 240, 0}, {0, 0, 240}, {240, 240, 0}, {0, 240, 240}, {240, 0, 240}}, + {{246, 0, 0}, {0, 246, 0}, {0, 0, 246}, {246, 246, 0}, {0, 246, 246}, {246, 0, 246}}, + {{252, 0, 0}, {0, 252, 0}, {0, 0, 252}, {252, 252, 0}, {0, 252, 252}, {252, 0, 252}} +}; +#endif + + +/* WebUSB Vars */ + +enum +{ + VENDOR_REQUEST_WEBUSB = 1, + VENDOR_REQUEST_MICROSOFT = 2 +}; + +extern uint8_t const desc_ms_os_20[]; + +#define URL "github.com/LouDnl/USBSID-Pico" + +// extern const tusb_desc_webusb_url_t desc_url; + +static const tusb_desc_webusb_url_t desc_url = +{ + .bLength = 3 + sizeof(URL) - 1, + .bDescriptorType = 3, // WEBUSB URL type + .bScheme = 1, // 0: http, 1: https + .url = URL +}; + +static bool web_serial_connected = false; + + +/* Buffers */ + +/* Incoming USB data buffer + * + * 4 bytes + * Byte 0 ~ command byte + * Byte 1 ~ address range byte + * Byte 2 ~ address destination byte + * Byte 3 ~ data byte + * + * 4 byte build up MSB -> LSB: +`* Byte 0 : see enum above + * Byte 1 : address range e.g. $D4 + * Byte 2 : address e.g. $1C + * Byte 3 : value to write e.g. $FF + * + * Example MSB -> LSB: + * 0x00D4181A + * Byte 0 0x10 ~ 0b00010000 no reset, write mode + * Byte 1 0xD4 ~ address range $D400 ~ $DFFF + * Byte 2 0x18 ~ address + * Byte 3 0x1A ~ value to write +*/ +static unsigned char buffer[4]; + +/* Outgoing USB data buffer + * + * 4 bytes: + * byte 0 : value to return + * byte 1~3 : copies of value to return + */ +static unsigned char data[4]; + +/* SID read data buffer + * + * Exactly 1 byte with data pin values: + * byte 0 : value read from the SID + */ +static unsigned char rdata[1]; + + +/* Setup */ + +/* Initialize debug logging if enabled + * WARNING: any debug logging other then SID memory + * _WILL_ slow down SID play + */ +void init_logging(void); +/* Create fake 16bit memory filled with nops */ +void init_memory(void); +/* Init 1MHz square wave output */ +void square_wave(void); +/* Detect clock signal */ +int detect_clock(void); +#if defined(USE_RGB) +/* Init the RGB LED pio */ +void init_rgb(void); +/* Experimental function for detecting the SMPS + * type on GPIO23/GPIO24 */ +int detect_smps(void); +#endif + + +/* Buffer tasks */ + +/* Handle the received data */ +void handle_buffer_task(unsigned char buff[4]); +/* Handle the received Sysex ASID data */ +void handle_asidbuffer_task(uint8_t a, uint8_t d); + + +/* IO Tasks */ + +/* Read from host to device */ +void read_task(void); +/* Write from device to host */ +void write_task(void); +/* Read from host to device */ +void webserial_read_task(void); +/* Write from device to host */ +void webserial_write_task(void); +/* Handle incoming midi data */ +void midi_task(void); + + +/* LED tasks */ + +/* It goes up and it goes down */ +void led_vuemeter_task(void); +/* Mouth breather! */ +void led_breathe_task(void); + + +/* Supporting functions */ + +/* Address conversion */ +uint16_t sid_address(uint16_t addr); +/* Map n in range x to range y + * Source: https://stackoverflow.com/a/61000581 */ +long map(long x, long in_min, long in_max, long out_min, long out_max); +#if defined(USE_RGB) +/* Write a single RGB pixel */ +void put_pixel(uint32_t pixel_grb); +/* Generate uint32_t with RGB colors */ +uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b); +/* Generate RGB brightness */ +uint8_t rgbb(double inp); +#endif + + +/* Device callbacks */ + +/* Invoked when device is mounted */ +void tud_mount_cb(void); +/* Invoked when device is unmounted */ +void tud_umount_cb(void); +/* Invoked when usb bus is resumed */ +void tud_suspend_cb(bool remote_wakeup_en); +/* Invoked when usb bus is suspended + * remote_wakeup_en : if host allow us to perform remote wakeup + * Within 7ms, device must draw an average of current less than 2.5 mA from bus + */ +void tud_resume_cb(void); + + +/* CDC callbacks */ + +/* Invoked when received new data */ +void tud_cdc_rx_cb(uint8_t itf); +/* Invoked when received `wanted_char` */ +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); +/* Invoked when last rx transfer finished */ +void tud_cdc_tx_complete_cb(uint8_t itf); +/* Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE */ +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); +/* Invoked when line coding is change via SET_LINE_CODING */ +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding); +/* Invoked when received send break */ +void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms); + + +/* WebUSB Vendor callbacks */ + +/* Invoked when received control request with VENDOR TYPE */ +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request); + +#ifdef __cplusplus + } +#endif + +#endif /* _USBSID_H_ */ diff --git a/src/ws2812.pio b/src/ws2812.pio new file mode 100644 index 0000000..747da5a --- /dev/null +++ b/src/ws2812.pio @@ -0,0 +1,71 @@ +; +; USBSID-Pico is a RPi Pico (RP2040) based board for interfacing one or two +; MOS SID chips and/or hardware SID emulators over (WEB)USB with your computer, +; phone or ASID supporting player +; +; ws2812.pio +; This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) +; File author: LouD +; +; Source: https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812.pio +; +; Any licensing conditions from the above named source automatically apply to this code +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, version 2. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +; Description: +; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is +; empty. Write the least significant bit to the OUT pin group. +; +.program ws2812 +.side_set 1 + +.define public T1 3 +.define public T2 3 +.define public T3 4 + +.lang_opt python sideset_init = pico.PIO.OUT_HIGH +.lang_opt python out_init = pico.PIO.OUT_HIGH +.lang_opt python out_shiftdir = 1 + +.wrap_target +bitloop: + out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [T2 - 1] ; Or drive low, for a short pulse +.wrap + +% c-sdk { +#include "hardware/clocks.h" + +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) { + + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%}