mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-06 17:03:17 +00:00
Decouple hardware specific code from generic, agnostic code
This commit is contained in:
+22
-121
@@ -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/<name>/ 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
|
||||
# <avr/...>, 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=$<IF:$<BOOL:${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})
|
||||
|
||||
+39
-26
@@ -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,45 +45,48 @@
|
||||
"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",
|
||||
"inherits": [
|
||||
"relwithdebinfo-base",
|
||||
"usb1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "relwithdebinfo-usb0",
|
||||
"displayName": "RelWithDebInfo (ttyUSB0)",
|
||||
"description": "RelWithDebInfo build, program over /dev/ttyUSB0",
|
||||
"name": "attiny3216-relwithdebinfo-usb0",
|
||||
"displayName": "ATtiny3216 RelWithDebInfo (ttyUSB0)",
|
||||
"description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB0",
|
||||
"inherits": [
|
||||
"avr-attiny3216",
|
||||
"relwithdebinfo-base",
|
||||
"usb0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "attiny3216-relwithdebinfo-usb1",
|
||||
"displayName": "ATtiny3216 RelWithDebInfo (ttyUSB1)",
|
||||
"description": "ATtiny3216 RelWithDebInfo build, program over /dev/ttyUSB1",
|
||||
"inherits": [
|
||||
"avr-attiny3216",
|
||||
"relwithdebinfo-base",
|
||||
"usb1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+15
-381
@@ -1,400 +1,34 @@
|
||||
##########################################################################
|
||||
# "THE ANY BEVERAGE-WARE LICENSE" (Revision 42 - based on beer-ware
|
||||
# license):
|
||||
# <dev@layer128.net> 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
|
||||
# (<dev@layer128.net>, "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(<EXECUTABLE_NAME>-${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
|
||||
# <LIBRARY_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")
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#ifndef AVCLAN_DEFS_H
|
||||
#define AVCLAN_DEFS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
|
||||
+50
-202
@@ -20,58 +20,16 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/sfr_defs.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
err.errno = AVCLAN_readstartbit();
|
||||
if (err.errno)
|
||||
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;
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
+29
-32
@@ -20,45 +20,44 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// 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 <avr/io.h>
|
||||
#include <avr/sfr_defs.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
+12
-14
@@ -16,27 +16,25 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// 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();
|
||||
|
||||
+15
-12
@@ -16,23 +16,26 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ~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
|
||||
|
||||
@@ -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=$<IF:$<BOOL:${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:$<TARGET_FILE:mockingboard>:e
|
||||
-P ${AVRDUDE_PORT}
|
||||
DEPENDS mockingboard
|
||||
COMMENT "Flashing mockingboard to ${AVR_MCU} using ${AVR_PROGRAMMER}"
|
||||
VERBATIM USES_TERMINAL
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
AVCLAN-Mockingboard
|
||||
Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATtiny3216 board bring-up: main-clock prescaler + GPIO config for pins not
|
||||
// owned by a peripheral's own init.
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/xmega.h> // _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(); }
|
||||
@@ -21,11 +21,12 @@
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#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:
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
AVCLAN-Mockingboard
|
||||
Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// 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
|
||||
@@ -68,28 +68,37 @@
|
||||
#include <avr/io.h>
|
||||
#include <avr/sfr_defs.h>
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#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
|
||||
@@ -16,10 +16,12 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
AVCLAN-Mockingboard
|
||||
Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// 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
|
||||
+29
-2
@@ -24,9 +24,10 @@
|
||||
#include <avr/io.h>
|
||||
#include <avr/sfr_defs.h>
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#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
|
||||
|
||||
+8
-3
@@ -25,10 +25,15 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
|
||||
+13
-67
@@ -20,10 +20,6 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/sfr_defs.h>
|
||||
#include <avr/xmega.h>
|
||||
#include <ctype.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -31,6 +27,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user