├── .DS_Store ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── extensions.json ├── launch.json └── settings.json ├── CMakeLists.txt ├── README.md ├── build └── .cmake │ └── api │ └── v1 │ ├── query │ └── client-vscode │ │ └── query.json │ └── reply │ └── directory-.-Debug-f5ebdc15457944623624.json ├── cmake_install.cmake ├── conference_mode.c ├── conference_mode.h ├── dev_lowlevel.c ├── dev_lowlevel.h ├── differential_manchester.pio ├── display.c ├── display.h ├── dnvt-switch.c ├── dnvt-switch.h ├── font.h ├── host ├── Makefile ├── README.md ├── busy.cvs ├── dialtone.cvs ├── dialtone2.cvs ├── main.c ├── rick.cvs └── ringtone.cvs ├── load_calculator.c ├── load_calculator.h ├── pico_sdk_import.cmake ├── process_phones.c ├── process_phones.h ├── recording.h ├── rickroll.h ├── ssd1306.c ├── ssd1306.h ├── usb_common.h └── usb_structures.h /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickandre/dnvt-fw/5825a58c6d3ef175236a2e1db233dd5b6638ec1f/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | CMakeCache.txt 3 | generated 4 | elf2uf2 5 | pico-sdk 6 | host/main 7 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${env:PICO_SDK_PATH}/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/arm-none-eabi-gcc", 11 | "cStandard": "gnu17", 12 | "cppStandard": "gnu++14", 13 | "intelliSenseMode": "linux-gcc-arm", 14 | "configurationProvider" : "ms-vscode.cmake-tools" 15 | } 16 | ], 17 | "version": 4 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "marus25.cortex-debug", 4 | "ms-vscode.cmake-tools", 5 | "ms-vscode.cpptools" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Cortex Debug", 9 | "cwd": "${workspaceRoot}", 10 | "executable": "${command:cmake.launchTargetPath}", 11 | "request": "launch", 12 | "type": "cortex-debug", 13 | "servertype": "openocd", 14 | "gdbPath": "gdb-multiarch", 15 | "device": "RP2040", 16 | "configFiles": [ 17 | "interface/picoprobe.cfg", 18 | "target/rp2040.cfg" 19 | ], 20 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 21 | "runToEntryPoint": "main", 22 | // Give restart the same functionality as runToEntryPoint - main 23 | "postRestartCommands": [ 24 | "break main", 25 | "continue" 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": true, 3 | "cmake.statusbar.advanced": { 4 | "debug" : { 5 | "visibility": "hidden" 6 | }, "launch" : { 7 | "visibility": "hidden" 8 | }, 9 | "build" : { 10 | "visibility": "hidden" 11 | }, 12 | "buildTarget" : { 13 | "visibility": "hidden" 14 | }, 15 | }, 16 | "files.associations": { 17 | "rickroll.h": "c", 18 | "differential_manchester.pio.h": "c" 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generated Cmake Pico project file 2 | 3 | cmake_minimum_required(VERSION 3.13) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | # Initialise pico_sdk from installed location 9 | # (note this can come from environment, CMake cache etc) 10 | set(PICO_SDK_PATH CACHE STRING "/home/nandre/src/pico-sdk") 11 | 12 | set(PICO_BOARD pico CACHE STRING "Board type") 13 | 14 | # Pull in Raspberry Pi Pico SDK (must be before project) 15 | include(pico_sdk_import.cmake) 16 | 17 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") 18 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") 19 | endif() 20 | 21 | project(dnvt-switch C CXX ASM) 22 | 23 | # Initialise the Raspberry Pi Pico SDK 24 | pico_sdk_init() 25 | 26 | # Add executable. Default name is the project name, version 0.1 27 | 28 | add_executable(dnvt-switch dnvt-switch.c ssd1306.c process_phones.c display.c load_calculator.c dev_lowlevel.c) 29 | pico_generate_pio_header(dnvt-switch ${CMAKE_CURRENT_LIST_DIR}/differential_manchester.pio) 30 | 31 | pico_set_program_name(dnvt-switch "dnvt-switch") 32 | pico_set_program_version(dnvt-switch "0.1") 33 | 34 | pico_enable_stdio_uart(dnvt-switch 1) 35 | pico_enable_stdio_usb(dnvt-switch 0) 36 | target_compile_definitions(dnvt-switch PRIVATE 37 | PICO_DEFAULT_UART_TX_PIN=12 38 | PICO_DEFAULT_UART_BAUD_RATE=115200 39 | PICO_DEFAULT_UART=0 40 | ) 41 | 42 | # Add the standard library to the build 43 | target_link_libraries(dnvt-switch 44 | pico_stdlib) 45 | 46 | add_compile_options(-Wall 47 | -Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int 48 | -Wno-unused-function # we have some for the docs that aren't called 49 | -Wno-maybe-uninitialized 50 | ) 51 | 52 | # Add the standard include files to the build 53 | target_include_directories(dnvt-switch PRIVATE 54 | ${CMAKE_CURRENT_LIST_DIR} 55 | ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required 56 | ) 57 | 58 | # Add any user requested libraries 59 | target_link_libraries(dnvt-switch 60 | hardware_i2c 61 | hardware_pio 62 | pico_multicore 63 | hardware_resets 64 | hardware_irq 65 | pico_unique_id 66 | ) 67 | 68 | pico_add_extra_outputs(dnvt-switch) 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNVT Phone Switch 2 | 3 | This repo contains the code for the DNVT military phone switch. 4 | 5 | ## Release History 6 | 7 | ### Version 0.31 8 | 9 | - Fixes bug in connection to "unreachable" terminal (v 0.30 didn't present a connection failed tone) 10 | - shuffled a few of the states on the USB state mapping to better clarify "traffic" 11 | 12 | ### Version 0.30 13 | 14 | Initial public release. Includes usb-enabled firmware and host application. New features: 15 | 16 | - USB interrupt endpoint for control 17 | - Initial host application release 18 | - USB vendor specific control EP command to initiate firmware upload mode 19 | - Line supervision: lines without connected phone will show `---` on the display under status. The supervision check is run for all lines on boot and then every 10 minutes therafter. The switch initiates a "cue" command to the phone to prompt it to exit the idle state. If the phone replies we go back to idle. If the phone is determined idle, it will enter the "unreachable" state which will send a continuous cue command until receiving a reply. The 10 minute line supervisory check produces an audible click and visible blink and can be disabled by setting DIP switch 1 to the ON position. This will disable the periodic checking of idle phones, though the system will still do the initial check on boot and then mark phones as unreachable if a ring attempt fails. 20 | - Calling your own number in Line Simulator mode produces a "connection failed" repeated beep instead of doing nothing. 21 | - The connection has been refactored to queue to support tx and rx queues for USB mode. RX queue function similarly to rx_data in previous versions. 22 | - Several USB states added. 23 | - process phones switches behavior to USB states based upon usb activity. 24 | - USB mode on display now shows 'n/c' for not enumerated, 'inact' for inactive but enumerated, and 'activ' for actively detecting heartbeats from host application. 25 | - DIP switch code now does things. DIP 1 disables supervisory, DIP 2 now disables line simulator mode. 26 | -------------------------------------------------------------------------------- /build/.cmake/api/v1/query/client-vscode/query.json: -------------------------------------------------------------------------------- 1 | {"requests":[{"kind":"cache","version":2},{"kind":"codemodel","version":2},{"kind":"toolchains","version":1},{"kind":"cmakeFiles","version":1}]} -------------------------------------------------------------------------------- /build/.cmake/api/v1/reply/directory-.-Debug-f5ebdc15457944623624.json: -------------------------------------------------------------------------------- 1 | { 2 | "backtraceGraph" : 3 | { 4 | "commands" : [], 5 | "files" : [], 6 | "nodes" : [] 7 | }, 8 | "installers" : [], 9 | "paths" : 10 | { 11 | "build" : ".", 12 | "source" : "." 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cmake_install.cmake: -------------------------------------------------------------------------------- 1 | # Install script for directory: /home/nandre/src/dnvt-switch 2 | 3 | # Set the install prefix 4 | if(NOT DEFINED CMAKE_INSTALL_PREFIX) 5 | set(CMAKE_INSTALL_PREFIX "/usr/local") 6 | endif() 7 | string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") 8 | 9 | # Set the install configuration name. 10 | if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) 11 | if(BUILD_TYPE) 12 | string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" 13 | CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") 14 | else() 15 | set(CMAKE_INSTALL_CONFIG_NAME "Release") 16 | endif() 17 | message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") 18 | endif() 19 | 20 | # Set the component getting installed. 21 | if(NOT CMAKE_INSTALL_COMPONENT) 22 | if(COMPONENT) 23 | message(STATUS "Install component: \"${COMPONENT}\"") 24 | set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") 25 | else() 26 | set(CMAKE_INSTALL_COMPONENT) 27 | endif() 28 | endif() 29 | 30 | # Is this installation the result of a crosscompile? 31 | if(NOT DEFINED CMAKE_CROSSCOMPILING) 32 | set(CMAKE_CROSSCOMPILING "TRUE") 33 | endif() 34 | 35 | # Set default install directory permissions. 36 | if(NOT DEFINED CMAKE_OBJDUMP) 37 | set(CMAKE_OBJDUMP "/usr/bin/arm-none-eabi-objdump") 38 | endif() 39 | 40 | if(NOT CMAKE_INSTALL_LOCAL_ONLY) 41 | # Include the install script for each subdirectory. 42 | include("/home/nandre/src/dnvt-switch/pico-sdk/cmake_install.cmake") 43 | 44 | endif() 45 | 46 | if(CMAKE_INSTALL_COMPONENT) 47 | set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") 48 | else() 49 | set(CMAKE_INSTALL_MANIFEST "install_manifest.txt") 50 | endif() 51 | 52 | string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT 53 | "${CMAKE_INSTALL_MANIFEST_FILES}") 54 | file(WRITE "/home/nandre/src/dnvt-switch/${CMAKE_INSTALL_MANIFEST}" 55 | "${CMAKE_INSTALL_MANIFEST_CONTENT}") 56 | -------------------------------------------------------------------------------- /conference_mode.c: -------------------------------------------------------------------------------- 1 | 2 | void initialize_conference() { 3 | struct CONFERENCE *c; 4 | for (int i = 0; i < number_of_phones; i++) { 5 | c = conference_data + i; 6 | c->input_queue = malloc(sizeof(queue_t)); 7 | queue_init(c->input_queue, sizeof(u_int32_t), 3); 8 | c->previous_pcm = 0; 9 | c->coincidence_counter = 0; 10 | c->gain_value = 0; 11 | c->output_ready = false; 12 | c->line_active = false; 13 | } 14 | } 15 | 16 | u_int16_t u_multiply_shift(u_int16_t a, u_int16_t b, u_int8_t shift) { 17 | return (u_int16_t) ((a * b) >> shift); 18 | } 19 | 20 | u_int16_t s_multiply_shift(int16_t a, int16_t b, u_int8_t shift) { 21 | return (int16_t) ((a * b) >> shift); 22 | } 23 | 24 | void decode_cvsd_data(u_int32_t cvsd_block, struct CONFERENCE *c) { 25 | for (int i = 0; i < 32; i++) { 26 | bool bit = (1 << (31 - i)) & cvsd_block; 27 | u_int8_t previous_index = i > 0 ? i - 1 : 31; // i - 1 % 32 ? 28 | if (bit) { 29 | if (c->coincidence_counter < 0) { 30 | c->coincidence_counter = 1; 31 | } else { 32 | c->coincidence_counter++; 33 | } 34 | } else { 35 | if (c->coincidence_counter < 0) { 36 | c->coincidence_counter = 1; 37 | } else { 38 | c->coincidence_counter++; 39 | } 40 | } 41 | if (abs(c->coincidence_counter) >= 3 && c->gain_value <= 0xFFFF - GAIN_STEP) { 42 | c->gain_value += GAIN_STEP; 43 | } 44 | u_int16_t current_step = AUDIO_SCALE * 1 + u_multiply_shift(c->gain_value, AUDIO_SCALE, 8); 45 | int16_t decayed_value = s_multiply_shift(c->input_pcm[previous_index], SIGNAL_DECAY, 16); 46 | if (bit) { 47 | c->input_pcm[i] = decayed_value + current_step; 48 | } else { 49 | c->input_pcm[i] = decayed_value - current_step; 50 | } 51 | c->gain_value = u_multiply_shift(c->gain_value, GAIN_DECAY, 16); 52 | } 53 | } 54 | 55 | void create_mix_minus(struct CONFERENCE *conference_data, u_int8_t target_phone) { 56 | for (int s = 0; s < 31; s++) { 57 | for (int p = 0; p < number_of_phones; p++) { 58 | struct CONFERENCE *c = conference_data + p; 59 | if (c->line_active && p != target_phone) { 60 | 61 | } 62 | } 63 | } 64 | 65 | } 66 | 67 | void conference_task() { 68 | /* 69 | Loop through each conference struct. If at least one phone is active and all active phones have data ready, 70 | proceed with CVSD decode, mix minus calculation, and return. 71 | */ 72 | bool ready_to_process = true; 73 | bool at_least_one_phone_active = false; 74 | struct CONFERENCE *c; 75 | u_int32_t phone_input_data; 76 | for (int i = 0; i < number_of_phones; i++) { 77 | c = conference_data + i; 78 | if (c->line_active) { 79 | at_least_one_phone_active = true; 80 | if (queue_is_empty(c->input_queue)) { 81 | ready_to_process = false; 82 | } 83 | } 84 | } 85 | if (ready_to_process && at_least_one_phone_active) { 86 | for (int i = 0; i < number_of_phones; i++) { 87 | c = conference_data + i; 88 | if (c->line_active) { 89 | queue_remove_blocking(c->input_queue, &phone_input_data); 90 | decode_cvsd_data(phone_input_data, c); 91 | } 92 | } 93 | for (int i = 0; i < number_of_phones; i++) { 94 | c = conference_data + i; 95 | if (c->line_active) { 96 | queue_remove_blocking(c->input_queue, &phone_input_data); 97 | decode_cvsd_data(phone_input_data, c); 98 | } 99 | } 100 | } 101 | return; 102 | } -------------------------------------------------------------------------------- /conference_mode.h: -------------------------------------------------------------------------------- 1 | 2 | struct CONFERENCE { 3 | queue_t *input_queue; 4 | u_int16_t previous_pcm; 5 | int8_t coincidence_counter; 6 | u_int16_t gain_value; 7 | int8_t output_coincidence_counter; 8 | u_int16_t output_gain_value; 9 | int16_t input_pcm[32]; 10 | u_int32_t output_data; 11 | bool output_ready; 12 | bool line_active; 13 | }; -------------------------------------------------------------------------------- /dev_lowlevel.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include 8 | 9 | // Pico 10 | #include "pico/stdlib.h" 11 | 12 | // For memcpy 13 | #include 14 | 15 | // Include descriptor struct definitions 16 | #include "usb_common.h" 17 | // USB register definitions from pico-sdk 18 | #include "hardware/regs/usb.h" 19 | // USB hardware struct definitions from pico-sdk 20 | #include "hardware/structs/usb.h" 21 | // For interrupt enable and numbers 22 | #include "hardware/irq.h" 23 | // For resetting the USB controller 24 | #include "hardware/resets.h" 25 | 26 | #include "pico/unique_id.h" 27 | // Device descriptors 28 | #include "dev_lowlevel.h" 29 | #include "process_phones.h" 30 | 31 | #define usb_hw_set hw_set_alias(usb_hw) 32 | #define usb_hw_clear hw_clear_alias(usb_hw) 33 | 34 | // Function prototypes for our device specific endpoint handlers defined 35 | // later on 36 | void ep0_in_handler(uint8_t *buf, uint16_t len); 37 | void ep0_out_handler(uint8_t *buf, uint16_t len); 38 | void ep1_out_handler(uint8_t *buf, uint16_t len); 39 | void ep2_in_handler(uint8_t *buf, uint16_t len); 40 | void ep3_out_handler(uint8_t *buf, uint16_t len); 41 | void ep4_in_handler(uint8_t *buf, uint16_t len); 42 | 43 | // Global device address 44 | static bool should_set_address = false; 45 | static uint8_t dev_addr = 0; 46 | volatile bool configured = false; 47 | volatile bool inputs_primed = false; 48 | volatile bool fw_update_usb_request = false; 49 | volatile uint64_t fetch_time; 50 | 51 | // Global data buffer for EP0 52 | static uint8_t ep0_buf[200]; 53 | 54 | // Struct defining the device configuration 55 | static struct usb_device_configuration dev_config = { 56 | .device_descriptor = &device_descriptor, 57 | .interface_descriptor = &interface_descriptor, 58 | .config_descriptor = &config_descriptor, 59 | .lang_descriptor = lang_descriptor, 60 | .descriptor_strings = descriptor_strings, 61 | .endpoints = { 62 | { 63 | .descriptor = &ep0_out, 64 | .handler = &ep0_out_handler, 65 | .endpoint_control = NULL, // NA for EP0 66 | .buffer_control = &usb_dpram->ep_buf_ctrl[0].out, 67 | // EP0 in and out share a data buffer 68 | .data_buffer = &usb_dpram->ep0_buf_a[0], 69 | }, 70 | { 71 | .descriptor = &ep0_in, 72 | .handler = &ep0_in_handler, 73 | .endpoint_control = NULL, // NA for EP0, 74 | .buffer_control = &usb_dpram->ep_buf_ctrl[0].in, 75 | // EP0 in and out share a data buffer 76 | .data_buffer = &usb_dpram->ep0_buf_a[0], 77 | }, 78 | { 79 | .descriptor = &ep1_out, 80 | .handler = &ep1_out_handler, 81 | // EP1 starts at offset 0 for endpoint control 82 | .endpoint_control = &usb_dpram->ep_ctrl[0].out, 83 | .buffer_control = &usb_dpram->ep_buf_ctrl[1].out, 84 | // First free EPX buffer 85 | .data_buffer = &usb_dpram->epx_data[0 * 64], 86 | }, 87 | { 88 | .descriptor = &ep2_in, 89 | .handler = &ep2_in_handler, 90 | .endpoint_control = &usb_dpram->ep_ctrl[1].in, 91 | .buffer_control = &usb_dpram->ep_buf_ctrl[2].in, 92 | // Second free EPX buffer 93 | .data_buffer = &usb_dpram->epx_data[1 * 64], 94 | }, 95 | { 96 | .descriptor = &ep3_out, 97 | .handler = &ep3_out_handler, 98 | .endpoint_control = &usb_dpram->ep_ctrl[2].out, 99 | .buffer_control = &usb_dpram->ep_buf_ctrl[3].out, 100 | .data_buffer = &usb_dpram->epx_data[2 * 64], 101 | }, 102 | { 103 | .descriptor = &ep4_in, 104 | .handler = &ep4_in_handler, 105 | .endpoint_control = &usb_dpram->ep_ctrl[3].in, 106 | .buffer_control = &usb_dpram->ep_buf_ctrl[4].in, 107 | .data_buffer = &usb_dpram->epx_data[3 * 64], 108 | } 109 | } 110 | }; 111 | 112 | /** 113 | * @brief Given an endpoint address, return the usb_endpoint_configuration of that endpoint. Returns NULL 114 | * if an endpoint of that address is not found. 115 | * 116 | * @param addr 117 | * @return struct usb_endpoint_configuration* 118 | */ 119 | struct usb_endpoint_configuration *usb_get_endpoint_configuration(uint8_t addr) { 120 | struct usb_endpoint_configuration *endpoints = dev_config.endpoints; 121 | for (int i = 0; i < USB_NUM_ENDPOINTS; i++) { 122 | if (endpoints[i].descriptor && (endpoints[i].descriptor->bEndpointAddress == addr)) { 123 | return &endpoints[i]; 124 | } 125 | } 126 | return NULL; 127 | } 128 | 129 | /** 130 | * @brief Given a C string, fill the EP0 data buf with a USB string descriptor for that string. 131 | * 132 | * @param C string you would like to send to the USB host 133 | * @return the length of the string descriptor in EP0 buf 134 | */ 135 | uint8_t usb_prepare_string_descriptor(const unsigned char *str) { 136 | // 2 for bLength + bDescriptorType + strlen * 2 because string is unicode. i.e. other byte will be 0 137 | uint8_t bLength = 2 + (strlen((const char *)str) * 2); 138 | static const uint8_t bDescriptorType = 0x03; 139 | printf("String desc len %d\r\n",bLength); 140 | 141 | volatile uint8_t *buf = &ep0_buf[0]; 142 | *buf++ = bLength; 143 | *buf++ = bDescriptorType; 144 | 145 | uint8_t c; 146 | 147 | do { 148 | c = *str++; 149 | *buf++ = c; 150 | *buf++ = 0; 151 | } while (c != '\0'); 152 | return bLength; 153 | } 154 | 155 | /** 156 | * @brief Take a buffer pointer located in the USB RAM and return as an offset of the RAM. 157 | * 158 | * @param buf 159 | * @return uint32_t 160 | */ 161 | static inline uint32_t usb_buffer_offset(volatile uint8_t *buf) { 162 | return (uint32_t) buf ^ (uint32_t) usb_dpram; 163 | } 164 | 165 | /** 166 | * @brief Set up the endpoint control register for an endpoint (if applicable. Not valid for EP0). 167 | * 168 | * @param ep 169 | */ 170 | void usb_setup_endpoint(const struct usb_endpoint_configuration *ep) { 171 | printf("Set up endpoint 0x%x with buffer address 0x%p\n", ep->descriptor->bEndpointAddress, ep->data_buffer); 172 | 173 | // EP0 doesn't have one so return if that is the case 174 | if (!ep->endpoint_control) { 175 | return; 176 | } 177 | 178 | // Get the data buffer as an offset of the USB controller's DPRAM 179 | uint32_t dpram_offset = usb_buffer_offset(ep->data_buffer); 180 | uint32_t reg = EP_CTRL_ENABLE_BITS 181 | | EP_CTRL_INTERRUPT_PER_BUFFER 182 | | (ep->descriptor->bmAttributes << EP_CTRL_BUFFER_TYPE_LSB) 183 | | dpram_offset; 184 | printf("ep_control getting 0x%x w/ dpram 0x%x\n",reg, dpram_offset); 185 | *ep->endpoint_control = reg; 186 | } 187 | 188 | /** 189 | * @brief Set up the endpoint control register for each endpoint. 190 | * 191 | */ 192 | void usb_setup_endpoints() { 193 | const struct usb_endpoint_configuration *endpoints = dev_config.endpoints; 194 | for (int i = 0; i < sizeof(dev_config.endpoints)/sizeof(dev_config.endpoints[0]); i++) { 195 | if (endpoints[i].descriptor && endpoints[i].handler) { 196 | usb_setup_endpoint(&endpoints[i]); 197 | } 198 | } 199 | } 200 | 201 | /** 202 | * @brief Set up the USB controller in device mode, clearing any previous state. 203 | * 204 | */ 205 | void usb_device_init() { 206 | // Reset usb controller 207 | reset_block(RESETS_RESET_USBCTRL_BITS); 208 | unreset_block_wait(RESETS_RESET_USBCTRL_BITS); 209 | 210 | // Clear any previous state in dpram just in case 211 | memset(usb_dpram, 0, sizeof(*usb_dpram)); // <1> 212 | 213 | // Enable USB interrupt at processor 214 | irq_set_enabled(USBCTRL_IRQ, true); 215 | 216 | // Mux the controller to the onboard usb phy 217 | usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; 218 | 219 | // Force VBUS detect so the device thinks it is plugged into a host 220 | usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; 221 | 222 | // Enable the USB controller in device mode. 223 | usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS; 224 | 225 | // Enable an interrupt per EP0 transaction 226 | usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS; // <2> 227 | 228 | // Enable interrupts for when a buffer is done, when the bus is reset, 229 | // and when a setup packet is received 230 | usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | 231 | USB_INTS_BUS_RESET_BITS | 232 | USB_INTS_SETUP_REQ_BITS; 233 | 234 | // Set up endpoints (endpoint control registers) 235 | // described by device configuration 236 | usb_setup_endpoints(); 237 | printf("endpoint setup complete\n"); 238 | // Present full speed device by enabling pull up on DP 239 | usb_hw_set->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; 240 | } 241 | 242 | /** 243 | * @brief Given an endpoint configuration, returns true if the endpoint 244 | * is transmitting data to the host (i.e. is an IN endpoint) 245 | * 246 | * @param ep, the endpoint configuration 247 | * @return true 248 | * @return false 249 | */ 250 | static inline bool ep_is_tx(struct usb_endpoint_configuration *ep) { 251 | return ep->descriptor->bEndpointAddress & USB_DIR_IN; 252 | } 253 | 254 | /** 255 | * @brief Starts a transfer on a given endpoint. 256 | * 257 | * @param ep, the endpoint configuration. 258 | * @param buf, the data buffer to send. Only applicable if the endpoint is TX 259 | * @param len, the length of the data in buf (this example limits max len to one packet - 64 bytes) 260 | */ 261 | void usb_start_transfer(struct usb_endpoint_configuration *ep, uint8_t *buf, uint16_t len) { 262 | // We are asserting that the length is <= 64 bytes for simplicity of the example. 263 | // For multi packet transfers see the tinyusb port. 264 | 265 | 266 | //printf("Start transfer of len %d on ep addr 0x%x\n", len, ep->descriptor->bEndpointAddress); 267 | assert(len <= 64); 268 | // Prepare buffer control register value 269 | uint32_t val = len | USB_BUF_CTRL_AVAIL; 270 | 271 | if (ep_is_tx(ep)) { 272 | // Need to copy the data from the user buffer to the usb memory 273 | memcpy((void *) ep->data_buffer, (void *) buf, len); 274 | // Mark as full 275 | val |= USB_BUF_CTRL_FULL; 276 | } 277 | 278 | // Set pid and flip for next transfer 279 | val |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; 280 | ep->next_pid ^= 1u; 281 | 282 | *ep->buffer_control = val; 283 | } 284 | 285 | /** 286 | * @brief Send device descriptor to host 287 | * 288 | */ 289 | void usb_handle_device_descriptor(void) { 290 | const struct usb_device_descriptor *d = dev_config.device_descriptor; 291 | // EP0 in 292 | struct usb_endpoint_configuration *ep = usb_get_endpoint_configuration(EP0_IN_ADDR); 293 | // Always respond with pid 1 294 | ep->next_pid = 1; 295 | usb_start_transfer(ep, (uint8_t *) d, sizeof(struct usb_device_descriptor)); 296 | } 297 | 298 | /** 299 | * @brief Send interface descriptor to host 300 | * 301 | */ 302 | void usb_handle_interface_descriptor(void) { 303 | const struct usb_interface_descriptor *d = dev_config.interface_descriptor; 304 | // EP0 in 305 | struct usb_endpoint_configuration *ep = usb_get_endpoint_configuration(EP0_IN_ADDR); 306 | // Always respond with pid 1 307 | ep->next_pid = 1; 308 | usb_start_transfer(ep, (uint8_t *) d, sizeof(struct usb_interface_descriptor)); 309 | } 310 | 311 | /** 312 | * @brief Send device status packet. 313 | * 314 | */ 315 | void usb_get_status(void) { 316 | // EP0 in 317 | struct usb_endpoint_configuration *ep = usb_get_endpoint_configuration(EP0_IN_ADDR); 318 | // Always respond with pid 1 319 | ep->next_pid = 1; 320 | uint8_t device_status[] = {0x0, 0x0}; 321 | usb_start_transfer(ep, device_status, 2); 322 | } 323 | 324 | /** 325 | * @brief Send the configuration descriptor (and potentially the configuration and endpoint descriptors) to the host. 326 | * 327 | * @param pkt, the setup packet received from the host. 328 | */ 329 | void usb_handle_config_descriptor(volatile struct usb_setup_packet *pkt) { 330 | uint8_t *buf = &ep0_buf[0]; 331 | 332 | // First request will want just the config descriptor 333 | const struct usb_configuration_descriptor *d = dev_config.config_descriptor; 334 | memcpy((void *) buf, d, sizeof(struct usb_configuration_descriptor)); 335 | buf += sizeof(struct usb_configuration_descriptor); 336 | 337 | // If we more than just the config descriptor copy it all 338 | if (pkt->wLength >= d->wTotalLength) { 339 | memcpy((void *) buf, dev_config.interface_descriptor, sizeof(struct usb_interface_descriptor)); 340 | buf += sizeof(struct usb_interface_descriptor); 341 | const struct usb_endpoint_configuration *ep = dev_config.endpoints; 342 | 343 | // Copy all the endpoint descriptors starting from EP1 344 | for (uint i = 2; i < USB_NUM_ENDPOINTS; i++) { 345 | if (ep[i].descriptor) { 346 | memcpy((void *) buf, ep[i].descriptor, sizeof(struct usb_endpoint_descriptor)); 347 | buf += sizeof(struct usb_endpoint_descriptor); 348 | } 349 | } 350 | 351 | } 352 | 353 | // Send data 354 | // Get len by working out end of buffer subtract start of buffer 355 | uint32_t len = (uint32_t) buf - (uint32_t) &ep0_buf[0]; 356 | printf("config descriptor size: %d\n", len); 357 | usb_start_transfer(usb_get_endpoint_configuration(EP0_IN_ADDR), &ep0_buf[0], len); 358 | } 359 | 360 | /** 361 | * @brief Handle a BUS RESET from the host by setting the device address back to 0. 362 | * 363 | */ 364 | void usb_bus_reset(void) { 365 | // Set address back to 0 366 | dev_addr = 0; 367 | should_set_address = false; 368 | usb_hw->dev_addr_ctrl = 0; 369 | configured = false; 370 | } 371 | 372 | /** 373 | * @brief Send the requested string descriptor to the host. 374 | * 375 | * @param pkt, the setup packet from the host. 376 | */ 377 | void usb_handle_string_descriptor(volatile struct usb_setup_packet *pkt) { 378 | uint8_t i = pkt->wValue & 0xff; 379 | uint8_t len = 0; 380 | if (i == 0) { 381 | len = 4; 382 | memcpy(&ep0_buf[0], dev_config.lang_descriptor, len); 383 | } else if (i == 3) { 384 | pico_unique_board_id_t id; 385 | pico_get_unique_board_id(&id); 386 | // byte by byte conversion 387 | uint8_t serial_str[64]; 388 | int i; 389 | for (i = 0; i < 16; i += 2) { 390 | const char *hexdig = "0123456789ABCDEF"; 391 | serial_str[i + 0] = hexdig[id.id[i >> 1] >> 4]; 392 | serial_str[i + 1] = hexdig[id.id[i >> 1] & 0x0F]; 393 | } 394 | //printf("serial: %s\r\n", serial_str); 395 | len = usb_prepare_string_descriptor(serial_str); 396 | } else { 397 | // Prepare fills in ep0_buf 398 | len = usb_prepare_string_descriptor(dev_config.descriptor_strings[i - 1]); 399 | } 400 | 401 | //printf("String desc req %d, len %d\r\n", i, len); 402 | usb_start_transfer(usb_get_endpoint_configuration(EP0_IN_ADDR), &ep0_buf[0], len); 403 | } 404 | 405 | /** 406 | * @brief Sends a zero length status packet back to the host. 407 | */ 408 | void usb_acknowledge_out_request(void) { 409 | usb_start_transfer(usb_get_endpoint_configuration(EP0_IN_ADDR), NULL, 0); 410 | } 411 | 412 | /** 413 | * @brief Handles a SET_ADDR request from the host. The actual setting of the device address in 414 | * hardware is done in ep0_in_handler. This is because we have to acknowledge the request first 415 | * as a device with address zero. 416 | * 417 | * @param pkt, the setup packet from the host. 418 | */ 419 | void usb_set_device_address(volatile struct usb_setup_packet *pkt) { 420 | // Set address is a bit of a strange case because we have to send a 0 length status packet first with 421 | // address 0 422 | dev_addr = (pkt->wValue & 0xff); 423 | printf("Set address %d\r\n", dev_addr); 424 | // Will set address in the callback phase 425 | should_set_address = true; 426 | usb_acknowledge_out_request(); 427 | } 428 | 429 | /** 430 | * @brief Handles a SET_CONFIGRUATION request from the host. Assumes one configuration so simply 431 | * sends a zero length status packet back to the host. 432 | * 433 | * @param pkt, the setup packet from the host. 434 | */ 435 | void usb_set_device_configuration(volatile struct usb_setup_packet *pkt) { 436 | // Only one configuration so just acknowledge the request 437 | printf("Device Enumerated\r\n"); 438 | usb_acknowledge_out_request(); 439 | configured = true; 440 | } 441 | 442 | /** 443 | * @brief Respond to a setup packet from the host. 444 | * 445 | */ 446 | void usb_handle_setup_packet(void) { 447 | volatile struct usb_setup_packet *pkt = (volatile struct usb_setup_packet *) &usb_dpram->setup_packet; 448 | uint8_t req_direction = pkt->bmRequestType & (1<<7); 449 | uint8_t req = pkt->bRequest; 450 | 451 | // Reset PID to 1 for EP0 IN 452 | usb_get_endpoint_configuration(EP0_IN_ADDR)->next_pid = 1u; 453 | 454 | if (req_direction == USB_DIR_OUT) { 455 | if (req == USB_REQUEST_SET_ADDRESS) { 456 | usb_set_device_address(pkt); 457 | } else if (req == USB_REQUEST_SET_CONFIGURATION) { 458 | usb_set_device_configuration(pkt); 459 | } else if (pkt->bmRequestType & (1<<5)) { 460 | printf("received fw update request via usb\r\n"); 461 | usb_acknowledge_out_request(); 462 | fw_update_usb_request = true; 463 | } else { 464 | usb_acknowledge_out_request(); 465 | printf("Other OUT request (0x%x)\r\n", pkt->bRequest); 466 | } 467 | } else if (req_direction == USB_DIR_IN) { 468 | if (pkt->bmRequestType & (1<<5)) { 469 | //vendor specific input 470 | printf("Received vendor specific (bmrqt 0x%x, breq 0x%x) \r\n", pkt->bmRequestType, pkt->bRequest); 471 | } else if (req == USB_REQUEST_GET_DESCRIPTOR) { 472 | uint16_t descriptor_type = pkt->wValue >> 8; 473 | 474 | switch (descriptor_type) { 475 | case USB_DT_DEVICE: 476 | usb_handle_device_descriptor(); 477 | printf("GET DEVICE DESCRIPTOR\r\n"); 478 | break; 479 | 480 | case USB_DT_CONFIG: 481 | printf("GET CONFIG DESCRIPTOR\r\n"); 482 | usb_handle_config_descriptor(pkt); 483 | break; 484 | 485 | case USB_DT_STRING: 486 | usb_handle_string_descriptor(pkt); 487 | printf("GET STRING DESCRIPTOR\r\n"); 488 | break; 489 | 490 | case 0xa: 491 | usb_handle_interface_descriptor(); 492 | printf("GET INTERFACE DESCRIPTOR\r\n"); 493 | break; 494 | default: 495 | printf("Unhandled GET_DESCRIPTOR type 0x%x\r\n", descriptor_type); 496 | } 497 | } else if (req == USB_REQUEST_GET_STATUS) { 498 | usb_get_status(); 499 | printf("GET STATUS\r\n"); 500 | } else { 501 | /* TODO: this fires with lsusb -v: 502 | GET INTERFACE DESCRIPTOR 503 | buf status interrupt 504 | EP 0 (in = 1) done 505 | Other IN request (bmrqt 0x80, breq 0x0) 506 | */ 507 | printf("Other IN request (bmrqt 0x%x, breq 0x%x) \r\n", pkt->bmRequestType, pkt->bRequest); 508 | } 509 | } 510 | } 511 | 512 | /** 513 | * @brief Notify an endpoint that a transfer has completed. 514 | * 515 | * @param ep, the endpoint to notify. 516 | */ 517 | static void usb_handle_ep_buff_done(struct usb_endpoint_configuration *ep) { 518 | uint32_t buffer_control = *ep->buffer_control; 519 | // Get the transfer length for this endpoint 520 | uint16_t len = buffer_control & USB_BUF_CTRL_LEN_MASK; 521 | 522 | // Call that endpoints buffer done handler 523 | ep->handler((uint8_t *) ep->data_buffer, len); 524 | } 525 | 526 | /** 527 | * @brief Find the endpoint configuration for a specified endpoint number and 528 | * direction and notify it that a transfer has completed. 529 | * 530 | * @param ep_num 531 | * @param in 532 | */ 533 | static void usb_handle_buff_done(uint ep_num, bool in) { 534 | uint8_t ep_addr = ep_num | (in ? USB_DIR_IN : 0); 535 | //printf("EP %d (in = %d) done\n", ep_num, in); 536 | for (uint i = 0; i < USB_NUM_ENDPOINTS; i++) { 537 | struct usb_endpoint_configuration *ep = &dev_config.endpoints[i]; 538 | if (ep->descriptor && ep->handler) { 539 | if (ep->descriptor->bEndpointAddress == ep_addr) { 540 | usb_handle_ep_buff_done(ep); 541 | return; 542 | } 543 | } 544 | } 545 | } 546 | 547 | /** 548 | * @brief Handle a "buffer status" irq. This means that one or more 549 | * buffers have been sent / received. Notify each endpoint where this 550 | * is the case. 551 | */ 552 | static void usb_handle_buff_status() { 553 | uint32_t buffers = usb_hw->buf_status; 554 | uint32_t remaining_buffers = buffers; 555 | //printf("buf status interrupt\n"); 556 | uint bit = 1u; 557 | for (uint i = 0; remaining_buffers && i < USB_NUM_ENDPOINTS * 2; i++) { 558 | if (remaining_buffers & bit) { 559 | // clear this in advance 560 | usb_hw_clear->buf_status = bit; 561 | // IN transfer for even i, OUT transfer for odd i 562 | usb_handle_buff_done(i >> 1u, !(i & 1u)); 563 | remaining_buffers &= ~bit; 564 | } 565 | bit <<= 1u; 566 | } 567 | } 568 | 569 | /** 570 | * @brief USB interrupt handler 571 | * 572 | */ 573 | /// \tag::isr_setup_packet[] 574 | void isr_usbctrl(void) { 575 | // USB interrupt handler 576 | uint32_t status = usb_hw->ints; 577 | uint32_t handled = 0; 578 | 579 | // Setup packet received 580 | if (status & USB_INTS_SETUP_REQ_BITS) { 581 | handled |= USB_INTS_SETUP_REQ_BITS; 582 | usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS; 583 | usb_handle_setup_packet(); 584 | } 585 | /// \end::isr_setup_packet[] 586 | 587 | // Buffer status, one or more buffers have completed 588 | if (status & USB_INTS_BUFF_STATUS_BITS) { 589 | handled |= USB_INTS_BUFF_STATUS_BITS; 590 | usb_handle_buff_status(); 591 | } 592 | 593 | // Bus is reset 594 | if (status & USB_INTS_BUS_RESET_BITS) { 595 | printf("BUS RESET\n"); 596 | handled |= USB_INTS_BUS_RESET_BITS; 597 | usb_hw_clear->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; 598 | usb_bus_reset(); 599 | } 600 | 601 | if (status ^ handled) { 602 | panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); 603 | } 604 | } 605 | 606 | /** 607 | * @brief EP0 in transfer complete. Either finish the SET_ADDRESS process, or receive a zero 608 | * length status packet from the host. 609 | * 610 | * @param buf the data that was sent 611 | * @param len the length that was sent 612 | */ 613 | void ep0_in_handler(uint8_t *buf, uint16_t len) { 614 | if (should_set_address) { 615 | // Set actual device address in hardware 616 | usb_hw->dev_addr_ctrl = dev_addr; 617 | should_set_address = false; 618 | } else { 619 | // Receive a zero length status packet from the host on EP0 OUT 620 | struct usb_endpoint_configuration *ep = usb_get_endpoint_configuration(EP0_OUT_ADDR); 621 | usb_start_transfer(ep, NULL, 0); 622 | } 623 | } 624 | 625 | void ep0_out_handler(uint8_t *buf, uint16_t len) { 626 | ; 627 | } 628 | 629 | // Device specific functions 630 | void ep1_out_handler(uint8_t *buf, uint16_t len) { 631 | //printf("ep1_out RX %d bytes from host\n", len); 632 | handle_device_packet(buf); 633 | // reload the out ep 634 | usb_start_transfer(usb_get_endpoint_configuration(EP1_OUT_ADDR), NULL, 64); 635 | 636 | } 637 | 638 | void ep2_in_handler(uint8_t *buf, uint16_t len) { 639 | //printf("ep2_in Sent %d bytes to host\n", len); 640 | fetch_time = time_us_64(); 641 | //printf("updating fetch_time %d\n", fetch_time); 642 | uint8_t tx_buf[64]; 643 | uint8_t packet_len = create_host_packet(tx_buf); 644 | usb_start_transfer(usb_get_endpoint_configuration(EP2_IN_ADDR), tx_buf, packet_len); 645 | } 646 | 647 | void ep3_out_handler(uint8_t *buf, uint16_t len) { 648 | printf("ep3 RX %d bytes from host\n", len); 649 | struct usb_endpoint_configuration *ep = usb_get_endpoint_configuration(EP4_IN_ADDR); 650 | usb_start_transfer(ep, buf, len); 651 | } 652 | 653 | void ep4_in_handler(uint8_t *buf, uint16_t len) { 654 | printf("ep4 Sent %d bytes to host\n", len); 655 | usb_start_transfer(usb_get_endpoint_configuration(EP3_OUT_ADDR), NULL, 64); 656 | } 657 | 658 | #define LED_PIN PICO_DEFAULT_LED_PIN 659 | 660 | bool usb_active() { 661 | //todo: make this switch on host activity 662 | if (configured) { 663 | if (time_us_64() - fetch_time < 10000) { 664 | return true; 665 | } else { 666 | return false; 667 | } 668 | } 669 | return false; 670 | } 671 | 672 | void usb_housekeeping() { 673 | static bool init_complete = false; 674 | if (!init_complete) { 675 | usb_device_init(); 676 | init_complete = true; 677 | gpio_init(LED_PIN); 678 | gpio_set_dir(LED_PIN, GPIO_OUT); 679 | } 680 | if (configured && !inputs_primed) { 681 | inputs_primed = true; 682 | // Get ready to rx from host 683 | usb_start_transfer(usb_get_endpoint_configuration(EP1_OUT_ADDR), NULL, 64); 684 | usb_start_transfer(usb_get_endpoint_configuration(EP3_OUT_ADDR), NULL, 64); 685 | uint8_t buf[64]; 686 | uint8_t len = create_host_packet(buf); 687 | usb_start_transfer(usb_get_endpoint_configuration(EP2_IN_ADDR), buf, len); 688 | } 689 | else if (!configured && inputs_primed) { 690 | // TODO: maybe handle this in reset function? 691 | inputs_primed = false; 692 | gpio_put(LED_PIN, 0); 693 | } 694 | } 695 | -------------------------------------------------------------------------------- /dev_lowlevel.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #ifndef DEV_LOWLEVEL_H_ 8 | #define DEV_LOWLEVEL_H_ 9 | 10 | #include "usb_common.h" 11 | 12 | void usb_init(); 13 | 14 | // Struct in which we keep the endpoint configuration 15 | typedef void (*usb_ep_handler)(uint8_t *buf, uint16_t len); 16 | struct usb_endpoint_configuration { 17 | const struct usb_endpoint_descriptor *descriptor; 18 | usb_ep_handler handler; 19 | 20 | // Pointers to endpoint + buffer control registers 21 | // in the USB controller DPSRAM 22 | volatile uint32_t *endpoint_control; 23 | volatile uint32_t *buffer_control; 24 | volatile uint8_t *data_buffer; 25 | 26 | // Toggle after each packet (unless replying to a SETUP) 27 | uint8_t next_pid; 28 | }; 29 | 30 | // Struct in which we keep the device configuration 31 | struct usb_device_configuration { 32 | const struct usb_device_descriptor *device_descriptor; 33 | const struct usb_interface_descriptor *interface_descriptor; 34 | const struct usb_configuration_descriptor *config_descriptor; 35 | const unsigned char *lang_descriptor; 36 | const unsigned char **descriptor_strings; 37 | // USB num endpoints is 16 38 | struct usb_endpoint_configuration endpoints[USB_NUM_ENDPOINTS]; 39 | }; 40 | 41 | #define EP0_IN_ADDR (USB_DIR_IN | 0) 42 | #define EP0_OUT_ADDR (USB_DIR_OUT | 0) 43 | #define EP1_OUT_ADDR (USB_DIR_OUT | 1) 44 | #define EP2_IN_ADDR (USB_DIR_IN | 2) 45 | #define EP3_OUT_ADDR (USB_DIR_OUT | 3) 46 | #define EP4_IN_ADDR (USB_DIR_IN | 4) 47 | 48 | // EP0 IN and OUT 49 | static const struct usb_endpoint_descriptor ep0_out = { 50 | .bLength = sizeof(struct usb_endpoint_descriptor), 51 | .bDescriptorType = USB_DT_ENDPOINT, 52 | .bEndpointAddress = EP0_OUT_ADDR, // EP number 0, OUT from host (rx to device) 53 | .bmAttributes = USB_TRANSFER_TYPE_CONTROL, 54 | .wMaxPacketSize = 64, 55 | .bInterval = 0 56 | }; 57 | 58 | static const struct usb_endpoint_descriptor ep0_in = { 59 | .bLength = sizeof(struct usb_endpoint_descriptor), 60 | .bDescriptorType = USB_DT_ENDPOINT, 61 | .bEndpointAddress = EP0_IN_ADDR, // EP number 0, OUT from host (rx to device) 62 | .bmAttributes = USB_TRANSFER_TYPE_CONTROL, 63 | .wMaxPacketSize = 64, 64 | .bInterval = 0 65 | }; 66 | 67 | 68 | // Descriptors 69 | static const struct usb_device_descriptor device_descriptor = { 70 | .bLength = sizeof(struct usb_device_descriptor), 71 | .bDescriptorType = USB_DT_DEVICE, 72 | .bcdUSB = 0x0110, // USB 1.1 device 73 | .bDeviceClass = 0, // Specified in interface descriptor 74 | .bDeviceSubClass = 0, // No subclass 75 | .bDeviceProtocol = 0, // No protocol 76 | .bMaxPacketSize0 = 64, // Max packet size for ep0 77 | .idVendor = 0xcafe, // Your vendor id 78 | .idProduct = 0x6942, // Your product ID 79 | .bcdDevice = 0x0031, // No device revision number 80 | .iManufacturer = 1, // Manufacturer string index 81 | .iProduct = 2, // Product string index 82 | .iSerialNumber = 3, // No serial number 83 | .bNumConfigurations = 1 // One configuration 84 | }; 85 | 86 | static const struct usb_interface_descriptor interface_descriptor = { 87 | .bLength = sizeof(struct usb_interface_descriptor), 88 | .bDescriptorType = USB_DT_INTERFACE, 89 | .bInterfaceNumber = 0, 90 | .bAlternateSetting = 0, 91 | .bNumEndpoints = 4, // Interface has 2 endpoints 92 | .bInterfaceClass = 0xff, // Vendor specific endpoint 93 | .bInterfaceSubClass = 0, 94 | .bInterfaceProtocol = 0, 95 | .iInterface = 0 96 | }; 97 | 98 | static const struct usb_endpoint_descriptor ep1_out = { 99 | .bLength = sizeof(struct usb_endpoint_descriptor), 100 | .bDescriptorType = USB_DT_ENDPOINT, 101 | .bEndpointAddress = EP1_OUT_ADDR, // EP number 1, OUT from host (rx to device) 102 | .bmAttributes = USB_TRANSFER_TYPE_BULK, 103 | .wMaxPacketSize = 64 104 | }; 105 | 106 | static const struct usb_endpoint_descriptor ep2_in = { 107 | .bLength = sizeof(struct usb_endpoint_descriptor), 108 | .bDescriptorType = USB_DT_ENDPOINT, 109 | .bEndpointAddress = EP2_IN_ADDR, // EP number 2, IN from host (tx from device) 110 | .bmAttributes = USB_TRANSFER_TYPE_BULK, 111 | .wMaxPacketSize = 64 112 | }; 113 | 114 | static const struct usb_endpoint_descriptor ep3_out = { 115 | .bLength = sizeof(struct usb_endpoint_descriptor), 116 | .bDescriptorType = USB_DT_ENDPOINT, 117 | .bEndpointAddress = EP3_OUT_ADDR, // EP number 1, OUT from host (rx to device) 118 | .bmAttributes = USB_TRANSFER_TYPE_INTERRUPT, 119 | .wMaxPacketSize = 64, 120 | .bInterval = 1 121 | }; 122 | 123 | static const struct usb_endpoint_descriptor ep4_in = { 124 | .bLength = sizeof(struct usb_endpoint_descriptor), 125 | .bDescriptorType = USB_DT_ENDPOINT, 126 | .bEndpointAddress = EP4_IN_ADDR, // EP number 2, IN from host (tx from device) 127 | .bmAttributes = USB_TRANSFER_TYPE_INTERRUPT, 128 | .wMaxPacketSize = 64, 129 | .bInterval = 0 130 | }; 131 | 132 | static const struct usb_configuration_descriptor config_descriptor = { 133 | .bLength = sizeof(struct usb_configuration_descriptor), 134 | .bDescriptorType = USB_DT_CONFIG, 135 | .wTotalLength = (sizeof(config_descriptor) + 136 | sizeof(interface_descriptor) + 137 | sizeof(ep1_out) + 138 | sizeof(ep2_in) + 139 | sizeof(ep3_out) + 140 | sizeof(ep4_in)), 141 | .bNumInterfaces = 1, 142 | .bConfigurationValue = 1, // Configuration 1 143 | .iConfiguration = 0, // No string 144 | .bmAttributes = 0xc0, // attributes: self powered, no remote wakeup 145 | .bMaxPower = 250 // 500ma 146 | }; 147 | 148 | static const unsigned char lang_descriptor[] = { 149 | 4, // bLength 150 | 0x03, // bDescriptorType == String Descriptor 151 | 0x09, 0x04 // language id = us english 152 | }; 153 | 154 | static const unsigned char *descriptor_strings[] = { 155 | (unsigned char *) "Nick's Knacks", // Vendor 156 | (unsigned char *) "DNVT Adapter" // Product 157 | }; 158 | 159 | 160 | void usb_housekeeping(); 161 | bool usb_active(); 162 | extern volatile bool configured; 163 | 164 | extern volatile bool fw_update_usb_request; 165 | 166 | 167 | #endif -------------------------------------------------------------------------------- /differential_manchester.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | ; 4 | ; SPDX-License-Identifier: BSD-3-Clause 5 | ; 6 | 7 | .program differential_manchester_tx 8 | .side_set 1 opt 9 | 10 | ; Transmit one bit every 16 cycles. In each bit period: 11 | ; - A '0' is encoded as a transition at the start of the bit period 12 | ; - A '1' is encoded as a transition at the start *and* in the middle 13 | ; 14 | ; Side-set bit 0 must be mapped to the data output pin. 15 | ; Autopull must be enabled. 16 | 17 | public start: 18 | initial_high: 19 | out x, 1 ; Start of bit period: always assert transition 20 | jmp !x high_0 side 1 [6] ; Test the data bit we just shifted out of OSR 21 | high_1: 22 | nop 23 | jmp initial_high side 0 [6] ; For `1` bits, also transition in the middle 24 | high_0: 25 | jmp initial_low [7] ; Otherwise, the line is stable in the middle 26 | 27 | initial_low: 28 | out x, 1 ; Always shift 1 bit from OSR to X so we can 29 | jmp !x low_0 side 0 [6] ; branch on it. Autopull refills OSR for us. 30 | low_1: 31 | nop 32 | jmp initial_low side 1 [6] ; If there are two transitions, return to 33 | low_0: 34 | jmp initial_high [7] ; the initial line state is flipped! 35 | 36 | % c-sdk { 37 | static inline void differential_manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) { 38 | pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin); 39 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); 40 | 41 | pio_gpio_init(pio, pin); 42 | // let's disable in/out/set as we use only side set 43 | //pio_sm_set_out_pins(pio, sm, 22, 0/*count*/); 44 | 45 | pio_sm_config c = differential_manchester_tx_program_get_default_config(offset); 46 | sm_config_set_sideset_pins(&c, pin); 47 | sm_config_set_out_shift(&c, false /* shift left*/, true, 32); 48 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 49 | sm_config_set_clkdiv(&c, div); 50 | pio_sm_init(pio, sm, offset + differential_manchester_tx_offset_start, &c); 51 | 52 | // Execute a blocking pull so that we maintain the initial line state until data is available 53 | pio_sm_exec(pio, sm, pio_encode_pull(false, true)); 54 | pio_sm_set_enabled(pio, sm, true); 55 | } 56 | %} 57 | .program differential_manchester_rx 58 | 59 | ; Assumes line is idle low 60 | ; One bit is 16 cycles. In each bit period: 61 | ; - A '0' is encoded as a transition at time 0 62 | ; - A '1' is encoded as a transition at time 0 and a transition at time T/2 63 | ; 64 | ; The IN mapping and the JMP pin select must both be mapped to the GPIO used for 65 | ; RX data. Autopush must be enabled. 66 | 67 | public start: 68 | initial_high: ; Find rising edge at start of bit period 69 | wait 1 pin, 0 [11] ; Delay to eye of second half-period (i.e 3/4 of way 70 | jmp pin high_0 ; through bit) and branch on RX pin high/low. 71 | high_1: 72 | in x, 1 ; Second transition detected (a `1` data symbol) 73 | jmp initial_high 74 | high_0: 75 | in y, 1 [1] ; Line still high, no centre transition (data is `0`) 76 | ; Fall-through 77 | 78 | .wrap_target 79 | initial_low: ; Find falling edge at start of bit period 80 | wait 0 pin, 0 [11] ; Delay to eye of second half-period 81 | jmp pin low_1 82 | low_0: 83 | in y, 1 ; Line still low, no centre transition (data is `0`) 84 | jmp initial_high 85 | low_1: ; Second transition detected (data is `1`) 86 | in x, 1 [1] 87 | .wrap 88 | 89 | % c-sdk { 90 | static inline void differential_manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) { 91 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); 92 | pio_gpio_init(pio, pin); 93 | 94 | pio_sm_config c = differential_manchester_rx_program_get_default_config(offset); 95 | sm_config_set_in_pins(&c, pin); // for WAIT 96 | //pio_sm_set_out_pins(pio, sm, 22, 0); 97 | sm_config_set_jmp_pin(&c, pin); // for JMP 98 | sm_config_set_in_shift(&c, false /* shift left */, true, 32); 99 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); 100 | sm_config_set_clkdiv(&c, div); 101 | pio_sm_init(pio, sm, offset, &c); 102 | 103 | // X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO. 104 | pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1)); 105 | pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0)); 106 | pio_sm_set_enabled(pio, sm, true); 107 | } 108 | %} 109 | -------------------------------------------------------------------------------- /display.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "display.h" 4 | #include "process_phones.h" 5 | #include "load_calculator.h" 6 | #include "dev_lowlevel.h" 7 | #include "dnvt-switch.h" 8 | 9 | 10 | ssd1306_t disp; 11 | 12 | void display_fw_update() { 13 | ssd1306_clear(&disp); 14 | ssd1306_draw_string(&disp, 0, 0*LINE_SPACE, 1, "Firmware Update"); 15 | ssd1306_show(&disp); 16 | } 17 | 18 | void update_display(){ 19 | char buf[30]; 20 | ssd1306_clear(&disp); 21 | bool awaiting_computer = get_dip_value(DIP_DISABLE_LINE_SIMULATOR) == 0 && !usb_active(); 22 | ssd1306_draw_string(&disp, 0, 0*LINE_SPACE, 1, "DNVT Switch ver 0.31"); 23 | ssd1306_draw_string(&disp, 0, 1*LINE_SPACE, 1, "Ln Status Conn. To"); 24 | if (awaiting_computer) { 25 | ssd1306_draw_string(&disp, 20, 3*LINE_SPACE, 1, "Awaiting"); 26 | ssd1306_draw_string(&disp, 20, 4*LINE_SPACE, 1, "USB Host"); 27 | } else { 28 | for (int i = 0; i < NUM_PHONES; i++) { 29 | struct PHONE *phone = phones + i; 30 | char phone_state[9], connected_phone[10]; 31 | switch(phone->phone_state) { 32 | case idle: 33 | case transition_to_idle: 34 | case ring_dismiss_send_cue: 35 | case ring_dismiss_send_dial: 36 | case send_release_ack: 37 | strcpy(phone_state, "idle"); 38 | break; 39 | case off_hook: 40 | case transition_to_dial: 41 | case receiving_dial: 42 | case cue_until_sieze: 43 | case cue_transition_to_dial: 44 | strcpy(phone_state, "dial"); 45 | break; 46 | case usb_dial: 47 | strcpy(phone_state, "USB dial"); 48 | break; 49 | case ringing: 50 | case requesting_ring: 51 | strcpy(phone_state, "ringing"); 52 | break; 53 | case transition_to_plaintext: 54 | case acknowledge_lock_in: 55 | case plain_text: 56 | strcpy(phone_state, "traffic"); 57 | break; 58 | case usb_traffic: 59 | strcpy(phone_state, "USB traf"); 60 | break; 61 | case awaiting_remote_ring: 62 | strcpy(phone_state, "r ring"); 63 | break; 64 | case transition_to_traffic_dial: 65 | case traffic_dial: 66 | case cue_transition_to_traffic_dial: 67 | strcpy(phone_state, "t dial"); 68 | break; 69 | case connection_failure: 70 | case not_in_service_recording: 71 | strcpy(phone_state, "con fail"); 72 | break; 73 | case rickroll: 74 | strcpy(phone_state, "rickroll"); 75 | break; 76 | case unreachable: 77 | strcpy(phone_state, "---"); 78 | break; 79 | default: 80 | strcpy(phone_state, "???"); 81 | break; 82 | } 83 | if (phone->phone_state == plain_text || phone->phone_state == traffic_dial) { 84 | strncpy(connected_phone, phone->digits, 9); 85 | } else { 86 | strcpy(connected_phone, "n/a"); 87 | } 88 | sprintf(buf, "%i %-10s%s", i + 1, phone_state, connected_phone); 89 | ssd1306_draw_string(&disp, 0, (2 + i)*LINE_SPACE, 1, buf); 90 | } 91 | ssd1306_draw_line(&disp, 1*CHAR_WIDTH + 2, 2*LINE_SPACE, 1*CHAR_WIDTH + 2, 6*LINE_SPACE); 92 | ssd1306_draw_line(&disp, 11*CHAR_WIDTH, 2*LINE_SPACE, 11*CHAR_WIDTH, 6*LINE_SPACE); 93 | } 94 | // u_int64_t cycle_ctr = used_cycles + unused_cycles; 95 | // u_int8_t load = ((u_int64_t)used_cycles) * 1000 / cycle_ctr; 96 | sprintf(buf, "Load: %02d%%|USB: %s", get_load(), configured ? usb_active() ? "activ": "inact" : "n/c"); 97 | ssd1306_draw_string(&disp, 0, 56, 1, buf); 98 | ssd1306_show(&disp); 99 | } 100 | 101 | void init_display() { 102 | // I2C Initialisation. Using it at 400Khz. 103 | i2c_init(I2C_PORT, 400*1000); 104 | 105 | gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); 106 | gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); 107 | gpio_pull_up(I2C_SDA); 108 | gpio_pull_up(I2C_SCL); 109 | 110 | disp.external_vcc=false; 111 | ssd1306_init(&disp, 128, 64, 0x3C, I2C_PORT); 112 | } -------------------------------------------------------------------------------- /display.h: -------------------------------------------------------------------------------- 1 | #include "ssd1306.h" 2 | 3 | // I2C defines 4 | // This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz. 5 | // Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments 6 | #define I2C_PORT i2c1 7 | #define I2C_SDA 6 8 | #define I2C_SCL 7 9 | #define LINE_SPACE 9 10 | #define CHAR_WIDTH 6 11 | #define NUM_PHONES 4 12 | 13 | 14 | void update_display(); 15 | void init_display(); 16 | void display_fw_update(); -------------------------------------------------------------------------------- /dnvt-switch.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "pico/stdlib.h" 4 | #include "pico/bootrom.h" 5 | #include "hardware/i2c.h" 6 | #include "hardware/pio.h" 7 | #include "pico/multicore.h" 8 | 9 | #include "display.h" 10 | #include "process_phones.h" 11 | #include "dev_lowlevel.h" 12 | #include "dnvt-switch.h" 13 | 14 | u_int32_t dips[] = {DIP0, DIP1, DIP2, DIP3}; 15 | 16 | void core1_entry() { 17 | while(1) { 18 | update_display(); 19 | sleep_ms(250); 20 | } 21 | } 22 | 23 | int init_dips() { 24 | gpio_init(RESET_BUTTON); 25 | gpio_set_pulls(RESET_BUTTON, true /*up*/, false /*down*/); 26 | gpio_set_dir(RESET_BUTTON, GPIO_IN); 27 | for (int i = 0; i < sizeof(dips)/sizeof(dips[0]); i++) { 28 | gpio_init(dips[i]); 29 | gpio_set_pulls(dips[i], true /*up*/, false /*down*/); 30 | gpio_set_dir(dips[i], GPIO_IN); 31 | } 32 | } 33 | 34 | void check_reset() { 35 | if (!gpio_get(RESET_BUTTON) || fw_update_usb_request) { 36 | display_fw_update(); 37 | reset_usb_boot(0,0); 38 | } 39 | } 40 | 41 | 42 | u_int8_t dip_values[4]; 43 | void get_dips() { 44 | // "ON" for each DIP means 45 | for (int i = 0; i < sizeof(dips)/sizeof(dips[0]); i++) { 46 | dip_values[i] = gpio_get(dips[i]); 47 | } 48 | } 49 | 50 | u_int8_t get_dip_value(int index) { 51 | return dip_values[index]; 52 | } 53 | 54 | 55 | int main() 56 | { 57 | stdout_uart_init(); 58 | 59 | init_phones(); 60 | init_display(); 61 | multicore_launch_core1(core1_entry); 62 | init_dips(); 63 | 64 | 65 | while(1) { 66 | usb_housekeeping(); 67 | phone_task(); 68 | check_reset(); 69 | get_dips(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dnvt-switch.h: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | 3 | #ifndef DNVT_SWITCH_H 4 | #define DNVT_SWITCH_H 5 | 6 | #define DIP0 15 7 | #define DIP1 14 8 | #define DIP2 13 9 | #define DIP3 28 10 | 11 | u_int8_t get_dip_value(int); 12 | 13 | #define DIP_DISABLE_SUPERVISORY 0 14 | #define DIP_DISABLE_LINE_SIMULATOR 1 15 | 16 | #define RESET_BUTTON 4 17 | 18 | #endif -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | #ifndef _inc_font 2 | #define _inc_font 3 | 4 | 5 | const uint8_t font_8x5[] = 6 | { 7 | 8, 5, //height, width 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x5F, 0x00, 0x00, 10 | 0x00, 0x07, 0x00, 0x07, 0x00, 11 | 0x14, 0x7F, 0x14, 0x7F, 0x14, 12 | 0x24, 0x2A, 0x7F, 0x2A, 0x12, 13 | 0x23, 0x13, 0x08, 0x64, 0x62, 14 | 0x36, 0x49, 0x56, 0x20, 0x50, 15 | 0x00, 0x08, 0x07, 0x03, 0x00, 16 | 0x00, 0x1C, 0x22, 0x41, 0x00, 17 | 0x00, 0x41, 0x22, 0x1C, 0x00, 18 | 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 19 | 0x08, 0x08, 0x3E, 0x08, 0x08, 20 | 0x00, 0x80, 0x70, 0x30, 0x00, 21 | 0x08, 0x08, 0x08, 0x08, 0x08, 22 | 0x00, 0x00, 0x60, 0x60, 0x00, 23 | 0x20, 0x10, 0x08, 0x04, 0x02, 24 | 0x3E, 0x51, 0x49, 0x45, 0x3E, 25 | 0x00, 0x42, 0x7F, 0x40, 0x00, 26 | 0x72, 0x49, 0x49, 0x49, 0x46, 27 | 0x21, 0x41, 0x49, 0x4D, 0x33, 28 | 0x18, 0x14, 0x12, 0x7F, 0x10, 29 | 0x27, 0x45, 0x45, 0x45, 0x39, 30 | 0x3C, 0x4A, 0x49, 0x49, 0x31, 31 | 0x41, 0x21, 0x11, 0x09, 0x07, 32 | 0x36, 0x49, 0x49, 0x49, 0x36, 33 | 0x46, 0x49, 0x49, 0x29, 0x1E, 34 | 0x00, 0x00, 0x14, 0x00, 0x00, 35 | 0x00, 0x40, 0x34, 0x00, 0x00, 36 | 0x00, 0x08, 0x14, 0x22, 0x41, 37 | 0x14, 0x14, 0x14, 0x14, 0x14, 38 | 0x00, 0x41, 0x22, 0x14, 0x08, 39 | 0x02, 0x01, 0x59, 0x09, 0x06, 40 | 0x3E, 0x41, 0x5D, 0x59, 0x4E, 41 | 0x7C, 0x12, 0x11, 0x12, 0x7C, 42 | 0x7F, 0x49, 0x49, 0x49, 0x36, 43 | 0x3E, 0x41, 0x41, 0x41, 0x22, 44 | 0x7F, 0x41, 0x41, 0x41, 0x3E, 45 | 0x7F, 0x49, 0x49, 0x49, 0x41, 46 | 0x7F, 0x09, 0x09, 0x09, 0x01, 47 | 0x3E, 0x41, 0x41, 0x51, 0x73, 48 | 0x7F, 0x08, 0x08, 0x08, 0x7F, 49 | 0x00, 0x41, 0x7F, 0x41, 0x00, 50 | 0x20, 0x40, 0x41, 0x3F, 0x01, 51 | 0x7F, 0x08, 0x14, 0x22, 0x41, 52 | 0x7F, 0x40, 0x40, 0x40, 0x40, 53 | 0x7F, 0x02, 0x1C, 0x02, 0x7F, 54 | 0x7F, 0x04, 0x08, 0x10, 0x7F, 55 | 0x3E, 0x41, 0x41, 0x41, 0x3E, 56 | 0x7F, 0x09, 0x09, 0x09, 0x06, 57 | 0x3E, 0x41, 0x51, 0x21, 0x5E, 58 | 0x7F, 0x09, 0x19, 0x29, 0x46, 59 | 0x26, 0x49, 0x49, 0x49, 0x32, 60 | 0x03, 0x01, 0x7F, 0x01, 0x03, 61 | 0x3F, 0x40, 0x40, 0x40, 0x3F, 62 | 0x1F, 0x20, 0x40, 0x20, 0x1F, 63 | 0x3F, 0x40, 0x38, 0x40, 0x3F, 64 | 0x63, 0x14, 0x08, 0x14, 0x63, 65 | 0x03, 0x04, 0x78, 0x04, 0x03, 66 | 0x61, 0x59, 0x49, 0x4D, 0x43, 67 | 0x00, 0x7F, 0x41, 0x41, 0x41, 68 | 0x02, 0x04, 0x08, 0x10, 0x20, 69 | 0x00, 0x41, 0x41, 0x41, 0x7F, 70 | 0x04, 0x02, 0x01, 0x02, 0x04, 71 | 0x40, 0x40, 0x40, 0x40, 0x40, 72 | 0x00, 0x03, 0x07, 0x08, 0x00, 73 | 0x20, 0x54, 0x54, 0x78, 0x40, 74 | 0x7F, 0x28, 0x44, 0x44, 0x38, 75 | 0x38, 0x44, 0x44, 0x44, 0x28, 76 | 0x38, 0x44, 0x44, 0x28, 0x7F, 77 | 0x38, 0x54, 0x54, 0x54, 0x18, 78 | 0x00, 0x08, 0x7E, 0x09, 0x02, 79 | 0x18, 0xA4, 0xA4, 0x9C, 0x78, 80 | 0x7F, 0x08, 0x04, 0x04, 0x78, 81 | 0x00, 0x44, 0x7D, 0x40, 0x00, 82 | 0x20, 0x40, 0x40, 0x3D, 0x00, 83 | 0x7F, 0x10, 0x28, 0x44, 0x00, 84 | 0x00, 0x41, 0x7F, 0x40, 0x00, 85 | 0x7C, 0x04, 0x78, 0x04, 0x78, 86 | 0x7C, 0x08, 0x04, 0x04, 0x78, 87 | 0x38, 0x44, 0x44, 0x44, 0x38, 88 | 0xFC, 0x18, 0x24, 0x24, 0x18, 89 | 0x18, 0x24, 0x24, 0x18, 0xFC, 90 | 0x7C, 0x08, 0x04, 0x04, 0x08, 91 | 0x48, 0x54, 0x54, 0x54, 0x24, 92 | 0x04, 0x04, 0x3F, 0x44, 0x24, 93 | 0x3C, 0x40, 0x40, 0x20, 0x7C, 94 | 0x1C, 0x20, 0x40, 0x20, 0x1C, 95 | 0x3C, 0x40, 0x30, 0x40, 0x3C, 96 | 0x44, 0x28, 0x10, 0x28, 0x44, 97 | 0x4C, 0x90, 0x90, 0x90, 0x7C, 98 | 0x44, 0x64, 0x54, 0x4C, 0x44, 99 | 0x00, 0x08, 0x36, 0x41, 0x00, 100 | 0x00, 0x00, 0x77, 0x00, 0x00, 101 | 0x00, 0x41, 0x36, 0x08, 0x00, 102 | 0x02, 0x01, 0x02, 0x04, 0x02, 103 | }; 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /host/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -Wall -ggdb3 -pthread 2 | CFLAGS += $(shell pkg-config --cflags-only-I libusb-1.0) 3 | LDLIBS += $(shell pkg-config --libs libusb-1.0) 4 | LDLIBS += $(shell pkg-config --libs ncurses) 5 | 6 | main: 7 | 8 | .PHONY: clean 9 | 10 | clean: 11 | $(RM) main 12 | -------------------------------------------------------------------------------- /host/README.md: -------------------------------------------------------------------------------- 1 | # Host Application 2 | 3 | This is a proof-of-concept host application. Requires libusb. The udev config file for USB must be mapped for the CAFE:6942 VID/PID 4 | to allow access by the console application for non-root. 5 | 6 | Detailed instructions TODO 7 | -------------------------------------------------------------------------------- /host/busy.cvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickandre/dnvt-fw/5825a58c6d3ef175236a2e1db233dd5b6638ec1f/host/busy.cvs -------------------------------------------------------------------------------- /host/dialtone.cvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickandre/dnvt-fw/5825a58c6d3ef175236a2e1db233dd5b6638ec1f/host/dialtone.cvs -------------------------------------------------------------------------------- /host/dialtone2.cvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickandre/dnvt-fw/5825a58c6d3ef175236a2e1db233dd5b6638ec1f/host/dialtone2.cvs -------------------------------------------------------------------------------- /host/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../usb_structures.h" 14 | 15 | #define MFGR_ID 0xCAFE // given manufacturer ID 16 | #define DEV_ID 0x6942 // given device ID 17 | 18 | 19 | 20 | #define USB_CONTROL_VENDOR_MESSAGE 0x2<<4 21 | #define DNVT_REQUEST_STATUS 0x0 22 | #define DNVT_REBOOT_FIRMWARE 0xff 23 | #define SETUP_INPUT 0x1 << 7 24 | 25 | typedef struct { 26 | uint32_t data[20]; 27 | uint8_t size; 28 | } QUEUE; 29 | 30 | enum line_state { 31 | line_uninitialized = 0, 32 | line_idle, 33 | line_dial, 34 | line_awaiting_remote_ring, 35 | line_request_ring, 36 | line_ringing, 37 | line_traffic, 38 | line_remote_hangup, 39 | line_busy_signal, 40 | line_unreachable 41 | }; 42 | 43 | #define NOT_CONNECTED 0xFFFF 44 | 45 | 46 | typedef struct { 47 | char digits[20]; 48 | uint8_t digit_count; 49 | uint8_t state; 50 | uint8_t connected_phone; 51 | QUEUE rxd; 52 | QUEUE txd; 53 | uint8_t pending_command; 54 | uint8_t line_state; 55 | uint16_t connected_device; 56 | uint32_t recording_index; 57 | bool playing_recording; 58 | int recording_number; 59 | struct timespec last_tx; 60 | } PHONE; 61 | 62 | typedef struct { 63 | uint32_t *data; 64 | uint32_t length; 65 | } RECORDING; 66 | 67 | #define DIALTONE_RECORDING 0 68 | #define BUSY_RECORDING 1 69 | #define RINGTONE_RECORDING 2 70 | #define RICKROLL_RECORDING 3 71 | 72 | RECORDING recording[4]; 73 | 74 | /* If device IDs are not known, use libusb_get_device_list() to see a 75 | list of all USB devices connected to the machine. Follow this call with 76 | libusb_free_device_list() to free the allocated device list memory. 77 | */ 78 | 79 | HOST_PACKET host_packet; 80 | DEVICE_PACKET device_packet; 81 | FILE *logfile; 82 | 83 | #define MAX_SWITCHES 4 84 | #define MAX_PHONES 4*MAX_SWITCHES 85 | 86 | PHONE phones[MAX_PHONES]; 87 | struct libusb_device_handle *dnvt_sw[MAX_SWITCHES]; 88 | unsigned char dev_serials[MAX_SWITCHES][20]; 89 | int open_devices = 0; 90 | 91 | bool thread_run = true; 92 | 93 | 94 | // apparently C doesn't have an STL queue, so... 95 | void init_queue(QUEUE *q) { 96 | q->size = 0; 97 | } 98 | 99 | uint8_t queue_size(QUEUE *q) { 100 | return q->size; 101 | } 102 | 103 | uint32_t queue_pop(QUEUE *q) { 104 | assert(q->size > 0); 105 | uint32_t return_value = q->data[0]; 106 | q->size--; 107 | for (int i = 0; i < q->size; i++) { 108 | q->data[i] = q->data[i+1]; 109 | } 110 | return return_value; 111 | } 112 | 113 | bool queue_push(QUEUE *q, uint32_t data) { 114 | if (q->size > 20) { 115 | return false; 116 | } 117 | q->data[q->size++] = data; 118 | return true; 119 | } 120 | 121 | void init_phones() { 122 | for (int i = 0; i < MAX_PHONES; i++) { 123 | init_queue(&phones[i].rxd); 124 | init_queue(&phones[i].txd); 125 | } 126 | } 127 | 128 | int device_packets_sent = 0; 129 | unsigned char tx_buf[64]; 130 | unsigned char rx_buf[64]; 131 | char debug[120]; 132 | uint32_t *dialtone; 133 | uint32_t dialtone_length; 134 | 135 | 136 | char * dialplan[] = { 137 | "01", 138 | "02", 139 | "03", 140 | "04", 141 | "05", 142 | "06", 143 | "07", 144 | "08" 145 | }; 146 | 147 | 148 | 149 | int match_dialplan(char digits[]) { 150 | for (int i = 0; i < open_devices * 4; i++) { 151 | if(strcmp(digits, dialplan[i]) == 0) { 152 | return i; 153 | } 154 | } 155 | return -1; 156 | } 157 | 158 | uint32_t clock_diff_ms(struct timespec *old, struct timespec *new) { 159 | return (new->tv_sec - old->tv_sec) * 1000 + (new->tv_nsec - old->tv_nsec) / 1000000; 160 | } 161 | 162 | void add_ms(struct timespec *ts, int ms) { 163 | ts->tv_nsec += ms * 1000000; 164 | if (ts->tv_nsec > 1000000000) { 165 | ts->tv_nsec %= 1000000000; 166 | ts->tv_sec++; 167 | } 168 | } 169 | 170 | 171 | void sync_line_state(int i) { 172 | PHONE *phone = phones + i; 173 | PHONE *connected_phone; 174 | if (phone->playing_recording) { 175 | int recording_number = phone->recording_number; 176 | int recording_length = recording[recording_number].length; 177 | uint32_t *data = recording[recording_number].data; 178 | phone->recording_index %= recording_length; 179 | struct timespec cur_clock; 180 | clock_gettime(CLOCK_MONOTONIC_RAW, &cur_clock); 181 | //sprintf(debug, "cur clock: %ld", cur_clock / CLOCKS_PER_SEC * 1000); 182 | int packets_to_send = clock_diff_ms(&phone->last_tx, &cur_clock); 183 | if (packets_to_send > 3) { 184 | packets_to_send = 3; 185 | clock_gettime(CLOCK_MONOTONIC_RAW, &phone->last_tx); 186 | } else { 187 | add_ms(&phone->last_tx, packets_to_send); 188 | } 189 | for (int r = 0; r < packets_to_send; r++) { 190 | uint32_t raw_data = data[phone->recording_index++]; 191 | uint32_t swapped = ((raw_data>>24)&0xff) | // move byte 3 to byte 0 192 | ((raw_data<<8)&0xff0000) | // move byte 1 to byte 2 193 | ((raw_data>>8)&0xff00) | // move byte 2 to byte 1 194 | ((raw_data<<24)&0xff000000); // byte 0 to byte 3 195 | queue_push(&phone->txd, swapped); 196 | } 197 | } 198 | if (phone->state == phone_idle && 199 | phone->line_state != line_idle && 200 | phone->line_state != line_request_ring) { 201 | if (phone->connected_device != NOT_CONNECTED) { 202 | int c = phone->connected_device; 203 | fprintf(logfile, "Clearing connected device %d\n ", c); 204 | phones[c].connected_device = NOT_CONNECTED; 205 | phone->connected_device = NOT_CONNECTED; 206 | } 207 | phone->line_state = line_idle; 208 | } 209 | switch(phone->line_state) { 210 | case line_idle: 211 | if (phone->playing_recording) { 212 | phone->playing_recording = false; 213 | } 214 | if (phone->state == phone_dial) { 215 | phone->line_state = line_dial; 216 | clock_gettime(CLOCK_MONOTONIC_RAW, &phone->last_tx); 217 | phone->playing_recording = true; 218 | phone->recording_number = DIALTONE_RECORDING; 219 | } 220 | if (phone->digit_count > 0) { 221 | phone->digit_count = 0; 222 | phone->digits[0] = '\0'; 223 | phone->playing_recording = false; 224 | phone->recording_index = 0; 225 | } 226 | break; 227 | case line_dial: 228 | if (phone->digit_count > 0) { 229 | phone->playing_recording = false; 230 | } 231 | int dialed_line; 232 | dialed_line = match_dialplan(phone->digits); 233 | if (phone->connected_device == NOT_CONNECTED 234 | && dialed_line != -1) { 235 | fprintf(logfile, "Line %d calling line %d\n", i, dialed_line); 236 | phone->connected_device = dialed_line; 237 | phone->line_state = line_awaiting_remote_ring; 238 | phone->playing_recording = true; 239 | phone->recording_number = RINGTONE_RECORDING; 240 | phone->recording_index = 0; 241 | phones[dialed_line].pending_command = RING_COMMAND; 242 | phones[dialed_line].connected_device = i; 243 | phones[dialed_line].line_state = line_request_ring; 244 | clock_gettime(CLOCK_MONOTONIC_RAW, &phones[dialed_line].last_tx); 245 | } 246 | break; 247 | case line_awaiting_remote_ring: 248 | if (phone->connected_device == NOT_CONNECTED) { 249 | fprintf(logfile, "line %d no longer connected, to busy\n", i); 250 | phone->line_state = line_busy_signal; 251 | clock_gettime(CLOCK_MONOTONIC_RAW, &phone->last_tx); 252 | phone->playing_recording = true; 253 | phone->recording_number = BUSY_RECORDING; 254 | return; 255 | } 256 | connected_phone = phones + phone->connected_device; 257 | if (connected_phone->line_state == line_traffic) { 258 | fprintf(logfile, "line %d remote terminal in traffic, go to traffic\n", i); 259 | phone->pending_command = PLAINTEXT_COMMAND; 260 | phone->line_state = line_traffic; 261 | phone->playing_recording = false; 262 | } else if (connected_phone->line_state == line_unreachable) { 263 | fprintf(logfile, "line %d remote terminal unreachable, to busy\n", i); 264 | phone->line_state = line_busy_signal; 265 | clock_gettime(CLOCK_MONOTONIC_RAW, &phone->last_tx); 266 | phone->playing_recording = true; 267 | phone->recording_number = BUSY_RECORDING; 268 | } 269 | break; 270 | case line_request_ring: 271 | if (phone->state == phone_unreachable) { 272 | phone->line_state = line_unreachable; 273 | phone->connected_device = NOT_CONNECTED; 274 | } 275 | if (phone->state == phone_ring) { 276 | fprintf(logfile, "line %d ringing\n", i); 277 | phone->line_state = line_ringing; 278 | } 279 | break; 280 | case line_ringing: 281 | if (phone->connected_device == NOT_CONNECTED) { 282 | phone->pending_command = RING_DISMISS_COMMAND; 283 | fprintf(logfile, "line %d Not Connected, Dismissing ring\n", i); 284 | } else { 285 | if (phone->state == phone_traffic) { 286 | fprintf(logfile, "line %d in state traffic, transition to line_traffic\n", i); 287 | phone->line_state = line_traffic; 288 | } 289 | } 290 | break; 291 | case line_traffic: 292 | if (phone->playing_recording) { 293 | phone->playing_recording = false; 294 | phone->recording_index = 0; 295 | } 296 | if (phone->state == phone_idle) { 297 | if (phone->connected_device != NOT_CONNECTED) { 298 | connected_phone = phones + phone->connected_device; 299 | connected_phone->connected_device = NOT_CONNECTED; 300 | } 301 | phone->line_state = line_idle; 302 | return; 303 | } 304 | if (phone->connected_device == NOT_CONNECTED) { 305 | fprintf(logfile, "Phone %d disconnect command\n", i); 306 | phone->pending_command = DISCONNECT_COMMAND; 307 | phone->line_state = line_dial; 308 | phone->digit_count = 0; 309 | phone->digits[0] = '\0'; 310 | phone->playing_recording = true; 311 | phone->recording_number = DIALTONE_RECORDING; 312 | phone->recording_index = 0; 313 | clock_gettime(CLOCK_MONOTONIC_RAW, &phone->last_tx); 314 | return; 315 | } 316 | connected_phone = phones + phone->connected_device; 317 | if (connected_phone->line_state == line_unreachable) { 318 | fprintf(logfile, "line %d connected phone unreachable 2, to busy\n", i); 319 | phone->line_state = line_busy_signal; 320 | clock_gettime(CLOCK_MONOTONIC_RAW, &connected_phone->last_tx); 321 | phone->playing_recording = true; 322 | phone->recording_number = BUSY_RECORDING; 323 | } 324 | break; 325 | case line_remote_hangup: 326 | break; 327 | case line_busy_signal: 328 | break; 329 | case line_unreachable: 330 | if (phone->connected_device != NOT_CONNECTED) { 331 | connected_phone = phones + phone->connected_device; 332 | connected_phone->connected_device = NOT_CONNECTED; 333 | phone->connected_device = NOT_CONNECTED; 334 | } 335 | break; 336 | } 337 | 338 | } 339 | 340 | void* usb_worker(void *unused) { 341 | int transferred_size; 342 | for (int i = 0; i < MAX_PHONES; i++) { 343 | phones[i].connected_device = NOT_CONNECTED; 344 | } 345 | while (thread_run) { 346 | for (int s = 0; s < open_devices; s++) { 347 | struct libusb_device_handle *dh = dnvt_sw[s]; 348 | int config = libusb_bulk_transfer((struct libusb_device_handle *)dh, 0x82, rx_buf, 64, &transferred_size, 1000); 349 | if (config < 0) { 350 | printf("ERROR: No data transmitted to device %x, err %s\n\n",DEV_ID, libusb_error_name(config)); 351 | thread_run = false; 352 | continue; 353 | } 354 | memcpy(&host_packet, rx_buf, sizeof(host_packet)); 355 | bool should_send_device_packet = false; 356 | device_packet.data_lengths = 0; 357 | for (int p = 0; p < 4; p++) { 358 | int i = s*4 + p; 359 | // parse incoming packet 360 | PHONE *phone = phones + i; 361 | phone->state = (host_packet.phone_states >> p*4) & 0xf; 362 | /* 363 | if (i == 1) { 364 | static uint8_t last_phone_state; 365 | if (last_phone_state != phone->state) { 366 | fprintf(logfile, "Phone %d state from %d to %d\n", i, last_phone_state, phone->state); 367 | last_phone_state = phone->state; 368 | } 369 | }*/ 370 | sync_line_state(i); 371 | uint8_t rx_data_len = (host_packet.data_lengths >> p*2) & 0x3; 372 | for (int d = 0; d < rx_data_len; d++) { 373 | if (phone->connected_device != NOT_CONNECTED) { 374 | queue_push(&phones[phone->connected_device].txd, host_packet.data[p][d]); 375 | } else { 376 | queue_push(&phone->rxd, host_packet.data[p][d]); 377 | } 378 | } 379 | if (host_packet.phone_digits[p]) { 380 | phone->digits[phone->digit_count++] = host_packet.phone_digits[p]; 381 | phone->digits[phone->digit_count] = '\0'; 382 | } 383 | // assemble outgoing packet 384 | uint8_t output_size = queue_size(&phone->txd); 385 | if (output_size > 0) { 386 | should_send_device_packet = true; 387 | if (output_size > 3) { 388 | output_size = 3; 389 | } 390 | for (int j = 0; j < output_size; j++) { 391 | uint32_t data = queue_pop(&phone->txd); 392 | device_packet.data[p][j] = data; 393 | } 394 | device_packet.data_lengths |= output_size << (p * 2); 395 | } 396 | if (phone->pending_command) { 397 | should_send_device_packet = true; 398 | device_packet.phone_commands[p] = phone->pending_command; 399 | phone->pending_command = NO_COMMAND; 400 | } else { 401 | device_packet.phone_commands[p] = 0; 402 | } 403 | } 404 | if (should_send_device_packet) { 405 | for (int t = 0; t < 4; t++) { 406 | if (device_packet.phone_commands[t]) { 407 | fprintf(logfile, "Sending command to %d\n", t); 408 | } 409 | } 410 | device_packets_sent++; 411 | memcpy(tx_buf, &device_packet, sizeof(DEVICE_PACKET)); 412 | //sprintf(debug, "phone1_packet: %d", ) 413 | int config = libusb_bulk_transfer((struct libusb_device_handle *)dh, 0x01, tx_buf, sizeof(DEVICE_PACKET), &transferred_size, 1000); 414 | if (config < 0) { 415 | fprintf(logfile, "ERROR: No data transmitted to device %x, err %s\n\n",DEV_ID, libusb_error_name(config)); 416 | thread_run = false; 417 | continue; 418 | } 419 | } 420 | } 421 | usleep(750); 422 | } 423 | pthread_exit(NULL); 424 | } 425 | 426 | void firmware_update(struct libusb_device_handle *dh) { 427 | 428 | // set fields for the setup packet as needed 429 | uint8_t bmReqType = USB_CONTROL_VENDOR_MESSAGE; // the request type (direction of transfer) 430 | uint8_t bReq = DNVT_REQUEST_STATUS; // the request field for this packet 431 | uint16_t wVal = 0; // the value field for this packet 432 | uint16_t wIndex = 0; // the index field for this packet 433 | unsigned char data[64]; // the data buffer for the in/output data 434 | uint16_t wLen = 64; // length of this setup packet 435 | unsigned int to = 1000; // timeout duration (if transfer fails) 436 | 437 | // transfer the setup packet to the USB device 438 | int config = 439 | libusb_control_transfer(dh,bmReqType,bReq,wVal,wIndex,data,wLen,to); 440 | 441 | if (config < 0) { 442 | errx(1,"ERROR: No data transmitted to device %x, err %s\n\n",DEV_ID, libusb_error_name(config)); 443 | } 444 | return; 445 | } 446 | 447 | void usb_test(struct libusb_device_handle *dh) { 448 | unsigned char buf[64]; 449 | printf("buffer: "); 450 | for (int i = 0; i < 64; i++) { 451 | printf("%02X:", buf[i]); 452 | } 453 | printf("\n"); 454 | } 455 | 456 | void open_recording(char *path, int index) { 457 | FILE *fp = fopen(path, "rb"); 458 | if (fp == NULL) { 459 | errx(1, "recording %s missing", path); 460 | } 461 | struct stat stat; 462 | fstat(fileno(fp), &stat); 463 | recording[index].data = malloc(stat.st_size); 464 | recording[index].length = stat.st_size / 4; 465 | size_t read_bytes = fread(recording[index].data, sizeof(uint32_t), stat.st_size/4, fp); 466 | printf("recording %s read %ld bytes, first word %08x\n", path, read_bytes, recording[index].data[0]); 467 | } 468 | 469 | 470 | int main() { 471 | logfile= fopen("logfile.txt", "a"); 472 | setlinebuf(logfile); 473 | if (!logfile) { 474 | errx(1, "Logfile open failed\n"); 475 | } 476 | 477 | fprintf(logfile, "DNVT Start\n"); 478 | int init = libusb_init(NULL); // NULL is the default libusb_context 479 | int config; 480 | if (init < 0) { 481 | errx(1,"\n\nERROR: Cannot Initialize libusb\n\n"); 482 | } 483 | open_recording("dialtone2.cvs", DIALTONE_RECORDING); 484 | open_recording("busy.cvs", BUSY_RECORDING); 485 | open_recording("ringtone.cvs", RINGTONE_RECORDING); 486 | open_recording("rick.cvs", RICKROLL_RECORDING); 487 | struct libusb_device **dl = NULL; 488 | int devices = libusb_get_device_list(NULL, &dl); 489 | for (int i = 0; i < devices; i++) { 490 | struct libusb_device *device = dl[i]; 491 | struct libusb_device_descriptor desc= {0}; 492 | 493 | int rc = libusb_get_device_descriptor(device, &desc); 494 | assert(rc == 0); 495 | printf("Vendor:Device = %04x:%04x v %02x.%02x\n", desc.idVendor, desc.idProduct, desc.bcdDevice >> 8, desc.bcdDevice); 496 | if (desc.idVendor == 0xCAFE) { 497 | libusb_open(device, &dnvt_sw[open_devices]); 498 | libusb_get_string_descriptor_ascii(dnvt_sw[open_devices], desc.iSerialNumber, dev_serials[open_devices], 20); 499 | printf("Serial: %s\n", dev_serials[open_devices]); 500 | open_devices++; 501 | } 502 | } 503 | printf("Sizeof clock %ld, clocks per sec %ld\n", sizeof(clock_t), CLOCKS_PER_SEC); 504 | //struct libusb_device_handle *dh = NULL; // The device handle 505 | //dh = libusb_open_device_with_vid_pid(NULL,MFGR_ID,DEV_ID); 506 | //if (!dh) { 507 | // errx(1,"\n\nERROR: Cannot connect to device %d\n\n",DEV_ID); 508 | //} 509 | //struct libusb_device_descriptor *dd = NULL; 510 | //config = libusb_get_device_descriptor(dh, dd); 511 | //printf("Version: %02x.%02x\n", dd->bcdDevice >> 8, dd->bcdDevice); 512 | /* 513 | int configuration; 514 | config = libusb_get_configuration(dh, &configuration); 515 | if (config < 0) { 516 | errx(1,"ERROR: cannot get config %x, err %s\n\n",DEV_ID, libusb_error_name(config)); 517 | } 518 | printf("configuration %d\n", configuration);*/ 519 | for (int i = 0; i < open_devices; i++) { 520 | config = libusb_claim_interface(dnvt_sw[i], 0); 521 | if (config < 0) { 522 | errx(1,"ERROR: cannot claim interface %x, err %s\n\n",DEV_ID, libusb_error_name(config)); 523 | } 524 | //printf("claimed interface 0\n"); 525 | //printf("sizeof device packet %ld\n", sizeof(DEVICE_PACKET)); 526 | } 527 | 528 | //printf("phone states: %x\n", host_packet.phone_states); 529 | // now you can use libusb_bulk_transfer to send raw data to the device 530 | bool quit = false; 531 | initscr(); 532 | raw(); 533 | keypad(stdscr, true); 534 | noecho(); 535 | timeout(100); 536 | pthread_t th1; 537 | pthread_create(&th1, NULL, usb_worker, (void*) NULL); 538 | while (!quit && thread_run) { /* Start curses mode */ 539 | int row, col; 540 | getmaxyx(stdscr,row,col); 541 | clear(); 542 | mvaddstr(0,0,"Official DNVT Console v0.1"); 543 | mvprintw(1,0,"this is hopefully line 2"); 544 | mvprintw(2,0,"You have r: %d, c: %d", row, col); 545 | for (int i = 0; i < open_devices * 4; i++) { 546 | PHONE *phone = phones + i; 547 | mvprintw(i+3, 0, "Phone %d: %x\n",i+1,phone->state); 548 | mvprintw(i+3, 13, "L: %d", phone->line_state); 549 | mvprintw(i+3, 20, "N: %s", phone->digits); 550 | mvprintw(i+3, 40, "R: %d", phone->recording_index); 551 | } 552 | //mvprintw(7,0, "Raw states: %04x", host_packet.phone_states); 553 | //mvprintw(8,0, "Device Packets: %d", device_packets_sent); 554 | //mvprintw(9,0, "Last phone1 cmd: %d", device_packet.phone_commands[1]); 555 | mvprintw(30,0, "%s", debug); 556 | /*for (int b = 0; b < 64; b++) { 557 | int row_ind = b / 15; 558 | int col_ind = (b % 15) *3; 559 | mvprintw(row_ind + 10, col_ind, "%02x", tx_buf[b]); 560 | }*/ 561 | refresh(); /* Print it on to the real screen */ 562 | char c = getch(); /* Wait for user input */ 563 | if (c == 'q') { 564 | quit = true; 565 | } else if (c == 'r') { 566 | phones[1].pending_command = RING_COMMAND; 567 | } else if (c == 'd') { 568 | phones[1].pending_command = RING_DISMISS_COMMAND; 569 | } 570 | 571 | } 572 | endwin(); /* End curses mode */ 573 | thread_run = false; 574 | pthread_join(th1, (void *) NULL); 575 | for (int i = 0; i< open_devices; i++) { 576 | libusb_release_interface(dnvt_sw[i],0); 577 | } 578 | libusb_exit(NULL); 579 | } 580 | -------------------------------------------------------------------------------- /host/rick.cvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickandre/dnvt-fw/5825a58c6d3ef175236a2e1db233dd5b6638ec1f/host/rick.cvs -------------------------------------------------------------------------------- /host/ringtone.cvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickandre/dnvt-fw/5825a58c6d3ef175236a2e1db233dd5b6638ec1f/host/ringtone.cvs -------------------------------------------------------------------------------- /load_calculator.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "pico/stdlib.h" 6 | #include "hardware/pio.h" 7 | #include "load_calculator.h" 8 | 9 | 10 | 11 | u_int32_t work_start = 0, work_end = 0, non_work_counter = 0, work_counter = 0; 12 | bool doing_work; 13 | 14 | void scale_counters() { 15 | if (non_work_counter > 0xAFFF || work_counter > 0xAFFF) { 16 | work_counter >>= 8; 17 | non_work_counter >>= 8; 18 | } 19 | } 20 | 21 | void load_counter_start_work() { 22 | work_start = time_us_32(); 23 | non_work_counter += work_start - work_end; 24 | doing_work = true; 25 | scale_counters(); 26 | } 27 | 28 | void load_counter_stop_work() { 29 | if (doing_work) { 30 | doing_work = false; 31 | work_end = time_us_32(); 32 | work_counter += work_end - work_start; 33 | scale_counters(); 34 | } 35 | } 36 | 37 | u_int8_t get_load() { 38 | u_int64_t total = non_work_counter + work_counter; 39 | return (u_int64_t)work_counter * 1000 / total; 40 | } -------------------------------------------------------------------------------- /load_calculator.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | 4 | void load_counter_stop_work(); 5 | void load_counter_start_work(); 6 | u_int8_t get_load(); -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /process_phones.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pico/stdlib.h" 12 | #include "pico/util/queue.h" 13 | #include "hardware/pio.h" 14 | 15 | #include "differential_manchester.pio.h" 16 | 17 | #include "process_phones.h" 18 | #include "recording.h" 19 | #include "rickroll.h" 20 | #include "load_calculator.h" 21 | #include "dev_lowlevel.h" 22 | 23 | #include "dnvt-switch.h" 24 | #include "usb_structures.h" 25 | 26 | 27 | const uint LED_PIN = PICO_DEFAULT_LED_PIN; 28 | 29 | // const u_int8_t FORCE_CLEAR = ?; 30 | // const u_int8_t IDLE = ?; 31 | 32 | // receiving 24 33 | 34 | u_int8_t codewords[] = { 35 | 0, 36 | //3, forces out of cue 37 | 5, 38 | 9, 39 | 15, 40 | 17, 41 | 23, 42 | 27, 43 | 29, 44 | 39, 45 | 43, 46 | 45, 47 | 51, 48 | 53, 49 | //63, - cue 50 | 85, 51 | 95, 52 | 111, 53 | 119, 54 | 255 55 | }; 56 | 57 | u_int32_t used_cycles, unused_cycles; 58 | 59 | struct PHONE phones[] = { 60 | { 61 | .pio = pio0, 62 | .sm_tx = 0, 63 | .sm_rx = 1, 64 | .pin_tx = 17, 65 | .pin_rx = 16, 66 | .phone_state = idle 67 | }, 68 | { 69 | .pio = pio0, 70 | .sm_tx = 2, 71 | .sm_rx = 3, 72 | .pin_tx = 19, 73 | .pin_rx = 18, 74 | .phone_state = idle 75 | }, 76 | { 77 | .pio = pio1, 78 | .sm_tx = 0, 79 | .sm_rx = 1, 80 | .pin_tx = 21, 81 | .pin_rx = 20, 82 | .phone_state = idle 83 | }, 84 | { 85 | .pio = pio1, 86 | .sm_tx = 2, 87 | .sm_rx = 3, 88 | .pin_tx = 27, 89 | .pin_rx = 26, 90 | .phone_state = idle 91 | }, 92 | }; 93 | 94 | struct CONNECTION connections[] = { 95 | { 96 | .active = false, 97 | .requested = false, 98 | .associated_device = NOT_CONNECTED 99 | }, 100 | { 101 | .active = false, 102 | .requested = false, 103 | .associated_device = NOT_CONNECTED 104 | }, 105 | { 106 | .active = false, 107 | .requested = false, 108 | .associated_device = NOT_CONNECTED 109 | }, 110 | { 111 | .active = false, 112 | .requested = false, 113 | .associated_device = NOT_CONNECTED 114 | } 115 | }; 116 | 117 | 118 | bool match_codeword(u_int32_t frame, u_int8_t codeword) { 119 | // rx state machine is fucked 120 | u_int8_t cw_to_match = ~ codeword; 121 | for (int i = 0; i < 8; i++) { 122 | // shift i bits left, bitwise and. If the XOR is 0, all bits are the same 123 | if ( (((frame >> i) & 0xFF ) ^ cw_to_match) == 0) { 124 | // if we match, try the next byte 125 | if ( (((frame >> (i + 8)) & 0xFF ) ^ cw_to_match) == 0) { 126 | if ( (((frame >> (i + 16)) & 0xFF ) ^ cw_to_match) == 0) { 127 | return true; 128 | } 129 | } 130 | } 131 | } 132 | return false; 133 | } 134 | 135 | u_int32_t create_codeword_word(u_int8_t cw_rob) { 136 | u_int8_t codeword = ~cw_rob; 137 | return codeword | codeword << 8 | codeword << 16 | codeword << 24; 138 | } 139 | 140 | char determine_digit(u_int32_t rx_word) { 141 | if (match_codeword(rx_word, DIGIT_0)) { 142 | return '0'; 143 | } 144 | if (match_codeword(rx_word, DIGIT_1)) { 145 | return '1'; 146 | } 147 | if (match_codeword(rx_word, DIGIT_2)) { 148 | return '2'; 149 | } 150 | if (match_codeword(rx_word, DIGIT_3)) { 151 | return '3'; 152 | } 153 | if (match_codeword(rx_word, DIGIT_4)) { 154 | return '4'; 155 | } 156 | if (match_codeword(rx_word, DIGIT_5)) { 157 | return '5'; 158 | } 159 | if (match_codeword(rx_word, DIGIT_6)) { 160 | return '6'; 161 | } 162 | if (match_codeword(rx_word, DIGIT_7)) { 163 | return '7'; 164 | } 165 | if (match_codeword(rx_word, DIGIT_8)) { 166 | return '8'; 167 | } 168 | if (match_codeword(rx_word, DIGIT_9)) { 169 | return '9'; 170 | } 171 | if (match_codeword(rx_word, DIGIT_R)) { 172 | return 'R'; 173 | } 174 | if (match_codeword(rx_word, DIGIT_C)) { 175 | return 'C'; 176 | } 177 | if (match_codeword(rx_word, DIGIT_P)) { 178 | return 'P'; 179 | } 180 | if (match_codeword(rx_word, DIGIT_I)) { 181 | return 'I'; 182 | } 183 | if (match_codeword(rx_word, DIGIT_F)) { 184 | return 'F'; 185 | } 186 | if (match_codeword(rx_word, DIGIT_FO)) { 187 | return 'O'; 188 | } 189 | return ' '; 190 | } 191 | 192 | 193 | 194 | void serial_task() { 195 | return; 196 | /* 197 | if (phone->phone_type == serial_console) { 198 | //get_serial_command(remote_command); 199 | if (connection->requested && !connection->active) { 200 | printf("serial console alive\n"); 201 | connection->active = true; 202 | remote_connection->active = true; 203 | } 204 | if (connection->active) { 205 | connection->has_data = true; 206 | connection->rx_word = NULL_AUDIO; 207 | if (remote_connection->has_data) { 208 | printf("p%dd%08x\n", connected_phone, remote_connection->rx_word); 209 | remote_connection->has_data = false; 210 | } 211 | } 212 | }*/ 213 | } 214 | 215 | 216 | void phone_task() { 217 | char digit; 218 | u_int32_t null_audio = NULL_AUDIO; 219 | if (used_cycles > 0xFFF || unused_cycles > 0xFFF) { 220 | used_cycles >>= 8; 221 | unused_cycles >>= 8; 222 | } 223 | for (int i = 0; i < NUMBER_OF_PHONES; i++) { 224 | u_int64_t current_time = time_us_64(); 225 | u_int32_t rx_word; 226 | struct PHONE *phone = phones + i; 227 | struct CONNECTION *connection = connections + i; 228 | u_int8_t connected_phone = connection->associated_device; 229 | struct CONNECTION *remote_connection = connections + connected_phone; 230 | 231 | // if we're in idle but a call is requested, move to ring 232 | if ((phone->phone_state == idle || phone->phone_state == unreachable) 233 | && connection->requested) { 234 | printf("phone %d received request, push ring command\n", i); 235 | phone->phone_state = requesting_ring; 236 | // initial ring requires pushing command without rx until ring ack received 237 | phone->pushing_command = true; 238 | phone->attempted_contact = current_time; 239 | } 240 | // this routine supervises phone lines to ensure they're connected 241 | if (get_dip_value(DIP_DISABLE_SUPERVISORY) == 1 242 | && phone->phone_state == idle 243 | && (phone->last_data_received_time == 0 || 244 | current_time - phone->last_data_received_time > 600*1000*1000)) { //10 min in us 245 | phone->phone_state = line_check; 246 | phone->pushing_command = true; 247 | phone->attempted_contact = current_time; 248 | printf("phone %d: supervisory check\n", i); 249 | } 250 | if (phone->phone_state == line_check && 251 | current_time - phone->attempted_contact > 50*1000) { 252 | phone->phone_state = unreachable; 253 | printf("phone %d: unreachable\n", i); 254 | } 255 | bool tx_fifo_full = pio_sm_is_tx_fifo_full(phone->pio, phone->sm_tx), 256 | rx_fifo_empty = pio_sm_is_rx_fifo_empty(phone->pio, phone->sm_rx); 257 | if (!phone->idle_data_cleared && phone->phone_state == idle && (current_time - phone->last_data_received_time > 1000000)) { 258 | printf("phone %d: idle for one second, clearing pio\n", i); 259 | phone->idle_data_cleared = true; 260 | pio_sm_restart(phone->pio, phone->sm_rx); 261 | } 262 | // phone not in idle, not pushing command, and > 0.5s since last data, send to idle 263 | if (phone->phone_state != idle && !phone->pushing_command && (current_time - phone->last_data_received_time > 5000000)) { 264 | printf("phone %d: no rx in .5 seconds, entering idle\n", i); 265 | phone->phone_state = idle; 266 | } 267 | if (phone->pushing_command) { 268 | if (tx_fifo_full) { 269 | // if our fifo is full we will block on push, therefore we should wait 270 | unused_cycles++; 271 | continue; 272 | } 273 | if (rx_fifo_empty) { 274 | // we set this to null audio and continue 275 | rx_word = NULL_AUDIO; 276 | load_counter_start_work(); 277 | } else { 278 | // if we received new data, phone is "alive" 279 | rx_word = pio_sm_get_blocking(phone->pio, phone->sm_rx); 280 | load_counter_start_work(); 281 | phone->last_data_received_time = current_time; 282 | phone->activity_counter++; 283 | phone->idle_data_cleared = false; 284 | } 285 | 286 | } else { 287 | // normal mode, expect back and fourth 288 | if (rx_fifo_empty) { 289 | // no data available for this phone, skip it 290 | unused_cycles++; 291 | continue; 292 | } else { 293 | used_cycles++; 294 | load_counter_start_work(); 295 | rx_word = pio_sm_get_blocking(phone->pio, phone->sm_rx); 296 | phone->activity_counter++; 297 | phone->last_data_received_time = current_time; 298 | phone->idle_data_cleared = false; 299 | } 300 | } 301 | // we expect release during transition to idle 302 | if (match_codeword(rx_word, RELEASE) && phone->phone_state != transition_to_idle) { 303 | printf("phone %d release rx, transition to idle\n", i); 304 | phone->phone_state = transition_to_idle; 305 | if (connection->requested || connection->active) { 306 | connection->requested = false; 307 | connection->active = false; 308 | if (!usb_active()) { 309 | connections[connected_phone].requested = false; 310 | connections[connected_phone].active = false; 311 | printf("phone %d clearing connected phone %d\n", i, connected_phone); 312 | } 313 | /* 314 | printf(" connection0 req %d, connection1 req %d, ", connections[0].requested, connections[1].requested); 315 | printf("connected phone %d []requested %d active %d\n", connected_phone, connections[connected_phone].requested, connections[connected_phone].active); 316 | */ 317 | } 318 | phone->sent_codewords = 0; 319 | phone->pushing_command = true; 320 | } 321 | 322 | switch (phone->phone_state) { 323 | case idle: 324 | if (match_codeword(rx_word, SEIZE)) { 325 | phone->phone_state = transition_to_dial; 326 | phone->sent_codewords = 0; 327 | printf("phone %d transition to dial, matched %02x with %08x\n", i, SEIZE, rx_word); 328 | } 329 | break; 330 | case line_check: 331 | case unreachable: 332 | if (match_codeword(rx_word, CUE_RELEASE_ACK)) { 333 | phone->phone_state = ring_dismiss_send_dial; 334 | printf("phone %d unreachable. response received, send cue to send dial\n", i); 335 | phone->pushing_command = false; 336 | } 337 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(CUE)); 338 | break; 339 | case transition_to_dial: 340 | if (match_codeword(rx_word, INTERDIGIT)) { 341 | if (usb_active()) { 342 | phone->phone_state = usb_dial; 343 | printf("phone %d entering usb dial mode\n", i); 344 | } else { 345 | u_int8_t dip_value = get_dip_value(DIP_DISABLE_LINE_SIMULATOR); 346 | printf("phone %d sent interdigit, dip value %d", i, dip_value); 347 | if (dip_value != 1 && !usb_active()) { 348 | phone->phone_state = connection_failure; 349 | } else { 350 | phone->phone_state = receiving_dial; 351 | printf("phone %d entering dial mode\n", i); 352 | } 353 | } 354 | phone->digit_count = 0; 355 | phone->current_digit = ' '; 356 | phone->last_transmitted_digit = 0; 357 | } 358 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(DIAL)); 359 | break; 360 | case cue_transition_to_dial: 361 | if (match_codeword(rx_word, SEIZE)) { 362 | phone->phone_state = transition_to_dial; 363 | printf("phone %d sent cue, received seize, go to regular dial\n", i); 364 | } 365 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(CUE)); 366 | break; 367 | case cue_transition_to_traffic_dial: 368 | if (match_codeword(rx_word, SEIZE)) { 369 | phone->phone_state = transition_to_traffic_dial; 370 | printf("phone %d sent cue, received seize, go to traffic dial\n", i); 371 | } 372 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(CUE)); 373 | break; 374 | case transition_to_traffic_dial: 375 | if (match_codeword(rx_word, INTERDIGIT)) { 376 | phone->phone_state = traffic_dial; 377 | printf("phone %d entering traffic dial mode\n", i); 378 | } 379 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(DIAL)); 380 | break; 381 | case traffic_dial: 382 | if (!connection->active) { 383 | // phone already in dial, so if it dies go straight to dial 384 | phone->phone_state = receiving_dial; 385 | phone->digit_count = 0; 386 | phone->current_digit = ' '; 387 | continue; 388 | } 389 | if (phone->current_digit == 'C' && phone->receiving_digit == false) { 390 | phone->phone_state = transition_to_plaintext; 391 | printf(" - phone %d dialing complete, transition to ring\n", i); 392 | phone->current_digit = ' '; 393 | } 394 | if (phone->receiving_digit) { 395 | if (match_codeword(rx_word, INTERDIGIT)) { 396 | printf("%c", phone->current_digit); 397 | phone->receiving_digit = false; 398 | phone->digit_count++; 399 | } 400 | } else { 401 | digit = determine_digit(rx_word); 402 | if (digit != ' ') { 403 | phone->current_digit = digit; 404 | phone->receiving_digit = true; 405 | } 406 | } 407 | if (!queue_is_full(&connection->rx_queue)) { 408 | queue_try_add(&connection->rx_queue, &null_audio); 409 | } 410 | if (!queue_is_empty(&connections[connected_phone].rx_queue)) { 411 | u_int32_t data; 412 | queue_remove_blocking(&connections[connected_phone].rx_queue, &data); 413 | pio_sm_put_blocking(phone->pio, phone->sm_tx, data); 414 | } 415 | break; 416 | case cue_until_sieze: 417 | if (match_codeword(rx_word, SEIZE)) { 418 | phone->phone_state = transition_to_dial; 419 | printf("phone %d sent cue, received seize, go to dial\n", i); 420 | } 421 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(CUE)); 422 | break; 423 | /*case test_transition_codewords: 424 | if (transmitted_test_codewords >= 4000) { 425 | transmitted_test_codewords = 0; 426 | if (test_codeword_index >= number_codewords - 1) { 427 | test_codeword_index = 0; 428 | } else { 429 | test_codeword_index++; 430 | } 431 | printf("\n** %d testing %02x - \n", i, codewords[test_codeword_index], rx_word); 432 | } 433 | printf("%08x", ~rx_word); 434 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(codewords[test_codeword_index])); 435 | transmitted_test_codewords++; 436 | break;*/ 437 | case receiving_dial: 438 | if (get_dip_value(DIP_DISABLE_LINE_SIMULATOR) != 1 && !usb_active()) { 439 | phone->phone_state = connection_failure; 440 | } 441 | if (phone->current_digit == 'R' && phone->receiving_digit == false) { 442 | printf("phone %d go to rickroll\n", i); 443 | phone->phone_state = rickroll; 444 | phone->recording_index = 0; 445 | phone->current_digit = ' '; 446 | continue; 447 | } 448 | if (phone->digit_count > 0) { 449 | u_int8_t target_device; 450 | if (phone->digits[0] == '1'){ 451 | target_device = 0; 452 | } else if (phone->digits[0] == '2') { 453 | target_device = 1; 454 | } else if (phone->digits[0] == '3') { 455 | target_device = 2; 456 | /*} else if (phone->digits[0] == 'C') { 457 | phone->phone_state = conference; 458 | phone->current_digit = ' '; 459 | printf(" - %d dialing complete, going to conference mode\n", i);*/ 460 | } else if (phone->digits[0] == '4') { 461 | target_device = 3; 462 | } else { 463 | target_device = 255; 464 | } 465 | phone->current_digit = ' '; 466 | if (target_device == i) { 467 | phone->phone_state = connection_failure; 468 | } else if (target_device != 255) { 469 | // phone number 1 to device 0 470 | connection->associated_device = target_device; 471 | connection->requested = true; 472 | phone->digits[phone->digit_count] = '\0'; 473 | // should trigger ring 474 | connections[target_device].associated_device = i; 475 | connections[target_device].requested = true; 476 | phone->phone_state = awaiting_remote_ring; 477 | printf(" - %d dialing complete, received %s connecting to %d\n", i, phone->digits, connection->associated_device); 478 | } else { 479 | phone->phone_state = not_in_service_recording; 480 | phone->recording_index = 0; 481 | } 482 | } 483 | if (phone->receiving_digit) { 484 | if (match_codeword(rx_word, INTERDIGIT)) { 485 | printf("%c", phone->current_digit); 486 | phone->digits[phone->digit_count] = phone->current_digit; 487 | phone->receiving_digit = false; 488 | phone->digit_count++; 489 | } 490 | } 491 | digit = determine_digit(rx_word); 492 | if (digit != ' ') { 493 | phone->current_digit = digit; 494 | phone->receiving_digit = true; 495 | } 496 | pio_sm_put_blocking(phone->pio, phone->sm_tx, 0xffeb14aa); 497 | break; 498 | case usb_dial: 499 | if (connection->active) { 500 | phone->phone_state = usb_traffic; 501 | } 502 | if (phone->receiving_digit) { 503 | if (match_codeword(rx_word, INTERDIGIT)) { 504 | printf("%c", phone->current_digit); 505 | phone->digits[phone->digit_count] = phone->current_digit; 506 | phone->receiving_digit = false; 507 | phone->digit_count++; 508 | } 509 | } 510 | digit = determine_digit(rx_word); 511 | if (digit != ' ') { 512 | phone->current_digit = digit; 513 | phone->receiving_digit = true; 514 | } 515 | if (!queue_is_empty(&connection->tx_queue)){ 516 | uint32_t data; 517 | queue_remove_blocking(&connection->tx_queue, &data); 518 | pio_sm_put_blocking(phone->pio, phone->sm_tx, data); 519 | } else { 520 | pio_sm_put_blocking(phone->pio, phone->sm_tx, NULL_AUDIO); 521 | } 522 | break; 523 | case not_in_service_recording: 524 | if (phone->recording_index > 5122) { 525 | phone->phone_state = connection_failure; 526 | } 527 | pio_sm_put_blocking(phone->pio, phone->sm_tx, recording[phone->recording_index++]); 528 | break; 529 | case rickroll: 530 | if (phone->recording_index > 23338) { 531 | phone->phone_state = receiving_dial; 532 | } 533 | pio_sm_put_blocking(phone->pio, phone->sm_tx, rickroll_recording[phone->recording_index++]); 534 | break; 535 | case connection_failure: 536 | if (phone->receiving_digit) { 537 | if (match_codeword(rx_word, INTERDIGIT)) { 538 | printf("phone %d received key, back to dial: %c\n", i, phone->current_digit); 539 | phone->receiving_digit = false; 540 | phone->digit_count = 0; 541 | phone->phone_state = receiving_dial; 542 | phone->current_digit = ' '; 543 | } 544 | } 545 | digit = determine_digit(rx_word); 546 | if (digit != ' ') { 547 | phone->current_digit = digit; 548 | phone->receiving_digit = true; 549 | } 550 | if (phone->activity_counter < 50) { 551 | pio_sm_put_blocking(phone->pio, phone->sm_tx, 0xffeb14aa); 552 | } else { 553 | pio_sm_put_blocking(phone->pio, phone->sm_tx, NULL_AUDIO); 554 | } 555 | break; 556 | case awaiting_remote_ring: 557 | if (!connection->requested) { 558 | phone->phone_state = connection_failure; 559 | } 560 | if (connection->active) { 561 | phone->phone_state = transition_to_plaintext; 562 | } 563 | // TODO ring tone 564 | pio_sm_put_blocking(phone->pio, phone->sm_tx, NULL_AUDIO); 565 | break; 566 | case requesting_ring: 567 | if (match_codeword(rx_word, RING_ACK)) { 568 | phone->phone_state = ringing; 569 | printf("%d received ring ack, she's alive, going to ringing state\n", i); 570 | phone->sent_codewords = 0; 571 | phone->pushing_command = false; 572 | } 573 | // we loitered here for half second without ack, assume line dead 574 | if (current_time - phone->attempted_contact > 250000) { 575 | connection->requested = false; 576 | connections[connected_phone].requested = false; 577 | // todo: conn failed state 578 | phone->phone_state = unreachable; 579 | phone->pushing_command = true; 580 | break; 581 | } 582 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(RING_VOICE)); 583 | break; 584 | case ringing: 585 | if (match_codeword(rx_word, RING_TRIP)) { 586 | phone->phone_state = transition_to_plaintext; 587 | connection->active = true; 588 | connections[connected_phone].active = true; 589 | printf("phone %d: received ring trip, transition_to_plaintext\n", i); 590 | } 591 | if (!connection->requested) { 592 | printf("phone %d: other connetion dropped, ring dismiss flow\n", i); 593 | phone->phone_state = ring_dismiss_send_cue; 594 | } 595 | pio_sm_put_blocking(phone->pio, phone->sm_tx, NULL_AUDIO); 596 | break; 597 | case transition_to_plaintext: 598 | if (match_codeword(rx_word, LOCK_IN)) { 599 | phone->phone_state = acknowledge_lock_in; 600 | printf("phone %d: received lock in request, acknowedge_lock_in\n", i); 601 | phone->sent_codewords = 0; 602 | } 603 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(GO_TO_PLAIN_TEXT)); 604 | break; 605 | case acknowledge_lock_in: 606 | if (phone->sent_codewords > 50) { 607 | phone->phone_state = usb_active() ? usb_traffic : plain_text; 608 | printf("phone %d transition to plaintext\n", i); 609 | } else { 610 | phone->sent_codewords++; 611 | } 612 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(LOCK_IN_ACK)); 613 | break; 614 | case transition_to_idle: 615 | if (phone->sent_codewords > 50) { 616 | phone->phone_state = idle; 617 | printf("phone %d codewords sent, entering idle\n", i); 618 | phone->pushing_command = false; 619 | // this should clear the ISR of partial RX 620 | pio_sm_restart(phone->pio, phone->sm_rx); 621 | } else { 622 | phone->sent_codewords++; 623 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(RELEASE_ACK)); 624 | } 625 | break; 626 | case plain_text: 627 | if (!connection->active) { 628 | phone->phone_state = cue_until_sieze; 629 | continue; 630 | } 631 | if (match_codeword(rx_word, DIGIT_C)) { 632 | if (phone->received_codeword_counter > 5) { 633 | printf("phone %d matched C during plaintext, go to cue\n", i); 634 | phone->phone_state = cue_transition_to_traffic_dial; 635 | continue; 636 | } else { 637 | phone->received_codeword_counter++; 638 | } 639 | } else { 640 | phone->received_codeword_counter = 0; 641 | } 642 | queue_try_add(&connection->rx_queue, &rx_word); 643 | if (!queue_is_empty(&connections[connected_phone].rx_queue)) { 644 | u_int32_t data; 645 | queue_remove_blocking(&connections[connected_phone].rx_queue, &data); 646 | pio_sm_put_blocking(phone->pio, phone->sm_tx, data); 647 | } 648 | break; 649 | case usb_traffic: 650 | if (match_codeword(rx_word, DIGIT_C)) { 651 | if (phone->received_codeword_counter > 5) { 652 | printf("phone %d matched C during plaintext, go to cue\n", i); 653 | phone->phone_state = cue_transition_to_traffic_dial; 654 | continue; 655 | } else { 656 | phone->received_codeword_counter++; 657 | } 658 | } else { 659 | phone->received_codeword_counter = 0; 660 | } 661 | queue_try_add(&connection->rx_queue, &rx_word); 662 | if (!queue_is_empty(&connection->tx_queue)) { 663 | u_int32_t data; 664 | queue_remove_blocking(&connection->tx_queue, &data); 665 | pio_sm_put_blocking(phone->pio, phone->sm_tx, data); 666 | } 667 | break; 668 | case ring_dismiss_send_cue: 669 | if (match_codeword(rx_word, CUE_RELEASE_ACK)) { 670 | phone->phone_state = ring_dismiss_send_dial; 671 | printf("phone %d ring dismiss, send cue to send dial\n", i); 672 | phone->pushing_command = false; 673 | } 674 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(CUE)); 675 | break; 676 | case ring_dismiss_send_dial: 677 | if(match_codeword(rx_word, RELEASE)) { 678 | printf("phone %d ring dismiss, received release now transition to idle\n", i); 679 | phone->phone_state = transition_to_idle; 680 | phone->pushing_command = true; 681 | phone->sent_codewords = 0; 682 | } 683 | pio_sm_put_blocking(phone->pio, phone->sm_tx, create_codeword_word(DIAL)); 684 | break; 685 | default: 686 | break; 687 | } 688 | load_counter_stop_work(); 689 | /*for (int i = 0; i < 100; i++) { 690 | pio_sm_put_blocking(pio, sm_tx, 0x00000000); 691 | pio_sm_put_blocking(pio, sm_tx, 0xffffffff); 692 | } 693 | for (int i = 0; i < 5000 ; i++) { 694 | recording[i] = pio_sm_get_blocking(pio, sm_rx); 695 | } 696 | for (int i = 0; i < 100; i++) { 697 | pio_sm_put_blocking(pio, sm_tx, 0x00000000); 698 | pio_sm_put_blocking(pio, sm_tx, 0xffffffff); 699 | } 700 | for (int i = 0; i < 5000; i++) { 701 | pio_sm_put_blocking(pio, sm_tx, recording[i]); 702 | }*/ 703 | } 704 | } 705 | 706 | void init_phones() { 707 | 708 | uint pio0_offset_tx = pio_add_program(pio0, &differential_manchester_tx_program); 709 | uint pio0_offset_rx = pio_add_program(pio0, &differential_manchester_rx_program); 710 | uint pio1_offset_tx = pio_add_program(pio1, &differential_manchester_tx_program); 711 | uint pio1_offset_rx = pio_add_program(pio1, &differential_manchester_rx_program); 712 | printf("Transmit program loaded at %d\n", pio0_offset_tx); 713 | printf("Receive program loaded at %d\n", pio0_offset_rx); 714 | 715 | for (int i = 0; i < NUMBER_OF_PHONES; i++) { 716 | queue_init(&connections[i].rx_queue, sizeof(u_int32_t), 8); 717 | queue_init(&connections[i].tx_queue, sizeof(u_int32_t), 8); 718 | phones[i].attempted_contact = 0; 719 | uint offset_tx, offset_rx; 720 | if (phones[i].pio == pio0) { 721 | offset_rx = pio0_offset_rx; 722 | offset_tx = pio0_offset_tx; 723 | } else { 724 | offset_rx = pio1_offset_rx; 725 | offset_tx = pio1_offset_tx; 726 | } 727 | // 16 cylces/symbol, 32 khz total, 125mhz base freq in khz 728 | differential_manchester_tx_program_init(phones[i].pio, phones[i].sm_tx, offset_tx, phones[i].pin_tx, 125000.f / (32 * 16)); 729 | differential_manchester_rx_program_init(phones[i].pio, phones[i].sm_rx, offset_rx, phones[i].pin_rx, 125000.f / (32 * 16)); 730 | } 731 | 732 | //uint32_t* recording = malloc(5000 * sizeof(uint32_t)); 733 | 734 | } 735 | 736 | uint8_t map_phone_state(struct PHONE *p) { 737 | switch (p->phone_state) { 738 | case idle: 739 | return phone_idle; 740 | 741 | case off_hook: 742 | case transition_to_dial: 743 | case transition_to_idle: 744 | case ring_dismiss_send_cue: 745 | case ring_dismiss_send_dial: 746 | case send_release_ack: 747 | case requesting_ring: 748 | case acknowledge_lock_in: 749 | case transition_to_plaintext: 750 | case cue_until_sieze: 751 | case cue_transition_to_dial: 752 | case connection_failure: 753 | case plain_text: 754 | case awaiting_remote_ring: 755 | case not_in_service_recording: 756 | case rickroll: 757 | return phone_transitioning; 758 | case receiving_dial: 759 | case usb_dial: 760 | return phone_dial; 761 | case transition_to_traffic_dial: 762 | case traffic_dial: 763 | case cue_transition_to_traffic_dial: 764 | case usb_traffic: 765 | return phone_traffic; //"active" 766 | case ringing: 767 | return phone_ring; 768 | case ringing_remote: 769 | return phone_awaiting_remote_ring; //probably not needed 770 | case unreachable: 771 | return phone_unreachable; 772 | //TODO: ring fail? 773 | } 774 | } 775 | 776 | void handle_device_packet(uint8_t buf[64]) { 777 | DEVICE_PACKET p; 778 | memcpy(&p, buf, sizeof(p)); 779 | //printf("phone cmds: "); 780 | for (int i = 0; i < 4; i++) { 781 | struct PHONE *phone = phones + i; 782 | //printf("%d ", p.phone_commands[i]); 783 | uint8_t data_length = (p.data_lengths >> i*2) & 0x3; 784 | if (data_length > 0) { 785 | for (int j = 0; j < data_length; j++) { 786 | queue_try_add(&connections[i].tx_queue, p.data[i] + j); 787 | } 788 | } 789 | uint8_t cmd = p.phone_commands[i]; 790 | if (cmd) { 791 | if (cmd == 0x1) { 792 | phone->phone_state = requesting_ring; 793 | phone->pushing_command = true; 794 | phone->attempted_contact = time_us_64(); 795 | connections[i].requested = true; 796 | printf("USB: requesting ring on %d\n", i); 797 | } else if (cmd == 0x2) { 798 | printf("USB: traffic req on %d\n", i); 799 | phone->phone_state = transition_to_plaintext; 800 | } else if (cmd == 0x3) { 801 | printf("USB: back to dial req on %d\n", i); 802 | phone->phone_state = cue_transition_to_dial; 803 | } else if (cmd == 0x4) { 804 | phone->phone_state = ring_dismiss_send_cue; 805 | printf("USB: dismissing ring on %d\n", i); 806 | } 807 | } 808 | } 809 | //printf("\n"); 810 | } 811 | 812 | void populate_host_packet(HOST_PACKET *p) { 813 | p->phone_states = 0; 814 | p->data_lengths = 0; 815 | for (int i = 0; i < NUMBER_OF_PHONES; i ++) { 816 | struct PHONE *ph = phones + i; 817 | struct CONNECTION *c = connections + i; 818 | // phone state 819 | p->phone_states |= (map_phone_state(ph) << i*4); 820 | // add data 821 | uint8_t level = queue_get_level(&c->rx_queue); 822 | if (level > 0) { 823 | if (level > 3) { 824 | level = 3; 825 | } 826 | // add the level (0-3 words per line) 827 | p->data_lengths |= level << i*2; 828 | for (int j = 0; j < level; j++) { 829 | uint32_t data; 830 | queue_remove_blocking(&c->rx_queue, &data); 831 | p->data[i][j] = data; 832 | } 833 | } 834 | if (ph->digit_count > ph->last_transmitted_digit) { 835 | p->phone_digits[i] = ph->digits[ph->last_transmitted_digit++]; 836 | } else { 837 | p->phone_digits[i] = 0; 838 | } 839 | } 840 | return; 841 | } 842 | 843 | uint8_t create_host_packet(uint8_t *buf) { 844 | HOST_PACKET p; 845 | populate_host_packet(&p); 846 | memcpy(buf, &p, sizeof(p)); 847 | return sizeof(p); 848 | } 849 | -------------------------------------------------------------------------------- /process_phones.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "pico/stdlib.h" 4 | #include "hardware/pio.h" 5 | #include "pico/util/queue.h" 6 | 7 | #define GAIN_STEP 0xB5// 0.71 = 0x0.B5C2, do 8 bit shift 8 | #define GAIN_DECAY 0xFCD1 // 0.9875778 = 0x0.FCD1, 16 bit shift 9 | #define SIGNAL_DECAY 0xFAE1 // 0.98 = 0.FAE1 10 | #define AUDIO_SCALE 250 11 | #define NUMBER_OF_PHONES 4 12 | 13 | #define CUE 63 14 | #define CUE_RELEASE_ACK 111 15 | #define SEIZE 111 // rx 0x90 = 10010000, rob 01101111 16 | #define DIAL 53 // 1100 1010 rob 0011 0101 17 | #define INTERDIGIT 85 // rob 01010101 18 | #define GO_TO_PLAIN_TEXT 3 // rob 00000011 19 | #define LOCK_IN 0 20 | #define LOCK_IN_ACK 0 21 | #define RELEASE 95 // rx 1010 0000, rob 01011111 22 | #define RELEASE_ACK 43 // tx 1011 1100, rob 01000011 23 | #define RING_VOICE 45 // 00101101 24 | #define RING_ACK 53 25 | #define RING_TRIP 43 26 | #define DIGIT_0 5 // rob 0000 0101 27 | #define DIGIT_1 15 28 | #define DIGIT_2 39 // rob 0010 0111 29 | #define DIGIT_3 63 // rob 0011 1111 30 | #define DIGIT_4 29 31 | #define DIGIT_5 3 32 | #define DIGIT_6 43 33 | #define DIGIT_7 119 34 | #define DIGIT_8 141 35 | #define DIGIT_9 9 36 | #define DIGIT_R 111 37 | #define DIGIT_C 17 // Conference Request 38 | #define DIGIT_P 23 // Priority 39 | #define DIGIT_I 45 //Immediate 40 | #define DIGIT_F 51 //Flash 41 | #define DIGIT_FO 53 //flash override 42 | //RATE CHANGE X Unsure 43 | //RATE CHANGE ACK X Unsure 44 | //RECALL X Unsure 45 | 46 | #define NULL_AUDIO 0xAAAAAAAA 47 | 48 | #ifndef PHONE_DATA 49 | #define PHONE_DATA 50 | 51 | #define number_codewords 18 52 | 53 | enum PHONE_STATE { 54 | idle = 0, 55 | off_hook = 1, 56 | transition_to_dial = 2, 57 | receiving_dial = 3, 58 | acknowledge_lock_in = 4, 59 | transition_to_plaintext = 5, 60 | ringing = 6, 61 | plain_text = 7, 62 | transition_to_idle = 8, 63 | record_and_playback = 9, 64 | ringing_remote = 10, 65 | awaiting_remote_ring = 11, 66 | ring_dismiss_send_cue = 12, 67 | ring_dismiss_send_dial = 13, 68 | send_release_ack = 14, 69 | transition_to_traffic_dial = 15, 70 | cue_until_sieze = 16, 71 | traffic_dial = 17, 72 | cue_transition_to_traffic_dial = 18, 73 | requesting_ring = 19, 74 | connection_failure = 20, 75 | cue_transition_to_dial = 21, 76 | not_in_service_recording = 22, 77 | rickroll = 23, 78 | usb_dial = 24, 79 | usb_traffic = 25, 80 | unreachable, 81 | line_check 82 | }; 83 | 84 | struct PHONE { 85 | // state machine/pin info 86 | PIO pio; 87 | u_int8_t sm_tx; 88 | u_int8_t sm_rx; 89 | u_int8_t pin_tx; 90 | u_int8_t pin_rx; 91 | // current state 92 | volatile enum PHONE_STATE phone_state; 93 | // are we pushing a command without rx? 94 | bool pushing_command; 95 | // list of received digits, current digit 96 | char digits[20]; 97 | char current_digit; 98 | uint8_t last_transmitted_digit; 99 | u_int8_t digit_count; 100 | bool receiving_digit; 101 | // if sending fixed qty of codewords, count that here 102 | u_int32_t sent_codewords; 103 | // last activity received from phone in microseconds 104 | u_int64_t last_data_received_time; 105 | // attempted contact timestamp 106 | u_int64_t attempted_contact; 107 | // activity counter for status blinky blinky 108 | u_int8_t activity_counter; 109 | // have we cleared the sm? 110 | bool idle_data_cleared; 111 | u_int32_t recording_index; 112 | u_int8_t received_codeword_counter; 113 | }; 114 | 115 | #define CONNECTED_TO_USB 0xFE 116 | #define NOT_CONNECTED 0xFF 117 | 118 | struct CONNECTION { 119 | queue_t rx_queue; 120 | queue_t tx_queue; 121 | bool active; 122 | bool requested; 123 | u_int8_t associated_device; 124 | }; 125 | 126 | extern u_int32_t used_cycles, unused_cycles; 127 | /* 128 | LED layout 129 | 1 3 5 7 23 130 | 0 2 4 6 (3.3V) 131 | */ 132 | extern struct PHONE phones[]; 133 | 134 | extern struct CONNECTION connections[]; 135 | 136 | void init_phones(); 137 | 138 | void phone_task(); 139 | 140 | uint8_t create_host_packet(uint8_t*); 141 | void handle_device_packet(uint8_t*); 142 | #endif 143 | -------------------------------------------------------------------------------- /ssd1306.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MIT License 4 | 5 | Copyright (c) 2021 David Schramm 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "ssd1306.h" 34 | #include "font.h" 35 | 36 | inline static void swap(int32_t *a, int32_t *b) { 37 | int32_t *t=a; 38 | *a=*b; 39 | *b=*t; 40 | } 41 | 42 | inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) { 43 | switch(i2c_write_blocking(i2c, addr, src, len, false)) { 44 | case PICO_ERROR_GENERIC: 45 | printf("[%s] addr not acknowledged!\n", name); 46 | break; 47 | case PICO_ERROR_TIMEOUT: 48 | printf("[%s] timeout!\n", name); 49 | break; 50 | default: 51 | //printf("[%s] wrote successfully %lu bytes!\n", name, len); 52 | break; 53 | } 54 | } 55 | 56 | inline static void ssd1306_write(ssd1306_t *p, uint8_t val) { 57 | uint8_t d[2]= {0x00, val}; 58 | fancy_write(p->i2c_i, p->address, d, 2, "ssd1306_write"); 59 | } 60 | 61 | bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance) { 62 | p->width=width; 63 | p->height=height; 64 | p->pages=height/8; 65 | p->address=address; 66 | 67 | p->i2c_i=i2c_instance; 68 | 69 | 70 | p->bufsize=(p->pages)*(p->width); 71 | if((p->buffer=malloc(p->bufsize+1))==NULL) { 72 | p->bufsize=0; 73 | return false; 74 | } 75 | 76 | ++(p->buffer); 77 | 78 | // from https://github.com/makerportal/rpi-pico-ssd1306 79 | uint8_t cmds[]= { 80 | SET_DISP, 81 | // timing and driving scheme 82 | SET_DISP_CLK_DIV, 83 | 0x80, 84 | SET_MUX_RATIO, 85 | height - 1, 86 | SET_DISP_OFFSET, 87 | 0x00, 88 | // resolution and layout 89 | SET_DISP_START_LINE, 90 | // charge pump 91 | SET_CHARGE_PUMP, 92 | p->external_vcc?0x10:0x14, 93 | SET_SEG_REMAP | 0x01, // column addr 127 mapped to SEG0 94 | SET_COM_OUT_DIR | 0x08, // scan from COM[N] to COM0 95 | SET_COM_PIN_CFG, 96 | width>2*height?0x02:0x12, 97 | // display 98 | SET_CONTRAST, 99 | 0xff, 100 | SET_PRECHARGE, 101 | p->external_vcc?0x22:0xF1, 102 | SET_VCOM_DESEL, 103 | 0x30, // or 0x40? 104 | SET_ENTIRE_ON, // output follows RAM contents 105 | SET_NORM_INV, // not inverted 106 | SET_DISP | 0x01, 107 | // address setting 108 | SET_MEM_ADDR, 109 | 0x00, // horizontal 110 | }; 111 | 112 | for(size_t i=0; ibuffer-1); 120 | } 121 | 122 | inline void ssd1306_poweroff(ssd1306_t *p) { 123 | ssd1306_write(p, SET_DISP|0x00); 124 | } 125 | 126 | inline void ssd1306_poweron(ssd1306_t *p) { 127 | ssd1306_write(p, SET_DISP|0x01); 128 | } 129 | 130 | inline void ssd1306_contrast(ssd1306_t *p, uint8_t val) { 131 | ssd1306_write(p, SET_CONTRAST); 132 | ssd1306_write(p, val); 133 | } 134 | 135 | inline void ssd1306_invert(ssd1306_t *p, uint8_t inv) { 136 | ssd1306_write(p, SET_NORM_INV | (inv & 1)); 137 | } 138 | 139 | inline void ssd1306_clear(ssd1306_t *p) { 140 | memset(p->buffer, 0, p->bufsize); 141 | } 142 | 143 | void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { 144 | if(x>=p->width || y>=p->height) return; 145 | 146 | p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8 147 | } 148 | 149 | void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) { 150 | if(x1>x2) { 151 | swap(&x1, &x2); 152 | swap(&y1, &y2); 153 | } 154 | 155 | if(x1==x2) { 156 | if(y1>y2) 157 | swap(&y1, &y2); 158 | for(int32_t i=y1; i<=y2; ++i) 159 | ssd1306_draw_pixel(p, x1, i); 160 | return; 161 | } 162 | 163 | float m=(float) (y2-y1) / (float) (x2-x1); 164 | 165 | for(int32_t i=x1; i<=x2; ++i) { 166 | float y=m*(float) (i-x1)+(float) y1; 167 | ssd1306_draw_pixel(p, i, (uint32_t) y); 168 | } 169 | } 170 | 171 | void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { 172 | for(uint32_t i=0; i '~') 187 | return; 188 | 189 | for(uint8_t i=0; i>=1) { 193 | if(line & 1) 194 | ssd1306_draw_square(p, x+i*scale, y+j*scale, scale, scale); 195 | } 196 | } 197 | } 198 | 199 | void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char *s) { 200 | for(int32_t x_n=x; *s; x_n+=(font[1]*scale + 1)) { 201 | ssd1306_draw_char_with_font(p, x_n, y, scale, font, *(s++)); 202 | } 203 | } 204 | 205 | void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c) { 206 | ssd1306_draw_char_with_font(p, x, y, scale, font_8x5, c); 207 | } 208 | 209 | void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char *s) { 210 | ssd1306_draw_string_with_font(p, x, y, scale, font_8x5, s); 211 | } 212 | 213 | static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) { 214 | switch(size) { 215 | case 1: 216 | return data[offset]; 217 | case 2: 218 | return data[offset]|(data[offset+1]<<8); 219 | case 4: 220 | return data[offset]|(data[offset+1]<<8)|(data[offset+2]<<16)|(data[offset+3]<<24); 221 | default: 222 | __builtin_unreachable(); 223 | } 224 | __builtin_unreachable(); 225 | } 226 | 227 | void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset) { 228 | if(size<54) // data smaller than header 229 | return; 230 | 231 | const uint32_t bfOffBits=ssd1306_bmp_get_val(data, 10, 4); 232 | const uint32_t biSize=ssd1306_bmp_get_val(data, 14, 4); 233 | const int32_t biWidth=(int32_t) ssd1306_bmp_get_val(data, 18, 4); 234 | const int32_t biHeight=(int32_t) ssd1306_bmp_get_val(data, 22, 4); 235 | const uint16_t biBitCount=(uint16_t) ssd1306_bmp_get_val(data, 28, 2); 236 | const uint32_t biCompression=ssd1306_bmp_get_val(data, 30, 4); 237 | 238 | if(biBitCount!=1) // image not monochrome 239 | return; 240 | 241 | if(biCompression!=0) // image compressed 242 | return; 243 | 244 | const int table_start=14+biSize; 245 | uint8_t color_val; 246 | 247 | for(uint8_t i=0; i<2; ++i) { 248 | if(!((data[table_start+i*4]<<16)|(data[table_start+i*4+1]<<8)|data[table_start+i*4+2])) { 249 | color_val=i; 250 | break; 251 | } 252 | } 253 | 254 | uint32_t bytes_per_line=(biWidth/8)+(biWidth&7?1:0); 255 | if(bytes_per_line&3) 256 | bytes_per_line=(bytes_per_line^(bytes_per_line&3))+4; 257 | 258 | const uint8_t *img_data=data+bfOffBits; 259 | 260 | int step=biHeight>0?-1:1; 261 | int border=biHeight>0?-1:biHeight; 262 | for(uint32_t y=biHeight>0?biHeight-1:0; y!=border; y+=step) { 263 | for(uint32_t x=0; x>3]>>(7-(x&7)))&1)==color_val) 265 | ssd1306_draw_pixel(p, x_offset+x, y_offset+y); 266 | } 267 | img_data+=bytes_per_line; 268 | } 269 | } 270 | 271 | inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) { 272 | ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0); 273 | } 274 | 275 | void ssd1306_show(ssd1306_t *p) { 276 | uint8_t payload[]= {SET_COL_ADDR, 0, p->width-1, SET_PAGE_ADDR, 0, p->pages-1}; 277 | if(p->width==64) { 278 | payload[1]+=32; 279 | payload[2]+=32; 280 | } 281 | 282 | for(size_t i=0; ibuffer-1)=0x40; 286 | 287 | fancy_write(p->i2c_i, p->address, p->buffer-1, p->bufsize+1, "ssd1306_show"); 288 | } 289 | -------------------------------------------------------------------------------- /ssd1306.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 David Schramm 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file ssd1306.h 27 | * 28 | * simple driver for ssd1306 displays 29 | */ 30 | 31 | #ifndef _inc_ssd1306 32 | #define _inc_ssd1306 33 | #include 34 | #include 35 | 36 | /** 37 | * @brief defines commands used in ssd1306 38 | */ 39 | typedef enum { 40 | SET_CONTRAST = 0x81, 41 | SET_ENTIRE_ON = 0xA4, 42 | SET_NORM_INV = 0xA6, 43 | SET_DISP = 0xAE, 44 | SET_MEM_ADDR = 0x20, 45 | SET_COL_ADDR = 0x21, 46 | SET_PAGE_ADDR = 0x22, 47 | SET_DISP_START_LINE = 0x40, 48 | SET_SEG_REMAP = 0xA0, 49 | SET_MUX_RATIO = 0xA8, 50 | SET_COM_OUT_DIR = 0xC0, 51 | SET_DISP_OFFSET = 0xD3, 52 | SET_COM_PIN_CFG = 0xDA, 53 | SET_DISP_CLK_DIV = 0xD5, 54 | SET_PRECHARGE = 0xD9, 55 | SET_VCOM_DESEL = 0xDB, 56 | SET_CHARGE_PUMP = 0x8D 57 | } ssd1306_command_t; 58 | 59 | /** 60 | * @brief holds the configuration 61 | */ 62 | typedef struct { 63 | uint8_t width; /**< width of display */ 64 | uint8_t height; /**< height of display */ 65 | uint8_t pages; /**< stores pages of display (calculated on initialization*/ 66 | uint8_t address; /**< i2c address of display*/ 67 | i2c_inst_t *i2c_i; /**< i2c connection instance */ 68 | bool external_vcc; /**< whether display uses external vcc */ 69 | uint8_t *buffer; /**< display buffer */ 70 | size_t bufsize; /**< buffer size */ 71 | } ssd1306_t; 72 | 73 | /** 74 | * @brief initialize display 75 | * 76 | * @param[in] p : pointer to instance of ssd1306_t 77 | * @param[in] width : width of display 78 | * @param[in] height : heigth of display 79 | * @param[in] address : i2c address of display 80 | * @param[in] i2c_instance : instance of i2c connection 81 | * 82 | * @return bool. 83 | * @retval true for Success 84 | * @retval false if initialization failed 85 | */ 86 | bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance); 87 | 88 | /** 89 | * @brief turn off display 90 | * 91 | * @param[in] p : instance of display 92 | * 93 | */ 94 | void ssd1306_poweroff(ssd1306_t *p); 95 | 96 | /** 97 | @brief turn on display 98 | 99 | @param[in] p : instance of display 100 | 101 | */ 102 | void ssd1306_poweron(ssd1306_t *p); 103 | 104 | /** 105 | @brief set contrast of display 106 | 107 | @param[in] p : instance of display 108 | @param[in] val : contrast 109 | 110 | */ 111 | void ssd1306_contrast(ssd1306_t *p, uint8_t val); 112 | 113 | /** 114 | @brief set invert display 115 | 116 | @param[in] p : instance of display 117 | @param[in] inv : inv==0: disable inverting, inv!=0: invert 118 | 119 | */ 120 | void ssd1306_invert(ssd1306_t *p, uint8_t inv); 121 | 122 | /** 123 | @brief display buffer, should be called on change 124 | 125 | @param[in] p : instance of display 126 | 127 | */ 128 | void ssd1306_show(ssd1306_t *p); 129 | 130 | /** 131 | @brief clear display buffer 132 | 133 | @param[in] p : instance of display 134 | 135 | */ 136 | void ssd1306_clear(ssd1306_t *p); 137 | 138 | /** 139 | @brief draw pixel on buffer 140 | 141 | @param[in] p : instance of display 142 | @param[in] x : x position 143 | @param[in] y : y position 144 | */ 145 | void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y); 146 | 147 | /** 148 | @brief draw pixel on buffer 149 | 150 | @param[in] p : instance of display 151 | @param[in] x1 : x position of starting point 152 | @param[in] y1 : y position of starting point 153 | @param[in] x2 : x position of end point 154 | @param[in] y2 : y position of end point 155 | */ 156 | void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2); 157 | 158 | /** 159 | @brief draw filled square at given position with given size 160 | 161 | @param[in] p : instance of display 162 | @param[in] x : x position of starting point 163 | @param[in] y : y position of starting point 164 | @param[in] width : width of square 165 | @param[in] height : height of square 166 | */ 167 | void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); 168 | 169 | /** 170 | @brief draw empty square at given position with given size 171 | 172 | @param[in] p : instance of display 173 | @param[in] x : x position of starting point 174 | @param[in] y : y position of starting point 175 | @param[in] width : width of square 176 | @param[in] height : height of square 177 | */ 178 | void ssd13606_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); 179 | 180 | /** 181 | @brief draw monochrome bitmap with offset 182 | 183 | @param[in] p : instance of display 184 | @param[in] data : image data (whole file) 185 | @param[in] size : size of image data in bytes 186 | @param[in] x_offset : offset of horizontal coordinate 187 | @param[in] y_offset : offset of vertical coordinate 188 | */ 189 | void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset); 190 | 191 | /** 192 | @brief draw monochrome bitmap 193 | 194 | @param[in] p : instance of display 195 | @param[in] data : image data (whole file) 196 | @param[in] size : size of image data in bytes 197 | */ 198 | void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size); 199 | 200 | /** 201 | @brief draw char with given font 202 | 203 | @param[in] p : instance of display 204 | @param[in] x : x starting position of char 205 | @param[in] y : y starting position of char 206 | @param[in] scale : scale font to n times of original size (default should be 1) 207 | @param[in] font : pointer to font 208 | @param[in] c : character to draw 209 | */ 210 | void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c); 211 | 212 | /** 213 | @brief draw char with builtin font 214 | 215 | @param[in] p : instance of display 216 | @param[in] x : x starting position of char 217 | @param[in] y : y starting position of char 218 | @param[in] scale : scale font to n times of original size (default should be 1) 219 | @param[in] c : character to draw 220 | */ 221 | void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c); 222 | 223 | /** 224 | @brief draw string with given font 225 | 226 | @param[in] p : instance of display 227 | @param[in] x : x starting position of text 228 | @param[in] y : y starting position of text 229 | @param[in] scale : scale font to n times of original size (default should be 1) 230 | @param[in] font : pointer to font 231 | @param[in] s : text to draw 232 | */ 233 | void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char *s ); 234 | 235 | /** 236 | @brief draw string with builtin font 237 | 238 | @param[in] p : instance of display 239 | @param[in] x : x starting position of text 240 | @param[in] y : y starting position of text 241 | @param[in] scale : scale font to n times of original size (default should be 1) 242 | @param[in] s : text to draw 243 | */ 244 | void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char *s); 245 | 246 | #endif 247 | -------------------------------------------------------------------------------- /usb_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #ifndef _USB_COMMON_H 8 | #define _USB_COMMON_H 9 | 10 | #include "pico/types.h" 11 | #include "hardware/structs/usb.h" 12 | 13 | // bmRequestType bit definitions 14 | #define USB_REQ_TYPE_STANDARD 0x00u 15 | #define USB_REQ_TYPE_TYPE_MASK 0x60u 16 | #define USB_REQ_TYPE_TYPE_CLASS 0x20u 17 | #define USB_REQ_TYPE_TYPE_VENDOR 0x40u 18 | 19 | #define USB_REQ_TYPE_RECIPIENT_MASK 0x1fu 20 | #define USB_REQ_TYPE_RECIPIENT_DEVICE 0x00u 21 | #define USB_REQ_TYPE_RECIPIENT_INTERFACE 0x01u 22 | #define USB_REQ_TYPE_RECIPIENT_ENDPOINT 0x02u 23 | 24 | #define USB_DIR_OUT 0x00u 25 | #define USB_DIR_IN 0x80u 26 | 27 | #define USB_TRANSFER_TYPE_CONTROL 0x0 28 | #define USB_TRANSFER_TYPE_ISOCHRONOUS 0x1 29 | #define USB_TRANSFER_TYPE_BULK 0x2 30 | #define USB_TRANSFER_TYPE_INTERRUPT 0x3 31 | #define USB_TRANSFER_TYPE_BITS 0x3 32 | 33 | // Descriptor types 34 | #define USB_DT_DEVICE 0x01 35 | #define USB_DT_CONFIG 0x02 36 | #define USB_DT_STRING 0x03 37 | #define USB_DT_INTERFACE 0x04 38 | #define USB_DT_ENDPOINT 0x05 39 | 40 | #define USB_REQUEST_GET_STATUS 0x0 41 | #define USB_REQUEST_CLEAR_FEATURE 0x01 42 | #define USB_REQUEST_SET_FEATURE 0x03 43 | #define USB_REQUEST_SET_ADDRESS 0x05 44 | #define USB_REQUEST_GET_DESCRIPTOR 0x06 45 | #define USB_REQUEST_SET_DESCRIPTOR 0x07 46 | #define USB_REQUEST_GET_CONFIGURATION 0x08 47 | #define USB_REQUEST_SET_CONFIGURATION 0x09 48 | #define USB_REQUEST_GET_INTERFACE 0x0a 49 | #define USB_REQUEST_SET_INTERFACE 0x0b 50 | #define USB_REQUEST_SYNC_FRAME 0x0c 51 | 52 | #define USB_REQUEST_MSC_GET_MAX_LUN 0xfe 53 | #define USB_REQUEST_MSC_RESET 0xff 54 | 55 | #define USB_FEAT_ENDPOINT_HALT 0x00 56 | #define USB_FEAT_DEVICE_REMOTE_WAKEUP 0x01 57 | #define USB_FEAT_TEST_MODE 0x02 58 | 59 | #define USB_DESCRIPTOR_TYPE_ENDPOINT 0x05 60 | 61 | struct usb_setup_packet { 62 | uint8_t bmRequestType; 63 | uint8_t bRequest; 64 | uint16_t wValue; 65 | uint16_t wIndex; 66 | uint16_t wLength; 67 | } __packed; 68 | 69 | struct usb_descriptor { 70 | uint8_t bLength; 71 | uint8_t bDescriptorType; 72 | }; 73 | 74 | struct usb_device_descriptor { 75 | uint8_t bLength; 76 | uint8_t bDescriptorType; 77 | uint16_t bcdUSB; 78 | uint8_t bDeviceClass; 79 | uint8_t bDeviceSubClass; 80 | uint8_t bDeviceProtocol; 81 | uint8_t bMaxPacketSize0; 82 | uint16_t idVendor; 83 | uint16_t idProduct; 84 | uint16_t bcdDevice; 85 | uint8_t iManufacturer; 86 | uint8_t iProduct; 87 | uint8_t iSerialNumber; 88 | uint8_t bNumConfigurations; 89 | } __packed; 90 | 91 | struct usb_configuration_descriptor { 92 | uint8_t bLength; 93 | uint8_t bDescriptorType; 94 | uint16_t wTotalLength; 95 | uint8_t bNumInterfaces; 96 | uint8_t bConfigurationValue; 97 | uint8_t iConfiguration; 98 | uint8_t bmAttributes; 99 | uint8_t bMaxPower; 100 | } __packed; 101 | 102 | struct usb_interface_descriptor { 103 | uint8_t bLength; 104 | uint8_t bDescriptorType; 105 | uint8_t bInterfaceNumber; 106 | uint8_t bAlternateSetting; 107 | uint8_t bNumEndpoints; 108 | uint8_t bInterfaceClass; 109 | uint8_t bInterfaceSubClass; 110 | uint8_t bInterfaceProtocol; 111 | uint8_t iInterface; 112 | } __packed; 113 | 114 | struct usb_endpoint_descriptor { 115 | uint8_t bLength; 116 | uint8_t bDescriptorType; 117 | uint8_t bEndpointAddress; 118 | uint8_t bmAttributes; 119 | uint16_t wMaxPacketSize; 120 | uint8_t bInterval; 121 | } __packed; 122 | 123 | struct usb_endpoint_descriptor_long { 124 | uint8_t bLength; 125 | uint8_t bDescriptorType; 126 | uint8_t bEndpointAddress; 127 | uint8_t bmAttributes; 128 | uint16_t wMaxPacketSize; 129 | uint8_t bInterval; 130 | uint8_t bRefresh; 131 | uint8_t bSyncAddr; 132 | } __attribute__((packed)); 133 | 134 | #endif -------------------------------------------------------------------------------- /usb_structures.h: -------------------------------------------------------------------------------- 1 | #define NO_COMMAND 0x0 2 | #define RING_COMMAND 0x1 3 | #define PLAINTEXT_COMMAND 0x2 4 | #define DISCONNECT_COMMAND 0x3 5 | #define RING_DISMISS_COMMAND 0x4 6 | 7 | typedef struct __attribute__ ((__packed__)) { 8 | uint16_t phone_states; 9 | uint8_t data_lengths; 10 | uint8_t reserved; 11 | uint32_t data[4][3]; 12 | uint8_t phone_digits[4]; 13 | } HOST_PACKET; 14 | 15 | typedef struct __attribute__ ((__packed__)) { 16 | uint32_t data[4][3]; // I shuffled this here to 32 bit align it 17 | uint8_t data_lengths; 18 | uint8_t phone_commands[4]; 19 | } DEVICE_PACKET; 20 | 21 | enum usb_phone_state { 22 | phone_idle = 0, 23 | phone_dial, 24 | phone_traffic, 25 | phone_ring, 26 | phone_awaiting_remote_ring, 27 | phone_unreachable, 28 | phone_requesting_ring, 29 | phone_transitioning 30 | }; --------------------------------------------------------------------------------