From ab4b3bf15ac141ac2c02dc06bf242d93ac97e9c3 Mon Sep 17 00:00:00 2001 From: loudnl Date: Thu, 29 Jan 2026 23:13:24 +0100 Subject: [PATCH 01/45] Tippy tappy typoooos --- lib/emulator/emudore_emulator.c | 4 ++-- lib/emulator/interface.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/emulator/emudore_emulator.c b/lib/emulator/emudore_emulator.c index 1c4f771..5814cd8 100644 --- a/lib/emulator/emudore_emulator.c +++ b/lib/emulator/emudore_emulator.c @@ -3,14 +3,14 @@ * for interfacing one or two MOS SID chips and/or hardware SID emulators over * (WEB)USB with your computer, phone or ASID supporting player * - * emulator_emodure.c + * emodure_emulator.c * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) * File author: LouD * * Any licensing conditions from the above named source automatically * apply to this code * - * Copyright (c) 2024-2025 LouD + * Copyright (c) 2024-2026 LouD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/emulator/interface.cpp b/lib/emulator/interface.cpp index 8450e78..c90c100 100644 --- a/lib/emulator/interface.cpp +++ b/lib/emulator/interface.cpp @@ -10,7 +10,7 @@ * Any licensing conditions from the above named source automatically * apply to this code * - * Copyright (c) 2024-2025 LouD + * Copyright (c) 2024-2026 LouD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From e02912096f73d551fa2f24838a28f0132acd40a9 Mon Sep 17 00:00:00 2001 From: loudnl Date: Fri, 30 Jan 2026 15:47:46 +0100 Subject: [PATCH 02/45] Windows fix schmix.. yuck --- examples/config-tool/cfg_usbsid | Bin 195824 -> 96728 bytes examples/config-tool/cfg_usbsid.c | 17 +++++++++-------- examples/send_sid/libusb-1.0.dll | Bin examples/send_sid/send_sid | Bin 22200 -> 22200 bytes examples/send_sid/send_sid.c | 3 ++- 5 files changed, 11 insertions(+), 9 deletions(-) mode change 100755 => 100644 examples/send_sid/libusb-1.0.dll diff --git a/examples/config-tool/cfg_usbsid b/examples/config-tool/cfg_usbsid index 16714989448702da7d97ccb36e80f87c8f777207..1daf72155f4b56200ba28a7c087fd9a0e34b035a 100755 GIT binary patch delta 28901 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 z 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 literal 195824 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 z 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 0 && rc == 7) ? 0 : -1; + usid_dev = (rc == 0 || rc == 7) ? 0 : -1; if (usid_dev < 0) { @@ -385,6 +385,7 @@ int usbsid_init(void) out:; if (devh != NULL) usbsid_close(); + rc = -1; return rc; } @@ -1247,7 +1248,7 @@ void config_skpico(int argc, char **argv) skpico_write_config(debug); printf("Start saving default config\n"); - sleep(0.5); + sleep(1); skpico_save_config(debug); printf("Extend config mode\n"); @@ -1373,7 +1374,7 @@ void config_skpico(int argc, char **argv) skpico_write_config(debug); printf("Start saving config\n"); - sleep(0.5); + sleep(1); skpico_save_config(debug); printf("Extend config mode\n"); @@ -1384,7 +1385,7 @@ void config_skpico(int argc, char **argv) skpico_read_config(debug); skpico_end_config_mode(debug); - sleep(0.5); + sleep(1); goto exit; break; } @@ -1721,7 +1722,7 @@ void config_usbsidpico(int argc, char **argv) } if (argc > 8) { uint8_t test_buffer[64] = {0}; - test_buffer[0] = ((COMMAND << 6) | 18); + test_buffer[0] = ((COMMAND << 6) | CONFIG); test_buffer[1] = WRITE_CONFIG; int tb_count = 2; for (int i = 2; i < argc; i++) { diff --git a/examples/send_sid/libusb-1.0.dll b/examples/send_sid/libusb-1.0.dll old mode 100755 new mode 100644 diff --git a/examples/send_sid/send_sid b/examples/send_sid/send_sid index 338908b12f382962f4999e8ce332a437b1b056d6..950eb24441fb8e68e00a7f9a5515ca406ce91558 100755 GIT binary patch delta 2882 zcmZuz32YQq7@oIvdeE)gYw2!Fk8NooKwF@+R7DNP(8Ys8DYi!N!c?I~)HbmOSj<{r z9T$MZHoH;#xs`Alwh$GW+-qX+ERp4xKH%YoNpE+mu9fw}1u z30Xn++iQ|WSrjFx^)P1lS~|3EhQ}Kwjp=S(&Awex^y(hp5w|ZGn$v3-h$Wa~^*0Q| zr&?G@?Y&J=nmd~&=;~#RK(Jxa?c1ZPC&c_~CgA%_w;#eBN}JJ5@Evvg%l%FSe8CQ8 zAym`rG>q<*L{Yq25An|HA+{wo2G6~O{?xa~lfiVopC|j&XP}^~Poiz^Y?!L6wHSHB zaSiY=5kq*G&2z%TU1C1?riT*D;UN*7@Q~s(dHDD`EL?^gK*E)ZGE12icb`AWkKt(Q z&2V@YFdgpU5MsJ|04+RB(be4;F%RQ)bt{JOa2R5|4lQE7oX=5*RhT0|13KYhx5MNi z8?du8boF^GLpo7c>o9_JV_XN#bR>CxAIUPGM~ku-LZ|@iDDbv?7~hHdDQ>^ISxAOX zGqreK-39pBa)4tQ7`ir$uOY~e_=#(Vu?svUR0()cJ%czZZ|LeNhR~;n_wBi2%ehwV z_WkOn?>KDJ+Y#5Rd&Ldeca@*gdUNb4NFocnO|O~bKLYF3dU3wJWPUqV>sba?Ei4ER z8)>|AP~!I$iehWO2t*;h)0s8U$pxGGBXAx|Y`-|U$bQ#k2$?N(fzxj34bxRl|Lpt2 z>;yE3;OFfmERU0!^=Vl(Y278(X zYRAv!VBxr}9UeQu4bV3-H}spt^6A=YrgjowpZ3Ac=}bB?Ymjrz2t|RW&%PW3z4}A< z>cvylHeGF|Z)VxaY}x`$GKgJT+o3M)qi?+0d&~eAtf6PJa>y~7(65ASq?P?fd9wdB z4Aa5r>W5{bFt?ftvmssVZYejf`Z%O(S0A>|Q4l~q8zs$re_oNaT0wh2LhdvvdlpJe zg}{_b%oG#mX7)4zu}a8v6Xd8=xhTwZ4;OndtJv*g4_^sLmwHrTYj-j9I$< zcP@QlnAp3^L`#&kZ$A(XlJh7x-}Yb(O+`y+L~xUxx!y1E<6bD;wg@I>&(OaDB$PI4A5u`-HW#~H`Hy5 zg)4>LkvB526svgQ*pDfoIg12 zL@Lwco8Nwsm$sO?@-wFmjmDSoc;k!69>9%Z*&!6UZ%a8c7+S@*pe~gVbK-Fl(m;3S zXOXAr5BaX_ivN)sAaR-0QBZEZkrJ`iN^1*T)^DSMxpZMc5vit|3hr=B7y_I>=`p?< zt$gaT{uw!$Yz>AX95FnQ1B0QzFVOP-6=~O#BWd*W(F&9uj>c#3cw;3h;2r#5!Oa{Q z4As&7{fCz=it?_W6hi_!1VMG>l60+tAmbk2_@;FQGqzel_~He#7&Iehi&b@wTfv26Ec1$qOxG zlVUY*OiWdbH7;IJ`XStg-!hssGMAjA#b_U<_oH1zpG8|i{b-NTO=zD8d^NH_CG~+z zqv}+$J5W1j5+P37I`&cL6}B(@eJwi0fq!39Eh$QUz%}k63t3B>#+Q=!Y3ukZQbI3{ jUxlmUs~SL>10Pk@C0Y_vY373^3H3#ST_2e8U|q~V&ec|1 delta 2970 zcmZuz3s4nR7~TV}fPy?PZxFaZCa54OKFCB`SWN>*DRIW6(HY8etn^k^bZc&=JKH7g zlv#|EQ_h5q8hO=-${-qVPSexgGi9UJtVxNYg-UJTdB9yNX6~MI{_}m`|DFGwJxj+v z(y@=UN0O+u$qN5l8;B=OVM#mtqjIlZ`R+GM{{FsUV`*JkU4?6U;HmI)ab$Pkk))dm zd6s_US{5@qOi_YbFJqqBNlTJbyqmfLL4S_qsLTFiScJl!00wz$1#RdJDT~%x4M5?OBw=3u!A|U z8V2oD`^yQ$@sNip7{fy}8sQ-!&EetDRogyu0d4@fJVQ~|C~G1X`eRzqt)x$+ zq4Gz zU}tYL)%!3F>9MAI2YQh9M09vrrOv7yC=2R;shn9jhbQV^0L6R&QR&V+ppKxBitbn2 zg|=le*+^6E0{m1D&Jz}<{JadY(7u3F(0tYAh3mFFNYW2mLk^lE?`@?>ZUfZ zS}z(^FaeMasuh8hL#r}=){SP%=-Z;5)`MD~Lf=VSU$<<4?Vbh``1!gB&&U>QXal;h zc|h@`p}Obr;<1_6*a}$1t|R-^E6G$oJ!1x8oFQnX5>nuR6i8*4fQ%bbo&t>3eUVFF zDHUsXN+(O+k0Sdw^xo`5Z-xxMErJI*Pugq}TDzpyB!`D+Nqa)jnkB?pfD{S2T|kyI zh~MSrQTD;;fSgibxkZu+semmUJ_vKALJplUeqPuZ7rlFYcGM-g$zM@AzS#Q{AMx+c zqJDmxtimg@3ol}%ZnAvuhXBO_FmEKFEd&@V0J$RpMhNih1(w1SYu$wFo%yG8aw5oB zTAfo)X3I8VqQLU34)!tUlu7fUKch?soOfl`vNsT!W3PfI@@wtiK zD(oe@$8>+pRzCU3P{AzgJU%^rqu9JUIGMwK0&L5_VBLTrAEsOnh2$TGCtOS);$r$x z@l1fkcT55=NxGx%3rjd7Xzc|2%qEd*n*_QYKuzL2^muL!xt?a^6(#?czyx^(j`r~# znPh5Rw9=h&%k0q93w+A69uAJ+TrmgG8s7Ox-=)KrNppl((J{bT&F>&ODE?^?;jJ2dQNo#e&P*#%R}7KC^&{l*VA%lE-Vk Date: Fri, 30 Jan 2026 15:49:30 +0100 Subject: [PATCH 03/45] =?UTF-8?q?What=20are=20you,=20blind=3F=20?= =?UTF-8?q?=F0=9F=98=B6=E2=80=8D=F0=9F=8C=AB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/buildtools.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index 429f984..3419207 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -7,8 +7,8 @@ env: on: push: paths: - - "src/examples/config-tool/**" - - "src/examples/send_sid/**" + - "examples/config-tool/**" + - "examples/send_sid/**" - ".github/workflows/buildtools.yml" # Only run on dev branch branches: [dev] From 774b705ac0c07afbd8324479c5025f442e4a0f09 Mon Sep 17 00:00:00 2001 From: loudnl Date: Fri, 30 Jan 2026 16:04:51 +0100 Subject: [PATCH 04/45] Fix upload download in release --- .github/workflows/buildtools.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index 3419207..e28a0e4 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -91,7 +91,7 @@ jobs: - name: Upload uses: actions/upload-artifact@v4 with: - name: ${{ matrix.buildname }}-${{ matrix.os }} + name: tools-${{ matrix.buildname }}-${{ matrix.os }} path: | repo/examples/${{ matrix.directory }}/${{ matrix.buildname }} ${{ matrix.os == 'windows-2022' && format('repo/examples/{0}/*.dll', matrix.directory) || '' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04b353b..47c5a95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: tag: ${{ env.SOURCE_TAG }} name: ${{ env.SOURCE_TAG }} token: ${{ secrets.GITHUB_TOKEN }} - artifacts: "release/*.uf2" + artifacts: "release/*.uf2,release/*.zip" prerelease: false removeArtifacts: true generateReleaseNotes: true @@ -375,7 +375,7 @@ jobs: - name: Download Tools Artifacts uses: actions/download-artifact@v4 with: - pattern: '*' + pattern: 'tools-*' path: tools-output merge-multiple: true From 0a4d574918fa293281bc583c91e43899413cd2a5 Mon Sep 17 00:00:00 2001 From: loudnl Date: Fri, 30 Jan 2026 16:18:57 +0100 Subject: [PATCH 05/45] Update libwinpthread-1.dll for config tool (was outdated) --- examples/config-tool/libwinpthread-1.dll | Bin 68679 -> 64931 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/config-tool/libwinpthread-1.dll b/examples/config-tool/libwinpthread-1.dll index 18922c7095f47695dafdd2c9c3e8e8155bf6dd83..2c88914060cd34b3183badaf8504f8b5775699e6 100755 GIT binary patch literal 64931 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 From 7486996d1bc40864d4893b6bcb8c19b084033e48 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 1 Feb 2026 11:18:56 +0100 Subject: [PATCH 06/45] Add custom naming parameter to build scripts --- examples/config-tool/CMakeLists.txt | 17 +++++++++-------- examples/send_sid/CMakeLists.txt | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/config-tool/CMakeLists.txt b/examples/config-tool/CMakeLists.txt index a6bdec6..43900d9 100644 --- a/examples/config-tool/CMakeLists.txt +++ b/examples/config-tool/CMakeLists.txt @@ -30,6 +30,7 @@ cmake_minimum_required(VERSION 3.17) ### Project magic sprinkles set(PROJECT_NAME cfg_usbsid) +set(EXECUTABLE ${PROJECT_NAME} CACHE STRING "EXECUTABLE") ### Project type project(${PROJECT_NAME} C ASM) @@ -73,18 +74,18 @@ set(TARGET_INCLUDE_DIRS PRIVATE ) ### Compile time -add_executable(${PROJECT_NAME} ${SOURCEFILES}) +add_executable(${EXECUTABLE} ${SOURCEFILES}) ### Copy build output to main directory add_custom_command(TARGET - ${PROJECT_NAME} + ${EXECUTABLE} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_LIST_DIR}) + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_LIST_DIR}) ### Remove build output from build directory add_custom_command(TARGET - ${PROJECT_NAME} + ${EXECUTABLE} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rm $) + COMMAND ${CMAKE_COMMAND} -E rm $) -target_include_directories(${PROJECT_NAME} ${TARGET_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} ${TARGET_LL}) -target_sources(${PROJECT_NAME} PUBLIC ${SOURCEFILES}) +target_include_directories(${EXECUTABLE} ${TARGET_INCLUDE_DIRS}) +target_link_libraries(${EXECUTABLE} ${TARGET_LL}) +target_sources(${EXECUTABLE} PUBLIC ${SOURCEFILES}) diff --git a/examples/send_sid/CMakeLists.txt b/examples/send_sid/CMakeLists.txt index efd39a3..deb71d7 100644 --- a/examples/send_sid/CMakeLists.txt +++ b/examples/send_sid/CMakeLists.txt @@ -30,6 +30,7 @@ cmake_minimum_required(VERSION 3.17) ### Project magic sprinkles set(PROJECT_NAME send_sid) +set(EXECUTABLE ${PROJECT_NAME} CACHE STRING "EXECUTABLE") ### Project type project(${PROJECT_NAME} C ASM) @@ -71,18 +72,18 @@ set(TARGET_INCLUDE_DIRS PRIVATE ) ### Compile time -add_executable(${PROJECT_NAME} ${SOURCEFILES}) +add_executable(${EXECUTABLE} ${SOURCEFILES}) ### Copy build output to main directory add_custom_command(TARGET - ${PROJECT_NAME} + ${EXECUTABLE} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_LIST_DIR}) + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_LIST_DIR}) ### Remove build output from build directory add_custom_command(TARGET - ${PROJECT_NAME} + ${EXECUTABLE} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rm $) + COMMAND ${CMAKE_COMMAND} -E rm $) -target_include_directories(${PROJECT_NAME} ${TARGET_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} ${TARGET_LL}) -target_sources(${PROJECT_NAME} PUBLIC ${SOURCEFILES}) +target_include_directories(${EXECUTABLE} ${TARGET_INCLUDE_DIRS}) +target_link_libraries(${EXECUTABLE} ${TARGET_LL}) +target_sources(${EXECUTABLE} PUBLIC ${SOURCEFILES}) From 9fc11c10bb77320500899f789937928ef66b7d0c Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 1 Feb 2026 11:20:12 +0100 Subject: [PATCH 07/45] Update buildscript --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0fae02..56961a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,10 @@ on: # Allows you to run this workflow manually from the Actions tab when needed workflow_dispatch: +concurrency: + group: Dev build + cancel-in-progress: true + jobs: build: name: Build From c6ed5d088ba907b4286e8be51131775eab412ca4 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 1 Feb 2026 11:20:25 +0100 Subject: [PATCH 08/45] Update tools buildscript, add MacOs --- .github/workflows/buildtools.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index e28a0e4..d65699c 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -21,6 +21,10 @@ on: type: boolean default: false +concurrency: + group: Dev build + cancel-in-progress: true + jobs: build-tools: name: Tools @@ -38,10 +42,18 @@ jobs: directory: config-tool, buildname: cfg_usbsid } - { os: ubuntu-latest, shell: bash, directory: send_sid, buildname: send_sid } - - { os: windows-2022, shell: 'msys2 {0}', + - { os: windows-2022, shell: 'msys2 {0}', directory: config-tool, buildname: cfg_usbsid.exe } - - { os: windows-2022, shell: 'msys2 {0}', + - { os: windows-2022, shell: 'msys2 {0}', directory: send_sid, buildname: send_sid.exe } + - { os: macos-14, shell: 'bash', + directory: config-tool, buildname: cfg_usbsid_macos14 } + - { os: macos-14, shell: 'bash', + directory: send_sid, buildname: send_sid_macos14 } + - { os: macos-15-intel, shell: 'bash', + directory: config-tool, buildname: cfg_usbsid_macos15intel } + - { os: macos-15-intel, shell: 'bash', + directory: send_sid, buildname: send_sid_macos15intel } defaults: run: shell: ${{ matrix.shell }} @@ -74,6 +86,13 @@ jobs: mingw-w64-x86_64-pkg-config mingw-w64-x86_64-ninja + - name: Install dependencies (MacOs) + if: matrix.os == 'macos-14' || matrix.os == 'macos-15-intel' + run: | + brew update + brew install pkg-config cmake ninja libusb + echo "brew_prefix=$(brew --prefix)" >> $GITHUB_OUTPUT + - name: Remove pre-built binaries run: | pushd repo/examples/${{ matrix.directory }} @@ -84,7 +103,7 @@ jobs: - name: Configure and build ${{ matrix.buildname }} run: | pushd repo/examples/${{ matrix.directory }} - cmake -S . -B build && cmake --build build -j$(nproc) + cmake -S . -B build -DEXECUTABLE=${{ matrix.buildname }} && cmake --build build -j$(nproc) ls -lhai popd From d0b964ee528d01958423688e5fe0cd2282d5128a Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 1 Feb 2026 11:24:31 +0100 Subject: [PATCH 09/45] Remove dependency installation for MacOs since these are all already installed, d0h... --- .github/workflows/buildtools.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index d65699c..1a7aae5 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -86,12 +86,11 @@ jobs: mingw-w64-x86_64-pkg-config mingw-w64-x86_64-ninja - - name: Install dependencies (MacOs) - if: matrix.os == 'macos-14' || matrix.os == 'macos-15-intel' - run: | - brew update - brew install pkg-config cmake ninja libusb - echo "brew_prefix=$(brew --prefix)" >> $GITHUB_OUTPUT + # - name: Install dependencies (MacOs) + # if: matrix.os == 'macos-14' || matrix.os == 'macos-15-intel' + # run: | + # brew update + # brew install pkg-config cmake ninja libusb - name: Remove pre-built binaries run: | From 50ba6b7355d2bbcac0d60a05e4cf3c1aad63a8d9 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 1 Feb 2026 18:59:56 +0100 Subject: [PATCH 10/45] Add optional custom paths in build script for emulator and sidplayer --- CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e7a1f3..1b77fc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -360,7 +360,12 @@ if(ONBOARD_SIDPLAYER EQUAL 1) # NOTICE: SIDPLAYER REQUIRES FULL OPTIMIZATIONS!! set(COMPILE_OPTS ${COMPILE_OPTS} -O3) ## Set sidplayer list directory - set(USPLAYER_LIST_DIR ${CMAKE_CURRENT_LIST_DIR}/lib/usbsid-player) + if(NOT ${EMUDIR} STREQUAL "") + set(USPLAYER_LIST_DIR ${EMUDIR}) + else() + set(USPLAYER_LIST_DIR ${CMAKE_CURRENT_LIST_DIR}/lib/usbsid-player) + endif() + message(DEBUG "USPLAYER_LIST_DIR is set to ${USPLAYER_LIST_DIR}") ### Adjust filenames for user interpretation foreach(FILENAME ${FILENAMES}) @@ -423,7 +428,12 @@ if(ONBOARD_EMULATOR EQUAL 1) set(COMPILE_OPTS ${COMPILE_OPTS} -DEMBEDDED=1) add_compile_definitions(ONBOARD_EMULATOR=1) set(EMULATOR_LIST_DIR ${CMAKE_CURRENT_LIST_DIR}/lib/emulator) - set(EMUDORE_LIST_DIR ${CMAKE_CURRENT_LIST_DIR}/lib/emudore-embedded) + if(NOT ${EMUDIR} STREQUAL "") + set(EMUDORE_LIST_DIR ${EMUDIR}) + else() + set(EMUDORE_LIST_DIR ${CMAKE_CURRENT_LIST_DIR}/lib/emudore-embedded) + endif() + message(DEBUG "EMUDORE_LIST_DIR is set to ${EMUDORE_LIST_DIR}") ### Adjust filenames for user interpretation foreach(FILENAME ${FILENAMES}) From 6f7bdb49e3842fc9c2ddd72a2201791bcc7dbf05 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 13:31:15 +0100 Subject: [PATCH 11/45] Cleanup and add docstrings --- src/bus.c | 195 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 136 insertions(+), 59 deletions(-) diff --git a/src/bus.c b/src/bus.c index c6c5357..c9f7a37 100644 --- a/src/bus.c +++ b/src/bus.c @@ -38,7 +38,7 @@ #ifdef ONBOARD_EMULATOR extern uint8_t *sid_memory; #else -extern uint8_t __not_in_flash("usbsid_buffer") sid_memory[(0x20 * 4)] __attribute__((aligned(2 * (0x20 * 4)))); +extern uint8_t sid_memory[(0x20 * 4)]; #endif /* config.c */ @@ -61,7 +61,7 @@ extern void sync_pios(bool at_boot); extern void stop_pios(void); /* globals */ -bool is_muted; /* Global muting state */ +volatile bool is_muted; /* Global muting state */ /* Direct Pio IRQ access */ volatile const uint32_t *IRQState = &pio0_hw->irq; @@ -71,7 +71,12 @@ uint8_t control_word, read_data; uint16_t delay_word; uint32_t data_word, dir_mask; - +/** + * @brief Set the bits going to the PIO databus based on provided address + * + * @param uint8_t address + * @param bool write + */ inline static int __not_in_flash_func(set_bus_bits)(uint8_t address, bool write) { /* CFG("[BUS BITS]$%02X:%02X ", address, data); */ @@ -116,8 +121,17 @@ inline static int __not_in_flash_func(set_bus_bits)(uint8_t address, bool write) return 1; } +/** + * @brief Write data to or read data from the databus + * @note uses PIO0 SM0, SM1, SM2 & SM3 + * WARNING: DEPRECATED AND NO LONGER WORKS, HERE FOR CODE HISTORY ONLY!! + * + * @param uint8_t command + * @param uint8_t address + * @param uint8_t data + */ uint8_t __no_inline_not_in_flash_func(bus_operation)(uint8_t command, uint8_t address, uint8_t data) -{ /* WARNING: DEPRECATED AND NO LONGER WORKS, HERE FOR CODE HISTORY ONLY!! */ +{ return 0; if __us_unlikely((command & 0xF0) != 0x10) { return 0; // Sync bit not set, ignore operation @@ -168,6 +182,13 @@ uint8_t __no_inline_not_in_flash_func(bus_operation)(uint8_t command, uint8_t ad return 0; } +/** + * @brief Cycle delay function + * blocks for supplied number of cycles (65535 max) + * @note uses DMA & PIO0 SM0 & SM3 + * + * @param uint16_t cycles + */ uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles) { /* This is a blocking function! */ if __us_unlikely(cycles == 0) return 0; /* No point in waiting zero cycles */ @@ -188,47 +209,105 @@ uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles) return 0; } -uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles) +/** + * @brief Write data to the bus at address + * does not wait for the PIO write to finish + * @note uses PIO0 SM0, SM1 & SM2 + * + * @param uint8_t address + * @param uint8_t data + */ +void __no_inline_not_in_flash_func(write_operation)(uint8_t address, uint8_t data) { - delay_word = cycles; - if __us_unlikely(set_bus_bits(address, false) != 1) { - return 0x00; + sid_memory[(address & 0x7F)] = data; + if __us_unlikely(set_bus_bits(address, true) != 1) { + return; } - dma_channel_set_read_addr(dma_tx_delay, &delay_word, false); - dma_channel_set_read_addr(dma_tx_control, &control_word, false); - dma_channel_set_read_addr(dma_tx_data, &data_word, false); - dma_channel_set_write_addr(dma_rx_data, &read_data, false); - dma_hw->multi_channel_trigger = ( - 1u << dma_tx_delay /* Delay cycles DMA transfer */ - //#if PICO_PIO_VERSION > 0 /* rp2040 only for now, see notice in setup_dmachannels */ - | 1u << dma_tx_control /* Control lines RW, CS1 & CS2 DMA transfer */ - | 1u << dma_tx_data /* Data & Address DMA transfer */ - //#endif - | 1u << dma_rx_data /* Read data DMA transfer */ - ); - dma_channel_wait_for_finish_blocking(dma_rx_data); /* Wait for data */ - sid_memory[(address & 0x7F)] = (read_data & 0xFF); - return (read_data & 0xFF); + pio_sm_exec(bus_pio, sm_control, pio_encode_irq_set(false, PIO_IRQ0)); /* Preset the statemachine IRQ to not wait for a 1 */ + pio_sm_exec(bus_pio, sm_data, pio_encode_irq_set(false, PIO_IRQ1)); /* Preset the statemachine IRQ to not wait for a 1 */ + pio_sm_exec(bus_pio, sm_data, pio_encode_wait_pin(true, PHI1)); + pio_sm_exec(bus_pio, sm_control, pio_encode_wait_pin(true, PHI1)); + pio_sm_put_blocking(bus_pio, sm_control, control_word); + pio_sm_put_blocking(bus_pio, sm_data, data_word); + + return; } -void __no_inline_not_in_flash_func(write_operation)(uint8_t address, uint8_t data) +/** + * @brief Write data to the bus at address + * The PIO bus waits n cycles before the write occurs + * does not wait for the PIO write to finish + * @note uses PIO0 SM0, SM1, SM2 & SM3 + * + * @param uint8_t address + * @param uint8_t data + * @param uint16_t cycles + */ +void __no_inline_not_in_flash_func(cycled_write_operation_nondma)(uint8_t address, uint8_t data, uint16_t cycles) { + delay_word = cycles; sid_memory[(address & 0x7F)] = data; if __us_unlikely(set_bus_bits(address, true) != 1) { return; } + pio_sm_put_blocking(bus_pio, sm_control, control_word); + pio_sm_put_blocking(bus_pio, sm_data, data_word); + pio_sm_put_blocking(bus_pio, sm_delay, delay_word); + + GPIODBG("[WC]$%04x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16" $%02X:%02X(%u %u)\n", + data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word), + address, data, cycles, delay_word); + return; +} + +/** + * @brief Write data to the bus at address + * The function waits n cycles before the write + * occurs by using `cycled_delay_operation` + * and then waits for the DMA to finish blocking + * @note uses DMA & PIO0 SM0, SM1, SM2 & SM3 + * + * @param uint8_t address + * @param uint8_t data + * @param uint16_t cycles + */ +uint16_t __no_inline_not_in_flash_func(cycled_delayed_write_operation)(uint8_t address, uint8_t data, uint16_t cycles) +{ /* This is a blocking function! */ + sid_memory[(address & 0x7F)] = data; + vu = (vu == 0 ? 100 : vu); /* NOTICE: Testfix for core1 setting dtype to 0 */ + if __us_unlikely(set_bus_bits(address, true) != 1) { + return 0; + } + + dma_channel_set_read_addr(dma_tx_control, &control_word, false); + dma_channel_set_read_addr(dma_tx_data, &data_word, false); + + cycled_delay_operation(cycles); /* Replaces the delay DMA */ pio_sm_exec(bus_pio, sm_control, pio_encode_irq_set(false, PIO_IRQ0)); /* Preset the statemachine IRQ to not wait for a 1 */ pio_sm_exec(bus_pio, sm_data, pio_encode_irq_set(false, PIO_IRQ1)); /* Preset the statemachine IRQ to not wait for a 1 */ pio_sm_exec(bus_pio, sm_data, pio_encode_wait_pin(true, PHI1)); pio_sm_exec(bus_pio, sm_control, pio_encode_wait_pin(true, PHI1)); - pio_sm_put_blocking(bus_pio, sm_control, control_word); - pio_sm_put_blocking(bus_pio, sm_data, data_word); + dma_hw->multi_channel_trigger = ( + 1u << dma_tx_control /* Control lines RW, CS1 & CS2 DMA transfer */ + | 1u << dma_tx_data /* Data & Address DMA transfer */ + ); + dma_channel_wait_for_finish_blocking(dma_tx_control); - return; + return cycles; } +/** + * @brief Write data to the bus at address + * and then waits for the DMA to finish blocking + * The PIO bus waits n cycles before the write occurs + * @note uses DMA & PIO0 SM0, SM1, SM2 & SM3 + * + * @param uint8_t address + * @param uint8_t data + * @param uint16_t cycles + */ void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles) { delay_word = cycles; @@ -261,49 +340,47 @@ void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint return; } -void __no_inline_not_in_flash_func(cycled_write_operation_nondma)(uint8_t address, uint8_t data, uint16_t cycles) +/** + * @brief Read data from the bus at address + * and then waits for the DMA to finish blocking + * The PIO bus waits n cycles before the read occurs + * @note uses DMA & PIO0 SM0, SM1, SM2 & SM3 + * + * @param uint8_t address + * @param uint16_t cycles + */ +uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles) { delay_word = cycles; - sid_memory[(address & 0x7F)] = data; - if __us_unlikely(set_bus_bits(address, true) != 1) { - return; - } - - pio_sm_put_blocking(bus_pio, sm_control, control_word); - pio_sm_put_blocking(bus_pio, sm_data, data_word); - pio_sm_put_blocking(bus_pio, sm_delay, delay_word); - - GPIODBG("[WC]$%04x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16" $%02X:%02X(%u %u)\n", - data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word), - address, data, cycles, delay_word); - return; -} - -uint16_t __no_inline_not_in_flash_func(cycled_delayed_write_operation)(uint8_t address, uint8_t data, uint16_t cycles) -{ /* This is a blocking function! */ - sid_memory[(address & 0x7F)] = data; - vu = (vu == 0 ? 100 : vu); /* NOTICE: Testfix for core1 setting dtype to 0 */ - if __us_unlikely(set_bus_bits(address, true) != 1) { - return 0; + if __us_unlikely(set_bus_bits(address, false) != 1) { + return 0x00; } + dma_channel_set_read_addr(dma_tx_delay, &delay_word, false); dma_channel_set_read_addr(dma_tx_control, &control_word, false); dma_channel_set_read_addr(dma_tx_data, &data_word, false); - - cycled_delay_operation(cycles); /* Replaces the delay DMA */ - pio_sm_exec(bus_pio, sm_control, pio_encode_irq_set(false, PIO_IRQ0)); /* Preset the statemachine IRQ to not wait for a 1 */ - pio_sm_exec(bus_pio, sm_data, pio_encode_irq_set(false, PIO_IRQ1)); /* Preset the statemachine IRQ to not wait for a 1 */ - pio_sm_exec(bus_pio, sm_data, pio_encode_wait_pin(true, PHI1)); - pio_sm_exec(bus_pio, sm_control, pio_encode_wait_pin(true, PHI1)); + dma_channel_set_write_addr(dma_rx_data, &read_data, false); dma_hw->multi_channel_trigger = ( - 1u << dma_tx_control /* Control lines RW, CS1 & CS2 DMA transfer */ + 1u << dma_tx_delay /* Delay cycles DMA transfer */ + //#if PICO_PIO_VERSION > 0 /* rp2040 only for now, see notice in setup_dmachannels */ + | 1u << dma_tx_control /* Control lines RW, CS1 & CS2 DMA transfer */ | 1u << dma_tx_data /* Data & Address DMA transfer */ + //#endif + | 1u << dma_rx_data /* Read data DMA transfer */ ); - dma_channel_wait_for_finish_blocking(dma_tx_control); - - return cycles; + dma_channel_wait_for_finish_blocking(dma_rx_data); /* Wait for data */ + sid_memory[(address & 0x7F)] = (read_data & 0xFF); + return (read_data & 0xFF); } +/** + * @brief Restart the PIO bus by unclaiming + * the DMA channels, stopping the PIO + * statemachines and then restarting + * everything + * Synchronizes the PIO statemachines + * afterwards + */ void restart_bus(void) { CFG("[RESTART BUS START]\n"); From 0e559eb02296ba090b3331c7dac15645beccd6ad Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:42:36 +0100 Subject: [PATCH 12/45] PIO cleanup and documentations --- src/pio.h | 51 +++++++++++++------ src/pio/bus_control.pio | 10 +++- src/pio/clock.pio | 2 + .../{clock_counter.pio => cycle_counter.pio} | 7 ++- src/pio/uart_rx.pio | 2 + src/pio/vu.pio | 3 ++ src/pio/vu_rgb.pio | 3 ++ 7 files changed, 60 insertions(+), 18 deletions(-) rename src/pio/{clock_counter.pio => cycle_counter.pio} (86%) diff --git a/src/pio.h b/src/pio.h index d698cad..eefd581 100644 --- a/src/pio.h +++ b/src/pio.h @@ -49,26 +49,45 @@ #endif -/* PIO & Statemachine usage +/* NOTICE: PIO & Statemachine usage * PIO0 - * SM0: PHI1 SID clock - * SM1: Control bus - * SM2: Data bus - * SM3: Delay timer + * SM0: sm_clkcnt - PHI1 SID clock (clock.pio / pio.c) + * SM1: sm_control - Bus control write and data read (bus_control.pio / pio.c) + * SM2: sm_data - Data and address bus write (bus_control.pio / pio.c) + * SM3: sm_delay - Delay cycle counter interrupt for bus SM1 & SM2 (bus_control.pio / pio.c) * PIO1 - * SM0: RGB LED control - * SM1: LED PWM control - * SM2: Uart RX - * SM3: PHI1 Cycle counter - * - * rp2350 only! - * PIO2 - * SM0: SID2 clock - * SM1: SID2 Control bus - * SM2: SID2 Data bus - * SM3: PHI2 Cycle counter + * SM0: Buffer raster cycle counter (ASID) (bus_control.pio / asid_buffer.c) + * SM1: LED PWM control (Non Wifi boards only, unused otherwise) (vu.pio) + * SM2: RGB LED control (as LED but for RGB Boards only, unused otherwise) (vu_rgb.pio) + * SM3: PHI1 Clock cycle counter (cycle_counter.pio) + * PIO2 (rp2350 only!) + * SM0: Uart RX (uart_rx.pio / uart.c) + * SM1: + * SM2: + * SM3: */ + /* NOTICE: PIO <-> DMA information + * all pio statemachines have one or more corresponding DMA + * configurations in dma.c where the corresponding DREQ is also set + * PIO0 + * SM0: - no DMA use + * SM1: dma_tx_control - DMA_SIZE_8 - DREQ_PIO0_TX1 + * SM1: dma_rx_data - DMA_SIZE_8 - DREQ_PIO0_RX1 + * SM2: dma_tx_data - DMA_SIZE_32 - DREQ_PIO0_TX2 + * SM3: dma_tx_delay - DMA_SIZE_16 - DREQ_PIO0_TX3 + * PIO1 + * SM0: - no DMA use + * SM1: dma_pwmled - DMA_SIZE_32 - DREQ_PIO1_TX0 + * SM2: dma_rgbled - DMA_SIZE_32 - DREQ_PIO1_TX1 + * SM3: dma_counter - DMA_SIZE_32 - DREQ_PIO1_RX3 (dual channel on rp2040) + * PIO2 (rp2350 only!) + * SM0: + * SM1: + * SM2: + * SM3: + */ + /* IRQ's */ #define PIO_IRQ0 0 diff --git a/src/pio/bus_control.pio b/src/pio/bus_control.pio index d7171c1..38c8699 100644 --- a/src/pio/bus_control.pio +++ b/src/pio/bus_control.pio @@ -62,8 +62,10 @@ .define PUBLIC DATABUS 1 ; 0b001 .define PUBLIC RASTER 2 ; 0b010 + ; Bus control program ~ PIO0 SM1 .program bus_control + .wrap_target pull block ; Pull data from TX fifo, block if no data wait 1 irq CONTROL ; Wait for IRQ signal @@ -82,9 +84,11 @@ done: out pins 3 ; Set control to next 3 bits from TX fifo .wrap + ; Data and address bus program ~ PIO0 SM2 ; pinstate stays the same until next write .program data_bus + .wrap_target pull block ; Pull data from FIFO, block if no data wait 1 irq DATABUS ; Wait for IRQ signal ~ should trigger exactly 1 cycle after CONTROL @@ -95,8 +99,10 @@ done: wait 0 gpio CLK ; Wait for clock to go low .wrap + ; Delay counter program ~ PIO0 SM3 .program delay_timer + .wrap_target pull block ; Pull data from FIFO, block if no data out x 16 ; Move data into scratch register X @@ -108,9 +114,11 @@ delay: irq wait DATABUS ; Set IRQ and wait for its release .wrap + ; Buffer raster cycle counter program ~ PIO1 SM0 -; If used, triggers an IRQ after each n raster cycles provided +; Triggers an IRQ after each n raster cycles provided when enabled .program raster_buffer + .wrap_target pull noblock ; Pull data from FIFO, if empty re-use previous data stored in X mov x osr ; Copy pulled data into OSR diff --git a/src/pio/clock.pio b/src/pio/clock.pio index 4f0af90..418327c 100644 --- a/src/pio/clock.pio +++ b/src/pio/clock.pio @@ -33,6 +33,8 @@ ; empty. Write the least significant bit to the OUT pin group. ; + +; PHI1 SID Clock ~ PIO0 SM0 .program clock .wrap_target ; Free 0 cycle unconditional jump diff --git a/src/pio/clock_counter.pio b/src/pio/cycle_counter.pio similarity index 86% rename from src/pio/clock_counter.pio rename to src/pio/cycle_counter.pio index dd68f75..2735e4f 100644 --- a/src/pio/clock_counter.pio +++ b/src/pio/cycle_counter.pio @@ -24,8 +24,13 @@ .define PUBLIC CLK 22 -; Infinite cycle counter, wraps at 32bit and starts over + +; Infinite cycle counter ~ PIO1 SM3 +; rp2350: Requires DMA for `dma_encode_endless_transfer_count()` +; rp2040: Requires 2 chained DMA channels for endless transfer +; wraps at 32bit and starts over after approximately ~71 minutes .program cycle_counter + .wrap_target init: set x 0 ; Set X to 0 diff --git a/src/pio/uart_rx.pio b/src/pio/uart_rx.pio index 0e2c314..c66c2f2 100644 --- a/src/pio/uart_rx.pio +++ b/src/pio/uart_rx.pio @@ -26,6 +26,8 @@ ; along with this program. If not, see . ; + +; Uart RX on steroids ~ PIO2 SM0 .program uart_rx ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and diff --git a/src/pio/vu.pio b/src/pio/vu.pio index 5490350..2b9aec1 100644 --- a/src/pio/vu.pio +++ b/src/pio/vu.pio @@ -28,7 +28,10 @@ ; along with this program. If not, see . ; + +; LED there be light! ~ PIO1 SM1 .program vu + .side_set 1 opt pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR. diff --git a/src/pio/vu_rgb.pio b/src/pio/vu_rgb.pio index 0588aa5..a36a58a 100644 --- a/src/pio/vu_rgb.pio +++ b/src/pio/vu_rgb.pio @@ -28,7 +28,10 @@ ; along with this program. If not, see . ; + +; My precious ~ PIO1 SM2 .program vu_rgb + .side_set 1 .define public T1 3 From d7b6d40be647b6e5c5b646cb7e3f15b4b2943f21 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:45:23 +0100 Subject: [PATCH 13/45] Remove unused PHI2 pio --- src/pio.c | 46 ++++++++++------------------------------------ 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/src/pio.c b/src/pio.c index f06a8f8..751006f 100644 --- a/src/pio.c +++ b/src/pio.c @@ -54,12 +54,6 @@ uint sm_delay, offset_delay; /* pio0 */ uint sm_clkcnt, offset_clkcnt; /* pio1 */ float sidclock_frequency, busclock_frequency; -/* Clock cycle counter */ -#if 0 && PICO_RP2350 && !USE_PIO_UART /* Only on rp2350 for testing and when not using PIO Uart */ -PIO sid2_pio = pio2; -uint sm_clock2, offset_clock2; -#endif - /* Shiny things */ #if defined(PICO_DEFAULT_LED_PIN) PIO led_pio = pio1; @@ -162,7 +156,7 @@ void setup_piobus(void) pio_sm_set_enabled(bus_pio, sm_data, true); } - { /* delay counter */ + { /* delay cycle counter */ sm_delay = 3; /* PIO0 SM3 */ pio_sm_claim(bus_pio, sm_delay); offset_delay = pio_add_program(bus_pio, &delay_timer_program); @@ -186,19 +180,19 @@ void setup_piobus(void) return; } -void sync_pios(bool at_boot) // TODO: SYNC COUNTER PIO WITH BUS PIO +void sync_pios(bool at_boot) { /* Sync PIO's */ #if PICO_PIO_VERSION == 0 CFG("[RESTART PIOS] Pico & Pico_w\n"); pio_sm_restart(bus_pio, 0b1111); - #elif PICO_PIO_VERSION > 0 // NOTE: rp2350 only + #elif PICO_PIO_VERSION > 0 /* NOTE: rp2350 only */ CFG("[SYNC PIOS] Pico2\n"); pio_clkdiv_restart_sm_multi_mask(bus_pio, 0, 0b1111, 0); - // pio_clkdiv_restart_sm_multi_mask(clkcnt_pio, 0, 0b0011, 0); + // pio_clkdiv_restart_sm_multi_mask(clkcnt_pio, 0, 0b0011, 0); /* TODO: SYNC COUNTER PIO WITH BUS PIO */ pio_clkdiv_restart_sm_multi_mask(clkcnt_pio, 0, 0b1000, 0); #endif if __us_likely(!at_boot) { - pio_sm_clear_fifos(bus_pio, sm_clock); // TODO: TEST! + pio_sm_clear_fifos(bus_pio, sm_clock); pio_sm_clear_fifos(bus_pio, sm_control); pio_sm_clear_fifos(bus_pio, sm_data); pio_sm_clear_fifos(bus_pio, sm_delay); @@ -213,13 +207,10 @@ void restart_bus_clocks(void) busclock_frequency = (float)pico_hz / (usbsid_config.clock_rate * 32) / 2; /* Clock frequency is 8 times the SID clock */ sidclock_frequency = (float)pico_hz / usbsid_config.clock_rate / 2; pio_sm_set_clkdiv(bus_pio, sm_clock, sidclock_frequency); -#if 0 && PICO_RP2350 && !USE_PIO_UART /* Only on rp2350 for testing and when not using PIO Uart */ - pio_sm_set_clkdiv(sid2_pio, sm_clock2, sidclock_frequency); -#endif - pio_sm_set_clkdiv(clkcnt_pio, sm_clkcnt, busclock_frequency); pio_sm_set_clkdiv(bus_pio, sm_control, busclock_frequency); pio_sm_set_clkdiv(bus_pio, sm_data, busclock_frequency); pio_sm_set_clkdiv(bus_pio, sm_delay, busclock_frequency); + pio_sm_set_clkdiv(clkcnt_pio, sm_clkcnt, busclock_frequency); CFG("[PI CLK]@%luMHz [DIV]@%.2f [BUS CLK]@%.2f [CFG SID CLK]%d\n", (pico_hz / 1000 / 1000), @@ -262,7 +253,7 @@ void init_sidclock(void) uint32_t pico_hz = clock_get_hz(clk_sys); sidclock_frequency = (float)pico_hz / usbsid_config.clock_rate / 2; - CFG("[SID CLK INIT] START\n"); + CFG("[SID] CLK INIT START\n"); CFG("[PI CLK]@%luMHz [DIV]@%.2f [SID CLK]@%.2f [CFG SID CLK]%d\n", (pico_hz / 1000 / 1000), sidclock_frequency, @@ -272,23 +263,8 @@ void init_sidclock(void) sm_clock = 0; /* PIO0 SM0 */ pio_sm_claim(bus_pio, sm_clock); clock_program_init(bus_pio, sm_clock, offset_clock, PHI1, sidclock_frequency); - CFG("[SID CLK INIT] FINISHED\n"); + CFG("[SID] CLK INIT FINISHED\n"); -/* DISABLED */ -#if 0 && PICO_RP2350 && !USE_PIO_UART /* Only on rp2350 for testing and when not using PIO Uart */ - CFG("[SID CLK2 INIT] START\n"); - CFG("[PI CLK]@%luMHz [DIV]@%.2f [SID CLK]@%.2f [CFG SID CLK]%d\n", - (pico_hz / 1000 / 1000), - sidclock_frequency, - ((float)pico_hz / sidclock_frequency / 2), - (int)usbsid_config.clock_rate); - offset_clock2 = pio_add_program(sid2_pio, &clock_program); - sm_clock2 = 0; /* PIO2 SM0 */ - pio_sm_claim(sid2_pio, sm_clock2); - clock_program_init(sid2_pio, sm_clock2, offset_clock2, PHI2, sidclock_frequency); - // clock_program_init(sid2_pio, sm_clock2, offset_clock2, PHI2, sidclock_frequency); - CFG("[SID CLK2 INIT] FINISHED\n"); -#endif return; } @@ -324,10 +300,8 @@ void setup_sidclock(void) /* De-init nMHz square wave output */ void deinit_sidclock(void) { - CFG("[DE-INIT CLOCK]\n"); + CFG("[SID] CLK DE-INIT\n"); clock_program_deinit(bus_pio, sm_clock, offset_clock, clock_program); -#if 0 && PICO_RP2350 && !USE_PIO_UART /* Only on rp2350 for testing and when not using PIO Uart */ - clock_program_deinit(sid2_pio, sm_clock2, offset_clock2, clock_program); -#endif + return; } From 5c0aa8682ca5a3f59bea152d04b82964ca68f148 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:45:30 +0100 Subject: [PATCH 14/45] Missed a rename --- src/pio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pio.h b/src/pio.h index eefd581..5089362 100644 --- a/src/pio.h +++ b/src/pio.h @@ -41,7 +41,7 @@ /* PIO */ #include "bus_control.pio.h" /* Busje komt zo! */ -#include "clock_counter.pio.h" /* !<(O.O)>! */ +#include "cycle_counter.pio.h" /* !<(O.O)>! */ #include "clock.pio.h" /* TikTak */ #include "vu.pio.h" /* Kiem em goan! */ #if defined(USE_RGB) From 2fc98fdd9de27a43f63acd588a83efe19abcb10a Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:45:44 +0100 Subject: [PATCH 15/45] Missed a rename --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b77fc2..7acf2c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,7 +513,7 @@ set(PIO_BUS ${CMAKE_CURRENT_LIST_DIR}/src/pio/bus_control.pio) set(PIO_CLOCK ${CMAKE_CURRENT_LIST_DIR}/src/pio/clock.pio) set(PIO_VU ${CMAKE_CURRENT_LIST_DIR}/src/pio/vu.pio) set(PIO_RGB ${CMAKE_CURRENT_LIST_DIR}/src/pio/vu_rgb.pio) -set(PIO_COUNTER ${CMAKE_CURRENT_LIST_DIR}/src/pio/clock_counter.pio) +set(PIO_COUNTER ${CMAKE_CURRENT_LIST_DIR}/src/pio/cycle_counter.pio) if(USE_PIO_UART EQUAL 1) set(PIO_UART ${CMAKE_CURRENT_LIST_DIR}/src/pio/uart_rx.pio) endif() From 2c88ad7dad2247f424d9dcfe7fc53bee3d2a6c5f Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:50:45 +0100 Subject: [PATCH 16/45] Housekeeping --- src/asid.c | 2 +- src/config.c | 10 +++++----- src/midi_handler.c | 4 ++-- src/sid.c | 14 +++----------- src/sid_cloneconfig.c | 4 ++-- src/sid_detection.c | 4 ++-- src/sid_tests.c | 2 +- src/uart.c | 24 ++++++++++++------------ src/uart.h | 2 ++ src/usbsid.c | 8 ++++---- src/vu.c | 2 +- 11 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/asid.c b/src/asid.c index dce19ca..80c965e 100644 --- a/src/asid.c +++ b/src/asid.c @@ -48,7 +48,7 @@ extern RuntimeCFG cfg; extern char *sidtypes[5]; /* GPIO */ -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); extern void pause_sid(void); extern void reset_sid(void); diff --git a/src/config.c b/src/config.c index 802f8b3..a0661fc 100644 --- a/src/config.c +++ b/src/config.c @@ -56,9 +56,9 @@ extern bool offload_ledrunner; #endif /* GPIO */ -extern uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles); -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); -extern uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles); +extern uint8_t cycled_read_operation(uint8_t address, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); +extern uint16_t cycled_delay_operation(uint16_t cycles); extern void reset_sid(void); extern void restart_bus(void); extern void restart_bus_clocks(void); @@ -1237,8 +1237,8 @@ void apply_clockrate(int n_clock, bool suspend_sids) } CFG("[CONFIG] [CLOCK FROM]%d [CLOCK TO]%d\n", usbsid_config.clock_rate, clockrates[n_clock]); usbsid_config.clock_rate = clockrates[n_clock]; - usbsid_config.refresh_rate = refreshrates[n_clock]; /* Experimental */ - usbsid_config.raster_rate = rasterrates[n_clock]; /* Experimental */ + usbsid_config.refresh_rate = refreshrates[n_clock]; /* Used by ASID */ + usbsid_config.raster_rate = rasterrates[n_clock]; /* Used by the Vu and the ASID buffer */ /* Cycled write buffer vars */ sid_hz = usbsid_config.clock_rate; sid_mhz = (sid_hz / 1000 / 1000); diff --git a/src/midi_handler.c b/src/midi_handler.c index 73d7189..b5f823f 100644 --- a/src/midi_handler.c +++ b/src/midi_handler.c @@ -51,8 +51,8 @@ extern RuntimeCFG cfg; /* GPIO */ -extern uint8_t __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); -extern uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles); +extern uint8_t cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); +extern uint16_t cycled_delay_operation(uint16_t cycles); /* Initialize variables */ extern const midi_ccvalues midi_ccvalues_defaults; /* midi.c */ diff --git a/src/sid.c b/src/sid.c index 06c76bc..bd69fe4 100644 --- a/src/sid.c +++ b/src/sid.c @@ -34,7 +34,7 @@ #ifdef ONBOARD_EMULATOR extern uint8_t *sid_memory; #else -extern uint8_t __not_in_flash("usbsid_buffer") sid_memory[(0x20 * 4)] __attribute__((aligned(2 * (0x20 * 4)))); +extern uint8_t sid_memory[(0x20 * 4)]; #endif extern int usbdata; @@ -46,12 +46,8 @@ extern RuntimeCFG cfg; extern uint16_t vu; /* bus.c */ -#if PICO_RP2350 extern void clockcycle_delay(uint32_t n_cycles); -#else -extern uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles); -#endif -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); /* (hot) locals */ static bool paused_state, reset_state; @@ -234,11 +230,7 @@ void reset_sid(void) cPIN(RES); if (cfg.chip_one == 0 || cfg.chip_two == 0) { /* 10x PHI1(02) cycles as per datasheet for REAL SIDs only */ -#if PICO_RP2350 clockcycle_delay(10); -#else - cycled_delay_operation(10); -#endif } sPIN(RES); set_reset_state(false); @@ -249,7 +241,7 @@ void reset_sid(void) * @brief Clear SID register / reset registers * @note https://csdb.dk/forums/?roomid=11&topicid=85713&showallposts=1 * @note thanks Wilfred for pointing this out! - * @param sidno + * @param int sidno */ void clear_sid_registers(int sidno) { diff --git a/src/sid_cloneconfig.c b/src/sid_cloneconfig.c index f3cb1fa..4aeb4c0 100644 --- a/src/sid_cloneconfig.c +++ b/src/sid_cloneconfig.c @@ -33,8 +33,8 @@ /* GPIO */ -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); -extern uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); +extern uint8_t cycled_read_operation(uint8_t address, uint16_t cycles); /* SID */ extern void clear_sid_memory(void); diff --git a/src/sid_detection.c b/src/sid_detection.c index ab4c734..ee0cb1b 100644 --- a/src/sid_detection.c +++ b/src/sid_detection.c @@ -34,8 +34,8 @@ /* GPIO */ -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); -extern uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); +extern uint8_t cycled_read_operation(uint8_t address, uint16_t cycles); extern void clear_bus(int sidno); /* Config */ diff --git a/src/sid_tests.c b/src/sid_tests.c index 51cb97b..44ad03e 100644 --- a/src/sid_tests.c +++ b/src/sid_tests.c @@ -31,7 +31,7 @@ /* GPIO */ -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); extern void clear_sid_registers(int sidno); extern void reset_sid_registers(void); diff --git a/src/uart.c b/src/uart.c index fcb7121..bde7b62 100644 --- a/src/uart.c +++ b/src/uart.c @@ -28,29 +28,28 @@ */ #ifdef USE_PIO_UART +#if PICO_RP2350 #include "globals.h" #include "uart.h" #include "logging.h" -/* USBSID */ -extern uint8_t __not_in_flash("usbsid_buffer") uart_buffer[64]; - -/* GPIO */ -extern uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles); -extern uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles); -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); -extern uint16_t __no_inline_not_in_flash_func(cycled_delayed_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); -extern void reset_sid(void); - -/* USBSID */ +/* usbsid.c */ +extern uint8_t uart_buffer[64]; extern int usbdata; extern char ntype, dtype, uart; extern bool offload_ledrunner; +/* gpio.c */ +extern uint16_t cycled_delay_operation(uint16_t cycles); +extern uint8_t cycled_read_operation(uint8_t address, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); +extern uint16_t cycled_delayed_write_operation(uint8_t address, uint8_t data, uint16_t cycles); +extern void reset_sid(void); + /* Locals */ -static PIO uart_pio = pio1; +static PIO uart_pio = pio2; static uint sm_uartrx; static int8_t pioirq_uartrx; static queue_t fifo_uartrx; @@ -236,4 +235,5 @@ void deinit_uart(void) return; } +#endif /* PICO_RP2350 */ #endif /* USE_PIO_UART */ diff --git a/src/uart.h b/src/uart.h index 05473a6..45dd08e 100644 --- a/src/uart.h +++ b/src/uart.h @@ -32,6 +32,7 @@ #endif #ifdef USE_PIO_UART +#if PICO_RP2350 /* Default includes */ @@ -58,6 +59,7 @@ #define UART_FIFO_SIZE 65535 /* TODO: Verify size */ +#endif /* PICO_RP2350 */ #endif /* USE_PIO_UART */ #ifdef __cplusplus diff --git a/src/usbsid.c b/src/usbsid.c index 540eb63..bfe8eb8 100644 --- a/src/usbsid.c +++ b/src/usbsid.c @@ -92,9 +92,9 @@ extern void reset_sid_registers(void); extern void enable_sid(bool unmute); extern void disable_sid(void); extern void clear_bus_all(void); -extern uint16_t __no_inline_not_in_flash_func(cycled_delay_operation)(uint16_t cycles); -extern uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, uint16_t cycles); -extern void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); +extern uint16_t cycled_delay_operation(uint16_t cycles); +extern uint8_t cycled_read_operation(uint8_t address, uint16_t cycles); +extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); extern bool is_muted; /* Uart */ @@ -918,7 +918,7 @@ int main() /* Check for default config bit * NOTE: This cannot be run from Core 1! */ if (!auto_config) detect_default_config(); - if (auto_config) { /* ISSUE: NOT WORKING ON RP2350 */ + if (auto_config) { /* NOTE: Does not work on rp2350 */ auto_detect_routine(auto_config, true); /* Double tap! */ save_config_ext(); auto_config = false; diff --git a/src/vu.c b/src/vu.c index 5dd630c..bcef544 100644 --- a/src/vu.c +++ b/src/vu.c @@ -39,7 +39,7 @@ extern Config usbsid_config; /* usbsid.c */ #ifdef ONBOARD_EMULATOR extern uint8_t *sid_memory; #else -extern uint8_t __not_in_flash("usbsid_buffer") sid_memory[(0x20 * 4)] __attribute__((aligned(2 * (0x20 * 4)))); /* usbsid.c */ +extern uint8_t sid_memory[(0x20 * 4)]; /* usbsid.c */ #endif extern int usbdata; /* usbsid.c */ extern int numsids; /* config.c */ From c0df3de775a8d5bd10bf8b0a8455866e59fb674a Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:52:51 +0100 Subject: [PATCH 17/45] Fix `clockcycle_delay` for rp2040 by chaining two DMA channels together, move functions into bus.c --- src/bus.c | 36 +++++++++++++++++ src/dma.c | 116 ++++++++++++++++++++++++++++++------------------------ 2 files changed, 100 insertions(+), 52 deletions(-) diff --git a/src/bus.c b/src/bus.c index c9f7a37..5423e67 100644 --- a/src/bus.c +++ b/src/bus.c @@ -54,6 +54,7 @@ extern uint16_t vu; /* dma.c */ extern void setup_dmachannels(void); extern void unclaim_dma_channels(void); +extern volatile uint32_t cycle_count_word; /* pio.c */ extern void setup_piobus(void); @@ -397,3 +398,38 @@ void restart_bus(void) CFG("[RESTART BUS END]\n"); return; } + +/** + * @brief Returns the amount of C64 cpu clock + * cycles counted by the counter SM and updated + * by a continous running DMA channel + * + * @note rp2350 uses a single DMA channel and native endless transfer + * @note rp2040 uses a two chained DMA channels for endless transfer + * + * @returns uint32_t */ +uint32_t clockcycles(void) +{ + return (uint32_t)cycle_count_word; +} + +/** + * @brief Delay for n PHI1 clockcycles + * Will do a cycled delay with cycle counter + * @note rp2350 uses a single DMA channel and native endless transfer + * @note rp2040 uses a two chained DMA channels for endless transfer + * + * NOTICE: Will crap out if delay cycles wrap around __UINT32_MAX__ after ~71 minutes + * + * @param uint32_t n_cycles + */ +void clockcycle_delay(uint32_t n_cycles) +{ /* */ + if __us_unlikely(n_cycles == 0) return; + int32_t now, end; + now = end = clockcycles(); + do { + end = clockcycles(); + } while (end < (now + n_cycles)); + return; +} diff --git a/src/dma.c b/src/dma.c index 5a58963..df1002e 100644 --- a/src/dma.c +++ b/src/dma.c @@ -31,14 +31,17 @@ #include "sid.h" -/* imports */ +/* pio.c */ extern PIO bus_pio, clkcnt_pio; extern uint sm_control, sm_data, sm_clock, sm_delay, sm_clkcnt; -/* locals */ +/* DMA */ int dma_tx_control, dma_tx_data, dma_rx_data, dma_tx_delay; -int dma_clkcnt_control; -volatile uint32_t clkcnt_word; +int dma_counter; +#if PICO_RP2040 +int dma_counter_chain; +#endif +volatile uint32_t cycle_count_word; /* Shiny things */ #if defined(PICO_DEFAULT_LED_PIN) @@ -65,7 +68,10 @@ void setup_dmachannels(void) dma_tx_data = dma_claim_unused_channel(true); dma_rx_data = dma_claim_unused_channel(true); dma_tx_delay = dma_claim_unused_channel(true); - dma_clkcnt_control = dma_claim_unused_channel(true); + dma_counter = dma_claim_unused_channel(true); +#if PICO_RP2040 + dma_counter_chain = dma_claim_unused_channel(true); +#endif { /* dma controlbus */ dma_channel_config tx_config_control = dma_channel_get_default_config(dma_tx_control); @@ -108,31 +114,70 @@ void setup_dmachannels(void) //#endif dma_channel_configure(dma_tx_delay, &tx_config_delay, &bus_pio->txf[sm_delay], NULL, 1, false); } - #if PICO_RP2350 /* NOTE: endless transfer not supported on rp2040 */ +#if PICO_RP2350 /* RP2350 has native endless transfer support */ { /* dma rx clock counter ~ should be an endless updating item */ - dma_channel_config clkcnt_config_data = dma_channel_get_default_config(dma_clkcnt_control); + dma_channel_config clkcnt_config_data = dma_channel_get_default_config(dma_counter); channel_config_set_transfer_data_size(&clkcnt_config_data, DMA_SIZE_32); channel_config_set_read_increment(&clkcnt_config_data, false); channel_config_set_write_increment(&clkcnt_config_data, false); channel_config_set_dreq(&clkcnt_config_data, DREQ_PIO1_RX3); - // channel_config_set_irq_ignore(&dma_clkcnt_control, false); - // dma_channel_configure(dma_clkcnt_control, &clkcnt_config_data, &clkcnt_word, &bus_pio->rxf[sm_clkcnt], 1, false); - clkcnt_word = 0; + // channel_config_set_irq_ignore(&dma_counter, false); + // dma_channel_configure(dma_counter, &clkcnt_config_data, &cycle_count_word, &bus_pio->rxf[sm_clkcnt], 1, false); + cycle_count_word = 0; dma_channel_configure( - dma_clkcnt_control, + dma_counter, &clkcnt_config_data, - &clkcnt_word, + &cycle_count_word, &clkcnt_pio->rxf[sm_clkcnt], - dma_encode_endless_transfer_count(), /* ENDLESS transfer count */ // BUG: Spinlocks the Pico - // dma_encode_transfer_count_with_self_trigger(1), /* Self triggered transfer count */ // BUG: Spinlocks the Pico + dma_encode_endless_transfer_count(), /* ENDLESS transfer count */ true /* Start transfers immediately */ - // false ); } - #endif +#else /* RP2040: ping-pong chain for continuous transfer */ + { + cycle_count_word = 0; + /* Channel A */ + dma_channel_config cfg_a = dma_channel_get_default_config(dma_counter); + channel_config_set_transfer_data_size(&cfg_a, DMA_SIZE_32); + channel_config_set_read_increment(&cfg_a, false); + channel_config_set_write_increment(&cfg_a, false); + channel_config_set_dreq(&cfg_a, DREQ_PIO1_RX3); + channel_config_set_chain_to(&cfg_a, dma_counter_chain); + dma_channel_configure( + dma_counter, + &cfg_a, + &cycle_count_word, + &clkcnt_pio->rxf[sm_clkcnt], + 1, + false + ); + /* Channel B — identical transfer, chains back to A */ + dma_channel_config cfg_b = dma_channel_get_default_config(dma_counter_chain); + channel_config_set_transfer_data_size(&cfg_b, DMA_SIZE_32); + channel_config_set_read_increment(&cfg_b, false); + channel_config_set_write_increment(&cfg_b, false); + channel_config_set_dreq(&cfg_b, DREQ_PIO1_RX3); + channel_config_set_chain_to(&cfg_b, dma_counter); + dma_channel_configure( + dma_counter_chain, + &cfg_b, + &cycle_count_word, + &clkcnt_pio->rxf[sm_clkcnt], + 1, + false); + + /* Start channel A — it will chain to B, B chains back, forever */ + dma_channel_start(dma_counter_chain); + } +#endif +#if PICO_RP2350 CFG("[DMA CHANNELS CLAIMED] C:%d TX:%d RX:%d D:%d CNT:%d\n", - dma_tx_control, dma_tx_data, dma_rx_data, dma_tx_delay, dma_clkcnt_control); + dma_tx_control, dma_tx_data, dma_rx_data, dma_tx_delay, dma_counter); +#else + CFG("[DMA CHANNELS CLAIMED] C:%d TX:%d RX:%d D:%d CNTA:%d CNTB:%d\n", + dma_tx_control, dma_tx_data, dma_rx_data, dma_tx_delay, dma_counter, dma_counter_chain); +#endif CFG("[DMA CHANNELS INIT] FINISHED\n"); return; @@ -164,39 +209,6 @@ void setup_vu_dma(void) #endif } -/** - * @brief returns the amount of C64 cpu clock - * cycles counted by the counter SM and updated - * by a continous running DMA channel - * - * @returns uint32_t */ -uint32_t clockcycles(void) -{ - #if PICO_RP2350 - return (uint32_t)clkcnt_word; - #else - return 0; /* Not supported on rp2040 */ - #endif -} - -/** - * @brief delay for n PHI1 clockcycles - * Will do a cycled delay if not rp2350 - * with cycle counter - * - * @param uint32_t n_cycles - */ -void clockcycle_delay(uint32_t n_cycles) -{ /* ISSUE: Will crap out if delay cycles wrap around __UINT32_MAX__ */ - if __us_unlikely(n_cycles == 0) return; - int32_t now, end; - now = end = clockcycles(); - do { - end = clockcycles(); - } while (end < (now + n_cycles)); - return; -} - void stop_dma_channels(void) { // TODO: Fix and finish per RP2040-E13 and RP2350-E5 // CFG("[STOP DMA CHANNELS]\n"); @@ -237,6 +249,6 @@ void unclaim_dma_channels(void) return; } -// dma_hw->abort = (1 << dma_clkcnt_control); +// dma_hw->abort = (1 << dma_counter); // while (dma_hw->abort) tight_loop_contents(); -// while (dma_hw->ch[dma_clkcnt_control].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents(); +// while (dma_hw->ch[dma_counter].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents(); From cfdb7bc735a2118e85ca6ec0a5b8bf774599574c Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:54:11 +0100 Subject: [PATCH 18/45] Update reset registers with simulated real world delay cycles --- src/sid.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sid.c b/src/sid.c index bd69fe4..e888599 100644 --- a/src/sid.c +++ b/src/sid.c @@ -239,6 +239,7 @@ void reset_sid(void) /** * @brief Clear SID register / reset registers + * 6 cycle delay for each write to simulate LDA (2) + STA (4) * @note https://csdb.dk/forums/?roomid=11&topicid=85713&showallposts=1 * @note thanks Wilfred for pointing this out! * @param int sidno @@ -246,13 +247,13 @@ void reset_sid(void) void clear_sid_registers(int sidno) { for (uint reg = 0; reg < count_of(sid_registers) - 4; reg++) { - cycled_write_operation(((sidno * 0x20) | sid_registers[reg]), 0xff, 0); + cycled_write_operation(((sidno * 0x20) | sid_registers[reg]), 0xff, 6); } for (uint reg = 0; reg < count_of(sid_registers) - 4; reg++) { - cycled_write_operation(((sidno * 0x20) | sid_registers[reg]), 0x08, 0); + cycled_write_operation(((sidno * 0x20) | sid_registers[reg]), 0x08, 6); } for (uint reg = 0; reg < count_of(sid_registers) - 4; reg++) { - cycled_write_operation(((sidno * 0x20) | sid_registers[reg]), 0x0, 0); + cycled_write_operation(((sidno * 0x20) | sid_registers[reg]), 0x0, 6); } memset(sid_memory, 0, (4 * 0x20)); return; From 57099b08911da7745d9814b5d55dd00bae18c445 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 14:54:31 +0100 Subject: [PATCH 19/45] Housekeeping --- src/midi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/midi.c b/src/midi.c index fd2c090..80f4110 100644 --- a/src/midi.c +++ b/src/midi.c @@ -44,7 +44,7 @@ const midi_ccvalues midi_ccvalues_defaults = MIDI_DEFAULT_CCVALUES_INIT; /* GPIO */ -extern uint8_t __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint8_t data, uint16_t cycles); +extern uint8_t cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); extern void reset_sid(void); /* Midi handler */ From d8bcce75f6094ea537622681fab0a2a1fb2a723f Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 16:02:51 +0100 Subject: [PATCH 20/45] Re introduce an ASID buffer (uses PIO and IRQ) for `write_ordered` ASID messages from SIDFactoryII for example. --- CMakeLists.txt | 1 + src/asid.c | 32 +++-- src/asid_buffer.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 328 insertions(+), 11 deletions(-) create mode 100644 src/asid_buffer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7acf2c9..9a93710 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,7 @@ set(SOURCEFILES ${CMAKE_CURRENT_LIST_DIR}/src/midi.c ${CMAKE_CURRENT_LIST_DIR}/src/midi_handler.c ${CMAKE_CURRENT_LIST_DIR}/src/asid.c + ${CMAKE_CURRENT_LIST_DIR}/src/asid_buffer.c ${CMAKE_CURRENT_LIST_DIR}/src/sysex.c ${CMAKE_CURRENT_LIST_DIR}/src/sid.c ${CMAKE_CURRENT_LIST_DIR}/src/sid_cloneconfig.c diff --git a/src/asid.c b/src/asid.c index 80c965e..901a12f 100644 --- a/src/asid.c +++ b/src/asid.c @@ -57,6 +57,7 @@ extern uint16_t vu; /* Some locals, rural and such */ static bool default_order = false; +static bool default_order_on_start = false; bool write_ordered = false; struct asid_regpair_t { /* thanks to thomasj */ uint8_t index; @@ -68,18 +69,26 @@ struct asid_regpair_t asid_to_writeorder[NO_SID_REGISTERS_ASID] = {}; /* thanks void reset_asid_to_writeorder(void) { DBG("[ASID] RESET WRITEORDER REGISTERS\n"); + if (write_ordered) { + /* inline extern as only used here */ + extern void asid_ring_deinit(void); + extern void stop_buffer_pio(void); + asid_ring_deinit(); + stop_buffer_pio(); + } write_ordered = false; for (int i = 0; i < NO_SID_REGISTERS_ASID; i++) { asid_to_writeorder[i].index = i; asid_to_writeorder[i].wait_us = 0; } - default_order = (default_order == false ? true : default_order); + default_order = true; + default_order_on_start = (default_order == false ? true : default_order); } void asid_init(void) { DBG("[%s]\n", __func__); - if (!default_order) reset_asid_to_writeorder(); /* Set defaults once on first write */ + if (!default_order_on_start) reset_asid_to_writeorder(); /* Set defaults once on boot */ } /* Pling, plong, ploink!? */ @@ -199,7 +208,6 @@ void handle_writeordered_asid_message(uint8_t sid, uint8_t* buffer) } writeOrder[chip][asid_to_writeorder[reg].index].reg = asid_sid_registers[mask * 7 + bit]; writeOrder[chip][asid_to_writeorder[reg].index].data = register_value; - // TODO: FIX Pico2 writes /* Pico 2 requires at least 10 cycles between writes * or it will be too damn fast! So if wait_us is lower then 10 we use 10 cycles * and do this for other Pico's aswell */ @@ -210,14 +218,15 @@ void handle_writeordered_asid_message(uint8_t sid, uint8_t* buffer) } } } - + /* inline extern as only used here */ + extern void asid_ring_write(uint8_t reg, uint8_t val, uint16_t c); for (size_t pos = 0; pos < NO_SID_REGISTERS_ASID; pos++) { if (writeOrder[chip][pos].wait_us != 0xff) { - /* Perform write including wait cycles */ - cycled_write_operation((writeOrder[chip][pos].reg |= sid), writeOrder[chip][pos].data, writeOrder[chip][pos].wait_us); - + /* Push data to ASID ringbuffer */ + asid_ring_write((writeOrder[chip][pos].reg |= sid), writeOrder[chip][pos].data, writeOrder[chip][pos].wait_us); WRITEDBG(dtype, pos, NO_SID_REGISTERS_ASID, (writeOrder[chip][pos].reg |= sid), writeOrder[chip][pos].data, writeOrder[chip][pos].wait_us); - /* DBG("[ASID2 %d] $%02X:%02X %u\n", pos, (writeOrder[chip][pos].reg |= sid), writeOrder[chip][pos].data, writeOrder[chip][pos].wait_us); */ + } else { + asid_ring_write(0xffu,0xffu,0xffffu); } } for (size_t pos = 0; pos < NO_SID_REGISTERS_ASID; pos++) { @@ -238,9 +247,10 @@ void handle_asid_writeorder_config(uint8_t* buffer) asid_to_writeorder[i].wait_us = cycles; CFG("[ASID WRITE ORDER %d] {%02u,%02u}\n", i, asid_to_writeorder[i].index, asid_to_writeorder[i].wait_us); } + default_order = false; } -void handle_asid_envmessage(uint8_t* buffer) /* TODO: Update clock settings on the fly when env differs from config */ +void handle_asid_envmessage(uint8_t* buffer) { /* SID environment is only logged and not used for now */ /* Incoming buffer skips first 3 bytes and starts at SETTINGS @@ -272,7 +282,7 @@ void handle_asid_envmessage(uint8_t* buffer) /* TODO: Update clock settings on t bits1-0: framedelta uS (MSB) bits6-2: 5 bits (reserved) */ - uint16_t framedelta_us = (buffer[1] & 0x7F) | (buffer[2] & 0x7F) << 7 | (buffer[3] & 0x03) << 14; + uint16_t framedelta_us = ((buffer[1] & 0x7F) | (buffer[2] & 0x7F) << 7 | ((buffer[3] & 0x03) << 14)); DBG("[ASID] Framedelta: %d\n", framedelta_us); } @@ -315,7 +325,7 @@ void decode_asid_message(uint8_t* buffer, int size) case 0x4F: /* Display characters */ break; case 0x4E: /* SID 1 */ - if (!default_order) reset_asid_to_writeorder(); /* Set defaults once on first write */ + if (!default_order_on_start) reset_asid_to_writeorder(); /* Set defaults once on first write of not set yet */ if (!write_ordered) handle_asid_message(0, &buffer[3]); else handle_writeordered_asid_message(0, &buffer[3]); break; diff --git a/src/asid_buffer.c b/src/asid_buffer.c new file mode 100644 index 0000000..6428c70 --- /dev/null +++ b/src/asid_buffer.c @@ -0,0 +1,306 @@ +/* + * USBSID-Pico is a RPi Pico/PicoW (RP2040) & Pico2/Pico2W (RP2350) based board + * for interfacing one or two MOS SID chips and/or hardware SID emulators over + * (WEB)USB with your computer, phone or ASID supporting player + * + * asid_buffer.c + * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) + * File author: LouD + * + * Huge thanks to Thomas Jansson for all his ASID improvement work (https://github.com/thomasj) + * + * The contents of this file are based upon and heavily inspired by the sourcecode from + * TherapSID by Twisted Electrons: https://github.com/twistedelectrons/TherapSID + * TeensyROM by Sensorium Embedded: https://github.com/SensoriumEmbedded/TeensyROM + * SID Factory II by Chordian: https://github.com/Chordian/sidfactory2 + * + * Any licensing conditions from either of the above named sources automatically + * apply to this code + * + * Copyright (c) 2024-2026 LouD + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include "globals.h" +#include "config.h" +#include "pio.h" +#include "gpio.h" +#include "logging.h" +#include "asid.h" +#include "sid.h" + + +/* config.c */ +extern Config usbsid_config; + +/* PIO */ +PIO raster_pio = pio1; +static uint sm_buffer, offset_buffer; +bool buffer_sm_started = false; +bool buffer_sm_claimed = false; + +/* IRQ */ +int pio_irq; +static int8_t buffer_irq; +const int BUFFPIOIRQ = 2; +bool buffer_irq_started = false; + +/* Ring buffer */ +static uint8_t __not_in_flash_func(ring_get)(void); +static int __not_in_flash_func(ring_diff)(void); +uint8_t diff_size = 64; +uint16_t ring_size = 8192; +typedef struct { + uint16_t ring_read; + uint16_t ring_write; + bool is_allocated; + uint8_t * __restrict__ ringbuffer; +} ring_buffer_t; +static ring_buffer_t asid_ringbuffer; + +/** + * @brief buffer interrupt handler + * @note writes data from the ringbuffer to the SIDs + */ +void __not_in_flash_func(buffer_irq_handler)(void) +{ + pio_interrupt_clear(raster_pio, BUFFPIOIRQ); + + /* inline extern as only used here */ + extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); + + if(asid_ringbuffer.ring_read != asid_ringbuffer.ring_write) { + if (ring_diff() > diff_size) { + for (size_t pos = 0; pos < 28; pos++) { + uint8_t reg = ring_get(); + uint8_t val = ring_get(); + uint8_t c_hi = ring_get(); + uint8_t c_lo = ring_get(); + if (reg != 0xffu) { + cycled_write_operation(reg,val,(c_hi<<8|c_lo)); + } + } + } + } + + /* Interrupt cleared at end of routine + * if play becomes irregular, irq's might be + * misfiring and moving the clearing to the + * beginning of the routine might help + */ + /* pio_interrupt_clear(raster_pio, BUFFPIOIRQ); */ + return; +} + +/** + * @brief initialize the buffer irq + * @note sets the interrupt number and handler + * @note sets the irq source and enables the irq + */ +void init_buffer_irq(void) +{ + if (!buffer_irq_started) { + DBG("[ASID] Enabling buffer IRQ\n"); + buffer_irq = pis_interrupt2; /* PIO_INTR_SM2_LSB hardware/pio.h */ + pio_irq = pio_get_irq_num(raster_pio, 0); + irq_set_exclusive_handler(pio_irq, buffer_irq_handler); + pio_set_irq0_source_enabled(raster_pio, buffer_irq + sm_buffer, true); + irq_set_enabled(pio_irq, true); + buffer_irq_started = true; + } + return; +} + +/** + * @brief initializes the raster_pio + * @note also claims the pio statemachine + */ +void init_buffer_pio(void) +{ /* buffer cycle counter */ + sm_buffer = 0; /* PIO1 SM0 */ + if (!buffer_sm_claimed) { + DBG("[ASID] Starting & claiming buffer pio\n"); + pio_sm_claim(raster_pio, sm_buffer); + offset_buffer = pio_add_program(raster_pio, &raster_buffer_program); + pio_sm_config c_buffer = raster_buffer_program_get_default_config(offset_buffer); + sm_config_set_fifo_join(&c_buffer, PIO_FIFO_JOIN_TX); + pio_sm_init(raster_pio, sm_buffer, offset_buffer, &c_buffer); + buffer_sm_claimed = true; + } + return; +} + +/** + * @brief enable the raster_pio statemachine + */ +void start_buffer_pio(void) +{ + if (!buffer_sm_started) { + DBG("[ASID] Enabling buffer statemachine\n"); + pio_sm_set_enabled(raster_pio, sm_buffer, true); + buffer_sm_started = true; + } + return; +} + +/** + * @brief stop the asid buffer handling + * @note frees the irq and stops the irq_handler + * @note disables the raster_pio statemachine + * @note unclaims the statemadhine on the pio + */ +void stop_buffer_pio(void) +{ + if (buffer_irq_started) { + irq_set_enabled(pio_irq, false); + pio_set_irq0_source_enabled(raster_pio, buffer_irq + sm_buffer, false); + irq_remove_handler(pio_irq, buffer_irq_handler); + buffer_irq_started = false; + DBG("[ASID] Buffer IRQ released\n"); + } + if (buffer_sm_started) { + pio_sm_set_enabled(raster_pio, sm_buffer, false); + buffer_sm_started = false; + DBG("[ASID] Buffer statemachine disabled\n"); + } + if (buffer_sm_claimed) { + pio_remove_program(raster_pio, &raster_buffer_program, offset_buffer); + pio_sm_unclaim(raster_pio, sm_buffer); + buffer_sm_claimed = false; + DBG("[ASID] Buffer statemachine released\n"); + } + return; +} + +/** + * @brief sets the asid buffer raster rate + * @note the raster_rate pio has a 1 cycle overhead + * @note this requires the rate to be corrected with + * @note subtraction of a single cycle. + * @note the function does this if the provided + * @note rate is higher than zero. + * + * @param uint16_t rate + */ +void set_buffer_rate(uint16_t rate) +{ + uint16_t corrected_rate = ((rate > 0) ? (rate - 1) : (usbsid_config.raster_rate - 1)); + DBG("[ASID] Rate received %u Rate set %u\n", rate, corrected_rate); + if (!buffer_irq_started) init_buffer_irq(); + pio_sm_put(raster_pio, sm_buffer, corrected_rate); + start_buffer_pio(); + return; +} + + + +/** + * @brief reset the head and tail to zero + */ +static void ring_buffer_reset(void) +{ + asid_ringbuffer.ring_read = asid_ringbuffer.ring_write = 0; + return; +} + +/** + * @brief returns true if the head is + * higher then the tail + */ +static bool __not_in_flash_func(ring_is_higher)(void) +{ + return (asid_ringbuffer.ring_read < asid_ringbuffer.ring_write); +} + +/** + * @brief returns the difference between head and tail + */ +static int __not_in_flash_func(ring_diff)(void) +{ + int d = (ring_is_higher() ? (asid_ringbuffer.ring_read - asid_ringbuffer.ring_write) : (asid_ringbuffer.ring_write - asid_ringbuffer.ring_read)); + return ((d < 0) ? (d * -1) : d); +} + +/** + * @brief put an item into the ringbuffer + * + * @param uint8_t item + */ +static void ring_put(uint8_t item) +{ + asid_ringbuffer.ringbuffer[asid_ringbuffer.ring_write] = item; + asid_ringbuffer.ring_write = (asid_ringbuffer.ring_write + 1) % ring_size; + return; +} + +/** + * @brief retrieves an item from the ring buffer + */ +static uint8_t __not_in_flash_func(ring_get)(void) +{ + uint8_t item = asid_ringbuffer.ringbuffer[asid_ringbuffer.ring_read]; + asid_ringbuffer.ring_read = (asid_ringbuffer.ring_read + 1) % ring_size; + return item; +} + +/** + * @brief intialize the ring buffer + */ +void asid_ring_init(void) +{ + if (!asid_ringbuffer.is_allocated) { + if (asid_ringbuffer.ringbuffer != NULL) { free(asid_ringbuffer.ringbuffer); } + asid_ringbuffer.ringbuffer = (uint8_t*)calloc(ring_size, 1); + asid_ringbuffer.is_allocated = true; + ring_buffer_reset(); + DBG("[ASID] Ringbuffer initialized\n"); + } + return; +} + +/** + * @brief de-intialize the ring buffer + */ +void asid_ring_deinit(void) +{ + if (asid_ringbuffer.is_allocated) { + ring_buffer_reset(); + free(asid_ringbuffer.ringbuffer); + asid_ringbuffer.ringbuffer = NULL; + asid_ringbuffer.is_allocated = false; + } + return; +} + +/** + * @brief write data to the ringbuffer + * @note writes 4 items per call + * + * @param uint8_t reg ~ SID register + * @param uint8_t val ~ value + * @param uint16_t c ~ delay cycles + */ +void asid_ring_write(uint8_t reg, uint8_t val, uint16_t c) +{ + ring_put(reg); + ring_put(val); + ring_put((uint8_t)((c&0xff00u)>>8)); + ring_put((uint8_t)(c&0xffu)); + return; +} From 3c4003f9a28ed4cb2b06fbf2affeff7eb8b42e43 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 16:03:50 +0100 Subject: [PATCH 21/45] Add `set_asid_env` for ASID to change the USBSID clockrate and enable the ASID (ring)buffer --- src/asid.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/asid.c b/src/asid.c index 901a12f..259a9e1 100644 --- a/src/asid.c +++ b/src/asid.c @@ -250,6 +250,39 @@ void handle_asid_writeorder_config(uint8_t* buffer) default_order = false; } +inline void set_asid_env(int refresh_rate, int speed_multiplier, int custom_speed, int buffering, uint16_t framedelta_us) +{ + (void)speed_multiplier; /* Not used */ + (void)custom_speed; /* Not used */ + (void)buffering; /* Not used */ + + { /* apply the new clock_rate based on the refresh_rate if applicable */ + extern void apply_clockrate(int n_clock, bool suspend_sids); + apply_clockrate( + ((refresh_rate == 0) + ? 1 /* PAL */ + : 2), /* NTSC */ + true); + } + + if (write_ordered) { + CFG("[ASID] Init buffer queue, timer and irq\n"); + extern Config usbsid_config; + extern void init_buffer_pio(void); + extern void set_buffer_rate(uint16_t rate); + extern void asid_ring_init(void); + + asid_ring_init(); + init_buffer_pio(); + if (framedelta_us != 0) { /* Above zero means always use framedelta_us */ + set_buffer_rate(framedelta_us); + } else { + set_buffer_rate(usbsid_config.refresh_rate); + } + } + return; +} + void handle_asid_envmessage(uint8_t* buffer) { /* SID environment is only logged and not used for now */ /* Incoming buffer skips first 3 bytes and @@ -284,6 +317,7 @@ void handle_asid_envmessage(uint8_t* buffer) */ uint16_t framedelta_us = ((buffer[1] & 0x7F) | (buffer[2] & 0x7F) << 7 | ((buffer[3] & 0x03) << 14)); DBG("[ASID] Framedelta: %d\n", framedelta_us); + set_asid_env(refresh_rate, speed_multiplier, custom_speed, buffering, framedelta_us); } void handle_asid_typemessage(uint8_t* buffer) From b84b5f57df95aebbe57d37961d4d2cceee1d94a3 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 16:07:30 +0100 Subject: [PATCH 22/45] Add config option for single SID in socket two --- src/config.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index a0661fc..6c541c3 100644 --- a/src/config.c +++ b/src/config.c @@ -653,8 +653,13 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case SINGLE_SID: - CFG("[CMD] SINGLE_SID\n"); - set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, false, false, usbsid_config.socketTwo.chiptype, false); + int single_socket = ((buffer[2] == 1) ? 2 : 0); + CFG("[CMD] SINGLE_SID SOCKET %d\n", single_socket); + if (single_socket == 2) { + set_socket_config(buffer[1], false, false, usbsid_config.socketOne.chiptype, true, false, usbsid_config.socketTwo.chiptype, false); + } else { + set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, false, false, usbsid_config.socketTwo.chiptype, false); + } break; case MIRRORED_SID: CFG("[CMD] MIRRORED_SID\n"); From c768d6873866a64fede292706e233d51643b6bac Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 16:09:47 +0100 Subject: [PATCH 23/45] Add single SID socket 2 config option --- examples/config-tool/cfg_usbsid | Bin 96728 -> 96728 bytes examples/config-tool/cfg_usbsid.c | 8 +++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/config-tool/cfg_usbsid b/examples/config-tool/cfg_usbsid index 1daf72155f4b56200ba28a7c087fd9a0e34b035a..fb69c88d97af3c24a78f9fd9402b025594fd6dc5 100755 GIT binary patch delta 12208 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 delta 12171 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 diff --git a/examples/config-tool/cfg_usbsid.c b/examples/config-tool/cfg_usbsid.c index eb89630..f725c47 100644 --- a/examples/config-tool/cfg_usbsid.c +++ b/examples/config-tool/cfg_usbsid.c @@ -1440,6 +1440,7 @@ void print_help(void) printf("--[PRESETS]---------------------------------------------------------------------------------------------------------\n"); printf(" (add '-q' before any of the preset commands for a quick change and apply the config without saving and rebooting)\n"); printf(" -single, --single-sid : Socket 1 enabled @ single SID, Socket 2 disabled\n"); + printf(" -single2, --single-sid-s2 : Socket 1 disabled, Socket 2 enabled @ single SID\n"); printf(" -dual, --dual-sid : Socket 1 enabled @ single SID, Socket 2 enabled @ single SID\n"); printf(" -duals1, --dual-sid-socket1 : Socket 1 enabled @ dual SID, Socket 2 disabled\n"); printf(" -duals2, --dual-sid-socket2 : Socket 1 disabled, Socket 2 enabled @ dual SID\n"); @@ -1585,10 +1586,15 @@ void config_usbsidpico(int argc, char **argv) quickchange = 1; }; if (!strcmp(argv[param_count], "-single") || !strcmp(argv[param_count], "--single-sid")) { - printf("Set USBSID-Pico config to single SID\n"); + printf("Set USBSID-Pico config to single SID @ Socket One\n"); write_config_command(SINGLE_SID, quickchange, 0, 0, 0); goto exit; } + if (!strcmp(argv[param_count], "-single2") || !strcmp(argv[param_count], "--single-sid-s2")) { + printf("Set USBSID-Pico config to single SID @ Socket Two\n"); + write_config_command(SINGLE_SID, quickchange, 1, 0, 0); + goto exit; + } if (!strcmp(argv[param_count], "-dual") || !strcmp(argv[param_count], "--dual-sid")) { printf("Set USBSID-Pico config to dual SID\n"); write_config_command(DUAL_SID, quickchange, 0 ,0 ,0); From 78f05ea7059591be0bff29ffe0a90adbb2fda48b Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 16:13:49 +0100 Subject: [PATCH 24/45] Fix concurrency groups --- .github/workflows/build.yml | 2 +- .github/workflows/buildtools.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56961a3..831dc92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ on: workflow_dispatch: concurrency: - group: Dev build + group: dev-firmware-build cancel-in-progress: true jobs: diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index 1a7aae5..7ad89a7 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -22,7 +22,7 @@ on: default: false concurrency: - group: Dev build + group: dev-tools-build cancel-in-progress: true jobs: From e84996efd9d4134609ea665e1e6d908c70ab7ce4 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 4 Feb 2026 16:14:36 +0100 Subject: [PATCH 25/45] Add shortcuts to command line arguments --- examples/send_sid/send_sid | Bin 22200 -> 22200 bytes examples/send_sid/send_sid.c | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/send_sid/send_sid b/examples/send_sid/send_sid index 950eb24441fb8e68e00a7f9a5515ca406ce91558..803bbf7d983bba9c3a7a449d7a7bb673589aab91 100755 GIT binary patch delta 1117 zcmZWpeJoXB7=O=kuX{7!E|+{=`M6=M7MGQ+B}XTHjHs#khh?&%8<$jT7}|8%wD*rn zS&61UC_4XGnW)y))*rN`R+J^V2qiXa<>@)+;!^MSKEJ)s@A>#W=e&En+L^9)rbEft zb*hQ9I@Q#biKIDcF7$hvLP%#cjH^s%;jBn6v$LwRBX=7MCwIg?_O6&{J`(}AjZbQY zS{rvCc2Yr`{Tnq&3PQ|e5um!)sX71;Ca#ykQj=gyh>`21djeI|pc!DpZE0daF54>? zIs;l+g@es1t)?YGuCf!BST$`9a*-P%R!)4IGa-D<5MN_5Z=V&L6vy&L`LS@(+xN&7 z8}*3Y_&Rx&6lpT=oCA2lEqvzdxSu_8k;B|&njGxwkzpa776a_BuqG2x?Mn}C}C`agx!*ar&5SCkHbF_Zdj?&Z<4xI2~kB>N{}c? z!n@G_B^+3h&^<$&g5B8id72XJ;}K%bKWr(zHaIwnMI=E18@rahwNeYyYv0~$rK{Jv z!5>JGCi5SxfP;^37`*QXKqX!Z{H43NHmBOfC4hPp*NypPnyEL3AkSA`>?a0$-pKWf zziWI7!05?!^Twk~C;4zy@K%g9$j!^>fB8(v3&~JE+*U4UifCE1w?dy;T$D}p^m?>! zXhC+VffSJByv%dC1vwh=s3|JWG87kN+mQ%;VRi|TOcouC)*knH4{#Gp#|;BKM?8o) zjF|BepbC-u1h9bE@EO1p@0t4;KrG@N#B{_t#8SkbF96pNE4~7BA%@Wuoz`n$1Rw;n zNrD2e5FtUhqi~nfE6D0U07!0;!llA(zeS4BI;jg}&QpgNf94I1KpRgFp*>CW z(fZLkw0*Qa#$S~*0Z^jEM9$Iq61&&I)+S#mjK*}x=)E|eZP5zzarfoSZ`u^UkCo|! zsdybDdbcDDv22M__?75q&nCFh&8d-gEm|ytt3^wXq$aX5TiSr$buYr}RNW^1%0rni>4H_g+jIXyZK?LvsIsr( zJl`3Wv7%JL(q9FweDLoMs$xoSU5%uR-G%k21!&tN+w4dYNtbHKt+wy2G&`HD_6AL(&D>gNK66&i!<2g$+S=MQ5i#}3%53p5plum%wgU*Rtex~#?=;<$%Zh#KYQ@RuW zZvFzLM$i$^8c^9Hr7fVdptnKqEK%wKEhARF7765rUWK~JV;Em0@AaybwojDCV*rLs z1KJ6nnIJ2Ww#Nm5M(a$63N gz-&3>gsB|+CFCw-BOca_NgrFI4;Iz%2~~agKa;~ Date: Thu, 5 Feb 2026 09:31:01 +0100 Subject: [PATCH 26/45] Fix possible overflow issue --- src/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bus.c b/src/bus.c index 5423e67..73162f4 100644 --- a/src/bus.c +++ b/src/bus.c @@ -430,6 +430,6 @@ void clockcycle_delay(uint32_t n_cycles) now = end = clockcycles(); do { end = clockcycles(); - } while (end < (now + n_cycles)); + } while ((uint32_t)(end - now) < n_cycles); return; } From ebf4ea506456e10403eddf756d697e4b684beeee Mon Sep 17 00:00:00 2001 From: loudnl Date: Thu, 5 Feb 2026 09:31:35 +0100 Subject: [PATCH 27/45] Housekeeping --- src/config.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/config.h b/src/config.h index fd91983..881042c 100644 --- a/src/config.h +++ b/src/config.h @@ -110,18 +110,18 @@ extern uint32_t ADDR_PERSISTENT[]; /* USBSID-Pico config struct */ typedef struct SIDChip { - uint8_t id; /* ID for this SID 0 ~ 4, 255 = disabled */ - uint8_t addr; /* Starting address to use for this SID, 255 = disabled */ - uint8_t type; /* 0 = unknown, 1 = N/A, 2 = MOS8580, 3 = MOS6581, 4 = FMopl */ + uint8_t id; /* ID for this SID 0 ~ 4, 255 = disabled */ + uint8_t addr; /* Starting address to use for this SID, 255 = disabled */ + uint8_t type; /* 0 = unknown, 1 = N/A, 2 = MOS8580, 3 = MOS6581, 4 = FMopl */ } SIDChip; typedef struct Socket { - uint8_t chiptype; /* 0 = real, 1 = clone, 2 = unknown */ - uint8_t clonetype; /* 0 = disabled, 1 = other, 2 = SKPico, 3 = ARMSID, 4 = FPGASID, 5 = RedipSID */ + uint8_t chiptype; /* 0 = real, 1 = clone, 2 = unknown */ + uint8_t clonetype; /* 0 = disabled, 1 = other, 2 = SKPico, 3 = ARMSID, 4 = FPGASID, 5 = RedipSID */ SIDChip sid1; SIDChip sid2; - bool enabled : 1; /* enable / disable this socket */ - bool dualsid : 1; /* enable / disable dual SID support for this socket (requires clone) */ + bool enabled : 1; /* enable / disable this socket */ + bool dualsid : 1; /* enable / disable dual SID support for this socket (requires clone) */ } Socket; typedef struct Config { @@ -155,7 +155,6 @@ typedef struct Config { bool enabled : 1; } Asid; /* 7 */ struct { - // uint8_t sid_states[4][32]; /* Stores states of each SID ~ 4 sids max */ bool enabled : 1; } Midi; /* 8 */ struct { @@ -166,8 +165,7 @@ typedef struct Config { bool lock_clockrate : 1; /* lock the set clockspeed from being changed */ bool stereo_en : 1; /* audio switch is off (mono) or on (stereo) ~ (PCB v1.3+ only) */ bool lock_audio_sw : 1; /* lock the audio switch into it's current stateand prevent it from being changed ~ (PCB v1.3+ only) */ - bool mirrored : 1; /* act as socket 1 */ - bool fauxstereo : 1; /* faux stereo effect */ + bool mirrored : 1; /* act as socket 1 */ } Config; #define USBSID_DEFAULT_CONFIG_INIT { \ From 54c7b3bb72c9d02478080b7f85e89e7796db5d88 Mon Sep 17 00:00:00 2001 From: loudnl Date: Thu, 5 Feb 2026 09:32:02 +0100 Subject: [PATCH 28/45] Housekeeping --- src/config.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config.h b/src/config.h index 881042c..7553970 100644 --- a/src/config.h +++ b/src/config.h @@ -238,7 +238,6 @@ typedef struct Config { .stereo_en = false, \ .lock_audio_sw = false, \ .mirrored = false, \ - .fauxstereo = false, \ } \ static const Config usbsid_default_config = USBSID_DEFAULT_CONFIG_INIT; From c1855fbbf58f1ad838c9cc460abaac0c025edb10 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 17:47:14 +0100 Subject: [PATCH 29/45] Logging overhaul --- CMakeLists.txt | 18 ++- src/config.c | 286 +++++++++++++++++++++--------------------- src/config_bus.c | 10 +- src/config_logging.c | 139 ++++++++++---------- src/config_socket.c | 16 +-- src/dma.c | 12 +- src/gpio.c | 14 +-- src/logging.h | 59 ++++++--- src/mcu.c | 2 +- src/midi.c | 18 +-- src/midi_handler.c | 8 +- src/pio.c | 26 ++-- src/sid.c | 14 +-- src/sid_cloneconfig.c | 99 ++++++++------- src/sid_detection.c | 69 +++++----- src/sid_tests.c | 60 ++++----- src/uart.c | 6 +- src/usbsid.c | 102 +++++++-------- src/vu.c | 2 +- 19 files changed, 496 insertions(+), 464 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a93710..eaf90c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,12 +40,15 @@ if(NOT DEFINED $ENV{DISABLE_DEBUGGING}) # MATCHES set(USBSID_DEBUGGING 1) # Enable debugging ~ mandatory enable for all other logging types set(UART_DEBUGGING 1) # Enable debugging over uart pins ~ USBCDC must be 0! set(USBCDC_DEBUGGING 0) # Enable debugging over cdc port 2 ~ UART must be 0! + set(FILENAME_LOGGING 0) # Enable logging of source filename + set(BOOT_LOGGING 0) # Enable bootlog in usbsid.c set(DEFAULT_DEBUGGING 1) # Enable debugging in usbsid.c set(CONFIG_DEBUGGING 1) # Enable debugging in config.c set(USBIO_DEBUGGING 0) # Enable debugging in usbsid.c set(GPIOBUS_DEBUGGING 0) # Enable debugging in gpio.c set(MIDI_DEBUGGING 0) # Enable debugging in midi.c set(MIDIVOICE_DEBUGGING 0) # Enable debugging in midi.c + set(ASID_DEBUGGING 0) # Enable debugging in acid.c and asid_buffer.c set(MEMORY_LOGGING 0) # Enable memory map of SID 1 voices printing set(SIDWRITES_DEBUGGING 0) # Enable logging of SID writes one core 2 set(EMULATOR_DEBUGGING 0) # Enable debugging in emulator @@ -549,15 +552,21 @@ if(USBSID_DEBUGGING EQUAL 1) if(DEFAULT_DEBUGGING EQUAL 1) add_compile_definitions(USBSID_DEBUG=1) endif() + if(FILENAME_LOGGING EQUAL 1) + add_compile_definitions(LOG_FILENAME=1) + endif() + if(BOOT_LOGGING EQUAL 1) + add_compile_definitions(USBSID_BOOTLOG=1) + endif() + if(CONFIG_DEBUGGING EQUAL 1) + add_compile_definitions(CONFIG_DEBUG=1) + endif() if(MEMORY_LOGGING EQUAL 1) add_compile_definitions(MEM_DEBUG=1) endif() if(SIDWRITES_DEBUGGING EQUAL 1) add_compile_definitions(WRITE_DEBUG=1) endif() - if(CONFIG_DEBUGGING EQUAL 1) - add_compile_definitions(CONFIG_DEBUG=1) - endif() if(USBIO_DEBUGGING EQUAL 1) add_compile_definitions(USBIO_DEBUG=1) endif() @@ -567,6 +576,9 @@ if(USBSID_DEBUGGING EQUAL 1) if(MIDIVOICE_DEBUGGING EQUAL 1) add_compile_definitions(MIDIVOICE_DEBUG=1) endif() + if(ASID_DEBUGGING EQUAL 1) + add_compile_definitions(ASID_DEBUG=1) + endif() if(GPIOBUS_DEBUGGING EQUAL 1) add_compile_definitions(USBSIDGPIO_DEBUG=1) endif() diff --git a/src/config.c b/src/config.c index 6c541c3..6761d4e 100644 --- a/src/config.c +++ b/src/config.c @@ -188,7 +188,7 @@ void read_config(Config* config) { memset(config_array, 0, sizeof config_array); /* Make sure we don't send garbled old data */ - CFG("[CONFIG] [READ CONFIG] [FROM]0x%X [TO]0x%X [SIZE]%u\n", (uint)config, &config_array, sizeof(Config)); + usCFG("[READ CONFIG] [FROM]0x%X [TO]0x%X [SIZE]%u\n", (uint)config, &config_array, sizeof(Config)); config_array[0] = READ_CONFIG; /* Initiator byte */ config_array[1] = 0x7F; /* Verification byte */ @@ -278,42 +278,41 @@ void __no_inline_not_in_flash_func(default_config)(Config* config) void __no_inline_not_in_flash_func(load_config)(Config* config) { print_cfg_addr(); - CFG("\n"); - CFG("[START LOAD CONFIG]\n"); + usCFG("[START LOAD CONFIG]\n"); int savelocationid = 0; /* counter for finding the current save location */ AGAIN: Config temp_config; /* NOTICE: Do not do any logging here after memcpy or the Pico will freeze! */ memcpy(&temp_config, (void *)(XIP_BASE + (FLASH_CONFIG_OFFSET + (FLASH_PAGE_SIZE * savelocationid))), sizeof(Config)); stdio_flush(); - CFG("[CONFIG] LOAD CONFIG FROM SAVE POSITION %d (SAVED CONFIG ID: %d)\n", savelocationid, temp_config.config_saveid); + usCFG("LOAD CONFIG FROM SAVE POSITION %d (SAVED CONFIG ID: %d)\n", savelocationid, temp_config.config_saveid); if (/* (temp_config.config_saveid >= 0) && */(temp_config.config_saveid <= 0xF) /* Max 16 saves */ && temp_config.config_saveid == savelocationid) { /* Found previously saved config */ savelocationid++; /* Increase id and try again */ goto AGAIN; } else { /* They are not equal, that means this config is an empty save location or corrupted, load the previous config */ - CFG("[CONFIG] FOUND LATEST CONFIG AT SAVE POSITION %d (255 == empty slot)\n", (savelocationid-1)); + usCFG("FOUND LATEST CONFIG AT SAVE POSITION %d (255 == empty slot)\n", (savelocationid-1)); savelocationid--; memcpy(config, (void *)(XIP_BASE + (FLASH_CONFIG_OFFSET + (FLASH_PAGE_SIZE * savelocationid))), sizeof(Config)); stdio_flush(); } config_saveid = config->config_saveid; /* copy saveid into variable */ - CFG("[CONFIG] LOADED FROM SAVE POSITION: %d\n", config->config_saveid); + usCFG("LOADED FROM SAVE POSITION: %d\n", config->config_saveid); - CFG("[CONFIG] COPIED CONFIG:\n[CONFIG] [FROM]0x%X\n[CONFIG] [TO]0x%X\n[CONFIG] [SIZE]%u\n", + usCFG("COPIED CONFIG:\n[FROM] 0x%X\n[TO] 0x%X\n[SIZE] %u\n", (FLASH_CONFIG_OFFSET + (FLASH_PAGE_SIZE * savelocationid)), (uint)config, sizeof(Config)); - CFG("[CONFIG] COPIED CONFIG ADDRESSES:\n[CONFIG] [&usbsid_config]0x%X\n[CONFIG] [config]0x%X\n[CONFIG] [&config]0x%X\n", + usCFG("COPIED CONFIG ADDRESSES:\n[&usbsid_config] 0x%X\n[config] 0x%X\n[&config] 0x%X\n", (uint)&usbsid_config, (uint)config, (uint)&config); cm_verification = config->magic; /* Store the current magic for later */ if (config->magic != MAGIC_SMOKE) { /* Verify the magic */ - CFG("[CONFIG] [MAGIC ERROR] config->magic: %u != MAGIC_SMOKE: %u\n", config->magic, MAGIC_SMOKE); - CFG("[CONFIG] RESET TO DEFAULT CONFIG!\n"); + usCFG("[MAGIC ERROR] config->magic: %u != MAGIC_SMOKE: %u\n", config->magic, MAGIC_SMOKE); + usCFG("RESET TO DEFAULT CONFIG!\n"); default_config(config); } - CFG("[END LOAD CONFIG]\n"); + usCFG("[END LOAD CONFIG]\n"); return; } @@ -336,7 +335,7 @@ void __no_inline_not_in_flash_func(write_config)(const Config* config) memcpy(config_data, config, sizeof(Config)); int err = flash_safe_execute(write_config_lowlevel, config_data, 100); if (err) { - CFG("[CONFIG] SAVE ERROR: %d\n", err); + usCFG("SAVE ERROR: %d\n", err); } sleep_ms(100); return; @@ -344,21 +343,21 @@ void __no_inline_not_in_flash_func(write_config)(const Config* config) void __no_inline_not_in_flash_func(save_config)(Config* config) { - CFG("[CONFIG] SAVE CONFIG START\n"); + usCFG("SAVE CONFIG START\n"); /* Verify config saveid and increase */ int noerr = (config->config_saveid == config_saveid); if (noerr) { /* Only increase id's if both are equal */ config_saveid++; config_saveid = config->config_saveid = (config_saveid <= 0xF ? config_saveid : 0); - CFG("[CONFIG] SAVING CONFIG WITH ID %d AT 0x%X (%u)\n", config_saveid, + usCFG("SAVING CONFIG WITH ID %d AT 0x%X (%u)\n", config_saveid, (FLASH_CONFIG_OFFSET + (FLASH_PAGE_SIZE * config_saveid)), (FLASH_CONFIG_OFFSET + (FLASH_PAGE_SIZE * config_saveid))); } else { - CFG("[ERROR] CONFIG SAVE ID's are not equal %d != %d. UNABLE TO SAVE CONFIG!!\n", config->config_saveid, config_saveid); + usCFG("[ERROR] CONFIG SAVE ID's are not equal %d != %d. UNABLE TO SAVE CONFIG!!\n", config->config_saveid, config_saveid); return; } write_config(config); - CFG("[CONFIG] SAVE CONFIG END\n"); + usCFG("SAVE CONFIG END\n"); return; } @@ -395,15 +394,15 @@ void write_back_data(size_t buffersize) void handle_config_request(uint8_t * buffer, uint32_t size) { if (buffer[0] < 0xD0) { /* Don't log incoming buffer to avoid spam above this region */ - CFG("[CONFIG BUFFER] %x %x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]); + usCFG("[CONFIG BUFFER] %x %x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]); } switch (buffer[0]) { case RESET_USBSID: - CFG("[CMD] RESET_USBSID\n"); + usCFG("[CMD] RESET_USBSID\n"); mcu_reset(); break; case READ_CONFIG: - CFG("[CMD] READ_CONFIG\n"); + usCFG("[CMD] READ_CONFIG\n"); /* ISSUE: Although 4 writes are performed, only the first 2 are received */ read_config(&usbsid_config); print_cfg(config_array, count_of(config_array)); @@ -415,7 +414,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case READ_SOCKETCFG: - CFG("[CMD] READ_SOCKETCFG\n"); + usCFG("[CMD] READ_SOCKETCFG\n"); read_socket_config(&usbsid_config); print_cfg(socket_config_array, 10); memset(write_buffer_p, 0, 64); @@ -423,23 +422,23 @@ void handle_config_request(uint8_t * buffer, uint32_t size) write_back_data(10); break; case READ_NUMSIDS: - CFG("[CMD] READ_NUMSIDS: %u\n", (uint8_t)cfg.numsids); + usCFG("[CMD] READ_NUMSIDS: %u\n", (uint8_t)cfg.numsids); memset(write_buffer_p, 0, 64); write_buffer_p[0] = (uint8_t)cfg.numsids; write_back_data(1); break; case READ_FMOPLSID: - CFG("[CMD] READ_FMOPLSID\n"); + usCFG("[CMD] READ_FMOPLSID\n"); memset(write_buffer_p, 0, 64); write_buffer_p[0] = (uint8_t)cfg.fmopl_sid; write_back_data(1); break; case APPLY_CONFIG: - CFG("[CMD] APPLY_CONFIG\n"); + usCFG("[CMD] APPLY_CONFIG\n"); apply_config(false, true); break; case RELOAD_CONFIG: - CFG("[CMD] RELOAD_CONFIG\n"); + usCFG("[CMD] RELOAD_CONFIG\n"); load_config(&usbsid_config); apply_config(false, true); for (uint i = 0; i < count_of(clockrates); i++) { @@ -449,7 +448,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case SET_CONFIG: - CFG("[CMD] SET_CONFIG\n"); + usCFG("[CMD] SET_CONFIG\n"); switch (buffer[1]) { case 0: /* clock_rate */ /* will always be available to change the setting since it doesn't apply it */ @@ -619,17 +618,17 @@ void handle_config_request(uint8_t * buffer, uint32_t size) }; break; case SAVE_CONFIG: - CFG("[CMD] SAVE_CONFIG and RESET_MCU\n"); + usCFG("[CMD] SAVE_CONFIG and RESET_MCU\n"); save_config(&usbsid_config); load_config(&usbsid_config); mcu_reset(); break; case SAVE_NORESET: - CFG("[CMD] SAVE_CONFIG no RESET\n"); + usCFG("[CMD] SAVE_CONFIG no RESET\n"); save_load_apply_config(false, true); break; case RESET_CONFIG: - CFG("[CMD] RESET_CONFIG\n"); + usCFG("[CMD] RESET_CONFIG\n"); default_config(&usbsid_config); save_config(&usbsid_config); if (buffer[1] != 0) { @@ -641,7 +640,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) break; case WRITE_CONFIG: /* TODO: FINISH */ /* Max size of incoming buffer = 61 */ - CFG("[CMD] WRITE_CONFIG\n"); + usCFG("[CMD] WRITE_CONFIG\n"); print_cfg(buffer, size); switch (buffer[1]) { case FULL_CONFIG: @@ -654,7 +653,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) break; case SINGLE_SID: int single_socket = ((buffer[2] == 1) ? 2 : 0); - CFG("[CMD] SINGLE_SID SOCKET %d\n", single_socket); + usCFG("[CMD] SINGLE_SID SOCKET %d\n", single_socket); if (single_socket == 2) { set_socket_config(buffer[1], false, false, usbsid_config.socketOne.chiptype, true, false, usbsid_config.socketTwo.chiptype, false); } else { @@ -662,106 +661,106 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case MIRRORED_SID: - CFG("[CMD] MIRRORED_SID\n"); + usCFG("[CMD] MIRRORED_SID\n"); set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, true, false, usbsid_config.socketTwo.chiptype, true); break; case DUAL_SID: - CFG("[CMD] DUAL_SID\n"); + usCFG("[CMD] DUAL_SID\n"); set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, true, false, usbsid_config.socketTwo.chiptype, false); break; case DUAL_SOCKET1: - CFG("[CMD] DUAL_SOCKET 1\n"); + usCFG("[CMD] DUAL_SOCKET 1\n"); set_socket_config(buffer[1], true, true, usbsid_config.socketOne.chiptype, false, false, usbsid_config.socketTwo.chiptype, false); break; case DUAL_SOCKET2: - CFG("[CMD] DUAL_SOCKET 2\n"); + usCFG("[CMD] DUAL_SOCKET 2\n"); set_socket_config(buffer[1], false, false, usbsid_config.socketOne.chiptype, true, true, usbsid_config.socketTwo.chiptype, false); break; case QUAD_SID: - CFG("[CMD] QUAD_SID\n"); + usCFG("[CMD] QUAD_SID\n"); set_socket_config(buffer[1], true, true, 1, true, true, 1, false); break; case TRIPLE_SID: - CFG("[CMD] TRIPLE_SID SOCKET 1\n"); + usCFG("[CMD] TRIPLE_SID SOCKET 1\n"); set_socket_config(buffer[1], true, true, 1, true, false, usbsid_config.socketTwo.chiptype, false); break; case TRIPLE_SID_TWO: - CFG("[CMD] TRIPLE_SID SOCKET 2\n"); + usCFG("[CMD] TRIPLE_SID SOCKET 2\n"); set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, true, true, 1, false); break; case LOAD_MIDI_STATE: /* Load from config into midimachine and apply to SIDs */ - // CFG("[CMD] LOAD_MIDI_STATE\n"); + // usCFG("[CMD] LOAD_MIDI_STATE\n"); // for (int i = 0; i < 4; i++) { - // CFG("[SID %d]", (i + 1)); + // usCFG("[SID %d]", (i + 1)); // for (int j = 0; j < 32; j++) { // /* ISSUE: this only loads 1 channel state and should actually save all channel states */ // midimachine.channel_states[curr_midi_channel][i][j] = usbsid_config.Midi.sid_states[i][j]; // midimachine.channelkey_states[curr_midi_channel][i][i] = 0; /* Make sure extras is always initialized @ zero */ - // CFG(" %02x", midimachine.channel_states[curr_midi_channel][i][j]); + // usCFG(" %02x", midimachine.channel_states[curr_midi_channel][i][j]); // midi_bus_operation((0x20 * i) | j, midimachine.channel_states[curr_midi_channel][i][j]); // } - // CFG("\n"); + // usCFG("\n"); // } break; case SAVE_MIDI_STATE: /* Save from midimachine into config and save to flash */ - // CFG("[CMD] SAVE_MIDI_STATE\n"); + // usCFG("[CMD] SAVE_MIDI_STATE\n"); // for (int i = 0; i < 4; i++) { - // CFG("[SID %d]", (i + 1)); + // usCFG("[SID %d]", (i + 1)); // for (int j = 0; j < 32; j++) { // /* ISSUE: this only loads 1 channel state and should actually save all channel states */ // usbsid_config.Midi.sid_states[i][j] = midimachine.channel_states[curr_midi_channel][i][j]; - // CFG(" %02x", usbsid_config.Midi.sid_states[i][j]); + // usCFG(" %02x", usbsid_config.Midi.sid_states[i][j]); // } - // CFG("\n"); + // usCFG("\n"); // } // save_config(&usbsid_config); break; case RESET_MIDI_STATE: /* Reset all settings to zero */ - // CFG("[CMD] RESET_MIDI_STATE\n"); + // usCFG("[CMD] RESET_MIDI_STATE\n"); // for (int i = 0; i < 4; i++) { - // CFG("[SID %d]", (i + 1)); + // usCFG("[SID %d]", (i + 1)); // for (int j = 0; j < 32; j++) { // // BUG: this only resets 1 channel state // usbsid_config.Midi.sid_states[i][j] = midimachine.channel_states[curr_midi_channel][i][j] = 0; - // CFG(" %02x", usbsid_config.Midi.sid_states[i][j]); + // usCFG(" %02x", usbsid_config.Midi.sid_states[i][j]); // midi_bus_operation((0x20 * i) | j, midimachine.channel_states[curr_midi_channel][i][j]); // } - // CFG("\n"); + // usCFG("\n"); // } // save_config(&usbsid_config); /* mcu_reset(); */ break; case SET_CLOCK: /* Change SID clock frequency by array id */ - CFG("[CMD] SET_CLOCK\n"); + usCFG("[CMD] SET_CLOCK\n"); /* locked clockrate check is done in apply_clockrate */ bool suspend_sids = (buffer[2] == 1) ? true : false; /* Set RES low while changing clock? */ apply_clockrate((int)buffer[1], suspend_sids); break; case GET_CLOCK: /* Returns the clockrate as array id in byte 0 */ - CFG("[CMD] GET_CLOCK\n"); + usCFG("[CMD] GET_CLOCK\n"); int clk_rate_id = return_clockrate(); memset(write_buffer_p, 0, 64); write_buffer_p[0] = clk_rate_id; write_back_data(1); break; case LOCK_CLOCK: /* Locks the clockrate from being changed, saved in config */ - CFG("[CMD] LOCK_CLOCK\n"); + usCFG("[CMD] LOCK_CLOCK\n"); if (buffer[1] == 0 || buffer[1] == 1) { /* Verify correct data */ usbsid_config.lock_clockrate = (bool)buffer[1]; } else { - CFG("[LOCK_CLOCK] Received incorrect value of %d\n", buffer[1]); + usCFG("[LOCK_CLOCK] Received incorrect value of %d\n", buffer[1]); } if (buffer[2] == 1) { /* Save and apply if set to a 1 */ - CFG("[LOCK_CLOCK] SAVE_CONFIG\n"); + usCFG("[LOCK_CLOCK] SAVE_CONFIG\n"); save_load_apply_config(false, true); } break; case TOGGLE_AUDIO: /* Toggle the audio state regardless of config setting */ - CFG("[CMD] TOGGLE_AUDIO\n"); + usCFG("[CMD] TOGGLE_AUDIO\n"); toggle_audio_switch(); /* if HAS_AUDIOSWITCH is not defined, this doesn't do anything */ break; case SET_AUDIO: /* Set the audio state from buffer setting (saves config if provided) */ - CFG("[CMD] SET_AUDIO\n"); + usCFG("[CMD] SET_AUDIO\n"); if (!usbsid_config.lock_audio_sw) { usbsid_config.stereo_en = (buffer[1] == 0 || buffer[1] == 1) @@ -769,11 +768,11 @@ void handle_config_request(uint8_t * buffer, uint32_t size) : true; /* Default to 1 ~ stereo if incorrect value */ set_audio_switch(usbsid_config.stereo_en); if (buffer[2] == 1) { /* Save and apply if set to a 1 */ - CFG("[SET_AUDIO] SAVE_CONFIG\n"); + usCFG("[SET_AUDIO] SAVE_CONFIG\n"); save_load_apply_config(false, true); } } else { - CFG("[CONFIG] Audio switch is locked at %d (%s), requested change to %d (%s)\n", + usCFG("Audio switch is locked at %d (%s), requested change to %d (%s)\n", (int)usbsid_config.stereo_en, mono_stereo[(int)usbsid_config.stereo_en], buffer[1], mono_stereo[buffer[1]]); return; } @@ -781,19 +780,19 @@ void handle_config_request(uint8_t * buffer, uint32_t size) case GET_AUDIO: /* TODO: Finish */ break; case LOCK_AUDIO: - CFG("[CMD] LOCK_AUDIO\n"); + usCFG("[CMD] LOCK_AUDIO\n"); usbsid_config.lock_audio_sw = (buffer[1] == 0 || buffer[1] == 1) ? (bool)buffer[1] : false; /* Default to false if incorrect value ~ don't lock */ if (buffer[2] == 1) { /* Save and apply if set to a 1 */ - CFG("[SET_AUDIO] SAVE_CONFIG\n"); + usCFG("[SET_AUDIO] SAVE_CONFIG\n"); save_load_apply_config(false, true); } break; case DETECT_SIDS: /* Detect SID types per socket */ if (buffer[1] == 0) { - CFG("[CMD] DETECT_SIDS (ALL)\n"); + usCFG("[CMD] DETECT_SIDS (ALL)\n"); detect_sid_type(&usbsid_config.socketOne, &usbsid_config.socketOne.sid1); if (usbsid_config.socketOne.dualsid) detect_sid_type(&usbsid_config.socketOne, &usbsid_config.socketOne.sid2); detect_sid_type(&usbsid_config.socketTwo, &usbsid_config.socketOne.sid1); @@ -811,13 +810,13 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } } else if (buffer[1] == 1) { /* Only if 1, else just skip! */ if (buffer[2] < 4) { - CFG("[CMD] SID DETECTION @ $%02x\n", buffer[3]); + usCFG("[CMD] SID DETECTION @ $%02x\n", buffer[3]); sid_detection[buffer[2]](buffer[3]); } } break; case DETECT_CLONES: - CFG("[CMD] DETECT_CLONES\n"); + usCFG("[CMD] DETECT_CLONES\n"); detect_clone_type(&usbsid_config.socketOne); detect_clone_type(&usbsid_config.socketTwo); memset(write_buffer_p, 0 ,64); /* Empty the write buffer pointer */ @@ -833,7 +832,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case AUTO_DETECT: - CFG("[CMD] AUTO_DETECT\n"); + usCFG("[CMD] AUTO_DETECT\n"); auto_detect_routine(true, true); /* Double tap! */ if (buffer[1] == 1) { /* Save and reboot */ save_config_ext(); @@ -843,10 +842,10 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case TEST_ALLSIDS: - CFG("[CMD] TEST_ALLSIDS\n"); + usCFG("[CMD] TEST_ALLSIDS\n"); running_tests = true; for (int s = 0; s < cfg.numsids; s++) { - CFG("[START TEST SID %d]\n", s); + usCFG("[START TEST SID %d]\n", s); if (running_tests) { sidtest_queue_entry_t s_entry = {sid_test, s, '1', 'A'}; queue_try_add(&sidtest_queue, &s_entry); @@ -871,17 +870,17 @@ void handle_config_request(uint8_t * buffer, uint32_t size) : buffer[2] == 3 ? 'P' /* Pulse */ : buffer[2] == 4 ? 'N' /* Noise */ : 'P'); /* Fallback to pulse waveform */ - CFG("[CMD] TEST_SID %d TEST: %c WF: %c\n", (s + 1), t, wf); + usCFG("[CMD] TEST_SID %d TEST: %c WF: %c\n", (s + 1), t, wf); running_tests = true; sidtest_queue_entry_t s_entry = {sid_test, s, t, wf}; queue_try_add(&sidtest_queue, &s_entry); break; case STOP_TESTS: - CFG("[CMD] STOP_TESTS\n"); + usCFG("[CMD] STOP_TESTS\n"); running_tests = false; break; case USBSID_VERSION: - CFG("[CMD] READ_FIRMWARE_VERSION\n"); + usCFG("[CMD] READ_FIRMWARE_VERSION\n"); memset(p_version_array, 0, count_of(p_version_array)); read_firmware_version(); memset(write_buffer_p, 0, MAX_BUFFER_SIZE); @@ -896,7 +895,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case US_PCB_VERSION: - CFG("[CMD] READ_PCB_VERSION\n"); + usCFG("[CMD] READ_PCB_VERSION\n"); if (buffer[1] == 0) { /* Large write 64 bytes */ memset(p_version_array, 0, count_of(p_version_array)); read_pcb_version(); @@ -918,45 +917,45 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case RESTART_BUS: - CFG("[CMD] RESTART_BUS\n"); + usCFG("[CMD] RESTART_BUS\n"); restart_bus(); break; case RESTART_BUS_CLK: - CFG("[CMD] RESTART_BUS_CLK\n"); + usCFG("[CMD] RESTART_BUS_CLK\n"); restart_bus_clocks(); break; case SYNC_PIOS: - CFG("[CMD] SYNC_PIOS\n"); + usCFG("[CMD] SYNC_PIOS\n"); sync_pios(false); break; case TEST_FN: /* TODO: Remove before v1 release */ if (buffer[1] == 0) { - CFG("[CMD] TEST_FN\n"); - CFG("[FLASH_CONFIG_OFFSET]0x%x\n", FLASH_CONFIG_OFFSET); - CFG("[PICO_FLASH_SIZE_BYTES]0x%x\n", PICO_FLASH_SIZE_BYTES); - CFG("[FLASH_SECTOR_SIZE]0x%x\n", FLASH_SECTOR_SIZE); - CFG("[FLASH_PAGE_SIZE]0x%x\n", FLASH_PAGE_SIZE); - CFG("[CONFIG_SIZE]0x%x\n", CONFIG_SIZE); - CFG("A %x %x %d\n", usbsid_config.magic, MAGIC_SMOKE, usbsid_config.magic != MAGIC_SMOKE); - CFG("A %x %x %d\n", (uint32_t)usbsid_config.magic, (uint32_t)MAGIC_SMOKE, usbsid_config.magic != MAGIC_SMOKE); - CFG("A %d %d\n", (int)usbsid_config.magic, (int)MAGIC_SMOKE); - CFG("A %d\n", usbsid_config.magic == MAGIC_SMOKE); + usCFG("[CMD] TEST_FN\n"); + usCFG("[FLASH_CONFIG_OFFSET]0x%x\n", FLASH_CONFIG_OFFSET); + usCFG("[PICO_FLASH_SIZE_BYTES]0x%x\n", PICO_FLASH_SIZE_BYTES); + usCFG("[FLASH_SECTOR_SIZE]0x%x\n", FLASH_SECTOR_SIZE); + usCFG("[FLASH_PAGE_SIZE]0x%x\n", FLASH_PAGE_SIZE); + usCFG("[CONFIG_SIZE]0x%x\n", CONFIG_SIZE); + usCFG("A %x %x %d\n", usbsid_config.magic, MAGIC_SMOKE, usbsid_config.magic != MAGIC_SMOKE); + usCFG("A %x %x %d\n", (uint32_t)usbsid_config.magic, (uint32_t)MAGIC_SMOKE, usbsid_config.magic != MAGIC_SMOKE); + usCFG("A %d %d\n", (int)usbsid_config.magic, (int)MAGIC_SMOKE); + usCFG("A %d\n", usbsid_config.magic == MAGIC_SMOKE); - CFG("[TEST_CONFIG_START]\n"); - CFG("[MAGIC_SMOKE ERROR?] config: %u header: %u cm_verification: %u\n", usbsid_config.magic, MAGIC_SMOKE, cm_verification); + usCFG("[TEST_CONFIG_START]\n"); + usCFG("[MAGIC_SMOKE ERROR?] config: %u header: %u cm_verification: %u\n", usbsid_config.magic, MAGIC_SMOKE, cm_verification); Config test_config; memcpy(&test_config, (void *)(XIP_BASE + FLASH_CONFIG_OFFSET), sizeof(Config)); - CFG("A %x %x %d\n", test_config.magic, MAGIC_SMOKE, test_config.magic != MAGIC_SMOKE); - CFG("A %x %x %d\n", (uint32_t)test_config.magic, (uint32_t)MAGIC_SMOKE, test_config.magic != MAGIC_SMOKE); - CFG("A %d %d\n", (int)test_config.magic, (int)MAGIC_SMOKE); - CFG("A %d\n", test_config.magic == MAGIC_SMOKE); + usCFG("A %x %x %d\n", test_config.magic, MAGIC_SMOKE, test_config.magic != MAGIC_SMOKE); + usCFG("A %x %x %d\n", (uint32_t)test_config.magic, (uint32_t)MAGIC_SMOKE, test_config.magic != MAGIC_SMOKE); + usCFG("A %d %d\n", (int)test_config.magic, (int)MAGIC_SMOKE); + usCFG("A %d\n", test_config.magic == MAGIC_SMOKE); read_config(&test_config); print_cfg(config_array, count_of(config_array)); - CFG("[TEST_CONFIG_END]\n"); + usCFG("[TEST_CONFIG_END]\n"); } if (buffer[1] == 1) { - CFG("[USBSID_SID_MEMORY]\n"); + usCFG("[USBSID_SID_MEMORY]\n"); print_cfg(sid_memory, (cfg.numsids * 0x20)); } break; @@ -964,13 +963,13 @@ void handle_config_request(uint8_t * buffer, uint32_t size) uint8_t st = 0xFF; #ifdef ONBOARD_EMULATOR if (buffer[1] == 0) { - CFG("START EMULATOR\n"); + usCFG("START EMULATOR\n"); emulator_running = false; offload_ledrunner = true; starting_emulator = true; } if (buffer[1] == 1) { - CFG("STOP EMULATOR\n"); + usCFG("STOP EMULATOR\n"); stopping_emulator = true; stop_cynthcart(); } @@ -986,23 +985,23 @@ void handle_config_request(uint8_t * buffer, uint32_t size) uint16_t dcyc = 1000; if (buffer[2] != 0 || buffer[3] != 0) dcyc = (buffer[2] << 8) | buffer[3]; - CFG("[CFG] DELAY TESTING FOR %ld US/CYCLES\n", dcyc); + usCFG("[CFG] DELAY TESTING FOR %ld US/CYCLES\n", dcyc); uint64_t test_before = to_us_since_boot(get_absolute_time()); sleep_ms(dcyc/1000); uint64_t test_after = to_us_since_boot(get_absolute_time()); - CFG("[CFG] SLEEP_MS before: %lld after: %lld difference %lld\n", test_before, test_after, (test_after - test_before)); + usCFG("[CFG] SLEEP_MS before: %lld after: %lld difference %lld\n", test_before, test_after, (test_after - test_before)); test_before = to_us_since_boot(get_absolute_time()); uint16_t waited_cycles = cycled_delay_operation(dcyc); test_after = to_us_since_boot(get_absolute_time()); - CFG("[CFG] DELAY_CYCLES %u = %.4fµs, ACTUAL: %uµs (including printf delay)\n", waited_cycles, (float)(waited_cycles * sid_us), (test_after - test_before)); + usCFG("[CFG] DELAY_CYCLES %u = %.4fµs, ACTUAL: %uµs (including get_absolute_time delay)\n", } if (buffer[1] == 6) { detect_fmopl(buffer[2]); } if (buffer[1] == 7) { - CFG("[CONFIG] SAVE ID BEFORE: %d (%d)\n", config_saveid, usbsid_config.config_saveid); + usCFG("SAVE ID BEFORE: %d (%d)\n", config_saveid, usbsid_config.config_saveid); config_saveid = usbsid_config.config_saveid = 0; /* reset saveid */ - CFG("[CONFIG] SAVE ID AFTER: %d (%d)\n", config_saveid, usbsid_config.config_saveid); + usCFG("SAVE ID AFTER: %d (%d)\n", config_saveid, usbsid_config.config_saveid); } if (buffer[1] == 8) { read_fpgasid_configuration(buffer[2]); @@ -1018,23 +1017,23 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case TEST_FN3: - CFG("[SID MEMORY]\n"); + usCFG("[SID MEMORY]\n"); for (uint i = 0; i < (4*0x20); i++) { - if (i!=0 && i%0x20 == 0) { CFG("\n");}; - CFG("$%02X ", sid_memory[i]); + if (i!=0 && i%0x20 == 0) { usCFG("\n");}; + usCFG("$%02X ", sid_memory[i]); } - CFG("\n"); + usCFG("\n"); break; case FPGASID: break; /* Reserved for config implementation */ case SKPICO: break; /* Reserved for config implementation */ case ARMSID: break; /* Reserved for config implementation */ case PDSID: - CFG("[TOGGLE PDSID TYPE]\n"); + usCFG("[TOGGLE PDSID TYPE]\n"); switch_pdsid_type(); break; #if defined(ONBOARD_SIDPLAYER) case UPLOAD_SID_START: - CFG("[UPLOAD_SID_START %d]\n",buffer[1]); + usCFG("[UPLOAD_SID_START %d]\n",buffer[1]); receiving_sidfile = true; sidbytes_received = 0; is_prg = ((buffer[1] == PRG_FILE) ? true : false); @@ -1047,7 +1046,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case UPLOAD_SID_DATA: - if (sidbytes_received == 0) CFG("[UPLOAD_SID_DATA]\n"); + if (sidbytes_received == 0) usCFG("[UPLOAD_SID_DATA]\n"); if (receiving_sidfile) { for (int i = 1; i < 63; i++) { /* Max buffer size minus command byte (config init byte is already gone) */ sidfile[sidbytes_received] = buffer[i]; @@ -1056,27 +1055,27 @@ void handle_config_request(uint8_t * buffer, uint32_t size) } break; case UPLOAD_SID_END: - CFG("[UPLOAD_SID_END]\n"); - DBG("Received %u bytes\n", sidbytes_received); + usCFG("[UPLOAD_SID_END]\n"); + usDBG("Received %u bytes\n", sidbytes_received); receiving_sidfile = false; /* ISSUE: These are never the same size */ /* sidfile_size = sidbytes_received; */ sidbytes_received = 0; break; case UPLOAD_SID_SIZE: - CFG("[UPLOAD_SID_SIZE]\n"); + usCFG("[UPLOAD_SID_SIZE]\n"); sidfile_size = (buffer[1]<<8|buffer[2]); - DBG("Received SID file size: %u\n", sidfile_size); + usDBG("Received SID file size: %u\n", sidfile_size); break; case SID_PLAYER_TUNE: - CFG("[SID_PLAYER_TUNE] %d\n", buffer[1]); - CFG("[CONFIG BUFFER SIZE] %d\n", count_of(buffer)); + usCFG("[SID_PLAYER_TUNE] %d\n", buffer[1]); + usCFG("[CONFIG BUFFER SIZE] %d\n", count_of(buffer)); tuneno = buffer[2]; /* Should be 0 if not supplied */ - CFG("[SUBTUNE] %d\n", tuneno); + usCFG("[SUBTUNE] %d\n", tuneno); unmute_sid(); /* Must unmute before play start or some tunes will be silent */ sidplayer_init = true; case SID_PLAYER_START: - CFG("[SID_PLAYER_START] %d\n", sidplayer_init); + usCFG("[SID_PLAYER_START] %d\n", sidplayer_init); if (sidplayer_init) { offload_ledrunner = true; sidplayer_start = true; @@ -1084,7 +1083,7 @@ void handle_config_request(uint8_t * buffer, uint32_t size) sidplayer_init = false; break; case SID_PLAYER_STOP: - CFG("[SID_PLAYER_STOP]\n"); + usCFG("[SID_PLAYER_STOP]\n"); if (sidplayer_playing) { sidplayer_stop = true; } @@ -1094,15 +1093,15 @@ void handle_config_request(uint8_t * buffer, uint32_t size) /* SID resets are handled in the emulator */ break; case SID_PLAYER_PAUSE: - CFG("[SID_PLAYER_PAUSE]\n"); + usCFG("[SID_PLAYER_PAUSE]\n"); sidplayer_playing = !sidplayer_playing; break; case SID_PLAYER_NEXT: - CFG("[SID_PLAYER_NEXT]\n"); + usCFG("[SID_PLAYER_NEXT]\n"); sidplayer_next = true; break; case SID_PLAYER_PREV: - CFG("[SID_PLAYER_PREV]\n"); + usCFG("[SID_PLAYER_PREV]\n"); sidplayer_prev = true; break; #endif @@ -1122,7 +1121,7 @@ void apply_led_config(void) break; } } - CFG("[CONFIG] RGBLED REQUESTED: %d\n", usbsid_config.RGBLED.sid_to_use); + usCFG("RGBLED REQUESTED: %d\n", usbsid_config.RGBLED.sid_to_use); /* check if requested sidno is actually configured */ usbsid_config.RGBLED.sid_to_use = (stou > cfg.numsids) @@ -1132,7 +1131,7 @@ void apply_led_config(void) ? sid /* use the first SID that is either 8580 or 6581 */ : 1 /* else default to SID 1 */ : usbsid_config.RGBLED.sid_to_use; /* Else use the programmed SID to use */ - CFG("[CONFIG] RGBLED CALCULATED: %d\n", usbsid_config.RGBLED.sid_to_use); + usCFG("RGBLED CALCULATED: %d\n", usbsid_config.RGBLED.sid_to_use); return; } @@ -1146,9 +1145,8 @@ void print_config(void) void apply_config(bool at_boot, bool print_cfg) { - CFG("\n"); - CFG("[START CONFIG APPLY]\n"); - CFG("[CONFIG] Verifying socket settings\n"); + usCFG("[START CONFIG APPLY]\n"); + usCFG("Verifying socket settings\n"); verify_socket_settings(); verify_sid_addr(print_cfg); @@ -1157,7 +1155,7 @@ void apply_config(bool at_boot, bool print_cfg) apply_fmopl_config(false); if (!at_boot) { - CFG("[CONFIG] Applying bus clock settings\n"); + usCFG("Applying bus clock settings\n"); stop_dma_channels(); restart_bus_clocks(); sync_pios(false); @@ -1165,12 +1163,12 @@ void apply_config(bool at_boot, bool print_cfg) } if (RGB_ENABLED) { - CFG("[CONFIG] Applying RGBLED SID\n"); + usCFG("Applying RGBLED SID\n"); apply_led_config(); } else { - CFG("[CONFIG] RGBLED not available\n"); + usCFG("RGBLED not available\n"); } - CFG("[END CONFIG APPLY]\n"); + usCFG("[END CONFIG APPLY]\n"); /* Don't print anything on auto config run */ if (auto_config) return; @@ -1178,14 +1176,11 @@ void apply_config(bool at_boot, bool print_cfg) /* Print config at end of apply if requested */ if (print_cfg) print_config(); - CFG("\n"); - return; } void save_load_apply_config(bool at_boot, bool print_cfg) { - CFG("\n"); save_config(&usbsid_config); load_config(&usbsid_config); apply_config(at_boot, print_cfg); @@ -1194,30 +1189,29 @@ void save_load_apply_config(bool at_boot, bool print_cfg) void save_config_ext(void) { /* For saving the config outside of config.c */ - CFG("\n"); save_config(&usbsid_config); return; } void detect_default_config(void) { - CFG("[CONFIG] DETECT DEFAULT CONFIG START\n"); - CFG("[CONFIG] IS DEFAULT CONFIG? %s\n", + usCFG("DETECT DEFAULT CONFIG START\n"); + usCFG("IS DEFAULT CONFIG? %s\n", true_false[usbsid_config.default_config]); if(usbsid_config.default_config == 1) { - CFG("[CONFIG] DETECTED DEFAULT CONFIG!\n"); - CFG("[CONFIG] [MAGIC_SMOKE ADDRESS] config struct: 0x%X cm_verification: 0x%X\n", + usCFG("DETECTED DEFAULT CONFIG!\n"); + usCFG("[MAGIC_SMOKE ADDRESS] config struct: 0x%X cm_verification: 0x%X\n", &usbsid_config.magic, &cm_verification); - CFG("[CONFIG] [MAGIC_SMOKE VERIFICATION] config_struct: %u header: %u cm_verification: %u\n", + usCFG("[MAGIC_SMOKE VERIFICATION] config_struct: %u header: %u cm_verification: %u\n", usbsid_config.magic, MAGIC_SMOKE, cm_verification); usbsid_config.default_config = 0; - CFG("[CONFIG] DEFAULT CONFIG STATE SET TO %d\n", usbsid_config.default_config); + usCFG("DEFAULT CONFIG STATE SET TO %d\n", usbsid_config.default_config); first_boot = true; /* Only at first boot the link popup will be sent */ auto_detect_routine(false, true); /* default auto detect routine based on default config */ save_config(&usbsid_config); - CFG("[CONFIG] CONFIG SAVED\n"); + usCFG("CONFIG SAVED\n"); } - CFG("[CONFIG] DETECT DEFAULT FINISHED\n"); + usCFG("DETECT DEFAULT FINISHED\n"); return; } @@ -1237,10 +1231,10 @@ void apply_clockrate(int n_clock, bool suspend_sids) if (!usbsid_config.lock_clockrate) { if (clockrates[n_clock] != usbsid_config.clock_rate) { if (suspend_sids) { - CFG("[CONFIG] [DISABLE SID]\n"); + usCFG("[DISABLE SID]\n"); disable_sid(); } - CFG("[CONFIG] [CLOCK FROM]%d [CLOCK TO]%d\n", usbsid_config.clock_rate, clockrates[n_clock]); + usCFG("[CLOCK FROM]%d [CLOCK TO]%d\n", usbsid_config.clock_rate, clockrates[n_clock]); usbsid_config.clock_rate = clockrates[n_clock]; usbsid_config.refresh_rate = refreshrates[n_clock]; /* Used by ASID */ usbsid_config.raster_rate = rasterrates[n_clock]; /* Used by the Vu and the ASID buffer */ @@ -1248,8 +1242,8 @@ void apply_clockrate(int n_clock, bool suspend_sids) sid_hz = usbsid_config.clock_rate; sid_mhz = (sid_hz / 1000 / 1000); sid_us = (1 / sid_mhz); - CFG("[CONFIG] [CFG PICO] %lu Hz, %.0f MHz, %.4f uS\n", clock_get_hz(clk_sys), cpu_mhz, cpu_us); - CFG("[CONFIG] [CFG C64] %.0f Hz, %.6f MHz, %.4f uS\n", sid_hz, sid_mhz, sid_us); + usCFG("[CFG PICO] %lu Hz, %.0f MHz, %.4f uS\n", clock_get_hz(clk_sys), cpu_mhz, cpu_us); + usCFG("[CFG C64] %.0f Hz, %.6f MHz, %.4f uS\n", sid_hz, sid_mhz, sid_us); /* Start clock set */ // ISSUE: EVEN THOUGH THIS IS BETTER IT DOES NOT SOLVE THE CRACKLING/BUS ROT ISSUE! stop_dma_channels(); @@ -1257,7 +1251,7 @@ void apply_clockrate(int n_clock, bool suspend_sids) start_dma_channels(); sync_pios(false); if (suspend_sids) { - CFG("[ENABLE SID WITH UNMUTE]\n"); + usCFG("[ENABLE SID WITH UNMUTE]\n"); enable_sid(true); } // ISSUE: WHEN THE BUS IS RESTARTED THE CRACKLING ON CYCLE EXACT TUNES IS IMMENSE! @@ -1265,11 +1259,11 @@ void apply_clockrate(int n_clock, bool suspend_sids) // restart_bus(); return; } else { - CFG("[CONFIG] [CLOCK FROM]%d AND [CLOCK TO]%d ARE THE SAME, SKIPPING SET_CLOCK\n", usbsid_config.clock_rate, clockrates[n_clock]); + usCFG("[CLOCK FROM]%d AND [CLOCK TO]%d ARE THE SAME, SKIPPING SET_CLOCK\n", usbsid_config.clock_rate, clockrates[n_clock]); return; } } else { - CFG("[CONFIG] [CLOCK] Clockrate is locked, change from %d to %d not applied\n", usbsid_config.clock_rate, clockrates[n_clock]); + usCFG("[CLOCK] Clockrate is locked, change from %d to %d not applied\n", usbsid_config.clock_rate, clockrates[n_clock]); return; } } @@ -1286,7 +1280,7 @@ void verify_clockrate(void) case CLOCK_DREAN: break; default: - CFG("[CONFIG] [CLOCK ERROR] Detected unconventional clockrate (%ld) error in config, revert to default\n", usbsid_config.clock_rate); + usCFG("[CLOCK ERROR] Detected unconventional clockrate (%ld) error in config, revert to default\n", usbsid_config.clock_rate); usbsid_config.clock_rate = clockrates[0]; usbsid_config.refresh_rate = refreshrates[0]; /* Experimental */ usbsid_config.raster_rate = rasterrates[0]; /* Experimental */ diff --git a/src/config_bus.c b/src/config_bus.c index 16f597a..651a3e8 100644 --- a/src/config_bus.c +++ b/src/config_bus.c @@ -152,14 +152,14 @@ void apply_bus_config_OLD(void) } } } - CFG("[TEST OLD] ONE:%02X ONE:%02X TWO:%02X TWO:%02X THREE:%02X THREE:%02X FOUR:%02X FOUR:%02X\n", + usCFG("[TEST OLD] ONE:%02X ONE:%02X TWO:%02X TWO:%02X THREE:%02X THREE:%02X FOUR:%02X FOUR:%02X\n", cfg.one, cfg.one_mask, cfg.two, cfg.two_mask, cfg.three, cfg.three_mask, cfg.four, cfg.four_mask); return; } void apply_bus_config(bool quiet) // ISSUE: FINISH { /* bus config doesn't care if the sid is real or fmopl */ - if (!quiet) CFG("[CONFIG] Applying bus settings\n"); + if (!quiet) usCFG("Applying bus settings\n"); /* Default everything first */ memset(cfg.ids, 4, 4); /* Default to 4 N/A */ @@ -213,7 +213,7 @@ void apply_bus_config(bool quiet) // ISSUE: FINISH if (!quiet) { for (int id = 0; id < 4; id++) { - CFG("[TEST0] ID:%d IDS:%d SIDID:%d TYPE:%02X ADDR:%02X MASK:%02X ADDRMASK:%02X\n", + usCFG("[TEST0] ID:%d IDS:%d SIDID:%d TYPE:%02X ADDR:%02X MASK:%02X ADDRMASK:%02X\n", id, cfg.ids[id], cfg.sidid[id], cfg.sidtype[id], cfg.sidaddr[id], cfg.sidmask[cfg.ids[id]], cfg.addrmask[cfg.ids[id]]); } } @@ -243,7 +243,7 @@ void apply_bus_config(bool quiet) // ISSUE: FINISH cfg.three_mask = cfg.ids[2] != 4 ? cfg.addrmask[cfg.sidid[cfg.ids[2]]] : addrmask_default[cfg.ids[2]]; cfg.four_mask = cfg.ids[3] != 4 ? cfg.addrmask[cfg.sidid[cfg.ids[3]]] : addrmask_default[cfg.ids[3]]; - if (!quiet) CFG("[TEST 1] ONE:%02X ONE:%02X TWO:%02X TWO:%02X THREE:%02X THREE:%02X FOUR:%02X FOUR:%02X\n", + if (!quiet) usCFG("[TEST 1] ONE:%02X ONE:%02X TWO:%02X TWO:%02X THREE:%02X THREE:%02X FOUR:%02X FOUR:%02X\n", cfg.one, cfg.one_mask, cfg.two, cfg.two_mask, cfg.three, cfg.three_mask, cfg.four, cfg.four_mask); /* EVERYTHING WORKS UP TO HERE BUT IGNORES SOCKET SETTINGS */ @@ -269,7 +269,7 @@ void apply_bus_config(bool quiet) // ISSUE: FINISH } - if (!quiet) CFG("[TEST 2] ONE:%02X ONE:%02X TWO:%02X TWO:%02X THREE:%02X THREE:%02X FOUR:%02X FOUR:%02X\n", + if (!quiet) usCFG("[TEST 2] ONE:%02X ONE:%02X TWO:%02X TWO:%02X THREE:%02X THREE:%02X FOUR:%02X FOUR:%02X\n", cfg.one, cfg.one_mask, cfg.two, cfg.two_mask, cfg.three, cfg.three_mask, cfg.four, cfg.four_mask); } diff --git a/src/config_logging.c b/src/config_logging.c index 6591cb0..003a357 100644 --- a/src/config_logging.c +++ b/src/config_logging.c @@ -51,38 +51,37 @@ const char __in_flash("usbsid_vars") *mono_stereo[2] = { "Mono", "Stereo" }; void print_cfg(const uint8_t *buf, size_t len) { - CFG("[PRINT CFG START]\n"); + usCFG("[PRINT CFG START]\n"); for (size_t i = 0; i < len; ++i) { if (i == 0) - CFG("[R%03d] ", i); - CFG("%02x", buf[i]); + usCFG("[R%03d] ", i); + usCFG("%02x", buf[i]); if (i == (len - 1)) { - CFG("\n"); + usCFG("\n"); } else if ((i != 0) && (i % 16 == 15)) { - CFG("\n[R%03d] ", i); + usCFG("\n[R%03d] ", i); } else { - CFG(" "); + usCFG(" "); } } - CFG("[PRINT CFG END]\n"); + usCFG("[PRINT CFG END]\n"); return; } void print_cfg_addr(void) { - CFG("\n"); - CFG("[CONFIG] [XIP_BASE]0x%X (%u)\n\ -[CONFIG] [PICO_FLASH_SIZE_BYTES]0x%X (%u)\n\ -[CONFIG] [FLASH_PAGE_SIZE]0x%X (%u)\n\ -[CONFIG] [FLASH_SECTOR_SIZE]0x%X (%u)\n\ -[CONFIG] [ADDR_PERSISTENT_BASE_ADDR]0x%X (%u)\n\ -[CONFIG] [FLASH_PERSISTENT_OFFSET]0x%X (%u)\n\ -[CONFIG] [FLASH_PERSISTENT_SIZE]0x%X (%u)\n\ -[CONFIG] [FLASH_CONFIG_OFFSET]0x%X (%u)\n\ -[CONFIG] [XIP_BASE + FLASH_CONFIG_OFFSET]0x%X (%u)\n\ -[CONFIG] [CONFIG_SIZE]0x%X (%u)\n\ -[CONFIG] [SIZEOF_CONFIG]0x%X (%u)\n", + usCFG("\n[XIP_BASE] 0x%X (%u)\n\ +[PICO_FLASH_SIZE_BYTES] 0x%X (%u)\n\ +[FLASH_PAGE_SIZE] 0x%X (%u)\n\ +[FLASH_SECTOR_SIZE] 0x%X (%u)\n\ +[ADDR_PERSISTENT_BASE_ADDR] 0x%X (%u)\n\ +[FLASH_PERSISTENT_OFFSET] 0x%X (%u)\n\ +[FLASH_PERSISTENT_SIZE] 0x%X (%u)\n\ +[FLASH_CONFIG_OFFSET] 0x%X (%u)\n\ +[XIP_BASE + FLASH_CONFIG_OFFSET] 0x%X (%u)\n\ +[CONFIG_SIZE] 0x%X (%u)\n\ +[SIZEOF_CONFIG] 0x%X (%u)\n", XIP_BASE, XIP_BASE, PICO_FLASH_SIZE_BYTES, PICO_FLASH_SIZE_BYTES, FLASH_PAGE_SIZE, FLASH_PAGE_SIZE, FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE, ADDR_PERSISTENT_BASE_ADDR, ADDR_PERSISTENT_BASE_ADDR, @@ -95,135 +94,135 @@ void print_cfg_addr(void) void print_pico_features(void) { - CFG("\n"); /* new line before the actual pico features are printed */ - CFG("[START PRINT PICO DEFAULT FEATURES]\n"); + usCFG("\n[START PRINT PICO DEFAULT FEATURES]\n"); - CFG("PICO_PIO_VERSION = %d\n", PICO_PIO_VERSION); /* pio.h */ + usCFG("PICO_PIO_VERSION = %d\n", PICO_PIO_VERSION); /* pio.h */ #if defined(PICO_DEFAULT_LED_PIN) - CFG("PICO_DEFAULT_LED_PIN = %d\n", PICO_DEFAULT_LED_PIN); /* pico*.h */ + usCFG("PICO_DEFAULT_LED_PIN = %d\n", PICO_DEFAULT_LED_PIN); /* pico*.h */ #elif defined(CYW43_WL_GPIO_LED_PIN) - CFG("CYW43_WL_GPIO_LED_PIN = %d\n", CYW43_WL_GPIO_LED_PIN); /* pico*.h */ + usCFG("CYW43_WL_GPIO_LED_PIN = %d\n", CYW43_WL_GPIO_LED_PIN); /* pico*.h */ #endif - CFG("LED_PWM = %d\n", LED_PWM); /* config.h */ + usCFG("LED_PWM = %d\n", LED_PWM); /* config.h */ - CFG("[END PRINT PICO DEFAULT FEATURES]\n"); + usCFG("[END PRINT PICO DEFAULT FEATURES]\n"); + usCFG("\n"); return; } void print_config_settings(void) { - CFG("\n"); /* new line before the actual settings are printed */ - CFG("[START PRINT CONFIG SETTINGS]\n"); + usCFG("\n"); /* new line before the actual settings are printed */ + usCFG("[START PRINT CONFIG SETTINGS]\n"); - CFG("[USBSID PCB VERSION] %s\n", pcb_version); - CFG("[USBSID FIRMWARE VERSION] %s\n", project_version); + usCFG("[USBSID PCB VERSION] %s\n", pcb_version); + usCFG("[USBSID FIRMWARE VERSION] %s\n", project_version); - CFG("[CLOCK] %s @%d\n", + usCFG("[CLOCK] %s @%d\n", int_ext[(int)usbsid_config.external_clock], (int)usbsid_config.clock_rate); - CFG("[CLOCK RATE LOCKED] %s\n", + usCFG("[CLOCK RATE LOCKED] %s\n", true_false[(int)usbsid_config.lock_clockrate]); - CFG("[RASTER RATE] @%d\n", + usCFG("[RASTER RATE] @%d\n", (int)usbsid_config.raster_rate); - CFG("[SOCKET ONE] %s as %s\n", + usCFG("[SOCKET ONE] %s as %s\n", enabled[(int)usbsid_config.socketOne.enabled], ((int)usbsid_config.socketOne.dualsid == 1 ? single_dual[0] : single_dual[1])); - CFG("[SOCKET ONE] CHIP TYPE: %s, CLONE TYPE: %s\n", + usCFG("[SOCKET ONE] CHIP TYPE: %s, CLONE TYPE: %s\n", chiptypes[usbsid_config.socketOne.chiptype], clonetypes[usbsid_config.socketOne.clonetype]); - CFG("[SOCKET ONE] SID 1 TYPE: %s, SID 2 TYPE: %s\n", + usCFG("[SOCKET ONE] SID 1 TYPE: %s, SID 2 TYPE: %s\n", sidtypes[usbsid_config.socketOne.sid1.type], sidtypes[usbsid_config.socketOne.sid2.type]); - CFG("[SOCKET ONE] SID 1 ID: %d, SID 2 ID: %d\n", + usCFG("[SOCKET ONE] SID 1 ID: %d, SID 2 ID: %d\n", usbsid_config.socketOne.sid1.id, usbsid_config.socketOne.sid2.id); - CFG("[SOCKET ONE] SID 1 ADDR: 0x%02X, SID 2 ADDR: 0x%02X\n", + usCFG("[SOCKET ONE] SID 1 ADDR: 0x%02X, SID 2 ADDR: 0x%02X\n", usbsid_config.socketOne.sid1.addr, usbsid_config.socketOne.sid2.addr); - CFG("[SOCKET TWO] %s as %s\n", + usCFG("[SOCKET TWO] %s as %s\n", enabled[(int)usbsid_config.socketTwo.enabled], ((int)usbsid_config.socketTwo.dualsid == 1 ? single_dual[0] : single_dual[1])); - CFG("[SOCKET TWO] CHIP TYPE: %s, CLONE TYPE: %s\n", + usCFG("[SOCKET TWO] CHIP TYPE: %s, CLONE TYPE: %s\n", chiptypes[usbsid_config.socketTwo.chiptype], clonetypes[usbsid_config.socketTwo.clonetype]); - CFG("[SOCKET TWO] SID 1 TYPE: %s, SID 2 TYPE: %s\n", + usCFG("[SOCKET TWO] SID 1 TYPE: %s, SID 2 TYPE: %s\n", sidtypes[usbsid_config.socketTwo.sid1.type], sidtypes[usbsid_config.socketTwo.sid2.type]); - CFG("[SOCKET ONE] SID 1 ID: %d, SID 2 ID: %d\n", + usCFG("[SOCKET ONE] SID 1 ID: %d, SID 2 ID: %d\n", usbsid_config.socketTwo.sid1.id, usbsid_config.socketTwo.sid2.id); - CFG("[SOCKET ONE] SID 1 ADDR: 0x%02X, SID 2 ADDR: 0x%02X\n", + usCFG("[SOCKET ONE] SID 1 ADDR: 0x%02X, SID 2 ADDR: 0x%02X\n", usbsid_config.socketTwo.sid1.addr, usbsid_config.socketTwo.sid2.addr); - CFG("[SOCKET TWO AS ONE (MIRRORED)] %s\n", + usCFG("[SOCKET TWO AS ONE (MIRRORED)] %s\n", true_false[(int)usbsid_config.mirrored]); - CFG("[LED] %s, Idle breathe? %s\n", + usCFG("[LED] %s, Idle breathe? %s\n", enabled[(int)usbsid_config.LED.enabled], true_false[(int)usbsid_config.LED.idle_breathe]); - CFG("[RGBLED] %s, Idle breathe? %s\n", + usCFG("[RGBLED] %s, Idle breathe? %s\n", enabled[(int)usbsid_config.RGBLED.enabled], true_false[(int)usbsid_config.RGBLED.idle_breathe]); - CFG("[RGBLED SIDTOUSE] %d\n", + usCFG("[RGBLED SIDTOUSE] %d\n", (int)usbsid_config.RGBLED.sid_to_use); - CFG("[RGBLED BRIGHTNESS] %d\n", + usCFG("[RGBLED BRIGHTNESS] %d\n", (int)usbsid_config.RGBLED.brightness); - CFG("[CDC] %s\n", + usCFG("[CDC] %s\n", enabled[(int)usbsid_config.Cdc.enabled]); - CFG("[WebUSB] %s\n", + usCFG("[WebUSB] %s\n", enabled[(int)usbsid_config.WebUSB.enabled]); - CFG("[Asid] %s\n", + usCFG("[Asid] %s\n", enabled[(int)usbsid_config.Asid.enabled]); - CFG("[Midi] %s\n", + usCFG("[Midi] %s\n", enabled[(int)usbsid_config.Midi.enabled]); - CFG("[FMOpl] %s\n", + usCFG("[FMOpl] %s\n", enabled[(int)usbsid_config.FMOpl.enabled]); - CFG("[FMOpl] SIDno %d\n", + usCFG("[FMOpl] SIDno %d\n", usbsid_config.FMOpl.sidno); #if defined(HAS_AUDIOSWITCH) - CFG("[AUDIO_SWITCH] %s\n", + usCFG("[AUDIO_SWITCH] %s\n", mono_stereo[(int)usbsid_config.stereo_en]); - CFG("[AUDIO_SWITCH_LOCKED] %s\n", + usCFG("[AUDIO_SWITCH_LOCKED] %s\n", true_false[(int)usbsid_config.lock_audio_sw]); #endif - CFG("[END PRINT CONFIG SETTINGS]\n"); + usCFG("[END PRINT CONFIG SETTINGS]\n"); return; } void print_socket_config(void) { - CFG("\n"); /* new line before the actual socket settings are printed */ - CFG("[START PRINT SOCKET CONFIG]\n"); + usCFG("\n"); /* new line before the actual socket settings are printed */ + usCFG("[START PRINT SOCKET CONFIG]\n"); - CFG("SOCK_ONE EN: %s SOCK_TWO EN: %s\nACT_AS_ONE: %s\nNO SIDS\nSOCK_ONE #%d\nSOCK_TWO #%d\nTOTAL #%d\n", + usCFG("\nSOCK_ONE EN: %s SOCK_TWO EN: %s\nACT_AS_ONE: %s\nNO SIDS\nSOCK_ONE #%d\nSOCK_TWO #%d\nTOTAL #%d\n", true_false[cfg.sock_one], true_false[cfg.sock_two], true_false[cfg.mirrored], cfg.sids_one, cfg.sids_two, cfg.numsids); - CFG("[END PRINT SOCKET CONFIG]\n"); + usCFG("[END PRINT SOCKET CONFIG]\n"); return; } void print_bus_config(void) { - CFG("\n"); /* new line before the actual bus config is printed */ - CFG("[START PRINT BUS CONFIG]\n"); + usCFG("\n"); /* new line before the actual bus config is printed */ + usCFG("[START PRINT BUS CONFIG]\n"); - CFG("BUS\nONE: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\nTWO: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\nTHREE: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\nFOUR: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", + usCFG("BUS\nONE: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\nTWO: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\nTHREE: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\nFOUR: %02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.one, PRINTF_BYTE_TO_BINARY_INT8(cfg.one), cfg.two, PRINTF_BYTE_TO_BINARY_INT8(cfg.two), cfg.three, PRINTF_BYTE_TO_BINARY_INT8(cfg.three), cfg.four, PRINTF_BYTE_TO_BINARY_INT8(cfg.four)); - CFG("ADDRESS MASKS\n"); - CFG("MASK_ONE: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.one_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.one_mask)); - CFG("MASK_TWO: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.two_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.two_mask)); - CFG("MASK_THREE: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.three_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.three_mask)); - CFG("MASK_FOUR: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.four_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.four_mask)); + usCFG("ADDRESS MASKS\n"); + usCFG("MASK_ONE: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.one_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.one_mask)); + usCFG("MASK_TWO: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.two_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.two_mask)); + usCFG("MASK_THREE: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.three_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.three_mask)); + usCFG("MASK_FOUR: 0x%02x 0b"PRINTF_BINARY_PATTERN_INT8"\n", cfg.four_mask, PRINTF_BYTE_TO_BINARY_INT8(cfg.four_mask)); - CFG("[END PRINT BUS CONFIG]\n"); + usCFG("[END PRINT BUS CONFIG]\n"); return; } diff --git a/src/config_socket.c b/src/config_socket.c index 8fc259d..b55b37c 100644 --- a/src/config_socket.c +++ b/src/config_socket.c @@ -92,7 +92,7 @@ void verify_socket_settings(void) void verify_chipdetection_results(bool quiet) { /* Is run only from sid_detection.c:auto_detect_routine after detection in auto_config mode */ - if (!quiet) CFG("[SID] VERIFY SOCKET DETECTION RESULTS\n"); + if (!quiet) usCFG("[SID] VERIFY SOCKET DETECTION RESULTS\n"); /* Socket One */ if (usbsid_config.socketOne.sid1.type != 0 && usbsid_config.socketOne.sid2.type == 1) { /* Unidentified SID as sid2? */ @@ -104,7 +104,7 @@ void verify_chipdetection_results(bool quiet) usbsid_config.socketOne.enabled = false; /* Disable the socket */ } } - if (!quiet) CFG("[SID] SOCKET ONE CHIPTYPE: %d CLONETYPE: %d ENABLED: %d DUALSID: %d\n", + if (!quiet) usCFG("[SID] SOCKET ONE CHIPTYPE: %d CLONETYPE: %d ENABLED: %d DUALSID: %d\n", usbsid_config.socketOne.chiptype, usbsid_config.socketOne.clonetype, usbsid_config.socketOne.enabled, usbsid_config.socketOne.dualsid); @@ -119,14 +119,14 @@ void verify_chipdetection_results(bool quiet) usbsid_config.socketTwo.enabled = false; /* Disable the socket */ } } - if (!quiet) CFG("[SID] SOCKET TWO CHIPTYPE: %d CLONETYPE: %d ENABLED: %d DUALSID: %d\n", + if (!quiet) usCFG("[SID] SOCKET TWO CHIPTYPE: %d CLONETYPE: %d ENABLED: %d DUALSID: %d\n", usbsid_config.socketTwo.chiptype, usbsid_config.socketTwo.clonetype, usbsid_config.socketTwo.enabled, usbsid_config.socketTwo.dualsid); } void verify_sid_addr(bool quiet) { /* Is run only from sid_detection.c:auto_detect_routine after detection in auto_config mode */ - if (!quiet) CFG("[SID] REASSIGN SID ADDRESS AND ID BY SOCKET CONFIG\n"); + if (!quiet) usCFG("[SID] REASSIGN SID ADDRESS AND ID BY SOCKET CONFIG\n"); /* Socket One */ if (usbsid_config.socketOne.enabled) { if (usbsid_config.socketOne.dualsid) { @@ -347,11 +347,11 @@ int verify_fmopl_sidno(void) void apply_fmopl_config(bool quiet) { /* TODO: REWORK */ - if (!quiet) CFG("[CONFIG] Checking for optional FMOpl\n"); + if (!quiet) usCFG("Checking for optional FMOpl\n"); /* FMOpl */ int fmoplsid = verify_fmopl_sidno(); if (fmoplsid != -1) { - if (!quiet) CFG("[CONFIG] FMOpl @ SIDNO %d\n", fmoplsid); + if (!quiet) usCFG("FMOpl @ SIDNO %d\n", fmoplsid); cfg.fmopl_enabled = usbsid_config.FMOpl.enabled = true; usbsid_config.FMOpl.sidno = cfg.fmopl_sid = fmoplsid; } else { @@ -403,7 +403,7 @@ void set_socket_config(uint8_t cmd, bool s1en, bool s1dual, uint8_t s1chip, bool void apply_socket_config(bool quiet) { - if (!quiet) CFG("[CONFIG] Applying socket settings\n"); + if (!quiet) usCFG("Applying socket settings\n"); cfg.mirrored = usbsid_config.mirrored; cfg.sock_one = usbsid_config.socketOne.enabled; @@ -492,7 +492,7 @@ bool check_socket_config_errors(void) */ void socket_config_fallback(void) { - CFG("[SET DEFAULT FALLBACK SOCKET CONFIG]\n"); + usCFG("[SET DEFAULT FALLBACK SOCKET CONFIG]\n"); /* Socket One */ usbsid_config.socketOne.chiptype = 2; /* unknown */ usbsid_config.socketOne.clonetype = 0; /* disabled */ diff --git a/src/dma.c b/src/dma.c index df1002e..0e6a97b 100644 --- a/src/dma.c +++ b/src/dma.c @@ -58,7 +58,7 @@ uint32_t rgb_value = 0; void setup_dmachannels(void) { /* NOTE: Do not manually assign DMA channels, this causes a Panic on the PicoW */ - CFG("[DMA CHANNELS INIT] START\n"); + usCFG("[DMA CHANNELS INIT] START\n"); /* NOTICE: DMA read address is disabled for now, it is causing confirmed desync on the rp2040 (rp2350 seems to works, but needs improving) */ /* NOTICE: DMA chaining on rp2350 causes desync with cycled writes, probably due to RP2350-E8 */ @@ -172,14 +172,14 @@ void setup_dmachannels(void) } #endif #if PICO_RP2350 - CFG("[DMA CHANNELS CLAIMED] C:%d TX:%d RX:%d D:%d CNT:%d\n", + usCFG("[DMA CHANNELS CLAIMED] C:%d TX:%d RX:%d D:%d CNT:%d\n", dma_tx_control, dma_tx_data, dma_rx_data, dma_tx_delay, dma_counter); #else - CFG("[DMA CHANNELS CLAIMED] C:%d TX:%d RX:%d D:%d CNTA:%d CNTB:%d\n", + usCFG("[DMA CHANNELS CLAIMED] C:%d TX:%d RX:%d D:%d CNTA:%d CNTB:%d\n", dma_tx_control, dma_tx_data, dma_rx_data, dma_tx_delay, dma_counter, dma_counter_chain); #endif - CFG("[DMA CHANNELS INIT] FINISHED\n"); + usCFG("[DMA CHANNELS INIT] FINISHED\n"); return; } @@ -211,7 +211,7 @@ void setup_vu_dma(void) void stop_dma_channels(void) { // TODO: Fix and finish per RP2040-E13 and RP2350-E5 - // CFG("[STOP DMA CHANNELS]\n"); + // usCFG("[STOP DMA CHANNELS]\n"); /* Clear any Interrupt enable bits as per RP2040-E13 */ // TODO: FINISH /* Atomically abort channels */ @@ -231,7 +231,7 @@ void stop_dma_channels(void) void start_dma_channels(void) { /* NOTE: DO NOT USE, THIS STARTS A TRANSFER IN THE CURRENT CONFIG */ /* Trigger -> start dma channels all at once */ - // CFG("[START DMA CHANNELS]\n"); + // usCFG("[START DMA CHANNELS]\n"); // dma_hw->multi_channel_trigger = (1 << dma_tx_delay) | (1 << dma_rx_data) | (1 << dma_tx_data) | (1 << dma_tx_control); return; } diff --git a/src/gpio.c b/src/gpio.c index 003b93a..d5902ad 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -76,7 +76,7 @@ void init_gpio(void) /* Detect clock signal */ int detect_clocksignal(void) { - CFG("[DETECT CLOCK] START\n"); + usCFG("[DETECT CLOCK] START\n"); int c = 0, r = 0; gpio_init(PHI1); gpio_set_pulls(PHI1, false, true); @@ -84,8 +84,8 @@ int detect_clocksignal(void) b = *BUSState; /* read complete bus */ r |= c = (b & bPIN(PHI1)) >> PHI1; } - CFG("[RESULT] %d: %s\n", r, (r == 0 ? "INTERNAL CLOCK" : "EXTERNAL CLOCK")); - CFG("[DETECT CLOCK] END\n"); + usCFG("[RESULT] %d: %s\n", r, (r == 0 ? "INTERNAL CLOCK" : "EXTERNAL CLOCK")); + usCFG("[DETECT CLOCK] END\n"); return r; /* 1 if clock detected */ } @@ -96,10 +96,10 @@ void toggle_audio_switch(void) /* BUG: This _can_ cause a hardfault on rp2350 */ b = *BUSState; /* read complete bus */ int audio_state = (b & bPIN(AU_SW)) >> AU_SW; /* Pinpoint current audio switch state */ audio_state ^= 1; - CFG("[CONFIG] TOGGLE AUDIO SWITCH TO: %d (%s)\n", audio_state, mono_stereo[audio_state]); + usCFG("TOGGLE AUDIO SWITCH TO: %d (%s)\n", audio_state, mono_stereo[audio_state]); tPIN(AU_SW); /* toggle mono <-> stereo */ } else { - CFG("[CONFIG] Audio switch is locked at %d (%s), toggle not applied\n", + usCFG("Audio switch is locked at %d (%s), toggle not applied\n", (int)usbsid_config.stereo_en, mono_stereo[(int)usbsid_config.stereo_en]); return; } @@ -110,12 +110,12 @@ void set_audio_switch(bool state) { /* Set the SPST switch */ #if defined(HAS_AUDIOSWITCH) if (!usbsid_config.lock_audio_sw) { - CFG("[CONFIG] SET AUDIO SWITCH TO: %d (%s)\n", state, mono_stereo[state]); + usCFG("SET AUDIO SWITCH TO: %d (%s)\n", state, mono_stereo[state]); if (state) { sPIN(AU_SW); /* set mono <-> stereo pin */ } else cPIN(AU_SW); /* clear mono <-> stereo pin */ } else { - CFG("[CONFIG] Audio switch is locked at %d (%s), requested change to %d (%s) ignored\n", + usCFG("Audio switch is locked at %d (%s), requested change to %d (%s) ignored\n", (int)usbsid_config.stereo_en, mono_stereo[(int)usbsid_config.stereo_en], state, mono_stereo[state]); return; } diff --git a/src/logging.h b/src/logging.h index c927a7b..da32ec0 100644 --- a/src/logging.h +++ b/src/logging.h @@ -31,7 +31,6 @@ extern "C" { /* Logging to USB uart */ - #ifdef USB_PRINTF /* TinyUSB libs */ #if __has_include("bsp/board_api.h") /* Needed to account for update in tinyUSB */ @@ -58,47 +57,71 @@ do { \ /* Logging macro's */ - -#ifdef MEM_DEBUG -#define MDBG(...) _US_DBG(__VA_ARGS__) +#ifdef LOG_FILENAME +/* Searches for '/' (Unix) or '\' (Windows) */ +#define __FILENAME__ \ + (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : \ + strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) + #define __DBG(fmt, ...) \ + _US_DBG("[%s:%d] " fmt, __FILENAME__, __LINE__ __VA_OPT__(,) __VA_ARGS__) #else -#define MDBG(...) ((void)0) + #define __DBG(...) _US_DBG(__VA_ARGS__) #endif +#define usNFO(...) __DBG(__VA_ARGS__) /* Info logging, cannot be turned off */ + #ifdef USBSID_DEBUG -#define DBG(...) _US_DBG(__VA_ARGS__) +#define usDBG(fmt, ...) __DBG("[DBG] " fmt __VA_OPT__(,) __VA_ARGS__) #else -#define DBG(...) ((void)0) +#define usDBG(...) ((void)0) #endif -#ifdef USBIO_DEBUG -#define IODBG(...) _US_DBG(__VA_ARGS__) +#ifdef USBSID_BOOTLOG +#define usBOOT(fmt, ...) __DBG("[BOOT] " fmt __VA_OPT__(,) __VA_ARGS__) #else -#define IODBG(...) ((void)0) +#define usBOOT(...) ((void)0) #endif #ifdef CONFIG_DEBUG -#define CFG(...) _US_DBG(__VA_ARGS__) +#define usCFG(fmt, ...) __DBG("[CFG] " fmt __VA_OPT__(,) __VA_ARGS__) +#else +#define usCFG(...) ((void)0) +#endif + +#ifdef MEM_DEBUG +#define usMEM(fmt, ...) __DBG("[MEM] " fmt __VA_OPT__(,) __VA_ARGS__) +#else +#define usMEM(...) ((void)0) +#endif + +#ifdef USBIO_DEBUG +#define usIO(fmt, ...) __DBG("[IO] " fmt __VA_OPT__(,) __VA_ARGS__) #else -#define CFG(...) ((void)0) +#define usIO(...) ((void)0) #endif #ifdef USBSIDGPIO_DEBUG -#define GPIODBG(...) _US_DBG(__VA_ARGS__) +#define usGPIO(fmt, ...) __DBG("[GPIO] " fmt __VA_OPT__(,) __VA_ARGS__) #else -#define GPIODBG(...) ((void)0) +#define usGPIO(...) ((void)0) #endif #ifdef MIDI_DEBUG -#define MIDBG(...) _US_DBG(__VA_ARGS__) +#define usMIDI(fmt, ...) __DBG("[MIDI] " fmt __VA_OPT__(,) __VA_ARGS__) #else -#define MIDBG(...) ((void)0) +#define usMIDI(...) ((void)0) #endif #ifdef MIDIVOICE_DEBUG -#define MVDBG(...) _US_DBG(__VA_ARGS__) +#define usMVCE(fmt, ...) __DBG("[MVOICE] " fmt __VA_OPT__(,) __VA_ARGS__) +#else +#define usMVCE(...) ((void)0) +#endif + +#ifdef ASID_DEBUG +#define usASID(fmt, ...) __DBG("[ASID] " fmt __VA_OPT__(,) __VA_ARGS__) #else -#define MVDBG(...) ((void)0) +#define usASID(...) ((void)0) #endif diff --git a/src/mcu.c b/src/mcu.c index 5a9cbbf..07bd36f 100644 --- a/src/mcu.c +++ b/src/mcu.c @@ -47,7 +47,7 @@ uint64_t mcu_get_unique_id(void) void mcu_reset(void) { - CFG("[MCU_RESET]\n"); + usCFG("[MCU_RESET]\n"); sleep_ms(100); // sleep some ms to let commands prior to reset settle or finish /* watchdog_enable(1, 1); */ watchdog_reboot(0, 0, 0); diff --git a/src/midi.c b/src/midi.c index 80f4110..aa7be52 100644 --- a/src/midi.c +++ b/src/midi.c @@ -69,7 +69,7 @@ queue_t cynthcart_queue; /* Initialize the midi handlers */ void midi_init(void) { - CFG("[MIDI INIT] START\n"); + usNFO("[MIDI] Init start\n"); /* Set initial stream state and index */ midimachine.bus = FREE; @@ -84,7 +84,7 @@ void midi_init(void) /* Start the processor of midi buffers */ midi_processor_init(); - CFG("[MIDI INIT] FINISHED\n"); + usNFO("[MIDI] Init finished\n"); } #ifdef ONBOARD_EMULATOR @@ -135,7 +135,7 @@ void emulator_disable(void) inline void emulator_reset(void) { - DBG("[MIDI] Emulator reset not implemented yet!\n"); + usMIDI("Emulator reset not implemented yet!\n"); return; } @@ -165,7 +165,7 @@ inline void handle_emulator_cc(void) inline void midi_buffer_task(uint8_t buffer) { if (midimachine.index != 0) { - if (midimachine.type != SYSEX) MIDBG(" [B%d]$%02x#%03d", midimachine.index, buffer, buffer); + if (midimachine.type != SYSEX) usMIDI(" [B%d]$%02x#%03d", midimachine.index, buffer, buffer); } if (buffer & 0x80) { /* Handle start byte */ @@ -214,7 +214,7 @@ inline void midi_buffer_task(uint8_t buffer) dtype = midi; /* Set data type to midi */ midimachine.midi_bytes = 2; if (midimachine.bus != CLAIMED && midimachine.type == NONE) { - if (midimachine.index == 0) MIDBG("[M][B%d]$%02x#%03d", midimachine.index, buffer, buffer); + if (midimachine.index == 0) usMIDI("[M][B%d]$%02x#%03d", midimachine.index, buffer, buffer); midimachine.type = MIDI; midimachine.state = RECEIVING; midimachine.bus = CLAIMED; @@ -232,7 +232,7 @@ inline void midi_buffer_task(uint8_t buffer) dtype = midi; /* Set data type to midi */ midimachine.midi_bytes = 3; if (midimachine.bus != CLAIMED && midimachine.type == NONE) { - if (midimachine.index == 0) MIDBG("[M][B%d]$%02x#%03d", midimachine.index, buffer, buffer); + if (midimachine.index == 0) usMIDI("[M][B%d]$%02x#%03d", midimachine.index, buffer, buffer); midimachine.type = MIDI; midimachine.state = RECEIVING; midimachine.bus = CLAIMED; @@ -253,7 +253,7 @@ inline void midi_buffer_task(uint8_t buffer) if (midimachine.type == MIDI) { /* if (midimachine.streambuffer[0] >= 0x80 || midimachine.streambuffer[0] <= 0xEF) { */ if (midimachine.index == midimachine.midi_bytes) { - MIDBG("\n"); + usMIDI("\n"); dtype = midi; /* Set data type to midi */ /* Do something fancy now */ @@ -282,11 +282,11 @@ inline void midi_buffer_task(uint8_t buffer) } else { /* Buffer is full, receiving to much data too handle, wait for message to end */ midimachine.state = WAITING_FOR_END; - MIDBG("[EXCESS][IDX]%02d %02x \n", midimachine.index, buffer); + usMIDI("[EXCESS][IDX]%02d %02x \n", midimachine.index, buffer); } } else if (midimachine.state == WAITING_FOR_END) { /* Consuming SysEx messages, nothing else to do */ - MIDBG("[EXCESS][IDX]%02d %02x \n", midimachine.index, buffer); + usMIDI("[EXCESS][IDX]%02d %02x \n", midimachine.index, buffer); } } } diff --git a/src/midi_handler.c b/src/midi_handler.c index b5f823f..aa88a66 100644 --- a/src/midi_handler.c +++ b/src/midi_handler.c @@ -103,14 +103,14 @@ void midi_programs_init(void) void midi_processor_init(void) { - CFG("[START MIDI HANLDER INIT]\n"); + usCFG("Start Midi handler init\n"); /* NOTE: Temporary - always init defaults */ memcpy(&CC, &midi_ccvalues_defaults, sizeof(midi_ccvalues)); midi_programs_init(); - CFG("[END MIDI HANLDER INIT]\n"); + usCFG("End Midi handler init\n"); } @@ -176,7 +176,7 @@ void process_midi(uint8_t *buffer, int size) } if (instr->pt[i].end == true) pulsetable = false; if (instr->wt[i].end == true) wavetable = false; - // DBG("%d:%d%d%d%d\n", i, pulsetable, instr->pt[i].end, wavetable, instr->wt[i].end); + // usDBG("%d:%d%d%d%d\n", i, pulsetable, instr->pt[i].end, wavetable, instr->wt[i].end); i++; if (pulsetable || wavetable) { cycled_delay_operation((uint16_t)(R_PAL - (writes * 6))); /* One table entry each per frame ;) */ @@ -185,6 +185,6 @@ void process_midi(uint8_t *buffer, int size) } } if (((buffer[0] & 0xF0) == 0x80) && (keys_pressed > 0)) keys_pressed--; - // DBG("[K]%d\n", keys_pressed); + // usDBG("[K]%d\n", keys_pressed); return; } diff --git a/src/pio.c b/src/pio.c index 751006f..394d974 100644 --- a/src/pio.c +++ b/src/pio.c @@ -118,8 +118,8 @@ void setup_piobus(void) uint32_t pico_hz = clock_get_hz(clk_sys); busclock_frequency = (float)pico_hz / (usbsid_config.clock_rate * 32) / 2; /* Clock frequency is 8 times the SID clock */ - CFG("[BUS CLK INIT] START\n"); - CFG("[PI CLK]@%luMHz [DIV]@%.2f [BUS CLK]@%.2f [CFG SID CLK]%d\n", + usBOOT("[BUS CLK INIT] START\n"); + usDBG("[PI CLK]@%luMHz [DIV]@%.2f [BUS CLK]@%.2f [CFG SID CLK]%d\n", (pico_hz / 1000 / 1000), busclock_frequency, ((float)pico_hz / busclock_frequency / 2), @@ -176,17 +176,17 @@ void setup_piobus(void) pio_sm_init(clkcnt_pio, sm_clkcnt, offset_clkcnt, &clkcnt_delay); pio_sm_set_enabled(clkcnt_pio, sm_clkcnt, true); } - CFG("[BUS CLK INIT] FINISHED\n"); + usDBG("[BUS CLK INIT] FINISHED\n"); return; } void sync_pios(bool at_boot) { /* Sync PIO's */ #if PICO_PIO_VERSION == 0 - CFG("[RESTART PIOS] Pico & Pico_w\n"); + usDBG("[RESTART PIOS] Pico & Pico_w\n"); pio_sm_restart(bus_pio, 0b1111); #elif PICO_PIO_VERSION > 0 /* NOTE: rp2350 only */ - CFG("[SYNC PIOS] Pico2\n"); + usDBG("[SYNC PIOS] Pico2\n"); pio_clkdiv_restart_sm_multi_mask(bus_pio, 0, 0b1111, 0); // pio_clkdiv_restart_sm_multi_mask(clkcnt_pio, 0, 0b0011, 0); /* TODO: SYNC COUNTER PIO WITH BUS PIO */ pio_clkdiv_restart_sm_multi_mask(clkcnt_pio, 0, 0b1000, 0); @@ -202,7 +202,7 @@ void sync_pios(bool at_boot) void restart_bus_clocks(void) { - CFG("[CLK RE-INIT] START\n"); + usDBG("[CLK RE-INIT] START\n"); uint32_t pico_hz = clock_get_hz(clk_sys); busclock_frequency = (float)pico_hz / (usbsid_config.clock_rate * 32) / 2; /* Clock frequency is 8 times the SID clock */ sidclock_frequency = (float)pico_hz / usbsid_config.clock_rate / 2; @@ -212,17 +212,17 @@ void restart_bus_clocks(void) pio_sm_set_clkdiv(bus_pio, sm_delay, busclock_frequency); pio_sm_set_clkdiv(clkcnt_pio, sm_clkcnt, busclock_frequency); - CFG("[PI CLK]@%luMHz [DIV]@%.2f [BUS CLK]@%.2f [CFG SID CLK]%d\n", + usDBG("[PI CLK]@%luMHz [DIV]@%.2f [BUS CLK]@%.2f [CFG SID CLK]%d\n", (pico_hz / 1000 / 1000), busclock_frequency, ((float)pico_hz / busclock_frequency / 2), (int)usbsid_config.clock_rate); - CFG("[PI CLK]@%luMHz [DIV]@%.2f [SID CLK]@%.2f [CFG SID CLK]%d\n", + usDBG("[PI CLK]@%luMHz [DIV]@%.2f [SID CLK]@%.2f [CFG SID CLK]%d\n", (pico_hz / 1000 / 1000), sidclock_frequency, ((float)pico_hz / sidclock_frequency / 2), (int)usbsid_config.clock_rate); - CFG("[CLK RE-INIT] FINISHED\n"); + usDBG("[CLK RE-INIT] FINISHED\n"); return; } @@ -253,8 +253,8 @@ void init_sidclock(void) uint32_t pico_hz = clock_get_hz(clk_sys); sidclock_frequency = (float)pico_hz / usbsid_config.clock_rate / 2; - CFG("[SID] CLK INIT START\n"); - CFG("[PI CLK]@%luMHz [DIV]@%.2f [SID CLK]@%.2f [CFG SID CLK]%d\n", + usBOOT("[SID] CLK INIT START\n"); + usNFO("[NFO] [PI CLK]@%luMHz [DIV]@%.2f [SID CLK]@%.2f [CFG SID CLK]%d\n", (pico_hz / 1000 / 1000), sidclock_frequency, ((float)pico_hz / sidclock_frequency / 2), @@ -263,7 +263,7 @@ void init_sidclock(void) sm_clock = 0; /* PIO0 SM0 */ pio_sm_claim(bus_pio, sm_clock); clock_program_init(bus_pio, sm_clock, offset_clock, PHI1, sidclock_frequency); - CFG("[SID] CLK INIT FINISHED\n"); + usBOOT("[SID] CLK INIT FINISHED\n"); return; } @@ -300,7 +300,7 @@ void setup_sidclock(void) /* De-init nMHz square wave output */ void deinit_sidclock(void) { - CFG("[SID] CLK DE-INIT\n"); + usDBG("[SID] CLK DE-INIT\n"); clock_program_deinit(bus_pio, sm_clock, offset_clock, clock_program); return; diff --git a/src/sid.c b/src/sid.c index e888599..f37ea7a 100644 --- a/src/sid.c +++ b/src/sid.c @@ -56,7 +56,7 @@ static uint8_t volume_state[4] = {0}; static void log_memory(uint8_t * sid_memory) { - DBG("[%c:%d][PWM]$%04x[V1]$%02X%02X$%02X%02X$%02X$%02X$%02X[V2]$%02X%02X$%02X%02X$%02X$%02X$%02X[V3]$%02X%02X$%02X%02X$%02X$%02X$%02X[FC]$%02x%02x$%02x[VOL]$%02x\n", + usDBG("[%c:%d][PWM]$%04x[V1]$%02X%02X$%02X%02X$%02X$%02X$%02X[V2]$%02X%02X$%02X%02X$%02X$%02X$%02X[V3]$%02X%02X$%02X%02X$%02X$%02X$%02X[FC]$%02x%02x$%02x[VOL]$%02x\n", dtype, usbdata, vu, sid_memory[0x01], sid_memory[0x00], sid_memory[0x03], sid_memory[0x02], sid_memory[0x04], sid_memory[0x05], sid_memory[0x06], sid_memory[0x08], sid_memory[0x07], sid_memory[0x0A], sid_memory[0x09], sid_memory[0x0B], sid_memory[0x0C], sid_memory[0x0D], @@ -135,14 +135,14 @@ bool get_paused_state(void) */ void unmute_sid(void) { - DBG("[UNMUTE]\n"); + usDBG("[UNMUTE]\n"); /* is_muted = false; */ /* Is globally handled from usbsid.c */ for (int i = 0; i < cfg.numsids; i++) { uint8_t addr = ((0x20 * i) + 0x18); if ((volume_state[i] & 0xF) == 0) volume_state[i] = (volume_state[i] & 0xF0) | 0x0E; sid_memory[addr] = volume_state[i]; cycled_write_operation(((0x20 * i) + 0x18), volume_state[i], 0); /* Volume back */ - DBG("[%d] $%02X:%02X\n", i, addr, volume_state[i]); + usDBG("[%d] $%02X:%02X\n", i, addr, volume_state[i]); } return; } @@ -152,12 +152,12 @@ void unmute_sid(void) */ void mute_sid(void) { - DBG("[MUTE]\n"); + usDBG("[MUTE]\n"); for (int i = 0; i < cfg.numsids; i++) { uint8_t addr = ((0x20 * i) + 0x18); volume_state[i] = sid_memory[addr]; cycled_write_operation(addr, (volume_state[i] & 0xF0), 0); /* Volume to 0 */ - DBG("[%d] $%02X:%02X\n", i, addr, (volume_state[i] & 0xF0)); + usDBG("[%d] $%02X:%02X\n", i, addr, (volume_state[i] & 0xF0)); } /* is_muted = true; */ /* Is globally handled from usbsid.c */ return; @@ -210,14 +210,14 @@ void pause_sid(void) void pause_sid_withmute(void) { - DBG("[PAUSE STATE PRE] %d\n", paused_state); + usDBG("[PAUSE STATE PRE] %d\n", paused_state); if (!paused_state) mute_sid(); if (paused_state) unmute_sid(); // set_gpio(CS1, 1); // set_gpio(CS2, 1); pause_sid(); set_paused_state(!paused_state); - DBG("[PAUSE STATE POST] %d\n", paused_state); + usDBG("[PAUSE STATE POST] %d\n", paused_state); return; } diff --git a/src/sid_cloneconfig.c b/src/sid_cloneconfig.c index 4aeb4c0..bc6cfa1 100644 --- a/src/sid_cloneconfig.c +++ b/src/sid_cloneconfig.c @@ -69,19 +69,20 @@ void print_fpgasid_sidconfig(int slot, int sidno, uint8_t * configarray) int addr3 = ((configarray[1] & 0b1) == 1 ? 3 : 4); int addr = ((addr1 + addr2 + addr3) == 0 ? 12 : 4); - CFG("\n"); - CFG("[FPGASID CURRENT CONFIG SLOT%s SID %d]\n", slots[slot], sidno); + usCFG("\n"); + usCFG("[FPGASID CURRENT CONFIG SLOT%s SID %d]\n", slots[slot], sidno); if (sidno == 1) { - CFG("OUTPUT MODE: %s\n", outputmode[outp]); - CFG("SID2 ADDRESSES: %s%s%s%s\n", sid2addr[addr], sid2addr[addr1], sid2addr[addr2], sid2addr[addr3]); + usCFG("OUTPUT MODE: %s\n", outputmode[outp]); + usCFG("SID2 ADDRESSES: %s%s%s%s\n", sid2addr[addr], sid2addr[addr1], sid2addr[addr2], sid2addr[addr3]); } - CFG("SOURCE EXTIN: %s\n", extinsource[extin]); - CFG("REGISTER READ: %s\n", readback[readb]); - CFG("REGISTER DELAY: %s\n", regdelay[regd]); - CFG("MIXED WAVEFORM: %s\n", mixedwave[mixw]); - CFG("CRUNCHY DAC: %s\n", crunchydac[crunch]); - CFG("ANALOG FILTER: %s\n", filtermode[fltr]); - CFG("DIGIFIX VALUE: %02X\n", configarray[0]); + usCFG("SOURCE EXTIN: %s\n", extinsource[extin]); + usCFG("REGISTER READ: %s\n", readback[readb]); + usCFG("REGISTER DELAY: %s\n", regdelay[regd]); + usCFG("MIXED WAVEFORM: %s\n", mixedwave[mixw]); + usCFG("CRUNCHY DAC: %s\n", crunchydac[crunch]); + usCFG("ANALOG FILTER: %s\n", filtermode[fltr]); + usCFG("DIGIFIX VALUE: %02X\n", configarray[0]); + usCFG("\n"); } void read_fpgasid_configuration(uint8_t base_address) @@ -89,7 +90,7 @@ void read_fpgasid_configuration(uint8_t base_address) if (((/* base_address >= 0x0 && */base_address < 0x40) && (usbsid_config.socketOne.clonetype != 4)) || ((base_address >= 0x40 && base_address < 0x80) && (usbsid_config.socketTwo.clonetype != 4))) { - CFG("[SID] ERROR INCORRECT ADDRESS (0x%02x) AND CLONETYPE (%d %d)\n", + usCFG("[SID] ERROR INCORRECT ADDRESS (0x%02x) AND CLONETYPE (%d %d)\n", base_address, usbsid_config.socketOne.clonetype, usbsid_config.socketTwo.clonetype); return; /* Do nothing if no FPGASID configured */ } @@ -100,7 +101,7 @@ void read_fpgasid_configuration(uint8_t base_address) uint8_t sid_two_a[3]; uint8_t sid_one_b[3]; uint8_t sid_two_b[3]; - CFG("[SID] READING CONFIGURATION FROM FPGASID @ 0x%02X\n", base_address); + usCFG("[SID] READING CONFIGURATION FROM FPGASID @ 0x%02X\n", base_address); /* Enable diag mode */ cycled_write_operation((0x19 + base_address), 0xEE, 6); /* Write magic cookie Hi */ cycled_write_operation((0x1A + base_address), 0xAB, 6); /* Write magic cookie Lo */ @@ -110,7 +111,7 @@ void read_fpgasid_configuration(uint8_t base_address) idHi = cycled_read_operation((0x1 + base_address), 4); /* Read identify Lo */ uint16_t fpgasid_id = (idHi << 8 | idLo); if (fpgasid_id != FPGASID_IDENTIFIER) { - CFG("[SID] ERROR: 0x%04X != 0x%04X FPGASID NOT FOUND @ 0x%02X\n", fpgasid_id, FPGASID_IDENTIFIER, base_address); + usCFG("[SID] ERROR: 0x%04X != 0x%04X FPGASID NOT FOUND @ 0x%02X\n", fpgasid_id, FPGASID_IDENTIFIER, base_address); return; } @@ -150,31 +151,31 @@ void read_fpgasid_configuration(uint8_t base_address) cycled_write_operation((0x19 + base_address), 0x0, 6); /* Clear magic cookie Hi */ cycled_write_operation((0x1A + base_address), 0x0, 6); /* Clear magic cookie Lo */ - CFG("\n"); - CFG("[FPGASID DIAGNOSTIC RESULT]\n"); - CFG("ID: %04X (FPGASID)\n", fpgasid_id); - CFG("CPLD REVISION: %02X\n", cpld); - CFG("FPGA REVISION: %02X\n", fpga); - CFG("PCA REVISION: %02X\n", pca); - CFG("UNIQUE IDENTIFIER: %02X%02X%02X%02X%02X%02X%02X%02X\n", + usCFG("\n"); + usCFG("[FPGASID DIAGNOSTIC RESULT]\n"); + usCFG("ID: %04X (FPGASID)\n", fpgasid_id); + usCFG("CPLD REVISION: %02X\n", cpld); + usCFG("FPGA REVISION: %02X\n", fpga); + usCFG("PCA REVISION: %02X\n", pca); + usCFG("UNIQUE IDENTIFIER: %02X%02X%02X%02X%02X%02X%02X%02X\n", unique_id[0], unique_id[1], unique_id[2], unique_id[3], unique_id[4], unique_id[5], unique_id[6], unique_id[7]); - CFG("CLOCK FREQUENCY READS: %.3f uS\n", frequency); - CFG("SELECT PINS: %02X\n", select_pins); - CFG("INDEX CFG A: %02X\n", idxa); - CFG("SID 1 A: %02X%02X%02X\n", sid_one_a[0], sid_one_a[1], sid_one_a[2]); - CFG("SID 2 A: %02X%02X%02X\n", sid_two_a[0], sid_two_a[1], sid_two_a[2]); - CFG("FILTERBIAS A SID1/SID2: %02X\n", flta); - CFG("INDEX CFG B: %02X\n", idxb); - CFG("SID 1 B: %02X%02X%02X\n", sid_one_b[0], sid_one_b[1], sid_one_b[2]); - CFG("SID 2 B: %02X%02X%02X\n", sid_two_b[0], sid_two_b[1], sid_two_b[2]); - CFG("FILTERBIAS B SID1/SID2: %02X\n", fltb); + usCFG("CLOCK FREQUENCY READS: %.3f uS\n", frequency); + usCFG("SELECT PINS: %02X\n", select_pins); + usCFG("INDEX CFG A: %02X\n", idxa); + usCFG("SID 1 A: %02X%02X%02X\n", sid_one_a[0], sid_one_a[1], sid_one_a[2]); + usCFG("SID 2 A: %02X%02X%02X\n", sid_two_a[0], sid_two_a[1], sid_two_a[2]); + usCFG("FILTERBIAS A SID1/SID2: %02X\n", flta); + usCFG("INDEX CFG B: %02X\n", idxb); + usCFG("SID 1 B: %02X%02X%02X\n", sid_one_b[0], sid_one_b[1], sid_one_b[2]); + usCFG("SID 2 B: %02X%02X%02X\n", sid_two_b[0], sid_two_b[1], sid_two_b[2]); + usCFG("FILTERBIAS B SID1/SID2: %02X\n", fltb); print_fpgasid_sidconfig(0, 1, sid_one_a); print_fpgasid_sidconfig(0, 2, sid_two_a); print_fpgasid_sidconfig(1, 1, sid_one_b); print_fpgasid_sidconfig(1, 2, sid_two_b); - CFG("\n"); + usCFG("\n"); return; } @@ -182,52 +183,54 @@ void read_fpgasid_configuration(uint8_t base_address) void print_skpico_configuration(uint8_t base_address, uint8_t * configarray) { - CFG("\n"); - CFG("[SIDKICK-pico @ 0x%02X CONFIG]\n", base_address); + usCFG("\n"); + usCFG("[SIDKICK-pico @ 0x%02X CONFIG]\n", base_address); for (size_t i = 0; i < 64; i++) { if (i >= 4 && i <= 7) continue; if (i >= 13 && i <= 56) continue; if (i == 62 || i == 63) continue; if (i == 0 || i == 8) { - CFG("[%02ld] %s: %02X ~ %s\n", i, config_names[i], configarray[i], (configarray[i] < s_sid_types) ? (char*)sid_types[configarray[i]] : (char*)error_type[0]); + usCFG("[%02ld] %s: %02X ~ %s\n", i, config_names[i], configarray[i], (configarray[i] < s_sid_types) ? (char*)sid_types[configarray[i]] : (char*)error_type[0]); continue; } if (i == 10) { - CFG("[%02ld] %s: %02X ~ %s\n", i, config_names[i], configarray[i], (configarray[i] < s_sid2_address) ? (char*)sid2_address[configarray[i]] : (char*)error_type[0]); + usCFG("[%02ld] %s: %02X ~ %s\n", i, config_names[i], configarray[i], (configarray[i] < s_sid2_address) ? (char*)sid2_address[configarray[i]] : (char*)error_type[0]); continue; } if (i == 59) { - CFG("[%02ld] %s: %02X ~ %s\n", i, config_names[i], configarray[i], (configarray[i] < s_clock_speed) ? (char*)clock_speed[configarray[i]] : (char*)error_type[0]); + usCFG("[%02ld] %s: %02X ~ %s\n", i, config_names[i], configarray[i], (configarray[i] < s_clock_speed) ? (char*)clock_speed[configarray[i]] : (char*)error_type[0]); continue; } - CFG("[%02ld] %s: %02X\n", i, config_names[i], configarray[i]); + usCFG("[%02ld] %s: %02X\n", i, config_names[i], configarray[i]); } - CFG("[PRINT CFG SETTINGS END]\n"); + usCFG("[PRINT CFG SETTINGS END]\n"); + usCFG("\n"); + } void read_skpico_configuration(uint8_t base_address) { /* Enter config mode */ cycled_write_operation((init_configmode[0] + base_address), init_configmode[1], 6); - /* CFG("[W]$%02X:%02X\n", (init_configmode[0] + base_address), init_configmode[1]); */ + /* usCFG("[W]$%02X:%02X\n", (init_configmode[0] + base_address), init_configmode[1]); */ /* Read config from SKPico */ for (int i = 0; i <= 63; ++i) { sleep_us(1); skpico_config[i] = cycled_read_operation((0x1D + base_address), 0); - /* CFG("[R%d]$%02X:%02X\n", i, (0x1D + base_address), skpico_config[i]); */ + /* usCFG("[R%d]$%02X:%02X\n", i, (0x1D + base_address), skpico_config[i]); */ } /* Exit config mode */ cycled_write_operation((config_exit[0] + base_address), config_exit[1], 6); - /* CFG("[W]$%02X:%02X\n", (config_exit[0] + base_address), config_exit[1]); */ + /* usCFG("[W]$%02X:%02X\n", (config_exit[0] + base_address), config_exit[1]); */ - CFG("\n"); - CFG("[SKPICO RECEIVED BUFFER]\n"); + usCFG("\n"); + usCFG("[SKPICO RECEIVED BUFFER]\n"); print_cfg(skpico_config, 64); print_skpico_configuration(base_address, skpico_config); - CFG("\n"); + usCFG("\n"); } @@ -242,11 +245,11 @@ void switch_pdsid_type(void) set_paused_state(false); // paused_state = 0; clear_volume_state(); // memset(volume_state, 0, 4); clear_sid_memory(); // memset(sid_memory, 0, count_of(sid_memory)); - CFG("[SID] RST PIN LOW\n"); + usCFG("[SID] RST PIN LOW\n"); cPIN(RES); // gpio_put(RES, 0); - CFG("[SID] SLEEP\n"); + usCFG("[SID] SLEEP\n"); sleep_ms(6000); - CFG("[SID] RST PIN HIGH\n"); + usCFG("[SID] RST PIN HIGH\n"); sPIN(RES); // gpio_put(RES, 1); set_reset_state(false); // reset_state = 0; return; diff --git a/src/sid_detection.c b/src/sid_detection.c index ee0cb1b..613a354 100644 --- a/src/sid_detection.c +++ b/src/sid_detection.c @@ -69,7 +69,7 @@ uint8_t (*sid_detection[4])(uint8_t) = { detect_sid_model, detect_sid_version, d /* This routine works on real MOS SID chips and does not work on SKPico */ uint8_t detect_sid_model(uint8_t start_addr) { /* https://github.com/GideonZ/1541ultimate/blob/master/software/6502/sidcrt/player/advanced/detection.asm */ - CFG("[SID] DETECT SID MODEL\n"); + usCFG("[SID] DETECT SID MODEL\n"); clear_bus(start_addr / 0x20); int restart = 0; restart: @@ -90,24 +90,24 @@ uint8_t detect_sid_model(uint8_t start_addr) if (restart == 3) goto end; if (sidtype != 0 && sidtype != 1) { restart++; - CFG("[SID] RESTART %d (ST:%u)\n", restart, sidtype); + usCFG("[SID] RESTART %d (ST:%u)\n", restart, sidtype); goto restart; } end: /* output 0 = 8580, 1 = 6581, 2 = unknown * that is: Carry flag is set for 6581, and clear for 8580. */ - CFG("[SID] 0x%02X detect_sid_model sidtype raw %02X\n", start_addr, sidtype); - CFG("[SID] 0x%02X detect_sid_model readtest %02X\n", start_addr, readtest); /* SHOULD READ 3 */ + usCFG("[SID] 0x%02X detect_sid_model sidtype raw %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_model readtest %02X\n", start_addr, readtest); /* SHOULD READ 3 */ sidtype = (sidtype == 0 ? 2 : sidtype == 1 ? 3 : 0); /* return 0 = unknown, 2 = 8580, 3 = 6581 */ if (readtest != 3) sidtype = 0; /* if the readtest does not read 3, return unknown */ - CFG("[SID] 0x%02X detect_sid_model return %u\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_model return %u\n", start_addr, sidtype); return sidtype; } /* This routine works on real MOS SID chips and does not work on SKPico */ uint8_t detect_sid_version(uint8_t start_addr) { /* https://codebase64.org/doku.php?id=base:detecting_sid_type_-_safe_method */ - CFG("[SID] DETECT SID VERSION\n"); + usCFG("[SID] DETECT SID VERSION\n"); int restart = 0; restart: /* LDA #$ff ~ 2 cycles */ @@ -126,21 +126,21 @@ uint8_t detect_sid_version(uint8_t start_addr) if (restart == 3) goto end; if (sidtype != 2 && sidtype != 3) { restart++; - CFG("[SID] RESTART %d (ST:%u)\n", restart, sidtype); + usCFG("[SID] RESTART %d (ST:%u)\n", restart, sidtype); goto restart; } end: restart = 0; - CFG("[SID] 0x%02X detect_sid_version raw %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_version raw %02X\n", start_addr, sidtype); // sidtype = (sidtype < 4 ? sidtype : 0); sidtype = ((sidtype >= 1) && (sidtype % 2) == 0 ? 2 : 3); - CFG("[SID] 0x%02X detect_sid_version return %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_version return %02X\n", start_addr, sidtype); return sidtype; } uint8_t detect_sid_unsafe(uint8_t start_addr) { /* https://codebase64.org/doku.php?id=base:detecting_sid_type */ - CFG("[SID] DETECT SID UNSAFE\n"); + usCFG("[SID] DETECT SID UNSAFE\n"); /* clear sid registers */ for (uint reg = 0; reg < count_of(sid_registers) - 4; reg++) { /* STA $D400,x ~ 5 cycles */ @@ -160,17 +160,17 @@ uint8_t detect_sid_unsafe(uint8_t start_addr) /* LDA $d41b ~ 4 cycles */ uint8_t sidtype = cycled_read_operation((start_addr + 0x1B), 8); - CFG("[SID] 0x%02X detect_sid_version raw %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_version raw %02X\n", start_addr, sidtype); // sidtype = (sidtype < 4 ? sidtype : 0); sidtype = ((sidtype >= 1) && (sidtype % 2) == 0 ? 2 : 3); - CFG("[SID] 0x%02X detect_sid_version return %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_version return %02X\n", start_addr, sidtype); return sidtype; } /* This routine works on SKPico and does not work on real MOS SID chips */ uint8_t detect_sid_version_skpico(uint8_t start_addr) /* Not working on real SIDS!? */ { /* https://codebase64.org/doku.php?id=base:detecting_sid_type_-_safe_method */ - CFG("[SID] DETECT SID VERSION SKPICO\n"); + usCFG("[SID] DETECT SID VERSION SKPICO\n"); int restart = 0; restart: /* LDA #$ff ~ 2 cycles */ @@ -191,20 +191,20 @@ uint8_t detect_sid_version_skpico(uint8_t start_addr) /* Not working on real SI if (restart == 3) goto end; if (sidtype != 2 && sidtype != 3) { restart++; - CFG("[SID] RESTART %d (ST:%u)\n", restart, sidtype); + usCFG("[SID] RESTART %d (ST:%u)\n", restart, sidtype); goto restart; } end: restart = 0; - CFG("[SID] 0x%02X detect_sid_version_skpico raw %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_version_skpico raw %02X\n", start_addr, sidtype); sidtype = (sidtype < 4 ? sidtype : 0); - CFG("[SID] 0x%02X detect_sid_version_skpico %02X\n", start_addr, sidtype); + usCFG("[SID] 0x%02X detect_sid_version_skpico %02X\n", start_addr, sidtype); return sidtype; /* that is: Carry flag is set for 6581, and clear for 8580. */ } bool detect_skpico(uint8_t base_address) { - CFG("[SID] CHECKING FOR SIDKICK-pico @ 0x%02X\n", base_address); + usCFG("[SID] CHECKING FOR SIDKICK-pico @ 0x%02X\n", base_address); /* SKPico routine */ char skpico_version[36] = {0}; cycled_write_operation((0x1F + base_address), 0xFF, 10); /* Init config mode */ @@ -222,7 +222,7 @@ bool detect_skpico(uint8_t base_address) && skpico_version[3] == 0x69 && skpico_version[4] == 0x63 && skpico_version[5] == 0x6F) { - CFG("[SID] SIDKICK-pico @ 0x%02X version is: %.36s\n", base_address, skpico_version); + usCFG("[SID] SIDKICK-pico @ 0x%02X version is: %.36s\n", base_address, skpico_version); return true; } return false; @@ -230,7 +230,7 @@ bool detect_skpico(uint8_t base_address) bool detect_fpgasid(uint8_t base_address) { - CFG("[SID] CHECKING FOR FPGASID @ 0x%02X\n", base_address); + usCFG("[SID] CHECKING FOR FPGASID @ 0x%02X\n", base_address); uint8_t idHi, idLo; /* Enable configuration mode (if available) */ cycled_write_operation((0x19 + base_address), 0x80, 6); /* Write magic cookie Hi */ @@ -243,9 +243,9 @@ bool detect_fpgasid(uint8_t base_address) cycled_write_operation((0x19 + base_address), 0x0, 6); /* Clear magic cookie Hi */ cycled_write_operation((0x1A + base_address), 0x0, 6); /* Clear magic cookie Lo */ uint16_t fpgasid_id = (idHi << 8 | idLo); - CFG("[SID] READ IDENTIFY 0x%04X (0x%02X,0x%02X) @ 0x%02X\n", fpgasid_id, idHi, idLo, base_address); + usCFG("[SID] READ IDENTIFY 0x%04X (0x%02X,0x%02X) @ 0x%02X\n", fpgasid_id, idHi, idLo, base_address); if (fpgasid_id == FPGASID_ID) { - CFG("[SID] Found FPGASID @ 0x%02X\n", base_address); + usCFG("[SID] Found FPGASID @ 0x%02X\n", base_address); return true; } return false; @@ -253,7 +253,7 @@ bool detect_fpgasid(uint8_t base_address) bool detect_fmopl(uint8_t base_address) { - CFG("[SID] DETECT FMOPL @: %02X\n", base_address); + usCFG("[SID] DETECT FMOPL @: %02X\n", base_address); int restart = 0; restart: cycled_write_operation(base_address, 0x04, 10); @@ -261,7 +261,7 @@ bool detect_fmopl(uint8_t base_address) cycled_write_operation(base_address, 0x04, 10); cycled_write_operation((base_address + 0x10), 0x80, 10); uint8_t r = cycled_read_operation(base_address, 10); - CFG("[SID] READ FMOPL = 0xC0? %02X\n", r); + usCFG("[SID] READ FMOPL = 0xC0? %02X\n", r); if (restart == 3) goto end; if (r != 0xC0) { restart++; @@ -306,7 +306,7 @@ uint8_t detect_sid_type(Socket * socket, SIDChip * sidchip) int detection_routine = (socket->clonetype != 2 ? 0 : 2); if (sidchip->addr != 0xFF) { uint8_t sid = sid_detection[detection_routine](sidchip->addr); - CFG("[CONFIG] [READ SID%d] [%02x %s]\n", (sidchip->id + 1), sid, sidtypes[sid]); + usCFG("[READ SID%d] [%02x %s]\n", (sidchip->id + 1), sid, sidtypes[sid]); sidchip->type = sid; goto done_sid; } @@ -320,7 +320,7 @@ uint8_t detect_sid_type(Socket * socket, SIDChip * sidchip) apply_fmopl_config(false); }; } - CFG("[CONFIG] SOCKET ONE SID%d TYPE: %s\n", sidchip->id, sidtypes[sidchip->type]); + usCFG("SOCKET ONE SID%d TYPE: %s\n", sidchip->id, sidtypes[sidchip->type]); return sidchip->type; } @@ -330,10 +330,10 @@ uint8_t detect_sid_type(Socket * socket, SIDChip * sidchip) */ void auto_detect_routine(bool auto_config, bool with_delay) { - CFG("\n"); - CFG("[START AUTO DETECT ROUTINE]\n"); + usCFG("\n"); + usCFG("[START AUTO DETECT ROUTINE]\n"); if (auto_config) { - CFG("[SID] SET AUTO CONFIG DEFAULT VALUES\n"); + usCFG("[SID] SET AUTO CONFIG DEFAULT VALUES\n"); usbsid_config.mirrored = false; /* Yeah let's just disable that for now okay? */ /* Socket One */ @@ -364,11 +364,11 @@ void auto_detect_routine(bool auto_config, bool with_delay) apply_socket_change(true); if (with_delay) sleep_ms(250); /* Stupid workaround for SKPico requiring a zillion ms to boot up */ } - CFG("[SID] START CHIP TYPE DETECTION FOR SOCKET ONE\n"); + usCFG("[SID] START CHIP TYPE DETECTION FOR SOCKET ONE\n"); if (with_delay) sleep_ms(500); /* Stupid workaround for SKPico requiring a zillion ms to boot up */ /* SocketOne (twice if failed) */ if (detect_clone_type(&usbsid_config.socketOne) == 0) detect_clone_type(&usbsid_config.socketOne); - CFG("[SID] START CHIP TYPE DETECTION FOR SOCKET TWO\n"); + usCFG("[SID] START CHIP TYPE DETECTION FOR SOCKET TWO\n"); if (with_delay) sleep_ms(500); /* SocketTwo (twice if failed) */ if (detect_clone_type(&usbsid_config.socketTwo) == 0) detect_clone_type(&usbsid_config.socketTwo); @@ -378,11 +378,11 @@ void auto_detect_routine(bool auto_config, bool with_delay) if (with_delay) sleep_ms(250); /* Stupid workaround for SKPico requiring a zillion ms to boot up */ /* Detect SID types at default config once, uses the chiptype to define the detection routine */ - CFG("[SID] START SID TYPE DETECTION FOR SOCKET ONE\n"); + usCFG("[SID] START SID TYPE DETECTION FOR SOCKET ONE\n"); detect_sid_type(&usbsid_config.socketOne, &usbsid_config.socketOne.sid1); if (usbsid_config.socketOne.dualsid) detect_sid_type(&usbsid_config.socketOne, &usbsid_config.socketOne.sid2); - CFG("[SID] START SID TYPE DETECTION FOR SOCKET TWO\n"); + usCFG("[SID] START SID TYPE DETECTION FOR SOCKET TWO\n"); detect_sid_type(&usbsid_config.socketTwo, &usbsid_config.socketTwo.sid1); if (usbsid_config.socketTwo.dualsid) detect_sid_type(&usbsid_config.socketTwo, &usbsid_config.socketTwo.sid2); @@ -393,10 +393,11 @@ void auto_detect_routine(bool auto_config, bool with_delay) /* Apply new socket and bus config */ apply_socket_change(true); - CFG("[END AUTO DETECT ROUTINE]\n"); + usCFG("[END AUTO DETECT ROUTINE]\n"); if (check_socket_config_errors()) { - CFG("[AUTO DETECT CONFIG ERROR DETECTED]\n"); + usCFG("[AUTO DETECT CONFIG ERROR DETECTED]\n"); socket_config_fallback(); } + usCFG("\n"); } diff --git a/src/sid_tests.c b/src/sid_tests.c index 44ad03e..4d9e518 100644 --- a/src/sid_tests.c +++ b/src/sid_tests.c @@ -83,29 +83,29 @@ void test_all_waveforms(uint8_t addr, uint8_t voices[3]) test_operation((sid_registers[MODVOL] + addr), 0x0F); /* Volume to full */ for (int v = 0; v < 3; v++) { if (!running_tests) { reset_sid_registers(); return; }; - DBG("TEST VOICE %d\n", (v + 1)); - DBG("WAVEFORM TESTS\n"); + usDBG("TEST VOICE %d\n", (v + 1)); + usDBG("WAVEFORM TESTS\n"); test_operation((voices[v] + sid_registers[ATTDEC]), 33); /* ATTDEC 2*16+1 */ test_operation((voices[v] + sid_registers[SUSREL]), 242); /* SUSREL 15*16+2 */ test_operation((voices[v] + sid_registers[PWMHI]), 8); /* PWMHI */ - DBG("TRIANGLE TESTING "); + usDBG("TRIANGLE TESTING "); wave_form_test(voices, v, 17, 16); - DBG("COMPLETE\n"); - DBG("SAWTOOTH TESTING "); + usDBG("COMPLETE\n"); + usDBG("SAWTOOTH TESTING "); wave_form_test(voices, v, 33, 32); - DBG("COMPLETE\n"); - DBG("PULSE TESTING "); + usDBG("COMPLETE\n"); + usDBG("PULSE TESTING "); wave_form_test(voices, v, 65, 64); - DBG("COMPLETE\n"); - DBG("NOISE TESTING "); + usDBG("COMPLETE\n"); + usDBG("NOISE TESTING "); wave_form_test(voices, v, 129, 128); - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); - DBG("PULSE WIDTH SWEEP TESTING "); + usDBG("PULSE WIDTH SWEEP TESTING "); test_operation((voices[v] + sid_registers[NOTEHI]), 40); /* NOTEHI */ pulse_sweep_test(voices, v, 65, 64); - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); } } @@ -113,18 +113,18 @@ void filter_tests(uint8_t addr, uint8_t voices[3], int wf) { if (!running_tests) { reset_sid_registers(); return; }; test_operation((sid_registers[MODVOL] + addr), 0x0F); /* Volume to full */ - DBG("FILTER TESTS %s\n", (wf == 0 ? "TRIANGLE" : wf == 1 ? "SAWTOOTH" : wf == 2 ? "PULSE" : "NOISE")); + usDBG("FILTER TESTS %s\n", (wf == 0 ? "TRIANGLE" : wf == 1 ? "SAWTOOTH" : wf == 2 ? "PULSE" : "NOISE")); for (int fq = 15; fq <= 45; fq += 15) { if (!running_tests) { reset_sid_registers(); return; }; - DBG("HIGH FILTER FREQUENCY = %d\n", fq); + usDBG("HIGH FILTER FREQUENCY = %d\n", fq); test_operation(sid_registers[RESFLT], 87); /* RESFLT 5*16+1+2+4 */ for (int flt = 1; flt <= 3; flt++) { if (!running_tests) { reset_sid_registers(); return; }; - DBG("%s PASS\n", (flt == 1 ? "LOW" : flt == 2 ? "BAND" : "HIGH")); + usDBG("%s PASS\n", (flt == 1 ? "LOW" : flt == 2 ? "BAND" : "HIGH")); test_operation(sid_registers[MODVOL], (flt == 1 ? 31 : flt == 2 ? 47 : 79)); /* MODVOL */ for (int v = 0; v < 3; v++) { if (!running_tests) { reset_sid_registers(); return; }; - DBG("VOICE %d TESTING ", (v + 1)); + usDBG("VOICE %d TESTING ", (v + 1)); test_operation((voices[v] + sid_registers[NOTEHI]), fq); /* NOTEHI */ test_operation((voices[v] + sid_registers[ATTDEC]), 0); /* ATTDEC */ test_operation((voices[v] + sid_registers[SUSREL]), 240); /* SUSREL */ @@ -136,7 +136,7 @@ void filter_tests(uint8_t addr, uint8_t voices[3], int wf) sleep_ms(8); } test_operation((voices[v] + sid_registers[CONTR]), waveforms[wf]); /* CONTR */ - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); } } } @@ -146,10 +146,10 @@ void envelope_tests(uint8_t addr, uint8_t voices[3], int wf) { if (!running_tests) { reset_sid_registers(); return; }; test_operation((sid_registers[MODVOL] + addr), 0x0F); /* Volume to full */ - DBG("A D S R TESTS %s\n", (wf == 0 ? "TRIANGLE" : wf == 1 ? "SAWTOOTH" : wf == 2 ? "PULSE" : "NOISE")); + usDBG("A D S R TESTS %s\n", (wf == 0 ? "TRIANGLE" : wf == 1 ? "SAWTOOTH" : wf == 2 ? "PULSE" : "NOISE")); for (int v = 0; v < 3; v++) { if (!running_tests) { reset_sid_registers(); return; }; - DBG("VOICE %d A D S R TESTING ", (v + 1)); + usDBG("VOICE %d A D S R TESTING ", (v + 1)); test_operation((voices[v] + sid_registers[ATTDEC]), 170); /* ATTDEC 10*16+10 */ test_operation((voices[v] + sid_registers[SUSREL]), 58); /* SUSREL 3*16+10 */ test_operation((voices[v] + sid_registers[NOTEHI]), 40); /* NOTEHI */ @@ -158,38 +158,38 @@ void envelope_tests(uint8_t addr, uint8_t voices[3], int wf) if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(3000); test_operation((voices[v] + sid_registers[CONTR]), waveforms[wf]); /* CONTR */ - DBG("RELEASE "); + usDBG("RELEASE "); if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(1000); - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(600); - DBG("VOICE %d S R TESTING ", (v + 1)); + usDBG("VOICE %d S R TESTING ", (v + 1)); test_operation((voices[v] + sid_registers[ATTDEC]), 0); /* ATTDEC */ test_operation((voices[v] + sid_registers[SUSREL]), 250); /* SUSREL 15*16+10 */ test_operation((voices[v] + sid_registers[CONTR]), (waveforms[wf] + 1)); /* CONTR */ if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(600); test_operation((voices[v] + sid_registers[CONTR]), waveforms[wf]); /* CONTR */ - DBG("RELEASE "); + usDBG("RELEASE "); if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(600); - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(600); - DBG("VOICE %d A D TESTING ", (v + 1)); + usDBG("VOICE %d A D TESTING ", (v + 1)); test_operation((voices[v] + sid_registers[ATTDEC]), 170); /* ATTDEC 10*16+10 */ test_operation((voices[v] + sid_registers[SUSREL]), 0); /* SUSREL */ test_operation((voices[v] + sid_registers[CONTR]), (waveforms[wf] + 1)); /* CONTR */ if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(2000); test_operation((voices[v] + sid_registers[CONTR]), waveforms[wf]); /* CONTR */ - DBG("RELEASE "); + usDBG("RELEASE "); if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(600); - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); if (!running_tests) { reset_sid_registers(); return; }; sleep_ms(600); } @@ -199,11 +199,11 @@ void modulation_tests(uint8_t addr, uint8_t voices[3], int wf) { if (!running_tests) { reset_sid_registers(); return; }; test_operation((sid_registers[MODVOL] + addr), 0x0F); /* Volume to full */ - DBG("RING MODULATION TESTS %s\n", (wf == 0 ? "TRIANGLE" : wf == 1 ? "SAWTOOTH" : wf == 2 ? "PULSE" : "NOISE")); + usDBG("RING MODULATION TESTS %s\n", (wf == 0 ? "TRIANGLE" : wf == 1 ? "SAWTOOTH" : wf == 2 ? "PULSE" : "NOISE")); for (int v = 0; v < 3; v++) { if (!running_tests) { reset_sid_registers(); return; }; int v2 = (v == 1 ? 0 : v == 2 ? 1 : 2); - DBG("VOICE %d WITH VOICE %d ", (v + 1), (v2 + 1)); + usDBG("VOICE %d WITH VOICE %d ", (v + 1), (v2 + 1)); test_operation((voices[v] + sid_registers[PWMHI]), 8); /* PWMHI */ test_operation((voices[v] + sid_registers[ATTDEC]), 0); /* ATTDEC */ test_operation((voices[v] + sid_registers[SUSREL]), 250); /* SUSREL 15*16+10 */ @@ -215,7 +215,7 @@ void modulation_tests(uint8_t addr, uint8_t voices[3], int wf) sleep_ms(24); } test_operation((voices[v] + sid_registers[CONTR]), 0); /* CONTR */ - DBG("COMPLETE\n"); + usDBG("COMPLETE\n"); } } diff --git a/src/uart.c b/src/uart.c index bde7b62..13cf47b 100644 --- a/src/uart.c +++ b/src/uart.c @@ -119,11 +119,11 @@ static void async_worker_func(__unused async_context_t *async_context, __unused if (!queue_try_remove(&fifo_uartrx, &uart_buffer[bytes_rxed])) { panic("fifo_uartrx empty"); } else { - // DBG("[%d/%d] %02X $%02X:%02X\n",i,bytes_per_rxpacket,uart_buffer[0],uart_buffer[1],uart_buffer[2]); + // usDBG("[%d/%d] %02X $%02X:%02X\n",i,bytes_per_rxpacket,uart_buffer[0],uart_buffer[1],uart_buffer[2]); if ((uart_buffer[0] == 0xFF && uart_buffer[1] == 0xFF) || (uart_buffer[1] == 0xFF && uart_buffer[2] == 0xFF) || (uart_buffer[2] == 0xFF && uart_buffer[3] == 0xFF)) { - DBG("[UART CONFIG] RESET\n"); + usDBG("[UART CONFIG] RESET\n"); memset(uart_buffer, 0, 8); bytes_per_rxpacket = 8; bytes_rxed = 0; @@ -150,7 +150,7 @@ static void async_worker_func(__unused async_context_t *async_context, __unused && uart_buffer[6] == 0xEE && uart_buffer[7] == 0xFF) { /* Receiving initiator packet */ bytes_per_rxpacket = (size_t)(uart_buffer[3] << 8 | uart_buffer[4]); - DBG("[UART CONFIG] BYTES PER PACKET SET TO %d\n", bytes_per_rxpacket); + usDBG("[UART CONFIG] BYTES PER PACKET SET TO %d\n", bytes_per_rxpacket); memset(uart_buffer, 0, 8); bytes_rxed = 0; } diff --git a/src/usbsid.c b/src/usbsid.c index bfe8eb8..dd3da44 100644 --- a/src/usbsid.c +++ b/src/usbsid.c @@ -184,27 +184,27 @@ static const tusb_desc_webusb_url_t desc_url = void reset_reason(void) { #if PICO_RP2040 - DBG("[RESET] Button double tapped? %d\n", flagged); + usNFO("[RESET] Button double tapped? %d\n", flagged); io_rw_32 *rr = (io_rw_32 *) (VREG_AND_CHIP_RESET_BASE + VREG_AND_CHIP_RESET_CHIP_RESET_OFFSET); if (*rr & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_POR_BITS) - DBG("[RESET] Caused by power-on reset or brownout detection\n"); + usNFO("[RESET] Caused by power-on reset or brownout detection\n"); if (*rr & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_RUN_BITS) - DBG("[RESET] Caused by RUN pin trigger ~ manual or ISA RESET signal\n"); + usNFO("[RESET] Caused by RUN pin trigger ~ manual or ISA RESET signal\n"); if (*rr & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_PSM_RESTART_BITS) - DBG("[RESET] Caused by debug port\n"); + usNFO("[RESET] Caused by debug port\n"); #elif PICO_RP2350 /* io_rw_32 *rr = (io_rw_32 *) (POWMAN_BASE + POWMAN_CHIP_RESET_OFFSET); */ - DBG("[RESET] Button double tapped? %d %X\n", flagged, powman_hw->chip_reset); + usNFO("[RESET] Button double tapped? %d %X\n", flagged, powman_hw->chip_reset); if (/* *rr */ powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_DP_RESET_REQ_BITS) - DBG("[RESET] Caused by arm debugger\n"); + usNFO("[RESET] Caused by arm debugger\n"); if (/* *rr */ powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_RESCUE_BITS) - DBG("[RESET] Caused by rescure reset from arm debugger\n"); + usNFO("[RESET] Caused by rescure reset from arm debugger\n"); if (/* *rr */ powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_RUN_LOW_BITS) - DBG("[RESET] Caused by RUN pin trigger ~ manual or ISA RESET signal\n"); + usNFO("[RESET] Caused by RUN pin trigger ~ manual or ISA RESET signal\n"); if (/* *rr */ powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_BOR_BITS) - DBG("[RESET] Caused by brownout detection\n"); + usNFO("[RESET] Caused by brownout detection\n"); if (/* *rr */ powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_POR_BITS) - DBG("[RESET] Caused by power-on reset\n"); + usNFO("[RESET] Caused by power-on reset\n"); #endif return; } @@ -219,7 +219,7 @@ void init_logging(void) stdio_uart_init_full(uart0, BAUD_RATE, TX, RX); sleep_ms(100); /* leave time for uart to settle */ stdio_flush(); - DBG("\n[%s]\n", __func__); + usNFO("[NFO] Uart logging initialized\n"); #endif return; } @@ -230,7 +230,7 @@ void init_logging(void) /* Write from device to host */ void cdc_write(uint8_t * itf, uint32_t n) { /* No need to check if write available with current driver code */ - IODBG("[O %d] [%c] $%02X:%02X\n", n, dtype, sid_buffer[1], write_buffer[0]); + usIO("[O %d] [%c] $%02X:%02X\n", n, dtype, sid_buffer[1], write_buffer[0]); tud_cdc_n_write(*itf, write_buffer, n); /* write n bytes of data to client */ tud_cdc_n_write_flush(*itf); return; @@ -239,7 +239,7 @@ void cdc_write(uint8_t * itf, uint32_t n) /* Write from device to host */ void webserial_write(uint8_t * itf, uint32_t n) { /* No need to check if write available with current driver code */ - IODBG("[O %d] [%c] $%02X:%02X\n", n, dtype, sid_buffer[1], write_buffer[0]); + usIO("[O %d] [%c] $%02X:%02X\n", n, dtype, sid_buffer[1], write_buffer[0]); tud_vendor_write(write_buffer, n); tud_vendor_flush(); return; @@ -253,7 +253,7 @@ int __no_inline_not_in_flash_func(do_buffer_tick)(int top, int step) static int i = 1; cycled_write_operation(sid_buffer[i], sid_buffer[i + 1], (step == 4 ? (sid_buffer[i + 2] << 8 | sid_buffer[i + 3]) : MIN_CYCLES)); WRITEDBG(dtype, i, top, sid_buffer[i], sid_buffer[i + 1], (step == 4 ? (sid_buffer[i + 2] << 8 | sid_buffer[i + 3]) : MIN_CYCLES)); - IODBG("[I %d] [%c] $%02X:%02X (%u)\n", i, dtype, sid_buffer[i], sid_buffer[i + 1], (step == 4 ? (sid_buffer[i + 2] << 8 | sid_buffer[i + 3]) : MIN_CYCLES)); + usIO("[I %d] [%c] $%02X:%02X (%u)\n", i, dtype, sid_buffer[i], sid_buffer[i + 1], (step == 4 ? (sid_buffer[i + 2] << 8 | sid_buffer[i + 3]) : MIN_CYCLES)); if (i+step >= top) { i = 1; return i; @@ -286,7 +286,7 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) if __us_unlikely(n_bytes == 0) { cycled_write_operation(sid_buffer[1], sid_buffer[2], (sid_buffer[3] << 8 | sid_buffer[4])); WRITEDBG(dtype, n_bytes, n_bytes, sid_buffer[1], sid_buffer[2], (sid_buffer[3] << 8 | sid_buffer[4])); - IODBG("[I %d] [%c] $%02X:%02X (%u)\n", n_bytes, dtype, sid_buffer[1], sid_buffer[2], (sid_buffer[3] << 8 | sid_buffer[4])); + usIO("[I %d] [%c] $%02X:%02X (%u)\n", n_bytes, dtype, sid_buffer[1], sid_buffer[2], (sid_buffer[3] << 8 | sid_buffer[4])); } else { buffer_task(n_bytes, 4); } @@ -297,14 +297,14 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) if __us_likely(n_bytes == 0) { cycled_write_operation(sid_buffer[1], sid_buffer[2], 6); /* Add 6 cycles to each write for LDA(2) & STA(4) */ WRITEDBG(dtype, n_bytes, n_bytes, sid_buffer[1], sid_buffer[2], 6); - IODBG("[I %d] [%c] $%02X:%02X (%u)\n", n_bytes, dtype, sid_buffer[1], sid_buffer[2], 6); + usIO("[I %d] [%c] $%02X:%02X (%u)\n", n_bytes, dtype, sid_buffer[1], sid_buffer[2], 6); } else { buffer_task(n_bytes, 2); } return; }; if __us_unlikely(command == READ) { /* READING CAN ONLY HANDLE ONE AT A TIME, PERIOD. */ - IODBG("[I %d] [%c] $%02X:%02X\n", n_bytes, dtype, sid_buffer[1], sid_buffer[2]); + usIO("[I %d] [%c] $%02X:%02X\n", n_bytes, dtype, sid_buffer[1], sid_buffer[2]); write_buffer[0] = cycled_read_operation(sid_buffer[1], 0); /* write the address to the SID and read the data back */ switch (rtype) { /* write the result to the USB client */ case 'C': @@ -314,7 +314,7 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) webserial_write(itf, BYTES_TO_SEND); break; default: - IODBG("[WRITE ERROR]%c\n", rtype); + usIO("[WRITE ERROR]%c\n", rtype); break; }; vu = (vu == 0 ? 100 : vu); /* NOTICE: Testfix for core1 setting dtype to 0 */ @@ -323,7 +323,7 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) if (command == COMMAND) { switch (subcommand) { case CYCLED_READ: - IODBG("[I %d] [%c] $%02X %u\n", n_bytes, dtype, sid_buffer[1], (sid_buffer[2] << 8 | sid_buffer[3])); + usIO("[I %d] [%c] $%02X %u\n", n_bytes, dtype, sid_buffer[1], (sid_buffer[2] << 8 | sid_buffer[3])); write_buffer[0] = cycled_read_operation(sid_buffer[1], (sid_buffer[2] << 8 | sid_buffer[3])); switch (rtype) { /* write the result to the USB client */ case 'C': @@ -333,7 +333,7 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) webserial_write(itf, BYTES_TO_SEND); break; default: - IODBG("[WRITE ERROR]%c\n", rtype); + usIO("[WRITE ERROR]%c\n", rtype); break; }; vu = (vu == 0 ? 100 : vu); /* NOTICE: Testfix for core1 setting dtype to 0 */ @@ -342,44 +342,44 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) cycled_delay_operation((sid_buffer[1] << 8 | sid_buffer[2])); return; case PAUSE: - DBG("[PAUSE_SID]\n"); + usDBG("[PAUSE_SID]\n"); pause_sid(); break; case MUTE: - DBG("[MUTE_SID] %d\n",sid_buffer[1]); + usDBG("[MUTE_SID] %d\n",sid_buffer[1]); mute_sid(); if (sid_buffer[1] == 1) is_muted = true; break; case UNMUTE: - DBG("[UNMUTE_SID] %d\n",sid_buffer[1]); + usDBG("[UNMUTE_SID] %d\n",sid_buffer[1]); if (sid_buffer[1] == 1) is_muted = false; unmute_sid(); break; case RESET_SID: if (sid_buffer[1] == 0) { - DBG("[RESET_SID]\n"); + usDBG("[RESET_SID]\n"); reset_sid(); } if (sid_buffer[1] == 1) { - DBG("[RESET_SID_REGISTERS]\n"); + usDBG("[RESET_SID_REGISTERS]\n"); reset_sid_registers(); } break; case DISABLE_SID: - DBG("[DISABLE_SID]\n"); + usDBG("[DISABLE_SID]\n"); disable_sid(); break; case ENABLE_SID: - DBG("[ENABLE_SID]\n"); + usDBG("[ENABLE_SID]\n"); enable_sid(true); break; case CLEAR_BUS: - DBG("[CLEAR_BUS]\n"); + usDBG("[CLEAR_BUS]\n"); clear_bus_all(); break; case CONFIG: if (sid_buffer[1] < 0xD0) { /* Don't log incoming buffer to avoid spam above this region */ - DBG("[CONFIG]\n"); + usDBG("[CONFIG]\n"); } /* Copy incoming buffer ignoring the command byte */ memcpy(config_buffer, (sid_buffer + 1), (int)*n - 1); @@ -387,11 +387,11 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) memset(config_buffer, 0, count_of(config_buffer)); break; case RESET_MCU: - DBG("[RESET_MCU]\n"); + usDBG("[RESET_MCU]\n"); mcu_reset(); break; case BOOTLOADER: - DBG("[BOOTLOADER]\n"); + usDBG("[BOOTLOADER]\n"); mcu_jump_to_bootloader(); break; default: @@ -407,27 +407,27 @@ void __no_inline_not_in_flash_func(process_buffer)(uint8_t * itf, uint32_t * n) void tud_mount_cb(void) { - DBG("[%s]\n", __func__); + usDBG("[%s]\n", __func__); usb_connected = 1; } void tud_umount_cb(void) { usb_connected = 0, usbdata = 0, dtype = rtype = ntype; - DBG("[%s]\n", __func__); + usDBG("[%s]\n", __func__); disable_sid(); /* NOTICE: Testing if this is causing the random lockups */ } void tud_suspend_cb(bool remote_wakeup_en) { /* (void) remote_wakeup_en; */ - DBG("[%s] remote_wakeup_en:%d\n", __func__, remote_wakeup_en); + usDBG("[%s] remote_wakeup_en:%d\n", __func__, remote_wakeup_en); usb_connected = 0, usbdata = 0, dtype = rtype = ntype; } void tud_resume_cb(void) { - DBG("[%s]\n", __func__); + usDBG("[%s]\n", __func__); usb_connected = 1; } @@ -509,20 +509,20 @@ void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) { (void)itf; (void)wanted_char; - /* DBG("[%s]\n", __func__); */ /* Disabled due to possible uart spam */ + /* usDBG("[%s]\n", __func__); */ /* Disabled due to possible uart spam */ } void tud_cdc_tx_complete_cb(uint8_t itf) { (void)itf; - /* DBG("[%s]\n", __func__); */ /* Disabled due to uart spam */ + /* usDBG("[%s]\n", __func__); */ /* Disabled due to uart spam */ } void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { /* (void) itf; */ /* (void) rts; */ - DBG("[%s] itf:%x, dtr:%d, rts:%d\n", __func__, itf, dtr, rts); + usDBG("[%s] itf:%x, dtr:%d, rts:%d\n", __func__, itf, dtr, rts); if ( dtr ) { /* Terminal connected */ @@ -539,14 +539,14 @@ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding) { /* (void)itf; */ /* (void)p_line_coding; */ - DBG("[%s] itf:%x, bit_rate:%u, stop_bits:%u, parity:%u, data_bits:%u\n", __func__, itf, (int)p_line_coding->bit_rate, p_line_coding->stop_bits, p_line_coding->parity, p_line_coding->data_bits); + usDBG("[%s] itf:%x, bit_rate:%u, stop_bits:%u, parity:%u, data_bits:%u\n", __func__, itf, (int)p_line_coding->bit_rate, p_line_coding->stop_bits, p_line_coding->parity, p_line_coding->data_bits); } void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) { /* (void)itf; */ /* (void)duration_ms; */ - DBG("[%s] its:%x, duration_ms:%x\n", __func__, itf, duration_ms); + usDBG("[%s] its:%x, duration_ms:%x\n", __func__, itf, duration_ms); } @@ -591,7 +591,7 @@ void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize) void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes) { (void)itf; - DBG("[%s] %lu\n", __func__, sent_bytes); + usDBG("[%s] %lu\n", __func__, sent_bytes); } @@ -600,7 +600,7 @@ void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes) /* Handle incoming vendor and webusb data */ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { - DBG("[%s] stage:%x, rhport:%x, bRequest:0x%x, wValue:%d, wIndex:%x, wLength:%x, bmRequestType:%x, type:%x, recipient:%x, direction:%x\n", + usDBG("[%s] stage:%x, rhport:%x, bRequest:0x%x, wValue:%d, wIndex:%x, wLength:%x, bmRequestType:%x, type:%x, recipient:%x, direction:%x\n", __func__, stage, rhport, request->bRequest, request->wValue, request->wIndex, request->wLength, request->bmRequestType, request->bmRequestType_bit.type, request->bmRequestType_bit.recipient, request->bmRequestType_bit.direction); @@ -612,25 +612,25 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ break; case TUSB_REQ_TYPE_CLASS: /* 1 */ if (request->bRequest == WEBUSB_COMMAND) { - DBG("request->bRequest == WEBUSB_COMMAND\n"); + usDBG("request->bRequest == WEBUSB_COMMAND\n"); if (request->wValue == WEBUSB_RESET) { - DBG("request->wValue == WEBUSB_RESET\n"); + usDBG("request->wValue == WEBUSB_RESET\n"); // BUG: NO WURKY CURKY /* reset_sid_registers(); */ // BUG: Temporary disabled reset_sid(); // NOTICE: Temporary until fixed! /* unmute_sid(); */ } if (request->wValue == RESET_SID) { - DBG("request->wValue == RESET_SID\n"); + usDBG("request->wValue == RESET_SID\n"); reset_sid(); } if (request->wValue == PAUSE) { - DBG("request->wValue == PAUSE\n"); + usDBG("request->wValue == PAUSE\n"); pause_sid(); mute_sid(); } if (request->wValue == WEBUSB_CONTINUE) { - DBG("request->wValue == WEBUSB_CONTINUE\n"); + usDBG("request->wValue == WEBUSB_CONTINUE\n"); pause_sid(); unmute_sid(); } @@ -820,7 +820,7 @@ void core1_main(void) if (usbdata == 1) { writelogging_queue_entry_t l_entry; if (queue_try_remove(&logging_queue, &l_entry)) { - DBG("[CORE2 %5u] [WRITE %c:%02d/%02d] $%02X:%02X %u\n", + usDBG("[CORE2 %5u] [WRITE %c:%02d/%02d] $%02X:%02X %u\n", queue_get_level(&logging_queue), l_entry.dtype, l_entry.n, l_entry.s, l_entry.reg, l_entry.val, l_entry.cycles); } @@ -889,9 +889,9 @@ int main() sid_mhz = (sid_hz / 1000 / 1000); sid_us = (1 / sid_mhz); if (!auto_config) { - CFG("[BOOT PICO] %lu Hz, %.0f MHz, %.4f uS\n", clock_get_hz(clk_sys), cpu_mhz, cpu_us); - CFG("[BOOT C64] %.0f Hz, %.6f MHz, %.4f uS\n", sid_hz, sid_mhz, sid_us); - CFG("[BOOT C64 RATES] REFRESH_RATE %lu Cycles, RASTER_RATE %lu Cycles\n", usbsid_config.refresh_rate, usbsid_config.raster_rate); + usNFO("[NFO] [PICO] %lu Hz, %.0f MHz, %.4f uS\n", clock_get_hz(clk_sys), cpu_mhz, cpu_us); + usNFO("[NFO] [C64] %.0f Hz, %.6f MHz, %.4f uS\n", sid_hz, sid_mhz, sid_us); + usNFO("[NFO] [C64] REFRESH_RATE %lu Cycles, RASTER_RATE %lu Cycles\n", usbsid_config.refresh_rate, usbsid_config.raster_rate); } /* Release core 0 semaphore */ sem_release(&core0_init); diff --git a/src/vu.c b/src/vu.c index bcef544..f1c1ac4 100644 --- a/src/vu.c +++ b/src/vu.c @@ -144,7 +144,7 @@ void led_vumeter_task(void) } #endif - MDBG("[%c:%d][PWM]$%04x[V1]$%02X%02X$%02X%02X$%02X$%02X$%02X[V2]$%02X%02X$%02X%02X$%02X$%02X$%02X[V3]$%02X%02X$%02X%02X$%02X$%02X$%02X[FC]$%02x%02x$%02x[VOL]$%02x\n", + usMEM("[%c:%d][PWM]$%04x[V1]$%02X%02X$%02X%02X$%02X$%02X$%02X[V2]$%02X%02X$%02X%02X$%02X$%02X$%02X[V3]$%02X%02X$%02X%02X$%02X$%02X$%02X[FC]$%02x%02x$%02x[VOL]$%02x\n", dtype, usbdata, vu, sid_memory[0x01], sid_memory[0x00], sid_memory[0x03], sid_memory[0x02], sid_memory[0x04], sid_memory[0x05], sid_memory[0x06], sid_memory[0x08], sid_memory[0x07], sid_memory[0x0A], sid_memory[0x09], sid_memory[0x0B], sid_memory[0x0C], sid_memory[0x0D], From e577cd3f85660e80ea7443fc592b3708bf231ca0 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 17:50:34 +0100 Subject: [PATCH 30/45] Fix regression in boot sequence where an rp2350 would freeze forever waiting for a semaphore to come available. Replace semaphore with arm native functions. Rearrange boot sequence and add more logging. --- src/usbsid.c | 176 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 107 insertions(+), 69 deletions(-) diff --git a/src/usbsid.c b/src/usbsid.c index dd3da44..b099c10 100644 --- a/src/usbsid.c +++ b/src/usbsid.c @@ -165,8 +165,13 @@ midi_machine midimachine; queue_t sidtest_queue; queue_t logging_queue; -/* Declare local variables */ -static semaphore_t core0_init, core1_init; +/* Multicore sync using atomic memory - avoids semaphore spin locks AND + * FIFO (which is consumed by flash_safe_execute IRQ handler) */ +static volatile uint32_t core_sync_state = 0; +#define SYNC_CORE1_STAGE1 0x11 /* Core 1 finished flash_safe_execute_core_init */ +#define SYNC_CORE0_STAGE1 0x01 /* Core 0 finished config loading */ +#define SYNC_CORE1_STAGE2 0x12 /* Core 1 finished queue/uart init */ +#define SYNC_CORE0_STAGE2 0x02 /* Core 0 finished hardware init */ /* WebUSB Description URL */ static const tusb_desc_webusb_url_t desc_url = @@ -681,26 +686,23 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ /* MAIN */ -/* Semaphore ping pong +/* Multicore sync using atomic memory (avoids semaphore spin locks AND + * FIFO which is consumed by flash_safe_execute IRQ handler) * - * Core 0 -> init semapohre core1_init * Core 0 -> launch core 1 - * Core 0 -> wait for core1_init signal + * Core 0 -> poll for SYNC_CORE1_STAGE1 * Core 1 -> init flash safe execute - * Core 1 -> release semaphore core1_init - * Core 0 -> continue to load and apply config - * Core 0 -> init SID clock - * Core 1 -> init semaphore core0_init - * Core 1 -> wait for core0_init signal - * Core 0 -> release semaphore core0_init - * Core 0 -> wait for core1_init signal - * Core 1 -> continue startup RGB & Vu - * Core 1 -> release semaphore core1_init - * Core 1 -> wait for core0_init signal - * Core 0 -> continue startup - * Core 0 -> release semaphore core0_init + * Core 1 -> set SYNC_CORE1_STAGE1 + * Core 1 -> poll for SYNC_CORE0_STAGE1 + * Core 0 -> load and apply config + * Core 0 -> set SYNC_CORE0_STAGE1 + * Core 0 -> poll for SYNC_CORE1_STAGE2 + * Core 1 -> init queues and PIO uart + * Core 1 -> set SYNC_CORE1_STAGE2 + * Core 1 -> poll for SYNC_CORE0_STAGE2 + * Core 0 -> init GPIO, SID clock, PIO, DMA, etc. + * Core 0 -> set SYNC_CORE0_STAGE2 * Core 0 -> enter while loop - * Core 1 -> continue startup * Core 1 -> enter while loop */ void core1_main(void) @@ -708,23 +710,18 @@ void core1_main(void) /* Set core locking for flash saving ~ note this makes SIO_IRQ_PROC1 unavailable */ flash_safe_execute_core_init(); /* This needs to start before any flash actions take place! */ - /* Workaround to make sure flash_safe_execute is executed - * before everything else if a default config is detected - * This just ping pongs bootup around with Core 0 - * - * Release core 1 semaphore */ - sem_release(&core1_init); + /* Signal Core 0 we're ready (sync point 1) */ + usBOOT(" Signaling core0 ready ~ 1\n"); + __dmb(); /* Memory barrier before write */ + core_sync_state = SYNC_CORE1_STAGE1; + __sev(); /* Signal event to wake Core 0 from WFE */ - /* Create a blocking semaphore with max 1 permit */ - sem_init(&core0_init, 0, 1); - /* Wait for core 0 to signal back */ - sem_acquire_blocking(&core0_init); - - /* Clear the dirt */ - memset(sid_memory, 0, sizeof sid_memory); - - /* Start the VU */ - init_vu(); + /* Wait for Core 0 to finish config loading */ + usBOOT(" Waiting for core0 sync ~ 1\n"); + while (core_sync_state != SYNC_CORE0_STAGE1) { + __wfe(); /* Wait for event - low power wait */ + } + __dmb(); /* Memory barrier after read */ /* Init queues */ queue_init(&sidtest_queue, sizeof(sidtest_queue_entry_t), 1); /* 1 entry deep */ @@ -737,13 +734,34 @@ void core1_main(void) init_uart(); #endif - /* Release semaphore when core 1 is started */ - sem_release(&core1_init); + /* Signal Core 0 we're ready (sync point 2) */ + usBOOT(" Signaling core0 ready ~ 2\n"); + __dmb(); + core_sync_state = SYNC_CORE1_STAGE2; + __sev(); - /* Wait for core 0 to signal boot finished */ - sem_acquire_blocking(&core0_init); + /* Wait for Core 0 to finish hardware init */ + usBOOT(" Waiting for core0 sync ~ 2\n"); + while (core_sync_state != SYNC_CORE0_STAGE2) { + __wfe(); + } + __dmb(); while (1) { + + /* Check SID test queue */ + if (running_tests) { + sidtest_queue_entry_t s_entry; + if (queue_try_remove(&sidtest_queue, &s_entry)) { + s_entry.func(s_entry.s, s_entry.t, s_entry.wf); + } + } + + /* Blinky blinky? */ + if (!offload_ledrunner) { + led_runner(); + } + #if ONBOARD_SIDPLAYER if (sidplayer_init) { sidplayer_init = false; @@ -804,18 +822,6 @@ void core1_main(void) } #endif /* ONBOARD_EMULATOR */ - /* Check SID test queue */ - if (running_tests) { - sidtest_queue_entry_t s_entry; - if (queue_try_remove(&sidtest_queue, &s_entry)) { - s_entry.func(s_entry.s, s_entry.t, s_entry.wf); - } - } - - if (!offload_ledrunner) { - led_runner(); - } - #ifdef WRITE_DEBUG /* Only run this queue when needed */ if (usbdata == 1) { writelogging_queue_entry_t l_entry; @@ -826,6 +832,7 @@ void core1_main(void) } } #endif + } /* Point of no return, this should never be reached */ return; @@ -842,7 +849,7 @@ int main() set_sys_clock_pll(1500000000, 6, 2); #endif #elif PICO_RP2350 -/* Onboard SID player requires atleast 200MHz! */ + /* Onboard SID player requires atleast 200MHz! */ #if ONBOARD_SIDPLAYER /* System clock @ 200MHz */ set_sys_clock_khz(200000, true); @@ -864,18 +871,18 @@ int main() reset_reason(); /* Clear flagged */ - if (flagged) { auto_config = true; flagged = 0; } /* BUG: NOT WORKING ON RP2350 */ - - /* Workaround to make sure flash_safe_execute is executed - * before everything else if a default config is detected - * This just ping pongs bootup around with Core 1 - * - * Create a blocking semaphore with max 1 permit */ - sem_init(&core1_init, 0, 1); - /* Init core 1 */ + if (flagged) { auto_config = true; flagged = 0; } /* NOTE: Does not work on rp2350 */ + + /* Launch Core 1 and wait for flash_safe_execute_core_init to complete */ + usBOOT("CORE0 Launching core1\n"); multicore_launch_core1(core1_main); - /* Wait for core 1 to signal back */ - sem_acquire_blocking(&core1_init); + + /* Wait for Core 1 to signal ready (sync point 1) */ + usBOOT("CORE0 Waiting for core1 ready ~ 1\n"); + while (core_sync_state != SYNC_CORE1_STAGE1) { + __wfe(); /* Wait for event - low power wait */ + } + __dmb(); /* Insert memory barrier after read */ /* Load config before init of USBSID settings ~ NOTE: This cannot be run from Core 1! */ load_config(&usbsid_config); @@ -893,31 +900,58 @@ int main() usNFO("[NFO] [C64] %.0f Hz, %.6f MHz, %.4f uS\n", sid_hz, sid_mhz, sid_us); usNFO("[NFO] [C64] REFRESH_RATE %lu Cycles, RASTER_RATE %lu Cycles\n", usbsid_config.refresh_rate, usbsid_config.raster_rate); } - /* Release core 0 semaphore */ - sem_release(&core0_init); - /* Wait for core one to finish startup */ - sem_acquire_blocking(&core1_init); + + /* Signal Core 1 to continue (sync point 1) */ + usBOOT(" Signaling core1 ~ 1\n"); + __dmb(); /* Insert memory barrier after read */ + core_sync_state = SYNC_CORE0_STAGE1; + __sev(); /* Signal event to wake Core 1 from WFE */ + + /* Wait for Core 1 to finish queue/uart init (sync point 2) */ + usBOOT(" Waiting for core1 ready ~ 2\n"); + while (core_sync_state != SYNC_CORE1_STAGE2) { + __wfe(); /* Wait for event - low power wait */ + } + __dmb(); /* Insert memory barrier after read */ /* Init GPIO */ + usBOOT("Initializing GPIO\n"); init_gpio(); /* Start verification, detect and init sequence of SID clock */ + usBOOT("Setup SID clock\n"); setup_sidclock(); /* Init PIO */ + usBOOT("Setup PIO bus\n"); setup_piobus(); /* Sync PIOS */ + usBOOT("Synchronize PIO's\n"); sync_pios(true); /* Init DMA */ + usBOOT("Setup DMA channels\n"); setup_dmachannels(); + /* Start the VU */ + usBOOT("Initialize Vu\n"); + init_vu(); /* Init midi */ + usBOOT("Initialize Midi\n"); midi_init(); /* Init ASID */ + usBOOT("Initialize ASID\n"); asid_init(); /* Enable SID chips */ + usBOOT("Enable SID chips\n"); enable_sid(false); + /* Clear the dirt */ +#if !defined(ONBOARD_EMULATOR) || !defined(ONBOARD_SIDPLAYER) + memset(sid_memory, 0, sizeof sid_memory); +#endif + /* Check for default config bit * NOTE: This cannot be run from Core 1! */ - if (!auto_config) detect_default_config(); + if (!auto_config) { + detect_default_config(); + } if (auto_config) { /* NOTE: Does not work on rp2350 */ auto_detect_routine(auto_config, true); /* Double tap! */ save_config_ext(); @@ -927,8 +961,11 @@ int main() /* Print config once at end of boot routine */ print_config(); - /* Release core 0 semaphore to signal boot finished */ - sem_release(&core0_init); + /* Signal Core 1 to enter main loop (sync point 2) */ + usBOOT(" Signaling core1 ~ 2\n"); + __dmb(); /* Memory barrier after read */ + core_sync_state = SYNC_CORE0_STAGE2; + __sev(); /* Signal event to wake Core 1 from WFE */ /* Loop IO tasks forever */ while (1) { @@ -939,6 +976,7 @@ int main() #ifdef USE_VENDOR_BUFFER vendor_task(); /* Only use this if buffering and fifo are enabled */ #endif + if (offload_ledrunner) { led_runner(); } From 955e4c9745e14cac0c16689d4c59cfb7b3fcef76 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 17:51:19 +0100 Subject: [PATCH 31/45] ASID overhaul --- src/asid.c | 222 +++++++++++++++------ src/asid_buffer.c | 481 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 619 insertions(+), 84 deletions(-) diff --git a/src/asid.c b/src/asid.c index 259a9e1..9eabf84 100644 --- a/src/asid.c +++ b/src/asid.c @@ -43,52 +43,107 @@ #include "logging.h" -/* Config */ +/* config.c */ +extern Config usbsid_config; extern RuntimeCFG cfg; extern char *sidtypes[5]; -/* GPIO */ +/* gpio.c */ extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); extern void pause_sid(void); extern void reset_sid(void); +extern void reset_sid_registers(void); -/* Vu */ +/* vu.c */ extern uint16_t vu; +/* asid_buffer.c */ +extern uint32_t track_asid_arrival(void); +extern void adjust_buffer_rate_dynamic(uint32_t target_rate); +extern void reset_arrival_tracking(void); +extern void ring_buffer_reset_size(void); +extern void set_buffer_rate(uint16_t rate); + /* Some locals, rural and such */ +static bool buffer_started = false; static bool default_order = false; static bool default_order_on_start = false; bool write_ordered = false; -struct asid_regpair_t { /* thanks to thomasj */ +/* thanks to thomasj */ +struct asid_regpair_t { uint8_t index; uint8_t wait_us; }; -struct asid_regpair_t asid_to_writeorder[NO_SID_REGISTERS_ASID] = {}; /* thanks to thomasj ~ SF2 Driver 11 no waits */ - +/* thanks to thomasj ~ SF2 Driver 11 no waits */ +struct asid_regpair_t asid_to_writeorder[NO_SID_REGISTERS_ASID] = {}; +/** + * @brief Resets SID write order back to defaults + */ void reset_asid_to_writeorder(void) { - DBG("[ASID] RESET WRITEORDER REGISTERS\n"); - if (write_ordered) { - /* inline extern as only used here */ - extern void asid_ring_deinit(void); - extern void stop_buffer_pio(void); - asid_ring_deinit(); - stop_buffer_pio(); - } + usASID("Reset writeorder registers\n"); write_ordered = false; for (int i = 0; i < NO_SID_REGISTERS_ASID; i++) { asid_to_writeorder[i].index = i; - asid_to_writeorder[i].wait_us = 0; + asid_to_writeorder[i].wait_us = 10; /* 10 cycles as default for rp2350 being too fast */ } - default_order = true; default_order_on_start = (default_order == false ? true : default_order); + default_order = true; + reset_arrival_tracking(); + ring_buffer_reset_size(); + return; } +/** + * @brief Initialize the asid buffer + * + * @param uint16_t framedelta_us ~ the rate to set the buffer to + */ +void init_asid_buffer(uint16_t framedelta_us) +{ + if (!buffer_started) { + usASID("Init buffer queue, timer and irq\n"); + extern void init_buffer_pio(void); + extern void asid_ring_init(void); + + asid_ring_init(); + init_buffer_pio(); + buffer_started = true; + } + /* Always set frame buffer timeout */ + if (framedelta_us != 0) { /* Above zero means always use framedelta_us */ + set_buffer_rate(framedelta_us); + } else { + set_buffer_rate(usbsid_config.refresh_rate); + } + return; +} + +/** + * @brief De-initialize the asid buffer + */ +void deinit_asid_buffer(void) +{ + if (buffer_started) { + usASID("De-init buffer queue, timer and irq\n"); + reset_asid_to_writeorder(); + extern void stop_buffer_pio(void); + extern void asid_ring_deinit(void); + asid_ring_deinit(); + stop_buffer_pio(); + buffer_started = false; + } +} + +/** + * @brief Initialize ASID on boot + */ void asid_init(void) { - DBG("[%s]\n", __func__); if (!default_order_on_start) reset_asid_to_writeorder(); /* Set defaults once on boot */ + else { ring_buffer_reset_size(); reset_arrival_tracking(); } + usNFO("[ASID] Initialized\n"); } /* Pling, plong, ploink!? */ @@ -131,7 +186,14 @@ void handle_asid_fmoplmessage(uint8_t* buffer) return; } -/* Well, it does what it does */ +/** + * @brief Well, it does what it does + * @note deprecated! + * + * @param uint8_t sid ~ the sidnumber + * @param uint8_t* buffer ~ the buffer to process + * @param int size ~ the size of the buffer + */ void handle_complete_asid_buffer(uint8_t sid, uint8_t* buffer, int size) { /* Assumes byte 0-2 are included in the buffer and skips these */ (void)size; /* Stop calling me fat, I'm just big boned! */ @@ -154,9 +216,16 @@ void handle_complete_asid_buffer(uint8_t sid, uint8_t* buffer, int size) } } } + return; } -/* And this does the same */ +/** + * @brief And this does the same + * @note deprecated! + * + * @param uint8_t sid ~ the sidnumber + * @param uint8_t* buffer ~ the buffer to process + */ void handle_asid_message(uint8_t sid, uint8_t* buffer) { /* Assumes byte 0-2 are not included in the buffer */ unsigned int reg = 0; @@ -178,9 +247,15 @@ void handle_asid_message(uint8_t sid, uint8_t* buffer) } } } + return; } -/* But this one lost track of time */ +/** + * @brief But this one lost track of time + * + * @param uint8_t sid ~ the sidnumber + * @param uint8_t* buffer ~ the buffer to process + */ void handle_writeordered_asid_message(uint8_t sid, uint8_t* buffer) { /* Assumes byte 0-2 are not included in the buffer */ int chip; @@ -212,7 +287,7 @@ void handle_writeordered_asid_message(uint8_t sid, uint8_t* buffer) * or it will be too damn fast! So if wait_us is lower then 10 we use 10 cycles * and do this for other Pico's aswell */ writeOrder[chip][asid_to_writeorder[reg].index].wait_us = asid_to_writeorder[reg].wait_us; - /* DBG("[ASID1 %d] $%02X:%02X %u\n", asid_to_writeorder[reg].index, (writeOrder[chip][asid_to_writeorder[reg].index].reg |= sid), writeOrder[chip][asid_to_writeorder[reg].index].data, writeOrder[chip][asid_to_writeorder[reg].index].wait_us); */ + /* usASID("[%d] $%02X:%02X %u\n", asid_to_writeorder[reg].index, (writeOrder[chip][asid_to_writeorder[reg].index].reg |= sid), writeOrder[chip][asid_to_writeorder[reg].index].data, writeOrder[chip][asid_to_writeorder[reg].index].wait_us); */ dtype = asid; /* Set data type to asid again */ reg++; } @@ -223,7 +298,13 @@ void handle_writeordered_asid_message(uint8_t sid, uint8_t* buffer) for (size_t pos = 0; pos < NO_SID_REGISTERS_ASID; pos++) { if (writeOrder[chip][pos].wait_us != 0xff) { /* Push data to ASID ringbuffer */ - asid_ring_write((writeOrder[chip][pos].reg |= sid), writeOrder[chip][pos].data, writeOrder[chip][pos].wait_us); + asid_ring_write( + (writeOrder[chip][pos].reg |= sid), + writeOrder[chip][pos].data, + /* Account for 1 cycle overhead in PIO bus */ + // ((writeOrder[chip][pos].wait_us >= 0) ? (writeOrder[chip][pos].wait_us - 1) : 0)); + writeOrder[chip][pos].wait_us); + // (write_ordered ? writeOrder[chip][pos].wait_us : 10)); /* Default write order has 10 cycles per write */ WRITEDBG(dtype, pos, NO_SID_REGISTERS_ASID, (writeOrder[chip][pos].reg |= sid), writeOrder[chip][pos].data, writeOrder[chip][pos].wait_us); } else { asid_ring_write(0xffu,0xffu,0xffffu); @@ -232,8 +313,14 @@ void handle_writeordered_asid_message(uint8_t sid, uint8_t* buffer) for (size_t pos = 0; pos < NO_SID_REGISTERS_ASID; pos++) { writeOrder[chip][pos].wait_us = 0xff; /* indicate not used */ } + return; } +/** + * @brief Process received writeorder configuration + * + * @param uint8_t* buffer ~ the buffer containing the configuration + */ void handle_asid_writeorder_config(uint8_t* buffer) { /* thanks to thomasj */ uint8_t data; @@ -243,13 +330,23 @@ void handle_asid_writeorder_config(uint8_t* buffer) asid_to_writeorder[i].index = data & 0x1f; /* Regular handling removes 7us, so adjust */ cycles = ((data & 0x40) << 1) + buffer[(i << 1) + 1]; - /* asid_to_writeorder[i].wait_us = max(0, cycles - 7); */ /* USP doesn't have overhead, disabled */ - asid_to_writeorder[i].wait_us = cycles; - CFG("[ASID WRITE ORDER %d] {%02u,%02u}\n", i, asid_to_writeorder[i].index, asid_to_writeorder[i].wait_us); + asid_to_writeorder[i].wait_us = MAX(1, cycles); /* USP has 1 cycle overhead */ + /* asid_to_writeorder[i].wait_us = cycles; */ + usASID("[WO%2d] {%02u,%02u}\n", i, asid_to_writeorder[i].index, asid_to_writeorder[i].wait_us); } default_order = false; + return; } +/** + * @brief Set ASID environment + * + * @param int refresh_rate + * @param int speed_multiplier + * @param int custom_speed + * @param int buffering + * @param uint16_t framedelta_us + */ inline void set_asid_env(int refresh_rate, int speed_multiplier, int custom_speed, int buffering, uint16_t framedelta_us) { (void)speed_multiplier; /* Not used */ @@ -264,25 +361,22 @@ inline void set_asid_env(int refresh_rate, int speed_multiplier, int custom_spee : 2), /* NTSC */ true); } + reset_sid_registers(); + reset_sid(); - if (write_ordered) { - CFG("[ASID] Init buffer queue, timer and irq\n"); - extern Config usbsid_config; - extern void init_buffer_pio(void); - extern void set_buffer_rate(uint16_t rate); - extern void asid_ring_init(void); + init_asid_buffer(framedelta_us); /* Init on env set */ + ring_buffer_reset_size(); /* Reset buffer to default size for new tune */ - asid_ring_init(); - init_buffer_pio(); - if (framedelta_us != 0) { /* Above zero means always use framedelta_us */ - set_buffer_rate(framedelta_us); - } else { - set_buffer_rate(usbsid_config.refresh_rate); - } - } + /* DON'T call reset_arrival_tracking() here */ + /* Instead, just accept that calculated rate will converge to framedelta */ return; } +/** + * @brief Process received environment + * + * @param uint8_t* buffer ~ the buffer containing the environment + */ void handle_asid_envmessage(uint8_t* buffer) { /* SID environment is only logged and not used for now */ /* Incoming buffer skips first 3 bytes and @@ -301,7 +395,7 @@ void handle_asid_envmessage(uint8_t* buffer) int speed_multiplier = (buffer[0] & 0b11110) >> 1; int custom_speed = (buffer[0] & 0b100000) >> 5; int buffering = (buffer[0] & 0b1000000) >> 6; - DBG("[ASID] Settings refresh rate: %s, speed multiplier: %d, custom speed: %d, buffering: %d\n", + usASID("Settings: refresh rate: %s, speed multiplier: %d, custom speed: %d, buffering: %d\n", (refresh_rate == 0 ? "PAL" : "NTSC"), speed_multiplier, custom_speed, buffering); /* @@ -316,8 +410,9 @@ void handle_asid_envmessage(uint8_t* buffer) bits6-2: 5 bits (reserved) */ uint16_t framedelta_us = ((buffer[1] & 0x7F) | (buffer[2] & 0x7F) << 7 | ((buffer[3] & 0x03) << 14)); - DBG("[ASID] Framedelta: %d\n", framedelta_us); + usASID("Framedelta: %d\n", framedelta_us); set_asid_env(refresh_rate, speed_multiplier, custom_speed, buffering, framedelta_us); + return; } void handle_asid_typemessage(uint8_t* buffer) @@ -328,22 +423,29 @@ void handle_asid_typemessage(uint8_t* buffer) 0xF0, 0x2D, 0x32, SIDNO, SIDTYPE, 0x7F */ int sidtype = ((buffer[1] == 0) ? 3 : (buffer[1] == 1) ? 2 : 0); - DBG("[ASID] Tune SID %d type: %s\n", buffer[0], sidtypes[sidtype]); + usASID("Tune SID %d type: %s\n", buffer[0], sidtypes[sidtype]); + return; } /* Spy vs Spy ? */ void decode_asid_message(uint8_t* buffer, int size) { + static uint32_t rate; /* Tracks the rate of ASID messages */ switch(buffer[2]) { case 0x4C: /* Play start */ - DBG("[ASID] PLAY START\n"); + usASID("Play start\n"); + if (!buffer_started) init_asid_buffer(usbsid_config.refresh_rate); /* Start buffer on play start */ + reset_arrival_tracking(); /* Reset timing on play start */ + ring_buffer_reset_size(); /* Reset buffer to default size for new tune */ midimachine.bus = CLAIMED; break; case 0x4D: /* Play stop */ - DBG("[ASID] PLAY STOP\n"); - reset_sid(); - pause_sid(); + usASID("Play stop\n"); + reset_sid_registers(); if (!default_order) reset_asid_to_writeorder(); + set_buffer_rate(usbsid_config.refresh_rate); + ring_buffer_reset_size(); /* Reset buffer to default size */ + if (buffer_started) deinit_asid_buffer(); /* Stop buffer on play stop */ midimachine.bus = FREE; break; case 0x30: /* Write order timing (order and delay between individual SID register writes, to closely match the C64 driver used) */ @@ -359,21 +461,31 @@ void decode_asid_message(uint8_t* buffer, int size) case 0x4F: /* Display characters */ break; case 0x4E: /* SID 1 */ - if (!default_order_on_start) reset_asid_to_writeorder(); /* Set defaults once on first write of not set yet */ - if (!write_ordered) handle_asid_message(0, &buffer[3]); - else handle_writeordered_asid_message(0, &buffer[3]); + if (!buffer_started) init_asid_buffer(usbsid_config.refresh_rate); /* Start buffer on first write */ + if (!default_order_on_start) reset_asid_to_writeorder(); /* Set defaults once on first write if not set yet */ + /* Only track arrival timing for SID1 - represents actual frame rate. + * Multi-SID tunes send multiple messages per frame (one per SID). */ + rate = track_asid_arrival(); + /* Adjust rate based on buffer state (called for all SIDs) */ + adjust_buffer_rate_dynamic(rate); + handle_writeordered_asid_message(0, &buffer[3]); break; case 0x50: /* SID 2 */ - if (!write_ordered) handle_asid_message(32, &buffer[3]); - else handle_writeordered_asid_message(32, &buffer[3]); + /* Update SID count and adjust rate - tracking only on SID1 for timing */ + extern void update_sid_count(uint8_t sid_num); + update_sid_count(2); + adjust_buffer_rate_dynamic(0); + handle_writeordered_asid_message(32, &buffer[3]); break; case 0x51: /* SID 3 */ - if (!write_ordered) handle_asid_message(64, &buffer[3]); - else handle_writeordered_asid_message(64, &buffer[3]); + update_sid_count(3); + adjust_buffer_rate_dynamic(0); + handle_writeordered_asid_message(64, &buffer[3]); break; case 0x52: /* SID 4 */ - if (!write_ordered) handle_asid_message(96, &buffer[3]); - else handle_writeordered_asid_message(96, &buffer[3]); + update_sid_count(4); + adjust_buffer_rate_dynamic(0); + handle_writeordered_asid_message(96, &buffer[3]); break; case 0x60: /* FMOpl */ if (cfg.fmopl_enabled) { /* Only if FMOpl is enabled, drop otherwise */ diff --git a/src/asid_buffer.c b/src/asid_buffer.c index 6428c70..4bbe3c2 100644 --- a/src/asid_buffer.c +++ b/src/asid_buffer.c @@ -34,6 +34,7 @@ */ #include +#include #include #include @@ -49,30 +50,394 @@ /* config.c */ extern Config usbsid_config; +/* bus.c */ +extern uint32_t clockcycles(void); + /* PIO */ PIO raster_pio = pio1; static uint sm_buffer, offset_buffer; bool buffer_sm_started = false; bool buffer_sm_claimed = false; +/* Arrival rate tracking */ +#define ARRIVAL_HISTORY_SIZE 8 +#define MULTISID_TIMEOUT_FRAMES 30 /* Reset to 1-SID after this many frames without SID2+ */ +#define NOWRITES_TIMEOUT_FRAMES 100 /* Disable everything after this amount of frames */ + +static bool still_receiving = false; +static uint32_t arrival_times[ARRIVAL_HISTORY_SIZE]; +volatile uint8_t arrival_index = 0; +volatile uint8_t arrival_count = 0; +volatile uint32_t calculated_rate = 0; +static uint16_t base_rate = 0; /* The rate from env message */ +static uint8_t sid_count_estimate = 1; /* Estimated number of SIDs in tune */ +volatile /* static */ uint8_t frames_since_nowrites = 0; /* Frames since last SID2/3/4 message */ +static uint8_t frames_since_multisid = 0; /* Frames since last SID2/3/4 message */ + /* IRQ */ int pio_irq; static int8_t buffer_irq; const int BUFFPIOIRQ = 2; bool buffer_irq_started = false; +volatile uint32_t irq_now_at = 0; +volatile uint32_t irq_end_at = 0; +volatile uint32_t irq_prev_at = 0; /* Ring buffer */ -static uint8_t __not_in_flash_func(ring_get)(void); -static int __not_in_flash_func(ring_diff)(void); -uint8_t diff_size = 64; -uint16_t ring_size = 8192; +static const uint8_t ASID_FRAME_WRITES_MAX = 28; +static uint8_t ring_get(void); +static int ring_diff(void); +volatile uint16_t corrected_rate = 0; +uint8_t diff_size = (2 * (4 * ASID_FRAME_WRITES_MAX)); /* = 224 bytes | 112 bytes == 1 frame, was 64 bytes */ typedef struct { uint16_t ring_read; uint16_t ring_write; bool is_allocated; uint8_t * __restrict__ ringbuffer; } ring_buffer_t; -static ring_buffer_t asid_ringbuffer; +static ring_buffer_t __not_in_flash("asid_buffer") asid_ringbuffer; + +/* Dynamic ring buffer sizing */ +static const uint16_t RING_SIZE_MIN = (10 * 224); /* 2240 bytes - minimum size */ +static const uint16_t RING_SIZE_DEFAULT = (20 * 224); /* 4480 bytes - default/starting size */ +static const uint16_t RING_SIZE_MAX = (150 * 224); /* 33600 bytes - maximum size (~150 frames, ~33KB) */ +static const uint16_t RING_SIZE_STEP = (20 * 224); /* 4480 bytes - grow/shrink increment */ +volatile uint16_t ring_size = RING_SIZE_DEFAULT; /* Current effective size */ +static uint16_t ring_size_allocated = 0; /* Actual allocated size */ + +/* TODO: REMOVE ~ TEMPORARY */ +volatile bool log_asid_queue = false; +volatile bool in_asid_irq = false; +volatile int __not_in_flash_func(ring_diff_external)(void) +{ + return (asid_ringbuffer.ring_write - asid_ringbuffer.ring_read + ring_size) % ring_size; +} +volatile uint16_t __not_in_flash_func(ring_head_external)(void) +{ + return asid_ringbuffer.ring_write; +} +volatile uint16_t __not_in_flash_func(ring_tail_external)(void) +{ + return asid_ringbuffer.ring_read; +} +/* TODO: REMOVE ~ TEMPORARY */ + + +/** + * Here be magicians + */ + +/** + * @brief Set the baseline rate from env message + * + * @param uint16_t rate ~ The framedelta_us from ASID env message + */ +void set_base_rate(uint16_t rate) +{ + base_rate = rate; + /* Reset tracking when base rate changes to avoid stale data */ + arrival_index = 0; + arrival_count = 0; + calculated_rate = 0; + for (int i = 0; i < ARRIVAL_HISTORY_SIZE; i++) { + arrival_times[i] = 0; + } + return; +} + +/** + * @brief Clamp value to asymmetric bounds around base_rate + * + * @param uint32_t rate ~ The rate to clamp + * + * @return uint32_t ~ Clamped rate within bounds + * + * Multi-SID tunes need faster consumption: + * - 2-SID: rate = base_rate / 2 + * - 3-SID: rate = base_rate / 3 + * - 4-SID: rate = base_rate / 4 + * - Fast CIA-timed 2-SID: can need up to base_rate / 6 + * Not working, the buffer just overflows: + * - 7x speed SID tunes: can need up to base_rate / 7 (Callapse_to_Center.sid Mibri) + * + * Allow rate to go as low as base_rate / 6 (~17%) for fast + * CIA-timed multi-SID tunes. + */ +static uint32_t clamp_to_base_rate(uint32_t rate) +{ + if (base_rate == 0) { + return rate; /* No base rate set, can't clamp */ + } + + /* Asymmetric bounds: + * - Minimum: base_rate / 6 (for fast CIA-timed multi-SID) + * - Maximum: base_rate * 1.2 (20% slower is plenty) */ + uint32_t min_rate = base_rate / 6; + if (min_rate < 1000) min_rate = 1000; /* Absolute minimum */ + + uint32_t max_rate = base_rate + (base_rate / 5); /* +20% */ + + if (rate < min_rate) return min_rate; + if (rate > max_rate) return max_rate; + return rate; +} + +/** + * @brief Track packet arrival and calculate dynamic rate + * @note Call this from decode_asid_message for SID1 only + * @note Auto-detects base_rate if not set by env message + * @note Auto-resets on long gaps (new tune detection) + * + * @return uint32_t ~ calculated frame rate in clock cycles (0 if not enough data) + */ +uint32_t track_asid_arrival(void) +{ + still_receiving = true; + + uint32_t now = clockcycles(); + + /* Detect long gap (new tune started without stop/start commands) + * If gap > 500ms (~500000 based on observed timing values), treat as new tune + * Typical frame delta is 16000-20000, so 500000 = ~25-30 frames missed */ + if (arrival_count > 0) { + uint8_t last_idx = (arrival_index + ARRIVAL_HISTORY_SIZE - 1) % ARRIVAL_HISTORY_SIZE; + uint32_t gap = now - arrival_times[last_idx]; + if (gap > 500000) { + /* Long gap detected - reset tracking for new tune */ + usASID("Long gap detected (%u) - new tune assumed\n", gap); + arrival_index = 0; + arrival_count = 0; + sid_count_estimate = 1; + frames_since_multisid = 0; + base_rate = 0; /* Reset base rate for new auto-detection */ + ring_size = RING_SIZE_DEFAULT; /* Reset buffer size for new tune */ + for (int i = 0; i < ARRIVAL_HISTORY_SIZE; i++) { + arrival_times[i] = 0; + } + } + } + + arrival_times[arrival_index] = now; + arrival_index = (arrival_index + 1) % ARRIVAL_HISTORY_SIZE; + + if (arrival_count < ARRIVAL_HISTORY_SIZE) { + arrival_count++; + return 0; /* Not enough samples yet */ + } + + /* Calculate average delta from oldest to newest */ + uint8_t oldest = arrival_index; /* Next write position = oldest */ + uint8_t newest = (arrival_index + ARRIVAL_HISTORY_SIZE - 1) % ARRIVAL_HISTORY_SIZE; + + /* Sanity check: oldest must be non-zero (not from cleared array) */ + if (arrival_times[oldest] == 0) { + return 0; + } + + uint32_t total_delta = arrival_times[newest] - arrival_times[oldest]; + uint32_t avg_delta = total_delta / (ARRIVAL_HISTORY_SIZE - 1); + + /* Absolute sanity bounds (reasonable frame rates: 10Hz to 500Hz) + * Based on log values, typical avg_delta is 5000-25000 + * PAL ~20000, NTSC ~17000, CIA-timed tunes can go much faster */ + if (avg_delta < 1000 || avg_delta > 50000) { + return 0; /* Outside reasonable range */ + } + + /* If base_rate not set (player skipped env message), auto-detect it ONCE. + * Don't update base_rate mid-tune - CIA-timed tunes have variable rates + * and we should let the dynamic adjustment handle that, not keep changing base_rate. */ + if (base_rate == 0) { + /* Use measured rate as base_rate - only set once */ + base_rate = (uint16_t)avg_delta; + usASID("Auto-detected base_rate: %u\n", base_rate); + } + + /* Don't validate against base_rate for CIA-timed tunes. + * Just use the measured rate directly - the clamping will + * keep it within reasonable bounds. */ + calculated_rate = avg_delta; + + return calculated_rate; +} + +/** + * @brief Grow the ring buffer when approaching overflow + * + * @return boolean true ~ if buffer was grown + * @return boolean false ~ if already at max + * + * Handles wrapped ring buffer by relocating data: + * - If write >= read: data is contiguous, just extend size + * - If write < read: data wrapped, need to move the tail portion + */ +static bool ring_buffer_grow(void) +{ + if (ring_size >= RING_SIZE_MAX) { + return false; /* Already at maximum */ + } + + uint16_t new_size = ring_size + RING_SIZE_STEP; + if (new_size > RING_SIZE_MAX) { + new_size = RING_SIZE_MAX; + } + + /* Handle wrapped data: relocate the portion at the start of buffer */ + if (asid_ringbuffer.ring_write < asid_ringbuffer.ring_read) { + /* Data is in two segments: [read..ring_size) and [0..write) + * Move [0..write) to [ring_size..ring_size+write) to make it contiguous */ + if (asid_ringbuffer.ring_write > 0) { + memmove(&asid_ringbuffer.ringbuffer[ring_size], + &asid_ringbuffer.ringbuffer[0], + asid_ringbuffer.ring_write); + asid_ringbuffer.ring_write = ring_size + asid_ringbuffer.ring_write; + } + } + + ring_size = new_size; + usASID("Buffer grown to %u bytes (diff=%d)\n", ring_size, ring_diff()); + return true; +} + +/** + * @brief Reset ring buffer size to default + * @note Call when starting a new tune to reclaim memory + */ +void ring_buffer_reset_size(void) +{ + ring_size = RING_SIZE_DEFAULT; + usASID("Buffer size reset to %u bytes\n", ring_size); + return; +} + +/** + * @brief Update SID count when we see a multi-SID message + * @note Call from decode_asid_message when processing SID2/3/4 messages + * + * @param uint8_t sid_num ~ The SID number (1-4) from the message type + */ +void update_sid_count(uint8_t sid_num) +{ + still_receiving = true; + + /* Reset timeout - we just saw a multi-SID message */ + frames_since_multisid = 0; + + if (sid_num > sid_count_estimate) { + sid_count_estimate = sid_num; + usASID("SID count updated to %u\n", sid_count_estimate); + } + return; +} + +/** + * @brief Dynamically adjust buffer rate based on calculated rate and buffer state + * @param uint32_t target_rate ~ The calculated frame rate from track_asid_arrival (0 if not available) + * + * Strategy: + * 1. Use calculated rate (CR) as the primary guide for rate + * 2. Use actual SID count from received messages (set via update_sid_count) + * 3. Apply buffer-based corrections only when buffer is critically full/empty + * 4. Grow buffer if approaching overflow + */ +void adjust_buffer_rate_dynamic(uint32_t target_rate) +{ + still_receiving = true; + + /* Get current buffer fullness */ + int diff = ring_diff(); + + /* Track frames since last multi-SID message (only on SID1 frames) */ + if (target_rate > 0 && sid_count_estimate > 1) { + frames_since_multisid++; + if (frames_since_multisid > MULTISID_TIMEOUT_FRAMES) { + /* Haven't seen SID2/3/4 for a while - assume single-SID now */ + usASID("No multi-SID messages for %u frames, resetting to 1-SID\n", + frames_since_multisid); + sid_count_estimate = 1; + frames_since_multisid = 0; + } + } + + /* Calculate target rate based on CR and actual SID count */ + int32_t new_rate; + if (target_rate > 0) { + /* Use calculated rate divided by SID count */ + new_rate = (int32_t)(target_rate / sid_count_estimate); + } else { + /* No valid CR yet, use current rate with buffer adjustment */ + new_rate = (int32_t)corrected_rate; + } + + /* Check if buffer is approaching overflow - grow if needed */ + int headroom = (int)ring_size - diff; + if (headroom < (diff_size * 4)) { + /* Less than 4 frames of headroom - try to grow */ + if (!ring_buffer_grow()) { + /* Can't grow - emergency rate reduction needed */ + if (headroom < diff_size) { + /* Critical: less than 1 frame headroom, aggressive reduction */ + new_rate = new_rate * 3 / 4; /* 25% reduction */ + usASID("Emergency rate reduction: %d\n", new_rate); + } + } + } + + /* Apply buffer-based fine-tuning based on buffer fullness percentage */ + int fill_percent = (diff * 100) / (int)ring_size; + if (fill_percent > 95) { + new_rate -= 500; /* Near overflow - aggressive */ + } else if (fill_percent > 90) { + new_rate -= 300; + } else if (fill_percent > 80) { + new_rate -= 150; + } else if (fill_percent > 60) { + new_rate -= 50; + } else if (diff < (diff_size / 2)) { + new_rate += 100; /* Buffer nearly empty */ + } else if (diff < diff_size) { + new_rate += 50; + } + /* else: buffer in acceptable range, use rate as-is */ + + /* Clamp to bounds */ + new_rate = (int32_t)clamp_to_base_rate((uint32_t)(new_rate > 0 ? new_rate : 1)); + + /* Apply if significantly different (small hysteresis) */ + int32_t rate_diff = (int32_t)corrected_rate - new_rate; + if (rate_diff < 0) rate_diff = -rate_diff; + + if (rate_diff > 50) { + pio_sm_put(raster_pio, sm_buffer, (uint32_t)new_rate); + corrected_rate = (uint16_t)new_rate; + } + return; +} + +/** + * @brief Reset arrival tracking state + * @note Clears timestamps and counters but preserves base_rate + * @note base_rate is only set via set_buffer_rate() + */ +void reset_arrival_tracking(void) +{ + arrival_index = 0; + arrival_count = 0; + calculated_rate = 0; + sid_count_estimate = 1; /* Reset SID count estimate */ + frames_since_multisid = 0; /* Reset multi-SID timeout */ + /* Clear stale timestamps to prevent bogus deltas */ + for (int i = 0; i < ARRIVAL_HISTORY_SIZE; i++) { + arrival_times[i] = 0; + } + /* Note: don't reset base_rate here, it's set from set_buffer_rate() */ + return; +} + + +/** + * Here be IRQ and PIO magic mushrooms + */ /** * @brief buffer interrupt handler @@ -80,13 +445,20 @@ static ring_buffer_t asid_ringbuffer; */ void __not_in_flash_func(buffer_irq_handler)(void) { - pio_interrupt_clear(raster_pio, BUFFPIOIRQ); + in_asid_irq = true; + irq_prev_at = irq_now_at; + irq_now_at = clockcycles(); /* inline extern as only used here */ extern void cycled_write_operation(uint8_t address, uint8_t data, uint16_t cycles); + /* Retrieve the current diff */ + int current_diff = ring_diff(); + /* Only start if head and tail are not the same */ if(asid_ringbuffer.ring_read != asid_ringbuffer.ring_write) { - if (ring_diff() > diff_size) { + // if (current_diff > (diff_size + (28 * 4))) { // Ensure we keep diff_size bytes after reading + /* Only consume if we have enough data AND will stay above minimum */ + if (current_diff > diff_size) { // Ensure we keep diff_size bytes after reading for (size_t pos = 0; pos < 28; pos++) { uint8_t reg = ring_get(); uint8_t val = ring_get(); @@ -99,12 +471,36 @@ void __not_in_flash_func(buffer_irq_handler)(void) } } + if (!still_receiving) { + frames_since_nowrites++; + if (frames_since_nowrites > NOWRITES_TIMEOUT_FRAMES) { + extern void deinit_asid_buffer(void); + usASID("More then 100 frames since last write, deactivating\n"); + frames_since_nowrites = frames_since_multisid = 0; + base_rate = corrected_rate = calculated_rate = 0; + arrival_index = arrival_count = 0; + irq_prev_at = irq_now_at = irq_end_at = 0; + pio_interrupt_clear(raster_pio, BUFFPIOIRQ); + deinit_asid_buffer(); + return; + } + } + + irq_end_at = clockcycles(); + if ((irq_end_at-irq_now_at) <= 1) { + still_receiving = false; + } else { + frames_since_nowrites = 0; + still_receiving = true; + } + in_asid_irq = false; + /* Interrupt cleared at end of routine * if play becomes irregular, irq's might be * misfiring and moving the clearing to the * beginning of the routine might help */ - /* pio_interrupt_clear(raster_pio, BUFFPIOIRQ); */ + pio_interrupt_clear(raster_pio, BUFFPIOIRQ); return; } @@ -116,7 +512,7 @@ void __not_in_flash_func(buffer_irq_handler)(void) void init_buffer_irq(void) { if (!buffer_irq_started) { - DBG("[ASID] Enabling buffer IRQ\n"); + usASID("Enabling buffer IRQ\n"); buffer_irq = pis_interrupt2; /* PIO_INTR_SM2_LSB hardware/pio.h */ pio_irq = pio_get_irq_num(raster_pio, 0); irq_set_exclusive_handler(pio_irq, buffer_irq_handler); @@ -135,7 +531,7 @@ void init_buffer_pio(void) { /* buffer cycle counter */ sm_buffer = 0; /* PIO1 SM0 */ if (!buffer_sm_claimed) { - DBG("[ASID] Starting & claiming buffer pio\n"); + usASID("Starting & claiming buffer pio\n"); pio_sm_claim(raster_pio, sm_buffer); offset_buffer = pio_add_program(raster_pio, &raster_buffer_program); pio_sm_config c_buffer = raster_buffer_program_get_default_config(offset_buffer); @@ -152,7 +548,7 @@ void init_buffer_pio(void) void start_buffer_pio(void) { if (!buffer_sm_started) { - DBG("[ASID] Enabling buffer statemachine\n"); + usASID("Enabling buffer statemachine\n"); pio_sm_set_enabled(raster_pio, sm_buffer, true); buffer_sm_started = true; } @@ -172,18 +568,18 @@ void stop_buffer_pio(void) pio_set_irq0_source_enabled(raster_pio, buffer_irq + sm_buffer, false); irq_remove_handler(pio_irq, buffer_irq_handler); buffer_irq_started = false; - DBG("[ASID] Buffer IRQ released\n"); + usASID("Buffer IRQ released\n"); } if (buffer_sm_started) { pio_sm_set_enabled(raster_pio, sm_buffer, false); buffer_sm_started = false; - DBG("[ASID] Buffer statemachine disabled\n"); + usASID("Buffer statemachine disabled\n"); } if (buffer_sm_claimed) { pio_remove_program(raster_pio, &raster_buffer_program, offset_buffer); pio_sm_unclaim(raster_pio, sm_buffer); buffer_sm_claimed = false; - DBG("[ASID] Buffer statemachine released\n"); + usASID("Buffer statemachine released\n"); } return; } @@ -196,12 +592,16 @@ void stop_buffer_pio(void) * @note the function does this if the provided * @note rate is higher than zero. * - * @param uint16_t rate + * @param uint16_t rate ~ the buffer irq rate to set */ void set_buffer_rate(uint16_t rate) { - uint16_t corrected_rate = ((rate > 0) ? (rate - 1) : (usbsid_config.raster_rate - 1)); - DBG("[ASID] Rate received %u Rate set %u\n", rate, corrected_rate); + corrected_rate = ((rate > 0) ? (rate - 1) : (usbsid_config.refresh_rate - 1)); + + /* Store as base rate for dynamic adjustment bounds */ + set_base_rate(corrected_rate); + + usASID("Rate received %u Rate set %u Base rate %u\n", rate, corrected_rate, base_rate); if (!buffer_irq_started) init_buffer_irq(); pio_sm_put(raster_pio, sm_buffer, corrected_rate); start_buffer_pio(); @@ -209,6 +609,9 @@ void set_buffer_rate(uint16_t rate) } +/** + * Here be ringbuffer shizzle + */ /** * @brief reset the head and tail to zero @@ -220,37 +623,52 @@ static void ring_buffer_reset(void) } /** - * @brief returns true if the head is - * higher then the tail + * @brief returns the difference between head and tail + * + * @return int ~ the head -> tail difference */ -static bool __not_in_flash_func(ring_is_higher)(void) +static int __not_in_flash_func(ring_diff)(void) { - return (asid_ringbuffer.ring_read < asid_ringbuffer.ring_write); + return (asid_ringbuffer.ring_write - asid_ringbuffer.ring_read + ring_size) % ring_size; } /** - * @brief returns the difference between head and tail + * @brief Check if buffer would overflow with next write + * + * @return boolean ~ true if there's no room for another frame (112 bytes) + * @return boolean ~ false if there is */ -static int __not_in_flash_func(ring_diff)(void) +static bool ring_would_overflow(void) { - int d = (ring_is_higher() ? (asid_ringbuffer.ring_read - asid_ringbuffer.ring_write) : (asid_ringbuffer.ring_write - asid_ringbuffer.ring_read)); - return ((d < 0) ? (d * -1) : d); + int headroom = (int)ring_size - ring_diff(); + return headroom < 112; /* Need room for at least 1 frame (28 writes * 4 bytes) */ } /** * @brief put an item into the ringbuffer + * @note Checks for overflow and drops data if buffer is full * - * @param uint8_t item + * @param uint8_t item ~ item to store */ static void ring_put(uint8_t item) { + /* Check for overflow - if we're about to overwrite unread data, skip this write */ + uint16_t next_write = (asid_ringbuffer.ring_write + 1) % ring_size; + if (next_write == asid_ringbuffer.ring_read) { + /* Buffer full - would overflow. Drop this byte to prevent corruption. */ + usASID("Buffer overflow - dropping data\n"); + return; + } + asid_ringbuffer.ringbuffer[asid_ringbuffer.ring_write] = item; - asid_ringbuffer.ring_write = (asid_ringbuffer.ring_write + 1) % ring_size; + asid_ringbuffer.ring_write = next_write; return; } /** * @brief retrieves an item from the ring buffer + * + * @return uint8_t ~ the retrieved ring item */ static uint8_t __not_in_flash_func(ring_get)(void) { @@ -261,15 +679,20 @@ static uint8_t __not_in_flash_func(ring_get)(void) /** * @brief intialize the ring buffer + * @note Allocates maximum size upfront to allow dynamic growth */ void asid_ring_init(void) { if (!asid_ringbuffer.is_allocated) { if (asid_ringbuffer.ringbuffer != NULL) { free(asid_ringbuffer.ringbuffer); } - asid_ringbuffer.ringbuffer = (uint8_t*)calloc(ring_size, 1); + /* Allocate max size upfront - allows growth without reallocation */ + asid_ringbuffer.ringbuffer = (uint8_t*)calloc(RING_SIZE_MAX, 1); + ring_size_allocated = RING_SIZE_MAX; + ring_size = RING_SIZE_DEFAULT; /* Start with default logical size */ asid_ringbuffer.is_allocated = true; ring_buffer_reset(); - DBG("[ASID] Ringbuffer initialized\n"); + usASID("Ringbuffer initialized (allocated=%u, effective=%u)\n", + ring_size_allocated, ring_size); } return; } From 15f80930dd2675c6dc93e83a34cfc432836caab7 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 17:51:48 +0100 Subject: [PATCH 32/45] Missed log change --- src/bus.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bus.c b/src/bus.c index 73162f4..8b7a46a 100644 --- a/src/bus.c +++ b/src/bus.c @@ -80,7 +80,7 @@ uint32_t data_word, dir_mask; */ inline static int __not_in_flash_func(set_bus_bits)(uint8_t address, bool write) { - /* CFG("[BUS BITS]$%02X:%02X ", address, data); */ + /* usCFG("[BUS BITS]$%02X:%02X ", address, data); */ vu = (vu == 0 ? 100 : vu); /* NOTICE: Testfix for core1 setting dtype to 0 */ if __us_likely(write) { control_word = 0b111000; @@ -117,7 +117,7 @@ inline static int __not_in_flash_func(set_bus_bits)(uint8_t address, bool write) break; } data_word = (dir_mask << 16) | data_word; - // CFG("$%02X:%02X $%04X 0b"PRINTF_BINARY_PATTERN_INT32" $%04X 0b"PRINTF_BINARY_PATTERN_INT16"\n", + // usCFG("$%02X:%02X $%04X 0b"PRINTF_BINARY_PATTERN_INT32" $%04X 0b"PRINTF_BINARY_PATTERN_INT16"\n", // address, data, data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word)); return 1; } @@ -170,7 +170,7 @@ uint8_t __no_inline_not_in_flash_func(bus_operation)(uint8_t command, uint8_t ad read_data = 0x0; dma_channel_set_write_addr(dma_rx_data, &read_data, true); dma_channel_wait_for_finish_blocking(dma_rx_data); - GPIODBG("[W]$%08x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16"\n[R]$%08x 0b"PRINTF_BINARY_PATTERN_INT32"\n", + usGPIO("[W]$%08x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16"\n[R]$%08x 0b"PRINTF_BINARY_PATTERN_INT32"\n", data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word), read_data, PRINTF_BYTE_TO_BINARY_INT32(read_data)); @@ -179,7 +179,7 @@ uint8_t __no_inline_not_in_flash_func(bus_operation)(uint8_t command, uint8_t ad } /* WRITE, G_PAUSE & G_CLEAR_BUS*/ dma_channel_wait_for_finish_blocking(dma_tx_control); - GPIODBG("[W]$%08x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16"\n", data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word)); + usGPIO("[W]$%08x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16"\n", data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word)); return 0; } @@ -257,7 +257,7 @@ void __no_inline_not_in_flash_func(cycled_write_operation_nondma)(uint8_t addres pio_sm_put_blocking(bus_pio, sm_data, data_word); pio_sm_put_blocking(bus_pio, sm_delay, delay_word); - GPIODBG("[WC]$%04x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16" $%02X:%02X(%u %u)\n", + usGPIO("[WC]$%04x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16" $%02X:%02X(%u %u)\n", data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word), address, data, cycles, delay_word); return; @@ -335,7 +335,7 @@ void __no_inline_not_in_flash_func(cycled_write_operation)(uint8_t address, uint */ dma_channel_wait_for_finish_blocking(dma_tx_control); - GPIODBG("[WC]$%04x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16" $%02X:%02X(%u %u)\n", + usGPIO("[WC]$%04x 0b"PRINTF_BINARY_PATTERN_INT32" $%04x 0b"PRINTF_BINARY_PATTERN_INT16" $%02X:%02X(%u %u)\n", data_word, PRINTF_BYTE_TO_BINARY_INT32(data_word), control_word, PRINTF_BYTE_TO_BINARY_INT16(control_word), address, data, cycles, delay_word); return; @@ -384,7 +384,7 @@ uint8_t __no_inline_not_in_flash_func(cycled_read_operation)(uint8_t address, ui */ void restart_bus(void) { - CFG("[RESTART BUS START]\n"); + usCFG("[RESTART BUS START]\n"); /* unclaim dma channels */ unclaim_dma_channels(); /* stop all pio's */ @@ -395,7 +395,7 @@ void restart_bus(void) setup_dmachannels(); /* sync pios */ sync_pios(false); - CFG("[RESTART BUS END]\n"); + usCFG("[RESTART BUS END]\n"); return; } From abe8d20515fb7c914b2ba5e52f59bafb2eae7b74 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 17:52:56 +0100 Subject: [PATCH 33/45] That should have been removed --- src/asid_buffer.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/asid_buffer.c b/src/asid_buffer.c index 4bbe3c2..d120cfd 100644 --- a/src/asid_buffer.c +++ b/src/asid_buffer.c @@ -105,23 +105,6 @@ static const uint16_t RING_SIZE_STEP = (20 * 224); /* 4480 bytes - grow/shrin volatile uint16_t ring_size = RING_SIZE_DEFAULT; /* Current effective size */ static uint16_t ring_size_allocated = 0; /* Actual allocated size */ -/* TODO: REMOVE ~ TEMPORARY */ -volatile bool log_asid_queue = false; -volatile bool in_asid_irq = false; -volatile int __not_in_flash_func(ring_diff_external)(void) -{ - return (asid_ringbuffer.ring_write - asid_ringbuffer.ring_read + ring_size) % ring_size; -} -volatile uint16_t __not_in_flash_func(ring_head_external)(void) -{ - return asid_ringbuffer.ring_write; -} -volatile uint16_t __not_in_flash_func(ring_tail_external)(void) -{ - return asid_ringbuffer.ring_read; -} -/* TODO: REMOVE ~ TEMPORARY */ - /** * Here be magicians From e6dfa8d1afc099d6a412acc9bdf3e7cbce33af1d Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 17:57:35 +0100 Subject: [PATCH 34/45] Whoops --- src/config.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config.c b/src/config.c index 6761d4e..ad584fe 100644 --- a/src/config.c +++ b/src/config.c @@ -993,7 +993,6 @@ void handle_config_request(uint8_t * buffer, uint32_t size) test_before = to_us_since_boot(get_absolute_time()); uint16_t waited_cycles = cycled_delay_operation(dcyc); test_after = to_us_since_boot(get_absolute_time()); - usCFG("[CFG] DELAY_CYCLES %u = %.4fµs, ACTUAL: %uµs (including get_absolute_time delay)\n", } if (buffer[1] == 6) { detect_fmopl(buffer[2]); From 18bc1ac8070831d30cfa68808077c12ad7ccfba5 Mon Sep 17 00:00:00 2001 From: loudnl Date: Sun, 8 Feb 2026 18:00:59 +0100 Subject: [PATCH 35/45] =?UTF-8?q?=F0=9F=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/asid_buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/asid_buffer.c b/src/asid_buffer.c index d120cfd..d6f925f 100644 --- a/src/asid_buffer.c +++ b/src/asid_buffer.c @@ -104,6 +104,8 @@ static const uint16_t RING_SIZE_MAX = (150 * 224); /* 33600 bytes - maximum s static const uint16_t RING_SIZE_STEP = (20 * 224); /* 4480 bytes - grow/shrink increment */ volatile uint16_t ring_size = RING_SIZE_DEFAULT; /* Current effective size */ static uint16_t ring_size_allocated = 0; /* Actual allocated size */ +/* TODO: REMOVE ~ TEMPORARY */ +volatile bool in_asid_irq = false; /** From eccd98d432f94dab1f2ccb901789a0eea0e34eea Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:01:31 +0100 Subject: [PATCH 36/45] Fix hardfault/lockup on rp2350 when toggling the mono/stereo pin --- src/gpio.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/gpio.c b/src/gpio.c index d5902ad..452407d 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -42,11 +42,7 @@ extern char *mono_stereo[2]; * Partly copied from and inspired by SKPico code by frenetic * see: https://github.com/frntc/SIDKick-pico */ -#if PICO_RP2350 -register uint32_t b asm( "r13" ); -#else register uint32_t b asm( "r10" ); -#endif volatile const uint32_t *BUSState = &sio_hw->gpio_in; @@ -89,14 +85,14 @@ int detect_clocksignal(void) return r; /* 1 if clock detected */ } -void toggle_audio_switch(void) /* BUG: This _can_ cause a hardfault on rp2350 */ +void toggle_audio_switch(void) { /* Toggle the SPST switch stereo <-> mono */ #if defined(HAS_AUDIOSWITCH) if (!usbsid_config.lock_audio_sw) { b = *BUSState; /* read complete bus */ int audio_state = (b & bPIN(AU_SW)) >> AU_SW; /* Pinpoint current audio switch state */ audio_state ^= 1; - usCFG("TOGGLE AUDIO SWITCH TO: %d (%s)\n", audio_state, mono_stereo[audio_state]); + usCFG("TOGGLE AUDIO SWITCH TO: %d (%s)\n", (int)audio_state, mono_stereo[(int)audio_state]); tPIN(AU_SW); /* toggle mono <-> stereo */ } else { usCFG("Audio switch is locked at %d (%s), toggle not applied\n", @@ -110,7 +106,7 @@ void set_audio_switch(bool state) { /* Set the SPST switch */ #if defined(HAS_AUDIOSWITCH) if (!usbsid_config.lock_audio_sw) { - usCFG("SET AUDIO SWITCH TO: %d (%s)\n", state, mono_stereo[state]); + usCFG("SET AUDIO SWITCH TO: %d (%s)\n", (int)state, mono_stereo[(int)state]); if (state) { sPIN(AU_SW); /* set mono <-> stereo pin */ } else cPIN(AU_SW); /* clear mono <-> stereo pin */ From e0080aed3d132cf83826e5180d3af66b774c5e3b Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:10:09 +0100 Subject: [PATCH 37/45] Documentation date update --- README.md | 4 ++-- doc/USBSID-Pico-Cynthcart-manual.pdf | Bin 92687 -> 92551 bytes doc/USBSID-Pico-PCBv1.0-manual.pdf | Bin 1865050 -> 1865070 bytes doc/USBSID-Pico-PCBv1.3-manual.pdf | Bin 1090984 -> 1091004 bytes doc/USBSID-Pico-firmware-manual.pdf | Bin 1239361 -> 1239381 bytes doc/USBSID-Pico-software-manual.pdf | Bin 2946593 -> 2946613 bytes doc/USBSID-Player-manual.pdf | Bin 97285 -> 97148 bytes doc/license-hardware.adoc | 4 ++-- 8 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index af33a68..1d69f89 100644 --- a/README.md +++ b/README.md @@ -343,10 +343,10 @@ Any code in this repository that is not written by me automatically falls under For for more information about this license please read the LICENSE document in the root of the USBSID-Pico repository. ### USBSID-Pico v1.3 PCB Hardware License ~ Creative Commons -USBSID-Pico v1.3 PCB © 2025 by LouD is licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International +USBSID-Pico v1.3 PCB © 2025-2026 by LouD is licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International ### USBSID-Pico v1.0 PCB Hardware License ~ Creative Commons -USBSID-Pico v1.0 PCB © 2024 by LouD is licensed under Creative Commons Attribution-ShareAlike 4.0 International +USBSID-Pico v1.0 PCB © 2024-2026 by LouD is licensed under Creative Commons Attribution-ShareAlike 4.0 International # Certification ### USBSID-Pico v1.0 PCB OSHWA certification diff --git a/doc/USBSID-Pico-Cynthcart-manual.pdf b/doc/USBSID-Pico-Cynthcart-manual.pdf index 7b83d7b81bd37bf66c7ac4ab645c9a5a8692d757..b40cb4802b907ba789ee310bcfb8507bbb13813f 100644 GIT binary patch delta 11952 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 diff --git a/doc/USBSID-Pico-PCBv1.0-manual.pdf b/doc/USBSID-Pico-PCBv1.0-manual.pdf index 5ac9246c29a75e94e258f419b0f783d1f9874460..4bba2cba63a37b63666c82cc4b9490c3461fcd44 100644 GIT binary patch delta 2789 zcmaJ@zl+>e5a)h*mp$W;BqnC}f@={-K$kaf-n=(MKtZ_$3$YLzJ0T*bFiD}6@Fk=O zq%vNymReY=SS(ohFQk_PgJlqFOFz4p-MgJRJowykALhN8`OJ^^{p0D~f1cj`=BM47 zG7+WJh#&oS<>bG={Po@DZp}A2amH^tc>UVw;K@hV?;mbGINVwT)`1OR6PR(&0o%Y1 za0ciByTJ3nS>OfWMc^giW#Ale9=HI!0$c$2dDX)(bGT2gKuARy1jAm`}oG> zcHJINuYWdpXXD~BoW?YwMKllY-5N~q-P-$Pt+O;tv!CG?!yf)nL+Vr_C+aw5t`;~o z3Y@Wct_bs->y90(Wc9(QaQ4A~t05Q>jdo3{cPv&`&UVOHE5!&abnu6(P!3#6-m!_* z=IpF;^~zW+sq@lUt=X%9>w>ISEvRLUs?XROi%-OwlxK-_%eq^uEkxKf`HZa@hwL=9 zpn_%&XO&$d9W|R3)XSw+<6-5M+M#r&W&O3(wAv@Dp5`(a-XBE>! z&I!0&+?0T0TO`~)8#|CvTqfX9sNe*x#+o=u$b?B35-t^FJRd$7E+yK@akF+uTMtee1^HS1 z*3(pyZzOP##yc+Df(k|aC~B8VJ>^36+f0+B7L<{^nB&D5!>+`VNQ(iA&Ftp-A0Ni< z2@+PxA-k#)>fR_iX?QlhGRMmYekbqW+1o}F#>&A%sJg0OJ(`Tf4J^3QBG$bLi^S#Xg5Ec!mUOQ u?}2MKj9fV(^;~HzBU!bB{Gv6~SWaoUn#d;GU+%rvqttp=ukL>^?)?V=2S-By delta 2774 zcmZuzJ#U;v5SHyYb}pE&kPpAF5tkwb5wUi5cXnrlFcQdzs1QX&M<_rj2_lM6lJYGm zB2f{gc+>D3P^ya}V7BScNfa~I#8Zzd-_XZg|0&dxkDv-j_do6ld|{Pp*f zOd%>|%8Bkhe)s6V5C48}Y>VT!iBiCKOnU#zOCQ|-^YZ&YAFSU!SYH8Ffi>V5FyW2^ z8^9)T0vG@%f!Ba7;C0{);7#By;BDX(@D6YqI0Kvo&H>xwgZ1WWTnXcmG`F|e%yG;taT(oL`Z@+2!Z$`~mo2l;Rf=s5=_}QrW<-hHj*^sF0<~odXqd)fA^W)z> zZlA8LhR|QRw0e2`JHg`}UKybMq_IM7wn42LD`b&)#HE8<;gM`F6-y3i%rrG@o@Y z9HQfj+Q7;R&6lgHC1qO~aMIb*XekiGI@ju*wK%&#nu13sS4t zjM4q%vXj8B3KGfrN-~LbR8^R)PAa8%XQ3tSAe{q$+ii>)nv*FPHlD)h}cvMTq^D|#L3uU{;39+2}w1E z5@N6&3@(ysLU5_Nj3Z}%UJX3#mO35IQjEqDQb;b7L}gaOc?A<*i)A_Co1?emMYDD< zw;l|OnnnUA%gZ8=gp6X_AeqDk$!OU_(#@KE+AuKuP9E0or zNP76BF3!Vry0MEX>ygS#;zRSC?XH%@IoQf1E-nZLr`nNCRqU%MIVV@uoI5Ud>@sV# z&6Uw`){axnp3`D4%~*O?x=9{WyLzPjEX8TeuV}b}mD#tqc0L~jse=m__P-bn{s*b! BLt_8{ diff --git a/doc/USBSID-Pico-PCBv1.3-manual.pdf b/doc/USBSID-Pico-PCBv1.3-manual.pdf index 782faab8ff83079e6f12aef4b6a6b5e29a3508bf..847170f05a442b049e1832260f59395b8d9c7fc0 100644 GIT binary patch delta 2544 zcmZuzJ!_Of5au)==8`vW#6m@)7ciy^JF~m9yG05ETBOh%eqp0cEF+3wunJ-k>=cVd z@GE{ru#i*-LBW)gv#_$!LVrMF3VUaBiQMhvVD7lN*=OdNeP-T!w>J54ZSui~eo_bk zN;&WkSBBfaF8{vYRS?c-rIFu3L%jQ?IDfC%z0mBgun%htt~d_8QvZ2Q>y*s5&( zYy*vEcky_o%P56$GXtK(gUnD;W=%gQi# z$YC`&*Wm*Ns6hIR?qrbS2wQPicG{GjULMv;iNl%+<%u69gIQbFTAJQ(TZ|ts;@$BHAsc?QOD}L`$p%QJ^K;I^5P` z>sgmclj^^Kv{Sr4vQ9gzTz>n`qK}JPRpdk*Z>1c9*RLSr(|?^!hNxrCo7HCW`{orY=8<{W Xd`3NK6)(IQn(9NSj*gx>d%F4$#{?U$ delta 2456 zcmZuzJ!_Of5awcX(TlGa5fwr43O~Ri?#%A&?i3b@XklYS5o`j2jg24(u`)!9VBr^n zi})EeD5TI-2cn{-cotSRHu?wDf8cB`>fKHb=8oHY-ekQSz2E^8g4T|Nny4bMTTCsIv4Y3ii z_3h43b@%n4WybXL>-r17Ixv!U+rnuYN5TfxA|>vrW{^@rW=#Z?dg7}I$7@` z+t(+Xi#yA#j#rDf2M+E_@3_9D4ds$W$^4^WbzmDdnrkm}l>Cwx(YA$`hH@fKn znU=|~*11+Af3lHOTye&LL7!Pja9=1QD>zxN&!R2Q&_J5a6sX`xLBoTN3^}APLNVh* zA3GQTW_5-RVu?dM99zV6{(+?Bp!8)ubn(6LXf%#v`j9D# zx(mCrUQsy5{e8?hk7?sWp`iZO<)5O4lX0pFgeINN6iy{hj#U)oEtLsVh-oQPpo+p9 z->)2r%%fGbUIw@JC`X~#z2|=AWQvF?5zmy4WeQ#CfKEjgtnPFmo8~ixR~|!Pl}KPx zi9|5zccv(&LxPUhs}e_PNmS9W8L#1hjS)oVQ)vR8NvNnz9RW$7w=Yd8IJW z$-{-V&=I8%PefZ(Y6fYQdXiIvgG8J*KFtc(6s+`y9%4DF)?3**nL Q(Ie{&G&LqK{F!_jLdwq^J8cF?Le_?Q{C0|>eZ|M9z44F>!X|B{~m-%Fqn+>D6U+C#T!5Oy4`{MqAG$chiUW4_|$wPjMV(zwzzy0sh38CDlZ0SqrUsX1^-d`f0>nn;5EL?l&C$ zc6hl@HIs)GUMtQgRnc~D(n7aL7TC}I%&%WhKUMSgz#KW%tVzf(E5Vt~0{MAe_d|T( zo~sa^7IJMj8(2nb;2o_cW4~MpLGwOQ##Y$T1N(Wrz)v$}>^V?!ZZ=1-Ut^ya;mvj7 zxaL=4Abu1{lwHvcsLzltSvO+zGx&=cL0C#yuhV>URSH}K6SImCKd_aMuBrBXpcM*S z3lY<19oy91Lew^h&7_L_k}UT2tL6vaatomaqxQ5=v?Z5?>U0^1cZ-5WY4-LpT2zAH zVx}5fG$NrzFO(gcgw`ampNLA|LL=&JNlbF&1C3hDqy$1xY9_QatU9M(YOkpzc2rVA z(=1eDH7=cc-C#P1Zdx1oxEdORU(Iu%QJqIW>l*8e1ta6q@dt z<2>(mbv6hr1<8p^owHFDvaJwb0iFwu56g8U;WKrHh&v%}!u&LhwiX3T7{r?s&`>OD z7W2R{N-xAktBDTuzz4cSI?%Nc{GKzR>X7!cORuT3c`1BgHM(Hov0zYQ3Q|y~7MbF@ zu{Kj@jH5)|SE1VNW+*KC@#qH=$j=d{P0sP`>)QkI7$u=YBp=+jxlB+xKt<;L1 zcwl-;(OPx(l!?Kf3cW8pRrbpgH?u=>Igw&}#%Nt2vr=czi}&K%bJ24($_~SfxL_DlLQYF84Mt z-cHo-=wh1QHPEF6shz*t`uT?m7E3F)5;V52YYCWTO?DXkr5!L5_|+ycq;@z8_)ilM zEXpf_Ea1W#I(f651b1%x9c#61I}wYEFQOIFSUZ~~AFabL0x~pp7b)j#U0w#CtF5R& b6+r9ef{M+<-+vvxv+w2L+O@adef!{l*9R3) delta 3962 zcmZu!J&T=H5aw=nvtMfRZVFM6EJ_3wmzg=|%$y?@vG60Nu+sbiQ=|#RZ5LiaA&rHy z!rRE&Sfmhi3%R8RE8BogK(Mf}6#szp?(W@t?>t!;mV54eKJ$71cy#mOqnls-u*uSd zrAYhw=d1G%$A8l5I?E?XQX3wAIr-@GJ8SpvtWBI&oGv)6I*o4^oz|T;oHm`doVJ~w za(de78K+B5m#25uy8qr>AsJ|E^5Ss)i^&_)2iLp2K1|;3t_(+?cDFZ}_KG%Z!|{9F z^TTKFcegf@?PVo7d-~NTtB04qzc@VD@9qwN-RQQb2RFL4SBKvYCQ-*P_qrE`SN6M) z=Q_rx>393xH&FitgZu_Wv>Hg!RZuRG;awX4cJJJ|llY%q3)Fr-tjM(iWW6Ks57RHE65&^!#fNIf9z-QJYVkwdEV6oBGx)%obwqWpYap;Qi6yr*X7`P_u4cAgjT&I zL%~@?zDgD9ql!R%)MS8kElhnhQZ)mF4gv}{2E&c8Fq;qr*HW4x-JiFe*BdHS1;edk zVYUVis@KEJ$jpXMrxJqeSt(OAqEu3ZP)Wf-rOL6mlv)9OOf{mQEH2wG=UBbNf`_b} zA;-!JMd%HoDBBp`gywwx+0;|<ih zDZwPvR0wjM6jPVXVmE1OW9m-uKksdyQBc8}xq&m;2Y84?GZwIB#})W&!A5_&bTrqt zJQ$d@5DXCCb%=uB1Yo4N*U@k(8JzBmBK)PoGSSj7Vs8_GUm1&469fESGULt}@t?!5 z1^QjpXK;)}D?wpuleomx2=y7a1%0D^huV??{N}4I8uktYSgq=U!ZUp>gM$@!gw{S8 z7^&q1nk8haF+^9JfHTdfPjuS54*AaR0FQqTf2+V~M2m2pL}y-?QK3zw2T3SC3ZjvM*P@k{ z!5QTqR*Dw{%ql;B&_Tt)X+EhS)83D<-p%jw({wHz*XGht3-*K@n;$GO;>s|b9cYbc z`I%If>S#Q!YbEG#pY^c4{eF*2RLF0`a7y@_TUmBcC~-Wl#jl#uhoTXxHLesPyxN2* z$Gb+@8!|*J?0YMqZ|aHZJc{?=mKiTFhoofcdlA_l*U{OFCvy2s+bi{lva amjZBkYQn#*Md*{PqK3$KM|K4*2xT z_3XTeI9Gi7?7`;n-v_^cxO6o8tHA}2-x8$z;FIRc&E4f&yUPcFgTNtR3FvKyffe8> zU=>&c)`26yQQ&Fd8Q@vq81Njh0UQTT04IUxffs;Nz>C03z-eF;I0KvoM!?I!7H|$Y z4{QUk0IveC0T+PRC%end8`lm-yYukS``x|YHZ~{Web-;szUDGz297431u9d zFWomcn$z8%_nRw+a~z9>$=5fU&oBI+_r<-+`>nlZ?`+N3_4DJqKdzMShd(whO}_f6 zd2{L1Je-Z9=vZ~+XVJWMmw#!tCTAWtvb^u)vy6QnIDP+XvvuqCqh@mZ(ay@rHkXm> ze@53vYxv`}pqG(t#gR=Vvn_kFnNzlQz8aq=A@&xo`YDXt|n}ZI>F=vRhL4cLQ>*(rW|H5+JT%>;oc?1z*Nj+ z=cvaSx+6bd64@|=LtER^pHo~gPHnlcO&wL7Es$SvJfua8l7TK;%DP1ET}ev)EJdhw zMI*JTSf&Kk*_^>6`)fpfckwSwWZ?K33o)))D0e8C{ZvU#=6LE&;#4{d6KS3UTjcgz z;x1U`)n-L0L9;L6?ibH}SESqDk@dHL|Z+81WzusF@yCEWz4lMQT5vI0)pP>trBzdPVw8 zwa}A3a0vL^t^z~aQqgJw`l#qP9IeG_!IW5t&ZXmZhS7!8{ru0)rPborwzuED{Lb3H D)?0^l delta 3088 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 R2@DW76oY|5TtNck{{gK|mc#%6 delta 12013 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 EKMBU-a{vGU diff --git a/doc/license-hardware.adoc b/doc/license-hardware.adoc index d440158..9409607 100644 --- a/doc/license-hardware.adoc +++ b/doc/license-hardware.adoc @@ -1,11 +1,11 @@ === USBSID-Pico v1.3 PCB Hardware License ~ Creative Commons -https://github.com/LouDnl/USBSID-Pico/tree/master/hardware/USBSID-Pico-v1.3[USBSID-Pico v1.3 PCB] © 2025 by +https://github.com/LouDnl/USBSID-Pico/tree/master/hardware/USBSID-Pico-v1.3[USBSID-Pico v1.3 PCB] © 2025-2026 by https://github.com/LouDnl[LouD] is licensed under + https://creativecommons.org/licenses/by-nc-nd/4.0/[Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International] + image:https://mirrors.creativecommons.org/presskit/icons/cc.svg[link="https://creativecommons.org/licenses/by-nc-nd/4.0/"]image:https://mirrors.creativecommons.org/presskit/icons/by.svg[link="https://creativecommons.org/licenses/by-nc-nd/4.0/"]image:https://mirrors.creativecommons.org/presskit/icons/nc.svg[link="https://creativecommons.org/licenses/by-nc-nd/4.0/"]image:https://mirrors.creativecommons.org/presskit/icons/nd.svg[link="https://creativecommons.org/licenses/by-nc-nd/4.0/"] === USBSID-Pico v1.0 PCB Hardware License ~ Creative Commons -https://github.com/LouDnl/USBSID-Pico/tree/master/hardware/USBSID-Pico-v1.0[USBSID-Pico v1.0 PCB] © 2024 by +https://github.com/LouDnl/USBSID-Pico/tree/master/hardware/USBSID-Pico-v1.0[USBSID-Pico v1.0 PCB] © 2024-2026 by https://github.com/LouDnl[LouD] is licensed under + https://creativecommons.org/licenses/by-sa/4.0/[Creative Commons Attribution-ShareAlike 4.0 International] + image:https://mirrors.creativecommons.org/presskit/icons/cc.svg[link="https://creativecommons.org/licenses/by-sa/4.0/"]image:https://mirrors.creativecommons.org/presskit/icons/by.svg[link="https://creativecommons.org/licenses/by-sa/4.0/"]image:https://mirrors.creativecommons.org/presskit/icons/sa.svg[link="https://creativecommons.org/licenses/by-sa/4.0/"] From 971a8b542fb18859bafffed23ae75c813cdaff84 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:10:31 +0100 Subject: [PATCH 38/45] Fix date --- examples/config-tool/cfg_skpico.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/config-tool/cfg_skpico.h b/examples/config-tool/cfg_skpico.h index a61bc39..5eed68a 100644 --- a/examples/config-tool/cfg_skpico.h +++ b/examples/config-tool/cfg_skpico.h @@ -7,7 +7,7 @@ * This file is part of USBSID-Pico (https://github.com/LouDnl/USBSID-Pico) * File author: LouD * - * Copyright (c) 2024-2025 LouD + * Copyright (c) 2024-2026 LouD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 6ac7f9db68d2506f18b89443bd8f5d6e769f2ebc Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:12:10 +0100 Subject: [PATCH 39/45] Syntax fix --- src/vu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vu.c b/src/vu.c index f1c1ac4..24930c1 100644 --- a/src/vu.c +++ b/src/vu.c @@ -218,8 +218,7 @@ void led_runner(void) us_now = to_us_since_boot(get_absolute_time()); if (vu == 0 && usbdata == 1) { n_checks++; - if (n_checks >= MAX_CHECKS) /* 100 checks */ - { + if (n_checks >= MAX_CHECKS) { /* 100 checks */ n_checks = 0, usbdata = 0, dtype = ntype; /* NOTE: This sets dtype to 0 which causes buffertask write to go to default and error out with many consecutive reads from the bus */ offload_ledrunner = false; } From d7ce5c1a5adf9d02a41d0dbdc67ccd3b51e6b81a Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:13:41 +0100 Subject: [PATCH 40/45] Also reset the writeorder when midi/asid is no longer mounted --- src/vu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vu.c b/src/vu.c index 24930c1..766bd36 100644 --- a/src/vu.c +++ b/src/vu.c @@ -221,6 +221,15 @@ void led_runner(void) if (n_checks >= MAX_CHECKS) { /* 100 checks */ n_checks = 0, usbdata = 0, dtype = ntype; /* NOTE: This sets dtype to 0 which causes buffertask write to go to default and error out with many consecutive reads from the bus */ offload_ledrunner = false; + /** + * @brief Let's make sure we always reset the write order + * when the interface is no longer mounted + */ + extern bool tud_midi_n_mounted (uint8_t itf); + if (!tud_midi_n_mounted(MIDI_ITF)) { + extern void reset_asid_to_writeorder(void); + reset_asid_to_writeorder(); + } } } return; From 45b54f0bf5e3ec3c752982361b64969dca5c75c4 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:14:32 +0100 Subject: [PATCH 41/45] Bump version for next release --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf90c8..0e8e3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ set(PROJECT_NAME usbsidpico) set(PROJECT_MANUFACTURER "LouD") set(PRODUCT_STRING "USBSID-Pico") string(TIMESTAMP MAGIC_SMOKE "%Y%m%d") # Auto generate dated version -set(PROJECT_VERSION "0.6.0-BETA.${MAGIC_SMOKE}") # Generate compile version +set(PROJECT_VERSION "0.6.1-BETA.${MAGIC_SMOKE}") # Generate compile version ### Want a cookie? # NOTICE: ENABLING THESE DEBUGGING DEFINITIONS WILL HAVE SIGNIFICANT IMPACT AND WILL DELAY PLAYING! From 39fdc8e836ace85e3901fb351a274cc700be632d Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 10:41:31 +0100 Subject: [PATCH 42/45] Fix logging issue in cfg buffer printing --- src/config_logging.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config_logging.c b/src/config_logging.c index 003a357..ccc6a82 100644 --- a/src/config_logging.c +++ b/src/config_logging.c @@ -54,14 +54,14 @@ void print_cfg(const uint8_t *buf, size_t len) usCFG("[PRINT CFG START]\n"); for (size_t i = 0; i < len; ++i) { if (i == 0) - usCFG("[R%03d] ", i); - usCFG("%02x", buf[i]); + usNFO("[R%03d] ", i); + usNFO("%02x", buf[i]); if (i == (len - 1)) { - usCFG("\n"); + usNFO("\n"); } else if ((i != 0) && (i % 16 == 15)) { - usCFG("\n[R%03d] ", i); + usNFO("\n[R%03d] ", i); } else { - usCFG(" "); + usNFO(" "); } } usCFG("[PRINT CFG END]\n"); From 9ba9c2489659180cc507ad21816849c711d886b7 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 12:22:32 +0100 Subject: [PATCH 43/45] Add config option to flip sockets at runtime 1 <--> 2 --- src/config.c | 5 +++++ src/config.h | 1 + src/config_socket.c | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/src/config.c b/src/config.c index ad584fe..0715828 100644 --- a/src/config.c +++ b/src/config.c @@ -660,6 +660,11 @@ void handle_config_request(uint8_t * buffer, uint32_t size) set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, false, false, usbsid_config.socketTwo.chiptype, false); } break; + case FLIP_SOCKETS: + usCFG("[CMD] FLIP_SOCKETS\n"); + extern void flip_sockets(void); + flip_sockets(); + break; case MIRRORED_SID: usCFG("[CMD] MIRRORED_SID\n"); set_socket_config(buffer[1], true, false, usbsid_config.socketOne.chiptype, true, false, usbsid_config.socketTwo.chiptype, true); diff --git a/src/config.h b/src/config.h index 7553970..8337f04 100644 --- a/src/config.h +++ b/src/config.h @@ -313,6 +313,7 @@ enum MIRRORED_SID = 0x45, /* Socket Two is linked to Socket One */ DUAL_SOCKET1 = 0x46, /* Two SID's in socket One, Socket Two disabled */ DUAL_SOCKET2 = 0x47, /* Two SID's in socket Two, Socket One disabled */ + FLIP_SOCKETS = 0x48, /* Socket Two is Socket One and vice versa */ SET_CLOCK = 0x50, /* Change SID clock frequency by array id */ DETECT_SIDS = 0x51, /* Try to detect the SID types per socket ~ routines see sid_detection.c */ diff --git a/src/config_socket.c b/src/config_socket.c index b55b37c..b1ced6b 100644 --- a/src/config_socket.c +++ b/src/config_socket.c @@ -308,6 +308,31 @@ void set_sid_id_addr(int socket, int sid, int id) return; } +/** + * @brief Flip socket id's and addresses at runtime + * + */ +void flip_sockets(void) +{ + uint8_t ids[4] = { + usbsid_config.socketOne.sid1.id, + usbsid_config.socketOne.sid2.id, + usbsid_config.socketTwo.sid1.id, + usbsid_config.socketTwo.sid2.id + }; + usbsid_config.socketOne.sid1.id = ids[2]; + usbsid_config.socketOne.sid1.addr = sidaddr_default[ids[2]]; + usbsid_config.socketOne.sid2.id = ids[3]; + usbsid_config.socketOne.sid2.addr = sidaddr_default[ids[3]]; + usbsid_config.socketTwo.sid1.id = ids[0]; + usbsid_config.socketTwo.sid1.addr = sidaddr_default[ids[0]]; + usbsid_config.socketTwo.sid2.id = ids[1]; + usbsid_config.socketTwo.sid2.addr = sidaddr_default[ids[1]]; + extern void apply_bus_config(bool quiet); + apply_bus_config(false); + return; +} + /* Called from apply_config apply_socket_change */ int verify_fmopl_sidno(void) { /* TODO: THIS USES CONFIG AND RUNTIMECFG CROSSED, THIS MUST BE CONFIG ONLY */ From 558c7b56187a59a718b0860f66f816ce68d1122a Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 17:58:53 +0100 Subject: [PATCH 44/45] What is it with Windows that it always has to %#$%#$^$@%^& --- .github/workflows/buildtools.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index 7ad89a7..c71ab89 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -43,9 +43,9 @@ jobs: - { os: ubuntu-latest, shell: bash, directory: send_sid, buildname: send_sid } - { os: windows-2022, shell: 'msys2 {0}', - directory: config-tool, buildname: cfg_usbsid.exe } + directory: config-tool, buildname: cfg_usbsid } - { os: windows-2022, shell: 'msys2 {0}', - directory: send_sid, buildname: send_sid.exe } + directory: send_sid, buildname: send_sid } - { os: macos-14, shell: 'bash', directory: config-tool, buildname: cfg_usbsid_macos14 } - { os: macos-14, shell: 'bash', From b2f97c4a75a42292700b7fe3935648248d0039e4 Mon Sep 17 00:00:00 2001 From: loudnl Date: Wed, 11 Feb 2026 18:02:33 +0100 Subject: [PATCH 45/45] =?UTF-8?q?=F0=9F=A4=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/buildtools.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buildtools.yml b/.github/workflows/buildtools.yml index c71ab89..7dbdf64 100644 --- a/.github/workflows/buildtools.yml +++ b/.github/workflows/buildtools.yml @@ -111,6 +111,6 @@ jobs: with: name: tools-${{ matrix.buildname }}-${{ matrix.os }} path: | - repo/examples/${{ matrix.directory }}/${{ matrix.buildname }} + ${{ matrix.os == 'windows-2022' && format('repo/examples/{0}/{1}.exe', matrix.directory, matrix.buildname) || format('repo/examples/{0}/{1}', matrix.directory, matrix.buildname) }} ${{ matrix.os == 'windows-2022' && format('repo/examples/{0}/*.dll', matrix.directory) || '' }} if-no-files-found: ignore