From 0b0c9335f9b2623c0aca89501a68de7d1d3fca7b Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Tue, 23 Jun 2026 17:20:59 -0700 Subject: [PATCH] Decouple hardware specific code from generic, agnostic code --- CMakeLists.txt | 143 +------ CMakePresets.json | 57 ++- README.md | 5 +- cmake/avr-gcc-toolchain.cmake | 396 +----------------- src/avclan/avclan_defs.h | 34 +- src/avclan/avclan_frame.c | 252 +++-------- src/avclan/avclan_frame.h | 16 +- src/avclan/avclan_phy.h | 61 ++- src/avclan/avclan_protocol.c | 8 +- src/avclan/cdchanger.c | 6 +- src/avclan/mediacontrol.h | 26 +- src/avclan/statustimer.h | 27 +- .../target/avr-attiny3216/CMakeLists.txt | 135 ++++++ src/avclan/target/avr-attiny3216/board_avr.c | 62 +++ .../avr-attiny3216/media_avr.c} | 22 +- src/avclan/target/avr-attiny3216/media_avr.h | 32 ++ .../avr-attiny3216/phy_avr.c} | 168 +++++++- .../avr-attiny3216/statustick_avr.c} | 12 + .../target/avr-attiny3216/timing_avr.h} | 29 +- src/avclan/timing.h | 32 ++ src/board.h | 33 ++ src/com232.c | 31 +- src/com232.h | 11 +- src/sniffer.c | 80 +--- 24 files changed, 768 insertions(+), 910 deletions(-) create mode 100644 src/avclan/target/avr-attiny3216/CMakeLists.txt create mode 100644 src/avclan/target/avr-attiny3216/board_avr.c rename src/avclan/{mediacontrol.c => target/avr-attiny3216/media_avr.c} (89%) create mode 100644 src/avclan/target/avr-attiny3216/media_avr.h rename src/avclan/{avclan_phy.c => target/avr-attiny3216/phy_avr.c} (67%) rename src/avclan/{statustimer.c => target/avr-attiny3216/statustick_avr.c} (88%) rename src/{timing.h => avclan/target/avr-attiny3216/timing_avr.h} (62%) create mode 100644 src/avclan/timing.h create mode 100644 src/board.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa6dabe..029bd2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,67 +1,7 @@ cmake_minimum_required(VERSION 3.24) -include(CMakeDependentOption) - -set(WITH_MCU OFF) # Disable target name modification setting from toolchain -set(AVR_MCU "attiny3216") -set(AVR_PROGRAMMER serialupdi) -set(AVR_UPLOADTOOL_BAUDRATE 230400) - -set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/avr-gcc-toolchain.cmake") project(avclan-mockingboard VERSION 1 LANGUAGES C CXX ASM) -set(FREQSEL 16MHz CACHE STRING "Select the operating frequency") -set_property(CACHE FREQSEL PROPERTY STRINGS "20MHz" "16MHz") - -if(FREQSEL MATCHES "20MHz") -set(FREQSEL 20000000L) -set(AVR_UPLOADTOOL_BASE_OPTIONS ${AVR_UPLOADTOOL_BASE_OPTIONS} -U osccfg:w:0x2:m) -else() -set(FREQSEL 16000000L) -set(AVR_UPLOADTOOL_BASE_OPTIONS ${AVR_UPLOADTOOL_BASE_OPTIONS} -U osccfg:w:0x1:m) -endif() - -# Set startup time to 8 ms (0x4) -set(AVR_UPLOADTOOL_BASE_OPTIONS ${AVR_UPLOADTOOL_BASE_OPTIONS} -U syscfg1:w:0x4:m) - -option(CLK_PRESCALE "Enable the main clock prescaler") -cmake_dependent_option(CLK_PRESCALE_DIV "Prescaler divisor" CLKCTRL_PDIV_2X_gc STRING "CLK_PRESCALE") -if(DEFINED CACHE{CLK_PRESCALE_DIV}) - set_property(CACHE CLK_PRESCALE_DIV PROPERTY STRINGS - CLKCTRL_PDIV_2X_gc - CLKCTRL_PDIV_4X_gc - CLKCTRL_PDIV_8X_gc - CLKCTRL_PDIV_16X_gc - CLKCTRL_PDIV_32X_gc - CLKCTRL_PDIV_64X_gc - CLKCTRL_PDIV_6X_gc - CLKCTRL_PDIV_10X_gc - CLKCTRL_PDIV_12X_gc - CLKCTRL_PDIV_24X_gc - CLKCTRL_PDIV_48X_gc - ) -else() -set(CLK_PRESCALE_DIV CLKCTRL_PDIV_2X_gc) -endif() - -set(TCB_CLKSEL "TCB_CLKSEL_CLKDIV2_gc" CACHE STRING "Choose the clock for TCB") -set_property(CACHE TCB_CLKSEL PROPERTY STRINGS - TCB_CLKSEL_CLKDIV1_gc - TCB_CLKSEL_CLKDIV2_gc - TCB_CLKSEL_CLKTCA_gc -) - -set(USART_RXMODE "USART_RXMODE_CLK2X_gc" CACHE STRING "USART at normal or double speed operation") -set_property(CACHE USART_RXMODE PROPERTY STRINGS - USART_RXMODE_CLK2X_gc - USART_RXMODE_NORMAL_gc -) - -# Measured wall-clock duration (ms) of one nominal 32768-tick RTC period, used -# to calibrate out the internal OSCULP32K's tolerance for the status-update -# tick. 1000 = no correction; set per-board in CMakeUserPresets.json. -set(RTC_STATUS_PERIOD_MS 1000 CACHE STRING "Measured ms per nominal RTC status period (1000 = no correction)") - set(CMAKE_C_STANDARD 23) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -75,77 +15,38 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() endif() -# The AVC-LAN stack as a (currently AVR-only) static library. Establishing this -# boundary now sets up the planned HAL extraction + off-target protocol testing. -add_avr_library(avclan - src/avclan/avclan_phy.c +# Hardware target (port) selection. The chosen target// directory supplies +# the port implementation sources, the per-target include path, and all +# hardware-specific build configuration (compile defs/options, device-pack +# handling, flashing) via its own CMakeLists, pulled in with add_subdirectory +# below. The matching cross-compiler is selected separately by a toolchain file +# (e.g. cmake/avr-gcc-toolchain.cmake) named in a CMake preset. Adding a new +# board = a sibling target/ directory + a toolchain file + a preset wiring them. +set(AVCLAN_TARGET avr-attiny3216 CACHE STRING "Hardware target (port) to build") + +# The AVC-LAN stack as a static library: the target-agnostic generic core (no +# , no register access). The selected target's port sources and flags +# are contributed by its subdirectory. +add_library(avclan STATIC src/avclan/avclan_frame.c src/avclan/avclan_protocol.c - src/avclan/cdchanger.c - src/avclan/mediacontrol.c - src/avclan/statustimer.c) + src/avclan/cdchanger.c) -add_avr_executable(mockingboard +add_executable(mockingboard src/sniffer.c src/com232.c src/queue.c) -# avclan exports its public headers (src/avclan) to consumers, and reaches into -# src/ for sibling driver headers (com232.h, timing.h) during its own build. +# avclan exports its public generic headers (src/avclan) to consumers and +# reaches into src/ for sibling headers (com232.h, board.h) during its own +# build. The selected target adds its own per-target include path. target_include_directories(avclan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/avclan PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(mockingboard avclan) -# Handle libc versioning -try_compile(LIBC_VERSION_TEST - SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/cmake/libc-version-test.cpp" - COMPILE_DEFINITIONS -mmcu=${AVR_MCU} -) - -if(NOT LIBC_VERSION_TEST) - include(FetchContent) - FetchContent_Declare( - attiny_atpack - URL http://packs.download.atmel.com/Atmel.ATtiny_DFP.2.0.368.atpack - URL_HASH SHA512=ee16a8ebecb57bd998a9cd4373368e3d45982cbbc3825e18d1dcac58215db6b9d907ad1ba2020cba9187fed7ba8c6f255a4fa1214e40c7a17ab2d18474f4d079 - DOWNLOAD_NAME Atmel.ATtiny_DFP.2.0.368.atpack.zip - ) - FetchContent_MakeAvailable(attiny_atpack) - try_compile(LIBC_VERSION_TEST - SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/cmake/libc-version-test.cpp" - COMPILE_DEFINITIONS - -B "${attiny_atpack_SOURCE_DIR}/gcc/dev/${AVR_MCU}" - -isystem "${attiny_atpack_SOURCE_DIR}/include" - -mmcu=${AVR_MCU} - ) - if(NOT LIBC_VERSION_TEST) - message(FATAL_ERROR "Insufficient AVR-LIBC/Microchip pack for chosen MCU '${AVR_MCU}'") - else() - # PUBLIC on the library so its compile inherits the device headers and - # the requirement propagates to mockingboard via linking. - target_include_directories(avclan SYSTEM - PUBLIC "${attiny_atpack_SOURCE_DIR}/include") - target_link_options(mockingboard PUBLIC - -B "${attiny_atpack_SOURCE_DIR}/gcc/dev/${AVR_MCU}" - ) - endif() -endif() - -# PUBLIC on avclan so these reach both the library TUs and the executable TUs -# (mockingboard inherits them by linking avclan). -target_compile_definitions(avclan PUBLIC - FREQSEL=${FREQSEL} - CLK_PRESCALE=$,0x01,0x00> - CLK_PRESCALE_DIV=${CLK_PRESCALE_DIV} - __CLK_PRESCALE_DIV=__${CLK_PRESCALE_DIV} - TCB_CLKSEL=${TCB_CLKSEL} - USART_RXMODE=${USART_RXMODE} - RTC_STATUS_PERIOD_MS=${RTC_STATUS_PERIOD_MS} -) -target_compile_options(avclan PUBLIC - --param=min-pagesize=0 - -ffunction-sections - -fdata-sections -) +# Pull in the selected hardware target: its port sources, per-target headers, +# hardware-specific compile options/definitions, device-pack handling, and the +# flashing target. Added after the targets above so it can extend them. +add_subdirectory(src/avclan/target/${AVCLAN_TARGET}) diff --git a/CMakePresets.json b/CMakePresets.json index fb25bba..d32d085 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,12 +1,12 @@ { - "version": 2, + "version": 3, "configurePresets": [ { "name": "usb0", "hidden": true, "description": "Program over /dev/ttyUSB0", "cacheVariables": { - "AVR_UPLOADTOOL_PORT": "/dev/ttyUSB0" + "AVRDUDE_PORT": "/dev/ttyUSB0" } }, { @@ -14,7 +14,19 @@ "hidden": true, "description": "Program over /dev/ttyUSB1", "cacheVariables": { - "AVR_UPLOADTOOL_PORT": "/dev/ttyUSB1" + "AVRDUDE_PORT": "/dev/ttyUSB1" + } + }, + { + "name": "avr-attiny3216", + "hidden": true, + "description": "AVR ATtiny3216 cross-compile toolchain + matching port", + "toolchainFile": "${sourceDir}/cmake/avr-gcc-toolchain.cmake", + "cacheVariables": { + "AVCLAN_TARGET": "avr-attiny3216", + "FREQSEL": "20MHz", + "TCB_CLKSEL": "TCB_CLKSEL_CLKDIV1_gc", + "USART_RXMODE": "USART_RXMODE_CLK2X_gc" } }, { @@ -24,9 +36,7 @@ "generator": "Unix Makefiles", "binaryDir": "${sourceDir}/build", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug", - "FREQSEL": "20MHz", - "TCB_CLKSEL": "TCB_CLKSEL_CLKDIV1_gc" + "CMAKE_BUILD_TYPE": "Debug" } }, { @@ -35,44 +45,47 @@ "description": "RelWithDebInfo build settings", "inherits": "debug-base", "cacheVariables": { - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "USART_RXMODE": "USART_RXMODE_CLK2X_gc" + "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, { - "name": "default", - "displayName": "Debug (ttyUSB0)", - "description": "Debug build, program over /dev/ttyUSB0", + "name": "attiny3216-debug-usb0", + "displayName": "ATtiny3216 Debug (ttyUSB0)", + "description": "ATtiny3216 Debug build, program over /dev/ttyUSB0", "inherits": [ + "avr-attiny3216", "debug-base", "usb0" ] }, { - "name": "default-usb1", - "displayName": "Debug (ttyUSB1)", - "description": "Debug build, program over /dev/ttyUSB1", + "name": "attiny3216-debug-usb1", + "displayName": "ATtiny3216 Debug (ttyUSB1)", + "description": "ATtiny3216 Debug build, program over /dev/ttyUSB1", "inherits": [ + "avr-attiny3216", "debug-base", "usb1" ] }, { - "name": "relwithdebinfo", - "displayName": "RelWithDebInfo (ttyUSB1)", - "description": "RelWithDebInfo build, program over /dev/ttyUSB1", + "name": "attiny3216-relwithdebinfo-usb0", + "displayName": "ATtiny3216 RelWithDebInfo (ttyUSB0)", + "description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB0", "inherits": [ + "avr-attiny3216", "relwithdebinfo-base", - "usb1" + "usb0" ] }, { - "name": "relwithdebinfo-usb0", - "displayName": "RelWithDebInfo (ttyUSB0)", - "description": "RelWithDebInfo build, program over /dev/ttyUSB0", + "name": "attiny3216-relwithdebinfo-usb1", + "displayName": "ATtiny3216 RelWithDebInfo (ttyUSB1)", + "description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB1", "inherits": [ + "avr-attiny3216", "relwithdebinfo-base", - "usb0" + "usb1" ] } ] diff --git a/README.md b/README.md index 5506451..92c3449 100644 --- a/README.md +++ b/README.md @@ -80,13 +80,14 @@ I ordered a [cable harness](https://www.amazon.com/dp/B01EUZ8CFU) from Amazon to #### Natively/without VS Code Dev Containers 1. Install avr-gcc >= v13.1, binutils >= v2.39, cmake >= v3.24 -2. Configure cmake in repo with `cmake -B build` +2. Configure cmake with a hardware-target preset, e.g. `cmake --preset attiny3216-debug-usb0` + (the preset selects the AVR cross-compile toolchain; `cmake --list-presets` shows the rest) - Trigger builds with `cmake --build build` 3. Start developing! ### Flashing -The CMake target `upload_mockingboard` uses the AVRDude utility using the "serialupdi" programmer type. I use a [USB => Serial converter](https://www.adafruit.com/product/5335) with the Rx and Tx lines connected, using one of the options described [by SpenceKonde here](https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md). +The CMake target `flash` uses the AVRDude utility using the "serialupdi" programmer type. I use a [USB => Serial converter](https://www.adafruit.com/product/5335) with the Rx and Tx lines connected, using one of the options described [by SpenceKonde here](https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md). # Protocol reverse-engineering diff --git a/cmake/avr-gcc-toolchain.cmake b/cmake/avr-gcc-toolchain.cmake index 450e597..80c21b0 100644 --- a/cmake/avr-gcc-toolchain.cmake +++ b/cmake/avr-gcc-toolchain.cmake @@ -1,400 +1,34 @@ ########################################################################## -# "THE ANY BEVERAGE-WARE LICENSE" (Revision 42 - based on beer-ware -# license): -# wrote this file. As long as you retain this notice -# you can do whatever you want with this stuff. If we meet some day, and -# you think this stuff is worth it, you can buy me a be(ve)er(age) in -# return. (I don't like beer much.) +# AVR cross-compilation toolchain (ATtiny-0/1/2 series, UPDI programming). # -# Matthias Kleemann -########################################################################## - -########################################################################## -# The toolchain requires some variables set. +# Originally based on Matthias Kleemann's avr-cmake module +# (, "THE ANY BEVERAGE-WARE LICENSE"), but slimmed to just +# the cross-compiler definition, the AVR-wide compile/link flags, and the +# avrdude configuration the `flash` target needs. # -# AVR_MCU (default: atmega8) -# the type of AVR the application is built for -# AVR_L_FUSE (NO DEFAULT) -# the LOW fuse value for the MCU used -# AVR_H_FUSE (NO DEFAULT) -# the HIGH fuse value for the MCU used -# AVR_UPLOADTOOL (default: avrdude) -# the application used to upload to the MCU -# NOTE: The toolchain is currently quite specific about -# the commands used, so it needs tweaking. -# AVR_UPLOADTOOL_PORT (default: usb) -# the port used for the upload tool, e.g. usb -# AVR_PROGRAMMER (default: avrispmkII) -# the programmer hardware used, e.g. avrispmkII ########################################################################## ########################################################################## -# options -########################################################################## -option(WITH_MCU "Add the mCU type to the target file name." ON) - -########################################################################## -# executables in use +# Cross-compiler definition ########################################################################## find_program(AVR_CC avr-gcc REQUIRED) find_program(AVR_CXX avr-g++ REQUIRED) -find_program(AVR_OBJCOPY avr-objcopy REQUIRED) -find_program(AVR_SIZE_TOOL avr-size REQUIRED) -find_program(AVR_OBJDUMP avr-objdump REQUIRED) -########################################################################## -# toolchain starts with defining mandatory variables -########################################################################## set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR avr) set(CMAKE_C_COMPILER ${AVR_CC}) set(CMAKE_CXX_COMPILER ${AVR_CXX}) -########################################################################## -# Identification -########################################################################## -set(AVR 1) +set(AVR_MCU attiny3216 CACHE STRING "Target AVR device" FORCE) + +set(CMAKE_C_FLAGS_INIT "-mmcu=${AVR_MCU}") +set(CMAKE_CXX_FLAGS_INIT "-mmcu=${AVR_MCU}") +set(CMAKE_ASM_FLAGS_INIT "-mmcu=${AVR_MCU}") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-Wl,--gc-sections -mrelax") ########################################################################## -# some necessary tools and variables for AVR builds, which may not -# defined yet -# - AVR_UPLOADTOOL -# - AVR_UPLOADTOOL_PORT -# - AVR_PROGRAMMER -# - AVR_MCU -# - AVR_SIZE_ARGS +# Bypass the link step in CMake's compiler sanity check (and the +# libc-version try_compile): this is a cross compiler, so a full executable +# link would fail. See https://stackoverflow.com/q/53633705 ########################################################################## - -# default upload tool -if(NOT AVR_UPLOADTOOL) - set( - AVR_UPLOADTOOL avrdude - CACHE STRING "Set default upload tool: avrdude" - ) - find_program(AVR_UPLOADTOOL avrdude) -endif(NOT AVR_UPLOADTOOL) - -# default upload tool port -if(NOT AVR_UPLOADTOOL_PORT) - set( - AVR_UPLOADTOOL_PORT usb - CACHE STRING "Set default upload tool port: usb" - ) -endif(NOT AVR_UPLOADTOOL_PORT) - -# default programmer (hardware) -if(NOT AVR_PROGRAMMER) - set( - AVR_PROGRAMMER avrispmkII - CACHE STRING "Set default programmer hardware model: avrispmkII" - ) -endif(NOT AVR_PROGRAMMER) - -# default MCU (chip) -if(NOT AVR_MCU) - set( - AVR_MCU atmega8 - CACHE STRING "Set default MCU: atmega8 (see 'avr-gcc --target-help' for valid values)" - ) -endif(NOT AVR_MCU) - -#default avr-size args -if(NOT AVR_SIZE_ARGS) - if(APPLE) - set(AVR_SIZE_ARGS -B) - else(APPLE) - set(AVR_SIZE_ARGS -G) - endif(APPLE) -endif(NOT AVR_SIZE_ARGS) - -# prepare base flags for upload tool -set(AVR_UPLOADTOOL_BASE_OPTIONS -p ${AVR_MCU} -c ${AVR_PROGRAMMER}) - -# use AVR_UPLOADTOOL_BAUDRATE as baudrate for upload tool (if defined) -if(AVR_UPLOADTOOL_BAUDRATE) - set(AVR_UPLOADTOOL_BASE_OPTIONS ${AVR_UPLOADTOOL_BASE_OPTIONS} -b ${AVR_UPLOADTOOL_BAUDRATE}) -endif() - -########################################################################## -# check build types: -# - Debug -# - Release -# - RelWithDebInfo -# -# Release is chosen, because of some optimized functions in the -# AVR toolchain, e.g. _delay_ms(). -########################################################################## -if(NOT ((CMAKE_BUILD_TYPE MATCHES Release) OR -(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) OR -(CMAKE_BUILD_TYPE MATCHES Debug) OR -(CMAKE_BUILD_TYPE MATCHES MinSizeRel))) - set( - CMAKE_BUILD_TYPE Release - CACHE STRING "Choose cmake build type: Debug Release RelWithDebInfo MinSizeRel" - FORCE - ) -endif(NOT ((CMAKE_BUILD_TYPE MATCHES Release) OR -(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) OR -(CMAKE_BUILD_TYPE MATCHES Debug) OR -(CMAKE_BUILD_TYPE MATCHES MinSizeRel))) - - - -########################################################################## - -########################################################################## -# target file name add-on -########################################################################## -if(WITH_MCU) - set(MCU_TYPE_FOR_FILENAME "-${AVR_MCU}") -else(WITH_MCU) - set(MCU_TYPE_FOR_FILENAME "") -endif(WITH_MCU) - -########################################################################## -# add_avr_executable -# - IN_VAR: EXECUTABLE_NAME -# -# Creates targets and dependencies for AVR toolchain, building an -# executable. Calls add_executable with ELF file as target name, so -# any link dependencies need to be using that target, e.g. for -# target_link_libraries(-${AVR_MCU}.elf ...). -########################################################################## -function(add_avr_executable EXECUTABLE_NAME) - - if(NOT ARGN) - message(FATAL_ERROR "No source files given for ${EXECUTABLE_NAME}.") - endif(NOT ARGN) - - # set file names - set(hex_file ${EXECUTABLE_NAME}${MCU_TYPE_FOR_FILENAME}.hex) - set(lst_file ${EXECUTABLE_NAME}${MCU_TYPE_FOR_FILENAME}.lst) - set(map_file ${EXECUTABLE_NAME}${MCU_TYPE_FOR_FILENAME}.map) - set(eeprom_image ${EXECUTABLE_NAME}${MCU_TYPE_FOR_FILENAME}-eeprom.hex) - - set (${EXECUTABLE_NAME}_ELF_TARGET ${EXECUTABLE_NAME} PARENT_SCOPE) - set (${EXECUTABLE_NAME}_HEX_TARGET ${hex_file} PARENT_SCOPE) - set (${EXECUTABLE_NAME}_LST_TARGET ${lst_file} PARENT_SCOPE) - set (${EXECUTABLE_NAME}_MAP_TARGET ${map_file} PARENT_SCOPE) - set (${EXECUTABLE_NAME}_EEPROM_TARGET ${eeprom_file} PARENT_SCOPE) - # elf file - add_executable(${EXECUTABLE_NAME} ${ARGN}) - - set_target_properties( - ${EXECUTABLE_NAME} - PROPERTIES - COMPILE_FLAGS "-mmcu=${AVR_MCU}" - LINK_FLAGS "-mmcu=${AVR_MCU} -Wl,--gc-sections -mrelax -Wl,-Map,${map_file}" - ) - - add_custom_command( - OUTPUT ${lst_file} - COMMAND - ${AVR_OBJDUMP} -d ${EXECUTABLE_NAME} > ${lst_file} - DEPENDS ${EXECUTABLE_NAME} - ) - - # eeprom - add_custom_command( - OUTPUT ${eeprom_image} - COMMAND - ${AVR_OBJCOPY} -j .eeprom --set-section-flags=.eeprom=alloc,load - --change-section-lma .eeprom=0 --no-change-warnings - -O ihex ${EXECUTABLE_NAME} ${eeprom_image} - DEPENDS ${EXECUTABLE_NAME} - ) - - # clean - get_directory_property(clean_files ADDITIONAL_MAKE_CLEAN_FILES) - set_directory_properties( - PROPERTIES - ADDITIONAL_MAKE_CLEAN_FILES "${map_file}" - ) - - # upload - with avrdude - add_custom_target( - upload_${EXECUTABLE_NAME} - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} ${AVR_UPLOADTOOL_OPTIONS} - -U flash:w:${EXECUTABLE_NAME}:e - -P ${AVR_UPLOADTOOL_PORT} - DEPENDS ${EXECUTABLE_NAME} - COMMENT "Uploading ${hex_file} to ${AVR_MCU} using ${AVR_PROGRAMMER}" - ) - - # upload eeprom only - with avrdude - # see also bug http://savannah.nongnu.org/bugs/?40142 - add_custom_target( - upload_${EXECUTABLE_NAME}_eeprom - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} ${AVR_UPLOADTOOL_OPTIONS} - -U eeprom:w:${eeprom_image} - -P ${AVR_UPLOADTOOL_PORT} - DEPENDS ${eeprom_image} - COMMENT "Uploading ${eeprom_image} to ${AVR_MCU} using ${AVR_PROGRAMMER}" - ) - - # disassemble - add_custom_target( - disassemble_${EXECUTABLE_NAME} - ${AVR_OBJDUMP} -h -S ${EXECUTABLE_NAME} > ${EXECUTABLE_NAME}.lst - DEPENDS ${EXECUTABLE_NAME} - ) -endfunction(add_avr_executable) - - -########################################################################## -# add_avr_library -# - IN_VAR: LIBRARY_NAME -# -# Calls add_library with an optionally concatenated name -# ${MCU_TYPE_FOR_FILENAME}. -# This needs to be used for linking against the library, e.g. calling -# target_link_libraries(...). -########################################################################## -function(add_avr_library LIBRARY_NAME) - if(NOT ARGN) - message(FATAL_ERROR "No source files given for ${LIBRARY_NAME}.") - endif(NOT ARGN) - - set(lib_file ${LIBRARY_NAME}${MCU_TYPE_FOR_FILENAME}) - set (${LIBRARY_NAME}_LIB_TARGET ${elf_file} PARENT_SCOPE) - - add_library(${lib_file} STATIC ${ARGN}) - - set_target_properties( - ${lib_file} - PROPERTIES - COMPILE_FLAGS "-mmcu=${AVR_MCU}" - OUTPUT_NAME "${lib_file}" - ) - - if(NOT TARGET ${LIBRARY_NAME}) - add_custom_target( - ${LIBRARY_NAME} - ALL - DEPENDS ${lib_file} - ) - - set_target_properties( - ${LIBRARY_NAME} - PROPERTIES - OUTPUT_NAME "${lib_file}" - ) - endif(NOT TARGET ${LIBRARY_NAME}) - -endfunction(add_avr_library) - -########################################################################## -# avr_target_link_libraries -# - IN_VAR: EXECUTABLE_TARGET -# - ARGN : targets and files to link to -# -# Calls target_link_libraries with AVR target names (concatenation, -# extensions and so on. -########################################################################## -function(avr_target_link_libraries EXECUTABLE_TARGET) - if(NOT ARGN) - message(FATAL_ERROR "Nothing to link to ${EXECUTABLE_TARGET}.") - endif(NOT ARGN) - - get_target_property(TARGET_LIST ${EXECUTABLE_TARGET} OUTPUT_NAME) - - foreach(TGT ${ARGN}) - if(TARGET ${TGT}) - get_target_property(ARG_NAME ${TGT} OUTPUT_NAME) - list(APPEND NON_TARGET_LIST ${ARG_NAME}) - else(TARGET ${TGT}) - list(APPEND NON_TARGET_LIST ${TGT}) - endif(TARGET ${TGT}) - endforeach(TGT ${ARGN}) - - target_link_libraries(${TARGET_LIST} ${NON_TARGET_LIST}) -endfunction(avr_target_link_libraries EXECUTABLE_TARGET) - -########################################################################## -# avr_target_include_directories -# -# Calls target_include_directories with AVR target names -########################################################################## - -function(avr_target_include_directories EXECUTABLE_TARGET) - if(NOT ARGN) - message(FATAL_ERROR "No include directories to add to ${EXECUTABLE_TARGET}.") - endif() - - get_target_property(TARGET_LIST ${EXECUTABLE_TARGET} OUTPUT_NAME) - set(extra_args ${ARGN}) - - target_include_directories(${TARGET_LIST} ${extra_args}) -endfunction() - -########################################################################## -# avr_target_compile_definitions -# -# Calls target_compile_definitions with AVR target names -########################################################################## - -function(avr_target_compile_definitions EXECUTABLE_TARGET) - if(NOT ARGN) - message(FATAL_ERROR "No compile definitions to add to ${EXECUTABLE_TARGET}.") - endif() - - get_target_property(TARGET_LIST ${EXECUTABLE_TARGET} OUTPUT_NAME) - set(extra_args ${ARGN}) - - target_compile_definitions(${TARGET_LIST} ${extra_args}) -endfunction() - -function(avr_generate_fixed_targets) - # get status - add_custom_target( - get_status - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} -P ${AVR_UPLOADTOOL_PORT} -n -v - COMMENT "Get status from ${AVR_MCU}" - ) - - # get fuses - add_custom_target( - get_fuses - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} -P ${AVR_UPLOADTOOL_PORT} -n - -U lfuse:r:-:b - -U hfuse:r:-:b - COMMENT "Get fuses from ${AVR_MCU}" - ) - - # set fuses - add_custom_target( - set_fuses - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} -P ${AVR_UPLOADTOOL_PORT} - -U lfuse:w:${AVR_L_FUSE}:m - -U hfuse:w:${AVR_H_FUSE}:m - COMMENT "Setup: High Fuse: ${AVR_H_FUSE} Low Fuse: ${AVR_L_FUSE}" - ) - - # get oscillator calibration - add_custom_target( - get_calibration - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} -P ${AVR_UPLOADTOOL_PORT} - -U calibration:r:${AVR_MCU}_calib.tmp:r - COMMENT "Write calibration status of internal oscillator to ${AVR_MCU}_calib.tmp." - ) - - # set oscillator calibration - add_custom_target( - set_calibration - ${AVR_UPLOADTOOL} ${AVR_UPLOADTOOL_BASE_OPTIONS} -P ${AVR_UPLOADTOOL_PORT} - -U calibration:w:${AVR_MCU}_calib.hex - COMMENT "Program calibration status of internal oscillator from ${AVR_MCU}_calib.hex." - ) -endfunction() - -########################################################################## -# Bypass the link step in CMake's "compiler sanity test" check -# -# CMake throws in a try_compile() target test in some generators, but does -# not know that this is a cross compiler so the executable can't link. -# Change the target type: -# -# https://stackoverflow.com/q/53633705 -########################################################################## - set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") diff --git a/src/avclan/avclan_defs.h b/src/avclan/avclan_defs.h index c1aab06..2fe3271 100644 --- a/src/avclan/avclan_defs.h +++ b/src/avclan/avclan_defs.h @@ -27,7 +27,6 @@ #ifndef AVCLAN_DEFS_H #define AVCLAN_DEFS_H -#include #include #define MAXMSGLEN 32 @@ -143,4 +142,37 @@ typedef struct AVCLAN_frame_struct { uint8_t *data; } AVCLAN_frame_t; +// A single bus symbol. bit_zero/bit_one carry data (and double as parity +// values); bit_start marks a frame start bit. +typedef enum avclan_bit : uint8_t { + bit_zero = 0x00, + bit_one = 0x01, + bit_start = 0x10 +} avclan_bit_t; + +// Error enums are ordered such that a lower numeric value corresponds to more +// progress/success before an error occured, with 0 being no errors +typedef enum : uint8_t { + rNO_ERROR = 0x00, + rBAD_DATA_PARITY, + rBAD_LENGTH_RANGE, + rBAD_LENGTH_PARITY, + rBAD_PERIPHERAL_PARITY, + rBAD_CONTROLLER_PARITY, + rBAD_CONTROL_PARITY, + rSTARTBIT_TOO_SHORT, + rSTARTBIT_TOO_LONG, + rLATCHED_COMPARATOR, +} avclan_readerr_t; + +typedef enum : uint8_t { + sNO_ERROR = 0x00, + sNAK_DATA, + sNAK_MESSAGE_LENGTH, + sNAK_CONTROL, + sNAK_ADDRESS, + sBUSY, + sMUTED, +} avclan_senderr_t; + #endif // AVCLAN_DEFS_H diff --git a/src/avclan/avclan_frame.c b/src/avclan/avclan_frame.c index 07d59be..925228a 100644 --- a/src/avclan/avclan_frame.c +++ b/src/avclan/avclan_frame.c @@ -20,58 +20,16 @@ along with this program. If not, see . */ -#include -#include -#include #include #include -#include #include "avclan_frame.h" -#include "avclan_phy.h" -#include "cdchanger.h" -#include "com232.h" -#include "mediacontrol.h" -#include "statustimer.h" +#include "avclan_phy.h" // bus symbol I/O + transaction guard (target-provided) +#include "com232.h" // error logging -// F_CPU defined in timing.h and potentially needed by avr-libc (e.g. delay.h) -#include "timing.h" - -/* Disable non-read related interrupts (USART RX, PIT, TCA) during AVCLAN reads. - */ -void AVCLAN_stopEvent() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - statustimer_disable(); - USART0.CTRLA &= ~USART_RXCIE_bm; - mediacontrol_syncDuringMask(); - } -} - -// Re-enable serial and periodic interrupts. -void AVCLAN_startEvent() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - if (AVCLAN_isPlaying()) // Reenable status interrupt if currently playing - statustimer_enable(); - USART0.CTRLA |= USART_RXCIE_bm; - } -} - -uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { +avclan_readerr_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { struct errtype { - // Error enum is ordered such that a lower numeric value corresponds to more - // successful read - enum : uint8_t { - NO_ERROR = 0x00, - BAD_DATA_PARITY = 0x01, - BAD_LENGTH_RANGE, - BAD_LENGTH_PARITY, - BAD_PERIPHERAL_PARITY, - BAD_CONTROLLER_PARITY, - BAD_CONTROL_PARITY, - STARTBIT_TOO_SHORT, - STARTBIT_TOO_LONG, - LATCHED_COMPARATOR, - } errno; + avclan_readerr_t errno; union { uint8_t val; // BAD_LENGTH_RANGE: the out-of-range length value struct { @@ -81,48 +39,13 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { }; } err = {0}; - AVCLAN_stopEvent(); // disable timer1 interrupt + AVCLAN_stopEvent(); // quiesce contending sources during the read uint8_t tmp = 0; - uint16_t startbitlen = TCB1.CNT = 0; - while (!BUS_IS_IDLE) { - startbitlen = TCB1.CNT; - if (startbitlen > (uint16_t)AVCLAN_STARTBIT_LOGIC_0 * 1.2) { - err.errno = STARTBIT_TOO_LONG; - while (!BUS_IS_IDLE) { - // If bus is "driven" too long, assume the AC2 is latched (e.g. - // because the bus is actually floating). Kick it if so. - // This should prevent/resolve a flood of "STARTBIT_TOO_LONG" errors - if (TCB1.CNT > (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 3)) { - err.errno = LATCHED_COMPARATOR; - PORTA.OUTSET = PIN7_bm; // preset high before enabling the driver - PORTA.DIRSET = PIN7_bm; // drive (-) hard high - TCB1.CNT = 0; - while (!BUS_IS_IDLE && TCB1.CNT < (uint16_t)AVCLAN_BIT0_LOGIC_1) { - // Wait a max of ~6μs until bus is idle - } - PORTA.DIRCLR = PIN7_bm; // back to high-Z comparator input - PORTA.OUTCLR = PIN7_bm; - } - } - goto handle_err; - } - } - if (startbitlen < (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) { - err.errno = STARTBIT_TOO_SHORT; - // We missed the beginning of this message; wait for it to finish (bus - // continuously idle for >1 bit length) before returning, so we don't have - // multiple false-starts while the in-progress message keeps sending more - // bits. - TCB1.CNT = 0; - while (TCB1.CNT < (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 1.2)) { - if (!BUS_IS_IDLE) - TCB1.CNT = 0; - } + err.errno = AVCLAN_readstartbit(); + if (err.errno) goto handle_err; - } - // Otherwise that was a start bit AVCLAN_readbits(&tmp, 1); frame->is_unicast = tmp; @@ -130,7 +53,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { uint8_t parity = AVCLAN_readbits(&frame->controller_addr, 12); AVCLAN_readbits(&tmp, 1); if (parity != (tmp &= 1)) { - err.errno = BAD_CONTROLLER_PARITY; + err.errno = rBAD_CONTROLLER_PARITY; if (print.verbose) { err.read_val = frame->controller_addr; err.parity = tmp; @@ -141,7 +64,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { parity = AVCLAN_readbits(&frame->peripheral_addr, 12); AVCLAN_readbits(&tmp, 1); if (parity != (tmp &= 1)) { - err.errno = BAD_PERIPHERAL_PARITY; + err.errno = rBAD_PERIPHERAL_PARITY; if (print.verbose) { err.read_val = frame->peripheral_addr; err.parity = tmp; @@ -159,7 +82,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { parity = AVCLAN_readbits(&frame->control, 4); AVCLAN_readbits(&tmp, 1); if (parity != (tmp &= 1)) { - err.errno = BAD_CONTROL_PARITY; + err.errno = rBAD_CONTROL_PARITY; if (print.verbose) { err.read_val = frame->control; err.parity = tmp; @@ -174,7 +97,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { parity = AVCLAN_readbyte(&frame->length); AVCLAN_readbits(&tmp, 1); if (parity != (tmp &= 1)) { - err.errno = BAD_LENGTH_PARITY; + err.errno = rBAD_LENGTH_PARITY; if (print.verbose) { err.read_val = frame->length; err.parity = tmp; @@ -187,7 +110,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { } if (frame->length == 0 || frame->length > MAXMSGLEN) { - err.errno = BAD_LENGTH_RANGE; + err.errno = rBAD_LENGTH_RANGE; err.val = frame->length; goto handle_err; } @@ -196,7 +119,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { parity = AVCLAN_readbyte(&frame->data[i]); AVCLAN_readbits(&tmp, 1); if (parity != (tmp &= 1)) { - err.errno = BAD_DATA_PARITY; + err.errno = rBAD_DATA_PARITY; if (print.verbose) { err.read_val = frame->data[i]; err.parity = tmp; @@ -214,23 +137,23 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { AVCLAN_startEvent(); RS232_Print("ERR(read): "); switch (err.errno) { - case LATCHED_COMPARATOR: RS232_Print("latched comparator"); break; - case STARTBIT_TOO_SHORT: RS232_Print("start bit too short"); break; - case STARTBIT_TOO_LONG: RS232_Print("start bit too long"); break; - case BAD_CONTROLLER_PARITY: + case rLATCHED_COMPARATOR: RS232_Print("latched comparator"); break; + case rSTARTBIT_TOO_SHORT: RS232_Print("start bit too short"); break; + case rSTARTBIT_TOO_LONG: RS232_Print("start bit too long"); break; + case rBAD_CONTROLLER_PARITY: RS232_Print("reading controller addr."); goto VERBOSE; - case BAD_PERIPHERAL_PARITY: + case rBAD_PERIPHERAL_PARITY: RS232_Print("reading peripheral addr."); goto VERBOSE; - case BAD_CONTROL_PARITY: RS232_Print("reading control"); goto VERBOSE; - case BAD_LENGTH_PARITY: RS232_Print("reading length"); goto VERBOSE; - case BAD_LENGTH_RANGE: + case rBAD_CONTROL_PARITY: RS232_Print("reading control"); goto VERBOSE; + case rBAD_LENGTH_PARITY: RS232_Print("reading length"); goto VERBOSE; + case rBAD_LENGTH_RANGE: RS232_Print("bad length 0x"); RS232_PrintHex4(err.val); break; - case BAD_DATA_PARITY: RS232_Print("reading data"); goto VERBOSE; - case NO_ERROR: + case rBAD_DATA_PARITY: RS232_Print("reading data"); goto VERBOSE; + case rNO_ERROR: __builtin_unreachable(); VERBOSE: if (print.verbose) { @@ -246,8 +169,8 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { } // Only print if some data has been correctly received - if (print.print && (err.errno < STARTBIT_TOO_SHORT)) { - if (err.errno > BAD_DATA_PARITY) + if (print.print && (err.errno < rSTARTBIT_TOO_SHORT)) { + if (err.errno > rBAD_DATA_PARITY) frame->length = 0; AVCLAN_printframe(frame, print.binary); } @@ -255,59 +178,27 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) { return err.errno; } -uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { +avclan_senderr_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { struct errtype { // Error enum is ordered such that a lower numeric value corresponds to more // success - enum : uint8_t { - NO_ERROR = 0x00, - NAK_DATA = 0x01, - NAK_MESSAGE_LENGTH, - NAK_CONTROL, - NAK_ADDRESS, - BUSY, - MUTED, - } errno; + avclan_senderr_t errno; uint8_t val; } err = {0}; if (AVCLAN_ismuted()) { - err.errno = MUTED; + err.errno = sMUTED; goto handle_err; } AVCLAN_stopEvent(); - // wait for free line - TCB1.CNT = 0; - while (BUS_IS_IDLE) { - // Wait for 120% of a bit length - if (TCB1.CNT >= (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 2)) - break; - } - - // End of first loop could be due to bus being driven - TCB1.CNT = 0; - if (!BUS_IS_IDLE) { - // Some other device started sending - // Can't yet simultaneously send and recieve to do proper CSMA/CD - err.errno = BUSY; + if (!AVCLAN_sendstartbit()) { + // Some other device is already driving the bus + err.errno = sBUSY; goto handle_err; - - // Beginnings of CSMA/CD - // do { - // if (TCB1.CNT >= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 1.2)) - // return 1; // Something's hinky; nothing is longer than the start bit - // } while (!BUS_IS_IDLE); - // if (TCB1.CNT <= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) - // return 1; // Shouldn't be possible (waiting 2 bit lengths with idle - // bus, - // // then next bit should be a long one ie start) - // set_AVC_logic_for(1, AVCLAN_STARTBIT_LOGIC_1); // wait for end of start - // bit - } else { - AVCLAN_sendbit(bit_start); } + AVCLAN_sendbits(&(uint8_t){frame->is_unicast}, 1); avclan_bit_t parity = AVCLAN_sendbits(&frame->controller_addr, 12); @@ -317,7 +208,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { AVCLAN_sendbit(parity); if (frame->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_ADDRESS; + err.errno = sNAK_ADDRESS; goto handle_err; } @@ -325,7 +216,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { AVCLAN_sendbit(parity); if (frame->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_CONTROL; + err.errno = sNAK_CONTROL; goto handle_err; } @@ -333,7 +224,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { AVCLAN_sendbit(parity); if (frame->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_MESSAGE_LENGTH; + err.errno = sNAK_MESSAGE_LENGTH; goto handle_err; } @@ -344,7 +235,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { // necessary (i.e. This deviates from the previous broadcast specific // function that sent an extra `1` bit after each byte/parity) if (frame->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_DATA; + err.errno = sNAK_DATA; err.val = i; goto handle_err; } @@ -358,28 +249,28 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) { AVCLAN_startEvent(); RS232_Print("Error"); switch (err.errno) { - case MUTED: RS232_Print(": Device muted"); break; - case BUSY: RS232_Print(": Busy bus"); break; - case NAK_ADDRESS: - case NAK_CONTROL: - case NAK_MESSAGE_LENGTH: - case NAK_DATA: + case sMUTED: RS232_Print(": Device muted"); break; + case sBUSY: RS232_Print(": Busy bus"); break; + case sNAK_ADDRESS: + case sNAK_CONTROL: + case sNAK_MESSAGE_LENGTH: + case sNAK_DATA: RS232_Print(" NAK: "); switch (err.errno) { - case NAK_ADDRESS: RS232_Print("address"); break; - case NAK_CONTROL: RS232_Print("Control"); break; - case NAK_MESSAGE_LENGTH: RS232_Print("Message length"); break; - case NAK_DATA: + case sNAK_ADDRESS: RS232_Print("address"); break; + case sNAK_CONTROL: RS232_Print("Control"); break; + case sNAK_MESSAGE_LENGTH: RS232_Print("Message length"); break; + case sNAK_DATA: RS232_Print(" data["); RS232_PrintDec(err.val); RS232_Print("]"); break; - case NO_ERROR: - case MUTED: - case BUSY: __builtin_unreachable(); + case sNO_ERROR: + case sMUTED: + case sBUSY: __builtin_unreachable(); } break; - case NO_ERROR: __builtin_unreachable(); + case sNO_ERROR: __builtin_unreachable(); } RS232_Print("\n"); } else { @@ -494,46 +385,3 @@ uint8_t AVCLAN_parseframe(const uint8_t *bytes, uint8_t len, return err.errno; } - -#ifndef NDEBUG - // Only used immediately below - #define XSTR(x) #x - #define STR(x) XSTR(x) - -uint16_t pulses[100]; -uint16_t periods[100]; - -void AVCLan_Measure() { - AVCLAN_stopEvent(); - - uint8_t tmp = 0; - - RS232_Print( - "Timing config: F_CPU=" STR(F_CPU) ", TCB_CLKSEL=" STR(TCB_CLKSEL) "\n"); - RS232_Print("Sampling bit (pulse-width and period) timing...\n"); - - for (uint8_t n = 0; n < 100; n++) { - while (pulse_count == tmp) {} - pulses[n] = pulsewidth; - periods[n] = period; - tmp = pulse_count; - } - - RS232_Print("Pulses:\n"); - for (uint8_t i = 0; i < 100; i++) { - RS232_PrintHex8((uint8_t)(pulses[i] >> 8)); - RS232_PrintHex8((uint8_t)pulses[i]); - RS232_Print("\n"); - } - - RS232_Print("Periods:\n"); - for (uint8_t i = 0; i < 100; i++) { - RS232_PrintHex8((uint8_t)(periods[i] >> 8)); - RS232_PrintHex8((uint8_t)periods[i]); - RS232_Print("\n"); - } - RS232_Print("\nDone.\n"); - - AVCLAN_startEvent(); -} -#endif diff --git a/src/avclan/avclan_frame.h b/src/avclan/avclan_frame.h index ab4c869..1a74f20 100644 --- a/src/avclan/avclan_frame.h +++ b/src/avclan/avclan_frame.h @@ -51,22 +51,10 @@ #include "avclan_defs.h" -uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print); -uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print); +avclan_readerr_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print); +avclan_senderr_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print); void AVCLAN_printframe(const AVCLAN_frame_t *frame, bool binary); uint8_t AVCLAN_parseframe(const uint8_t *bytes, uint8_t len, AVCLAN_frame_t *frame); -// Bus-transaction guard: quiesce the other async sources (USART RX, the RTC -// status tick, the mic timer) around a bus read/send so framing isn't disturbed. -// NOTE (temporary): these couple the frame layer to the statustimer / -// mediacontrol / cdchanger modules; this intermingling is accepted pending the -// RP2350 port rework. TCB0 must remain enabled. -void AVCLAN_stopEvent(); -void AVCLAN_startEvent(); - -#ifndef NDEBUG -void AVCLan_Measure(); -#endif - #endif // AVCLAN_FRAME_H diff --git a/src/avclan/avclan_phy.h b/src/avclan/avclan_phy.h index b685d76..f53e913 100644 --- a/src/avclan/avclan_phy.h +++ b/src/avclan/avclan_phy.h @@ -20,45 +20,44 @@ along with this program. If not, see . */ -// AVC-LAN PHY: bus bit-banging via TCB timers + analog comparator AC2. -// This is the lowest layer and has no dependencies on the higher layers -// (frame / protocol / cdchanger) — keep it that way. - #ifndef AVCLAN_PHY_H #define AVCLAN_PHY_H -#include -#include #include #include "avclan_defs.h" -// AVC LAN bus on AC2 (PA6/7) -// PA6 AINP0 + -// PA7 AINN1 - -#define BUS_IS_IDLE (bit_is_clear(AC2_STATUS, AC_STATE_bp)) +// One-time bring-up of the bus hardware. Leaves the bus idle and TX unmuted. +void AVCLAN_busInit(void); -typedef enum avclan_bit : uint8_t { - bit_zero = 0x00, - bit_one = 0x01, - bit_start = 0x10 -} avclan_bit_t; - -// One-time hardware bring-up for the PHY: AC2, EVSYS, TCB0/TCB1, the AC inputs -// (PA6/7) and AC2-OUT LED (PB2). Leaves the bus idle and TX unmuted. -void AVCLAN_phyInit(); - -// Returns true if device TX is muted on AVCLAN bus -static inline bool AVCLAN_ismuted() { - return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0); -} - -// Mute device TX on AVCLAN bus +// Mute/unmute device TX. "Muted" means we still listen, we just don't ACK or +// transmit. void AVCLAN_muteDevice(bool mute); +bool AVCLAN_ismuted(void); +// True when there is activity on the bus (something is driving it). +bool AVCLAN_busActive(void); + +// Bus-transaction guard: quiesce the target's other async sources around a bus +// read/send so framing isn't disturbed, then restore them. May be a no-op on a +// target without such contention. +void AVCLAN_stopEvent(void); +void AVCLAN_startEvent(void); + +// Start-bit handling, factored out of read/sendframe so the framing layer holds +// no bus-timing or hardware-recovery logic. +// - AVCLAN_readstartbit waits for and validates an incoming start bit, doing +// any target-specific bus recovery; see avclan_readerr_t. +// - AVCLAN_sendstartbit acquires the bus and emits a start bit; returns false +// if the bus was busy. +avclan_readerr_t AVCLAN_readstartbit(void); +bool AVCLAN_sendstartbit(void); + +// Per-symbol I/O. The send* helpers return the even parity of the bits sent; +// the read* helpers return the even parity of the bits read. void AVCLAN_sendbit(avclan_bit_t bit); -void AVCLAN_sendbit_ACK(); -uint8_t AVCLAN_readbit_ACK(); +void AVCLAN_sendbit_ACK(void); +uint8_t AVCLAN_readbit_ACK(void); avclan_bit_t AVCLAN_sendbitsi(const uint8_t *bits, int8_t len); avclan_bit_t AVCLAN_sendbitsl(const uint16_t *bits, int8_t len); @@ -83,10 +82,8 @@ uint8_t AVCLAN_readbyte(uint8_t *byte); uint8_t *: AVCLAN_readbitsi)(bits, len) #ifndef NDEBUG -// Bit-timing capture, populated by the TCB0 capture ISR; read by AVCLan_Measure. -extern volatile uint16_t pulsewidth; -extern volatile uint8_t pulse_count; -extern volatile uint16_t period; +// Sample and dump bus bit timing over the serial link (REPL `M`). +void AVCLan_Measure(void); #endif #endif // AVCLAN_PHY_H diff --git a/src/avclan/avclan_protocol.c b/src/avclan/avclan_protocol.c index e2a987e..cf496ba 100644 --- a/src/avclan/avclan_protocol.c +++ b/src/avclan/avclan_protocol.c @@ -235,7 +235,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) { cd_status.secs = 0x7f; cd_status.flags2 = 0xc0; AVCLAN_generateStatus(out, true, dev_CMD_SW); - AVCLAN_micSkipForward(); + AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); respond = r_TrackChange; break; case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Down): @@ -251,7 +251,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) { cd_status.secs = 0x7f; cd_status.flags2 = 0xc0; AVCLAN_generateStatus(out, true, dev_CMD_SW); - AVCLAN_micSkipBackward(); + AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); respond = r_TrackChange; break; case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Fast_Forward): { @@ -262,7 +262,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) { ++cd_status.mins; } AVCLAN_generateStatus(out, true, dev_CMD_SW); - AVCLAN_micSkipForward(); + AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is // ~1 sec from now respond = r_Handled; @@ -282,7 +282,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) { } else cd_status.secs -= 15; AVCLAN_generateStatus(out, true, dev_CMD_SW); - AVCLAN_micSkipBackward(); + AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is // ~1 sec from now respond = r_Handled; diff --git a/src/avclan/cdchanger.c b/src/avclan/cdchanger.c index 16ab2c2..0170973 100644 --- a/src/avclan/cdchanger.c +++ b/src/avclan/cdchanger.c @@ -37,7 +37,7 @@ static cd_modes CD_Mode; void AVCLAN_startPlaying() { static bool havePlayed = false; if (havePlayed) - AVCLAN_micPlayPause(); + AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); havePlayed |= true; CD_Mode = stPlay; statustimer_reset(); @@ -48,7 +48,7 @@ void AVCLAN_startPlaying() { void AVCLAN_stopPlaying() { statustimer_disable(); CD_Mode = stStop; - AVCLAN_micPlayPause(); + AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); } /* Pack a 0–99 count into 2-digit BCD. Values >99 (sentinels such as 0xFF / @@ -139,7 +139,7 @@ void AVCLAN_normalizeState() { } void AVCLAN_init() { - AVCLAN_phyInit(); + AVCLAN_busInit(); mediacontrol_init(); statustimer_init(); diff --git a/src/avclan/mediacontrol.h b/src/avclan/mediacontrol.h index a2d8999..217e199 100644 --- a/src/avclan/mediacontrol.h +++ b/src/avclan/mediacontrol.h @@ -16,27 +16,25 @@ along with this program. If not, see . */ -// Media-control actuator: emulates play/pause and skip button presses on the -// audio source by toggling MIC_CONTROL (PB1/WO1) with TCA0 in FRQ mode. +// Media-control: route/handle head-unit button presses to the audio source. #ifndef MEDIACONTROL_H #define MEDIACONTROL_H -#include +#include -// One-time hardware bring-up for the mic/button-press driver (TCA0 + PB1). +// Actions list +typedef enum : uint8_t { + MEDIA_PLAY_PAUSE = 0, + MEDIA_SKIP_FORWARD, + MEDIA_SKIP_BACKWARD, +} AVCLAN_media_fn_t; + +// One-time hardware bring-up for the media driver. void mediacontrol_init(); -// Emulate a single play/pause button press on the source device. -void AVCLAN_micPlayPause(); -// Emulate skip-forward/backward button presses -void AVCLAN_micSkipForward(); -void AVCLAN_micSkipBackward(); - -// Keep the press waveform roughly in sync while a bus transaction has masked -// interrupts. MUST be called with interrupts disabled (e.g. from within the -// frame layer's AVCLAN_stopEvent ATOMIC_BLOCK). -void mediacontrol_syncDuringMask(); +// Emulate a button press on the source device. +void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn); #ifndef NDEBUG bool AVCLAN_micToggle(); diff --git a/src/avclan/statustimer.h b/src/avclan/statustimer.h index 4188d58..2b601ff 100644 --- a/src/avclan/statustimer.h +++ b/src/avclan/statustimer.h @@ -16,23 +16,26 @@ along with this program. If not, see . */ -// ~1 Hz status-update tick, driven by the RTC overflow. The overflow handler -// (ISR(RTC_CNT_vect)) lives in the app (sniffer.c); this module owns only the -// RTC hardware configuration and enable/disable/reset of the tick. +// ~1 Hz status-update tick interface. The app +// polls statustimer_tickPending() and clears the tick with +// statustimer_clearTick() #ifndef STATUSTIMER_H #define STATUSTIMER_H -// One-time RTC hardware bring-up (clock source + period). Leaves the overflow -// interrupt disabled. -void statustimer_init(); +// One-time hardware bring-up. Leaves the tick disabled. +void statustimer_init(void); -// Reset the count so the next tick is ~1 s out, and enable the overflow tick. -void statustimer_reset(); +// Reset the count so the next tick is ~1 s out, and enable the tick. +void statustimer_reset(void); -// Enable / disable the ~1 Hz overflow interrupt (bare register RMW; wrap in a -// critical section if called with interrupts enabled and concurrency matters). -void statustimer_enable(); -void statustimer_disable(); +// Enable / disable the ~1 Hz tick. +void statustimer_enable(void); +void statustimer_disable(void); + +extern volatile bool tick_pending; + +static inline bool statustimer_tickPending() { return tick_pending; } +static inline void statustimer_clearTick() { tick_pending = false; } #endif // STATUSTIMER_H diff --git a/src/avclan/target/avr-attiny3216/CMakeLists.txt b/src/avclan/target/avr-attiny3216/CMakeLists.txt new file mode 100644 index 0000000..ab5a98c --- /dev/null +++ b/src/avclan/target/avr-attiny3216/CMakeLists.txt @@ -0,0 +1,135 @@ +# AVR / ATtiny3216 hardware target (port). + +include(CMakeDependentOption) + +# --- Port implementation sources ------------------------------------------- +target_sources(avclan PRIVATE + phy_avr.c + media_avr.c + statustick_avr.c + board_avr.c) + +target_include_directories(avclan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +find_program(AVRDUDE avrdude) +set(AVR_PROGRAMMER serialupdi CACHE STRING "avrdude programmer hardware") +set(AVRDUDE_PORT /dev/ttyUSB0 CACHE STRING "avrdude serial port") +set(AVRDUDE_BAUDRATE 230400 CACHE STRING "avrdude baud rate") + +set(AVRDUDE_BASE_OPTIONS + -p ${AVR_MCU} + -c ${AVR_PROGRAMMER} + -b ${AVRDUDE_BAUDRATE}) + +# --- Hardware configuration options ---------------------------------------- +set(FREQSEL 16MHz CACHE STRING "Select the operating frequency") +set_property(CACHE FREQSEL PROPERTY STRINGS "20MHz" "16MHz") + +if(FREQSEL MATCHES "20MHz") + set(FREQSEL 20000000L) + set(AVRDUDE_BASE_OPTIONS ${AVRDUDE_BASE_OPTIONS} -U osccfg:w:0x2:m) +else() + set(FREQSEL 16000000L) + set(AVRDUDE_BASE_OPTIONS ${AVRDUDE_BASE_OPTIONS} -U osccfg:w:0x1:m) +endif() + +# Set startup time to 8 ms (0x4) +set(AVRDUDE_BASE_OPTIONS ${AVRDUDE_BASE_OPTIONS} -U syscfg1:w:0x4:m) + +option(CLK_PRESCALE "Enable the main clock prescaler") +cmake_dependent_option(CLK_PRESCALE_DIV "Prescaler divisor" CLKCTRL_PDIV_2X_gc STRING "CLK_PRESCALE") +if(DEFINED CACHE{CLK_PRESCALE_DIV}) + set_property(CACHE CLK_PRESCALE_DIV PROPERTY STRINGS + CLKCTRL_PDIV_2X_gc + CLKCTRL_PDIV_4X_gc + CLKCTRL_PDIV_8X_gc + CLKCTRL_PDIV_16X_gc + CLKCTRL_PDIV_32X_gc + CLKCTRL_PDIV_64X_gc + CLKCTRL_PDIV_6X_gc + CLKCTRL_PDIV_10X_gc + CLKCTRL_PDIV_12X_gc + CLKCTRL_PDIV_24X_gc + CLKCTRL_PDIV_48X_gc + ) +else() + set(CLK_PRESCALE_DIV CLKCTRL_PDIV_2X_gc) +endif() + +set(TCB_CLKSEL "TCB_CLKSEL_CLKDIV2_gc" CACHE STRING "Choose the clock for TCB") +set_property(CACHE TCB_CLKSEL PROPERTY STRINGS + TCB_CLKSEL_CLKDIV1_gc + TCB_CLKSEL_CLKDIV2_gc + TCB_CLKSEL_CLKTCA_gc +) + +set(USART_RXMODE "USART_RXMODE_CLK2X_gc" CACHE STRING "USART at normal or double speed operation") +set_property(CACHE USART_RXMODE PROPERTY STRINGS + USART_RXMODE_CLK2X_gc + USART_RXMODE_NORMAL_gc +) + +# Measured wall-clock duration (ms) of one nominal 32768-tick RTC period, used +# to calibrate out the internal OSCULP32K's tolerance for the status-update +# tick. 1000 = no correction; set per-board in CMakeUserPresets.json. +set(RTC_STATUS_PERIOD_MS 1000 CACHE STRING "Measured ms per nominal RTC status period (1000 = no correction)") + +try_compile(LIBC_VERSION_TEST + SOURCES "${CMAKE_SOURCE_DIR}/cmake/libc-version-test.cpp" + COMPILE_DEFINITIONS -mmcu=${AVR_MCU} +) + +if(NOT LIBC_VERSION_TEST) + include(FetchContent) + FetchContent_Declare( + attiny_atpack + URL http://packs.download.atmel.com/Atmel.ATtiny_DFP.2.0.368.atpack + URL_HASH SHA512=ee16a8ebecb57bd998a9cd4373368e3d45982cbbc3825e18d1dcac58215db6b9d907ad1ba2020cba9187fed7ba8c6f255a4fa1214e40c7a17ab2d18474f4d079 + DOWNLOAD_NAME Atmel.ATtiny_DFP.2.0.368.atpack.zip + ) + FetchContent_MakeAvailable(attiny_atpack) + try_compile(LIBC_VERSION_TEST + SOURCES "${CMAKE_SOURCE_DIR}/cmake/libc-version-test.cpp" + COMPILE_DEFINITIONS + -B "${attiny_atpack_SOURCE_DIR}/gcc/dev/${AVR_MCU}" + -isystem "${attiny_atpack_SOURCE_DIR}/include" + -mmcu=${AVR_MCU} + ) + if(NOT LIBC_VERSION_TEST) + message(FATAL_ERROR "Insufficient AVR-LIBC/Microchip pack for chosen MCU '${AVR_MCU}'") + else() + # PUBLIC on the library so its compile inherits the device headers and + # the requirement propagates to mockingboard via linking. + target_include_directories(avclan SYSTEM + PUBLIC "${attiny_atpack_SOURCE_DIR}/include") + target_link_options(mockingboard PUBLIC + -B "${attiny_atpack_SOURCE_DIR}/gcc/dev/${AVR_MCU}" + ) + endif() +endif() + +# --- Compile definitions / options ----------------------------------------- +target_compile_definitions(avclan PUBLIC + FREQSEL=${FREQSEL} + CLK_PRESCALE=$,0x01,0x00> + CLK_PRESCALE_DIV=${CLK_PRESCALE_DIV} + __CLK_PRESCALE_DIV=__${CLK_PRESCALE_DIV} + TCB_CLKSEL=${TCB_CLKSEL} + USART_RXMODE=${USART_RXMODE} + RTC_STATUS_PERIOD_MS=${RTC_STATUS_PERIOD_MS} +) +target_compile_options(avclan PUBLIC + --param=min-pagesize=0 + -ffunction-sections + -fdata-sections +) + +# --- Flashing -------------------------------------------------------------- +add_custom_target(flash + ${AVRDUDE} ${AVRDUDE_BASE_OPTIONS} ${AVRDUDE_OPTIONS} + -U flash:w:$:e + -P ${AVRDUDE_PORT} + DEPENDS mockingboard + COMMENT "Flashing mockingboard to ${AVR_MCU} using ${AVR_PROGRAMMER}" + VERBATIM USES_TERMINAL +) diff --git a/src/avclan/target/avr-attiny3216/board_avr.c b/src/avclan/target/avr-attiny3216/board_avr.c new file mode 100644 index 0000000..171c532 --- /dev/null +++ b/src/avclan/target/avr-attiny3216/board_avr.c @@ -0,0 +1,62 @@ +/* + AVCLAN-Mockingboard + Copyright (C) 2015 Allen Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// ATtiny3216 board bring-up: main-clock prescaler + GPIO config for pins not +// owned by a peripheral's own init. + +#include +#include +#include // _PROTECTED_WRITE + +#include "board.h" + +void board_init(void) { + // Main clock prescale (CLK_PRESCALE / CLK_PRESCALE_DIV come from the build). + _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, (CLK_PRESCALE | CLK_PRESCALE_DIV)); + + // Set pins PC2-3, PB0,3-5 as inputs + PORTC.DIRCLR = (PIN2_bm | // Unconnected + PIN3_bm); // CTS + PORTB.DIRCLR = (PIN0_bm | // Unconnected + PIN3_bm | // IGN_SENSE + PIN4_bm | // Unused, but connected to WOC (PC0) + PIN5_bm); // Unused, but connected to WOD (PC1) + + // Enable pull-up resistor and disable input buffer (reduces any EM caused + // pin toggling and saves power) for unused and unconnected pins + PORTC.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc; + PORTB.PIN0CTRL = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc; + + // TODO: Remove once IGN_SENSE hardware is fixed + PORTB.DIRSET = PIN3_bm; + PORTB.OUTSET = PIN3_bm; + + // Output only pins: PA3-5, PB1-2,4-5; PC0-1 + // TODO: TxD (PA1), RTS (PA3) is output only, test if RxD needs the input + // buffer or if the UART peripheral bypasses it + PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; // RTS + PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOA + PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOB + PORTB.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // MIC_CONTROL + PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // non-driving WOC + PORTB.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // non-driving WOD + PORTC.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOC + PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOD +} + +void board_interruptsEnable(void) { sei(); } diff --git a/src/avclan/mediacontrol.c b/src/avclan/target/avr-attiny3216/media_avr.c similarity index 89% rename from src/avclan/mediacontrol.c rename to src/avclan/target/avr-attiny3216/media_avr.c index 50b6183..df6c465 100644 --- a/src/avclan/mediacontrol.c +++ b/src/avclan/target/avr-attiny3216/media_avr.c @@ -21,11 +21,12 @@ #include #include +#include "media_avr.h" // mediacontrol_syncDuringMask (used by the bus guard) #include "mediacontrol.h" -// F_CPU defined in timing.h; the mic tick constants below are derived from it. -// TODO(HAL): re-derive these from a target-agnostic TICK_NS instead of F_CPU. -#include "timing.h" +// F_CPU defined in timing_avr.h; the mic tick constants below are derived from +// it (this hardware generation's TCA0/PB1 button-press implementation). +#include "timing_avr.h" // pending WO1 toggles (even); signed to avoid underflows from a stray OVF static volatile int8_t mic_ntoggles = 0; @@ -111,12 +112,15 @@ static inline void mic_timer_isr_body(bool is_early) { ISR(TCA0_OVF_vect) { mic_timer_isr_body(false); } -// Emulate a single play/pause button press on the source device. -void AVCLAN_micPlayPause() { mic_pulse(1); } - -// Emulate skip-forward/backward button presses -void AVCLAN_micSkipForward() { mic_pulse(3); } // double-press -void AVCLAN_micSkipBackward() { mic_pulse(5); } // triple-press +// Emulate a transport-control button press on the source device. Each action +// maps to a press-train of a given length on MIC_CONTROL. +void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn) { + switch (fn) { + case MEDIA_PLAY_PAUSE: mic_pulse(1); break; // single press + case MEDIA_SKIP_FORWARD: mic_pulse(3); break; // double-press + case MEDIA_SKIP_BACKWARD: mic_pulse(5); break; // triple-press + } +} // Pre-emptively "overflow" and run the OVF ISR body early if a press is in // progress and likely to overflow within the masked window. This maintains: diff --git a/src/avclan/target/avr-attiny3216/media_avr.h b/src/avclan/target/avr-attiny3216/media_avr.h new file mode 100644 index 0000000..51ddddc --- /dev/null +++ b/src/avclan/target/avr-attiny3216/media_avr.h @@ -0,0 +1,32 @@ +/* + AVCLAN-Mockingboard + Copyright (C) 2015 Allen Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// AVR-internal media-driver hooks, shared between media_avr.c and the bus +// transaction guard (phy_avr.c's AVCLAN_stopEvent). Not part of the public +// mediacontrol.h interface. + +#ifndef MEDIA_AVR_H +#define MEDIA_AVR_H + +// Keep the TCA0 press waveform roughly in sync while a bus transaction has +// masked interrupts (runs the OVF ISR body early if an overflow is imminent). +// MUST be called with interrupts disabled (from within AVCLAN_stopEvent's +// ATOMIC_BLOCK). +void mediacontrol_syncDuringMask(); + +#endif // MEDIA_AVR_H diff --git a/src/avclan/avclan_phy.c b/src/avclan/target/avr-attiny3216/phy_avr.c similarity index 67% rename from src/avclan/avclan_phy.c rename to src/avclan/target/avr-attiny3216/phy_avr.c index 8ad66fc..64e80aa 100644 --- a/src/avclan/avclan_phy.c +++ b/src/avclan/target/avr-attiny3216/phy_avr.c @@ -68,28 +68,37 @@ #include #include #include +#include #include "avclan_phy.h" +#include "cdchanger.h" // AVCLAN_isPlaying (startEvent) +#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure) +#include "media_avr.h" // mediacontrol_syncDuringMask (guard) +#include "statustimer.h" // statustimer_enable/disable (guard) -// F_CPU defined in timing.h and potentially needed by avr-libc (e.g. delay.h) -#include "timing.h" +// F_CPU + TICK_US (timing.h) defined here; F_CPU potentially needed by +// avr-libc. +#include "timing_avr.h" // Name difference between avr-libc and Microchip pack #if defined(EVSYS_ASYNCCH00_bm) #define EVSYS_ASYNCCH0_0_bm EVSYS_ASYNCCH00_bm #endif +// AVC LAN bus on AC2 (PA6/7): PA6 AINP0 (+), PA7 AINN1 (-) +#define BUS_IS_IDLE (bit_is_clear(AC2_STATUS, AC_STATE_bp)) + #define READING_BYTE GPIOR1 #define READING_NBITS GPIOR2 #define READING_PARITY GPIOR3 #define TCB_CNTMODE TCB_CNTMODE_PW_gc -volatile uint16_t pulsewidth; +static volatile uint16_t pulsewidth; #ifndef NDEBUG -volatile uint8_t pulse_count = 0; -volatile uint16_t period = 0; +static volatile uint8_t pulse_count = 0; +static volatile uint16_t period = 0; #endif // clang-format off @@ -109,6 +118,15 @@ static inline void AVCLAN_setBusDriven() { } // clang-format on +// Returns true if device TX is muted on the AVCLAN bus (both drive pins are +// configured as inputs). +bool AVCLAN_ismuted() { + return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0); +} + +// True when the bus is being driven (i.e. not idle/floating). +bool AVCLAN_busActive() { return !BUS_IS_IDLE; } + // Mute device TX on AVCLAN bus void AVCLAN_muteDevice(bool mute) { if (mute) { @@ -333,7 +351,7 @@ uint8_t AVCLAN_readbyte(uint8_t *byte) { return (parity & 1); } -void AVCLAN_phyInit() { +void AVCLAN_busInit() { // Set pin 6 and 7 as input PORTA.DIRCLR = (PIN6_bm | PIN7_bm); // Disable input buffer; recommended when using AC @@ -367,3 +385,141 @@ void AVCLAN_phyInit() { AVCLAN_muteDevice(false); // unmute AVCLAN bus TX } + +// Wait for and validate an incoming start bit. On an over-long "driven" bus +// (AC2 latched high because the bus is actually floating) this kicks PA7 hard +// high to unlatch the comparator. The framing layer maps the result to its own +// error reporting; no printing happens here. +avclan_readerr_t AVCLAN_readstartbit() { + uint16_t startbitlen = TCB1.CNT = 0; + while (!BUS_IS_IDLE) { + startbitlen = TCB1.CNT; + if (startbitlen > (uint16_t)AVCLAN_STARTBIT_LOGIC_0 * 1.2) { + avclan_readerr_t result = rSTARTBIT_TOO_LONG; + while (!BUS_IS_IDLE) { + // If bus is "driven" too long, assume the AC2 is latched (e.g. + // because the bus is actually floating). Kick it if so. + // This should prevent/resolve a flood of "STARTBIT_TOO_LONG" errors + if (TCB1.CNT > (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 3)) { + result = rLATCHED_COMPARATOR; + PORTA.OUTSET = PIN7_bm; // preset high before enabling the driver + PORTA.DIRSET = PIN7_bm; // drive (-) hard high + TCB1.CNT = 0; + while (!BUS_IS_IDLE && TCB1.CNT < (uint16_t)AVCLAN_BIT0_LOGIC_1) { + // Wait a max of ~6μs until bus is idle + } + PORTA.DIRCLR = PIN7_bm; // back to high-Z comparator input + PORTA.OUTCLR = PIN7_bm; + } + } + return result; + } + } + if (startbitlen < (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) { + // We missed the beginning of this message; wait for it to finish (bus + // continuously idle for >1 bit length) before returning, so we don't have + // multiple false-starts while the in-progress message keeps sending more + // bits. + TCB1.CNT = 0; + while (TCB1.CNT < (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 1.2)) { + if (!BUS_IS_IDLE) + TCB1.CNT = 0; + } + return rSTARTBIT_TOO_SHORT; + } + return rNO_ERROR; // that was a start bit +} + +// Acquire the bus and emit a start bit. Returns false if another device is +// already driving the bus (we can't yet do proper CSMA/CD). +bool AVCLAN_sendstartbit() { + // wait for free line + TCB1.CNT = 0; + while (BUS_IS_IDLE) { + // Wait for 120% of a bit length + if (TCB1.CNT >= (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 2)) + break; + } + + // End of first loop could be due to bus being driven + TCB1.CNT = 0; + if (!BUS_IS_IDLE) { + // Some other device started sending + // Can't yet simultaneously send and receive to do proper CSMA/CD + + // Beginnings of CSMA/CD + // do { + // if (TCB1.CNT >= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 1.2)) + // return false; // Something's hinky; nothing is longer than start bit + // } while (!BUS_IS_IDLE); + // if (TCB1.CNT <= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) + // return false; // Shouldn't be possible + // set_AVC_logic_for(1, AVCLAN_STARTBIT_LOGIC_1); // wait for end of start + return false; + } + AVCLAN_sendbit(bit_start); + return true; +} + +/* Disable non-read related interrupts (USART RX, RTC status tick, mic timer) + during AVCLAN bus transactions so framing isn't disturbed. TCB0 must remain + enabled. */ +void AVCLAN_stopEvent() { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + statustimer_disable(); + RS232_setRxInterrupt(false); + mediacontrol_syncDuringMask(); + } +} + +// Re-enable serial and periodic interrupts after a bus transaction. +void AVCLAN_startEvent() { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + if (AVCLAN_isPlaying()) // Reenable status interrupt if currently playing + statustimer_enable(); + RS232_setRxInterrupt(true); + } +} + +#ifndef NDEBUG + // Only used immediately below + #define XSTR(x) #x + #define STR(x) XSTR(x) + +static uint16_t pulses[100]; +static uint16_t periods[100]; + +void AVCLan_Measure() { + AVCLAN_stopEvent(); + + uint8_t tmp = 0; + + RS232_Print( + "Timing config: F_CPU=" STR(F_CPU) ", TCB_CLKSEL=" STR(TCB_CLKSEL) "\n"); + RS232_Print("Sampling bit (pulse-width and period) timing...\n"); + + for (uint8_t n = 0; n < 100; n++) { + while (pulse_count == tmp) {} + pulses[n] = pulsewidth; + periods[n] = period; + tmp = pulse_count; + } + + RS232_Print("Pulses:\n"); + for (uint8_t i = 0; i < 100; i++) { + RS232_PrintHex8((uint8_t)(pulses[i] >> 8)); + RS232_PrintHex8((uint8_t)pulses[i]); + RS232_Print("\n"); + } + + RS232_Print("Periods:\n"); + for (uint8_t i = 0; i < 100; i++) { + RS232_PrintHex8((uint8_t)(periods[i] >> 8)); + RS232_PrintHex8((uint8_t)periods[i]); + RS232_Print("\n"); + } + RS232_Print("\nDone.\n"); + + AVCLAN_startEvent(); +} +#endif diff --git a/src/avclan/statustimer.c b/src/avclan/target/avr-attiny3216/statustick_avr.c similarity index 88% rename from src/avclan/statustimer.c rename to src/avclan/target/avr-attiny3216/statustick_avr.c index cc1e75c..18373a3 100644 --- a/src/avclan/statustimer.c +++ b/src/avclan/target/avr-attiny3216/statustick_avr.c @@ -16,10 +16,12 @@ along with this program. If not, see . */ +#include #include #include #include +#include "cdchanger.h" #include "statustimer.h" // Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period, @@ -62,3 +64,13 @@ void statustimer_reset() { void statustimer_enable() { RTC.INTCTRL |= RTC_OVF_bm; } void statustimer_disable() { RTC.INTCTRL &= ~RTC_OVF_bm; } + +// Set once per overflow; consumed by the app via statustimer_tickPending(). +volatile bool tick_pending = false; + +// Periodic interrupt with a ~1 sec period; only enabled while playing. +ISR(RTC_CNT_vect) { + AVCLAN_incrementTime(); + tick_pending = true; + RTC.INTFLAGS = RTC_OVF_bm; +} diff --git a/src/timing.h b/src/avclan/target/avr-attiny3216/timing_avr.h similarity index 62% rename from src/timing.h rename to src/avclan/target/avr-attiny3216/timing_avr.h index e93cd8f..dfa8f0b 100644 --- a/src/timing.h +++ b/src/avclan/target/avr-attiny3216/timing_avr.h @@ -1,5 +1,12 @@ -#ifndef _TIMING_HPP_ -#define _TIMING_HPP_ +#ifndef TIMING_AVR_H +#define TIMING_AVR_H + +// AVR ATtiny3216 timing parameters. Derives F_CPU (needed by avr-libc, e.g. +// util/delay.h) and the bus-timer (TCB) tick period from the CMake-provided +// FREQSEL / CLK_PRESCALE / TCB_CLKSEL, then hands the generic timing.h a TICK_US +// (microseconds per TCB tick) so the physical bit-phase durations resolve to +// TCB-tick counts. TICK_US == TCB_TICK / 1000, so every derived constant is +// numerically identical to the previous F_CPU/TCB_CLKSEL formulation. #define __CLKCTRL_PDIV_2X_gc 2 #define __CLKCTRL_PDIV_4X_gc 4 @@ -21,6 +28,7 @@ #define CYCLE_MUL 1 #endif +// CPU_CYCLE / TCB_TICK are in nanoseconds. #if FREQSEL == 20000000L #define CPU_CYCLE (50 * CYCLE_MUL) #elif FREQSEL == 16000000L @@ -49,18 +57,9 @@ #error "Not implemented" #endif -// Measured at ±0.02 μs @ F_CPU=20MHz, TCB_CLKSEL=TCB_CLKSEL_CLKDIV1_gc -#define AVCLAN_STARTBIT_LOGIC_0 (169e3 / TCB_TICK) -#define AVCLAN_STARTBIT_LOGIC_1 (20.6e3 / TCB_TICK) +// TCB_TICK is nanoseconds/tick; the generic timing.h wants microseconds/tick. +#define TICK_US (TCB_TICK / 1000.0) -#define AVCLAN_BIT1_LOGIC_0 (19.7e3 / TCB_TICK) -#define AVCLAN_BIT1_LOGIC_1 (18.1e3 / TCB_TICK) +#include "timing.h" -#define AVCLAN_BIT0_LOGIC_0 (32.85e3 / TCB_TICK) -#define AVCLAN_BIT0_LOGIC_1 (6.2e3 / TCB_TICK) - -#define AVCLAN_READBIT_THRESHOLD (26e3 / TCB_TICK) - -#define AVCLAN_BIT_LENGTH_MAX (39.1e3 / TCB_TICK) - -#endif +#endif // TIMING_AVR_H diff --git a/src/avclan/timing.h b/src/avclan/timing.h new file mode 100644 index 0000000..684d11d --- /dev/null +++ b/src/avclan/timing.h @@ -0,0 +1,32 @@ +#ifndef _TIMING_HPP_ +#define _TIMING_HPP_ + +// Physical AVC-LAN bit-phase durations, in microseconds. These are protocol +// facts (the bus spec), independent of any particular hardware. The active +// target provides TICK_US — the wall-clock duration, in microseconds, of one +// tick of whatever free-running timer it uses to measure/generate bus bits — so +// each constant below resolves to a count of that target's ticks. +// +// Kept as #defines (not constexpr): no target is guaranteed to want these as a +// specific integer width, so leave the type to the use site / target. + +#ifndef TICK_US + #error \ + "target must define TICK_US (microseconds per bus-timer tick) before including timing.h" +#endif + +// Measured at ±0.02 μs @ F_CPU=20MHz, TCB_CLKSEL=TCB_CLKSEL_CLKDIV1_gc +#define AVCLAN_STARTBIT_LOGIC_0 (169.0 / TICK_US) +#define AVCLAN_STARTBIT_LOGIC_1 (20.6 / TICK_US) + +#define AVCLAN_BIT1_LOGIC_0 (19.7 / TICK_US) +#define AVCLAN_BIT1_LOGIC_1 (18.1 / TICK_US) + +#define AVCLAN_BIT0_LOGIC_0 (32.85 / TICK_US) +#define AVCLAN_BIT0_LOGIC_1 (6.2 / TICK_US) + +#define AVCLAN_READBIT_THRESHOLD (26.0 / TICK_US) + +#define AVCLAN_BIT_LENGTH_MAX (39.1 / TICK_US) + +#endif diff --git a/src/board.h b/src/board.h new file mode 100644 index 0000000..f66336f --- /dev/null +++ b/src/board.h @@ -0,0 +1,33 @@ +/* + AVCLAN-Mockingboard + Copyright (C) 2015 Allen Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Board / MCU bring-up interface (the BSP seam). Implemented per-target (the AVR +// implementation is target/avr-attiny3216/board_avr.c). Keeps the app +// (sniffer.c) free of clock/pin/interrupt register access. + +#ifndef BOARD_H +#define BOARD_H + +// Clock setup + GPIO/pin configuration. Call once, first thing at startup +// (before any peripheral init). +void board_init(void); + +// Globally enable interrupts. Call after all peripherals are initialized. +void board_interruptsEnable(void); + +#endif // BOARD_H diff --git a/src/com232.c b/src/com232.c index 3391522..0929dc3 100644 --- a/src/com232.c +++ b/src/com232.c @@ -24,9 +24,10 @@ #include #include #include +#include #include "com232.h" -#include "timing.h" +#include "timing_avr.h" // F_CPU (baud-rate calc) #if USART_RXMODE == USART_RXMODE_CLK2X_gc #define RXMODE_S 8 @@ -37,7 +38,10 @@ #define USART_BAUD_RATE(BAUD_RATE) \ (uint16_t)((float)(F_CPU * 64 / (RXMODE_S * (float)BAUD_RATE)) + 0.5) -volatile uint8_t RS232_RxCharBuffer[25], RS232_RxCharBegin, RS232_RxCharEnd; +// RX ring, owned entirely by this driver (filled by the ISR, drained by +// RS232_getChar). Kept internal so the app never touches UART buffer state. +static volatile uint8_t RS232_RxCharBuffer[25], RS232_RxCharBegin, + RS232_RxCharEnd; void RS232_Init(void) { RS232_RxCharBegin = RS232_RxCharEnd = 0; @@ -61,6 +65,29 @@ ISR(USART0_RXC_vect) { RS232_RxCharBuffer[RS232_RxCharEnd++] = USART0_RXDATAL; } +// Enable/disable the RX-complete interrupt (used by the bus-transaction guard +// to keep serial RX from disturbing bit-banged framing). +void RS232_setRxInterrupt(bool enable) { + if (enable) + USART0.CTRLA |= USART_RXCIE_bm; + else + USART0.CTRLA &= ~USART_RXCIE_bm; +} + +// True if at least one received byte is waiting. +bool RS232_hasChar(void) { return RS232_RxCharEnd != 0; } + +// Atomically dequeue the next received byte. Only call when RS232_hasChar(). +char RS232_getChar(void) { + char c; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + c = (char)RS232_RxCharBuffer[RS232_RxCharBegin++]; + if (RS232_RxCharBegin == RS232_RxCharEnd) // buffer consumed + RS232_RxCharBegin = RS232_RxCharEnd = 0; + } + return c; +} + void RS232_SendByte(uint8_t Data) { loop_until_bit_is_set(USART0_STATUS, USART_DREIF_bp); // wait for UART to become available diff --git a/src/com232.h b/src/com232.h index e68fd54..60154cf 100644 --- a/src/com232.h +++ b/src/com232.h @@ -25,10 +25,15 @@ #include -extern volatile uint8_t RS232_RxCharBuffer[25], RS232_RxCharBegin, - RS232_RxCharEnd; - void RS232_Init(void); + +// Receive path. The RX ring is private to the driver; the app polls hasChar() +// and drains with getChar(). setRxInterrupt() masks/unmasks RX completion (used +// by the bus-transaction guard). +void RS232_setRxInterrupt(bool enable); +bool RS232_hasChar(void); +char RS232_getChar(void); + void RS232_Print_P(const char *str_addr); void RS232_SendByte(uint8_t Data); void RS232_sendbytes(const uint8_t *bytes, uint8_t len); diff --git a/src/sniffer.c b/src/sniffer.c index 039fbfc..dc32c66 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -20,10 +20,6 @@ along with this program. If not, see . */ -#include -#include -#include -#include #include #include #include @@ -31,6 +27,7 @@ #include #include "avclandrv.h" +#include "board.h" #include "com232.h" #include "queue.h" @@ -51,10 +48,7 @@ static void *outgoingSlots[CACHE_SIZE]; static Queue_t cache, rcache, incoming, outgoing; -static volatile bool enqueueStatus = false; - void Setup(); -void general_GPIO_init(); void print_help(); static uint8_t return_resp(RFrame_t *resp) { @@ -129,8 +123,7 @@ int main() { print_help(); while (true) { - - if (!BUS_IS_IDLE) { + if (AVCLAN_busActive()) { if (AVCLAN_frame_t *msg = popQueue(&cache)) { err = AVCLAN_readframe(msg, (log_t){.print = printAllFrames, .binary = printBinary, @@ -181,7 +174,7 @@ int main() { resp = AVCLAN_statemachine(resp); push_or_return_resp(resp); } - } else if (enqueueStatus) { + } else if (statustimer_tickPending()) { AVCLAN_frame_t *status = AVCLAN_getStatusFrame(); AVCLAN_generateStatus(status, true, dev_STATUS); if (RFrame_t *resp = (RFrame_t *)popQueue(&rcache)) { @@ -191,18 +184,14 @@ int main() { RS232_Print("Outgoing queue full; unable to send status update\n"); pushQueue(&rcache, resp); } else - enqueueStatus = false; // Only clear if successful + statustimer_clearTick(); // Only clear if successful } // no further error handling needed; status isn't part of the cache } // Key handler - if (RS232_RxCharEnd) { - cli(); - char readkey = RS232_RxCharBuffer[RS232_RxCharBegin++]; - if (RS232_RxCharBegin == RS232_RxCharEnd) // if buffer is consumed - RS232_RxCharBegin = RS232_RxCharEnd = 0; // reset buffer - sei(); + if (RS232_hasChar()) { + char readkey = RS232_getChar(); switch (readkey) { case '?': print_help(); break; case 'v': toggle_flag(&verbose, "Verbose errors: "); break; @@ -260,22 +249,22 @@ int main() { case 'g': AVCLAN_micToggle(); break; case 'p': RS232_Print("First play/pause begin ... "); - AVCLAN_micPlayPause(); + AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); while (AVCLAN_isMediaFunctioning()) {} RS232_Print("end\nSecond play/pause begin ... "); - AVCLAN_micPlayPause(); + AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); while (AVCLAN_isMediaFunctioning()) {} RS232_Print("end\n"); break; case 's': RS232_Print("Skip begin ... "); - AVCLAN_micSkipForward(); + AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); while (AVCLAN_isMediaFunctioning()) {} RS232_Print("end\n"); break; case 'b': RS232_Print("Skip back begin ... "); - AVCLAN_micSkipBackward(); + AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); while (AVCLAN_isMediaFunctioning()) {} RS232_Print("end\n"); break; @@ -373,52 +362,16 @@ int main() { } } } // switch (readkey) - } // if (RS232_RxCharEnd) + } // if (RS232_hasChar()) } return 0; } void Setup() { - - _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, (CLK_PRESCALE | CLK_PRESCALE_DIV)); - - general_GPIO_init(); + board_init(); // clock + GPIO bring-up (target-specific) RS232_Init(); AVCLAN_init(); - - sei(); -} - -/* Configure pin settings which are not configured by peripherals */ -void general_GPIO_init() { - // Set pins PC2-3, PB0,3-5 as inputs - PORTC.DIRCLR = (PIN2_bm | // Unconnected - PIN3_bm); // CTS - PORTB.DIRCLR = (PIN0_bm | // Unconnected - PIN3_bm | // IGN_SENSE - PIN4_bm | // Unused, but connected to WOC (PC0) - PIN5_bm); // Unused, but connected to WOD (PC1) - - // Enable pull-up resistor and disable input buffer (reduces any EM caused - // pin toggling and saves power) for unused and unconnected pins - PORTC.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc; - PORTB.PIN0CTRL = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc; - - // TODO: Remove once IGN_SENSE hardware is fixed - PORTB.DIRSET = PIN3_bm; - PORTB.OUTSET = PIN3_bm; - - // Output only pins: PA3-5, PB1-2,4-5; PC0-1 - // TODO: TxD (PA1), RTS (PA3) is output only, test if RxD needs the input - // buffer or if the UART peripheral bypasses it - PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; // RTS - PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOA - PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOB - PORTB.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // MIC_CONTROL - PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // non-driving WOC - PORTB.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // non-driving WOD - PORTC.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOC - PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOD + board_interruptsEnable(); } void print_help() { @@ -442,10 +395,3 @@ void print_help() { #endif "? - Print this message\n"); } - -// Periodic interrupt with a ~1 sec period; only enabled when playing -ISR(RTC_CNT_vect) { - AVCLAN_incrementTime(); - enqueueStatus = true; - RTC.INTFLAGS = RTC_OVF_bm; -}