From 950f06980746c705809f45e43e28fc10a183b8ae Mon Sep 17 00:00:00 2001
From: Awawa <69086569+awawa-dev@users.noreply.github.com>
Date: Sun, 16 Apr 2023 15:55:22 +0000
Subject: [PATCH] HyperSerialPico first version
---
.github/FUNDING.yml | 1 +
.github/workflows/push-master.yml | 85 +++++
.gitignore | 355 +++++++++++++++++++
.gitmodules | 7 +
CMakeLists.txt | 85 +++++
LICENSE | 23 ++
README.md | 51 +++
include/base.h | 172 +++++++++
include/calibration.h | 134 +++++++
include/framestate.h | 261 ++++++++++++++
include/leds.h | 565 ++++++++++++++++++++++++++++++
include/main.h | 264 ++++++++++++++
include/statistics.h | 166 +++++++++
pio/neopixel.pio | 46 +++
sdk/config/FreeRTOSConfig.h | 139 ++++++++
sdk/freertos | 1 +
sdk/pico | 1 +
source/main.cpp | 177 ++++++++++
18 files changed, 2533 insertions(+)
create mode 100644 .github/FUNDING.yml
create mode 100644 .github/workflows/push-master.yml
create mode 100644 .gitignore
create mode 100644 .gitmodules
create mode 100644 CMakeLists.txt
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 include/base.h
create mode 100644 include/calibration.h
create mode 100644 include/framestate.h
create mode 100644 include/leds.h
create mode 100644 include/main.h
create mode 100644 include/statistics.h
create mode 100644 pio/neopixel.pio
create mode 100644 sdk/config/FreeRTOSConfig.h
create mode 160000 sdk/freertos
create mode 160000 sdk/pico
create mode 100644 source/main.cpp
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..7743b06
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: awawa-dev
diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml
new file mode 100644
index 0000000..9a5944d
--- /dev/null
+++ b/.github/workflows/push-master.yml
@@ -0,0 +1,85 @@
+name: HyperSerialPico CI Build
+
+on: [push]
+
+jobs:
+
+###########################
+#### HyperSerialPico ######
+###########################
+
+ HyperSerialPico:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Install GNU Arm Embedded Toolchain
+ uses: carlosperate/arm-none-eabi-gcc-action@v1
+ with:
+ release: '12.2.Rel1'
+
+ - name: Build packages
+ shell: bash
+ run: |
+ mkdir build
+ cd build
+ cmake ..
+ cmake --build . --config Release
+
+ - uses: actions/upload-artifact@v3
+ name: Upload artifacts (commit)
+ if: (startsWith(github.event.ref, 'refs/tags') != true)
+ with:
+ path: |
+ firmwares/*.uf2
+
+ - uses: actions/upload-artifact@v3
+ name: Upload artifacts (release)
+ if: startsWith(github.ref, 'refs/tags/')
+ with:
+ name: firmware-release
+ path: |
+ firmwares/*.uf2
+
+################################
+###### Publish Releases ########
+################################
+
+ publish:
+ name: Publish Releases
+ if: startsWith(github.event.ref, 'refs/tags')
+ needs: [HyperSerialPico]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ # generate environment variables
+ - name: Generate environment variables from version and tag
+ run: |
+ echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
+ echo "preRelease=false" >> $GITHUB_ENV
+
+ # If version contains alpha or beta, mark draft release as pre-release
+ - name: Mark release as pre-release
+ if: contains(env.VERSION, 'alpha') || contains(env.VERSION, 'beta')
+ run: echo "preRelease=true" >> $GITHUB_ENV
+
+ - uses: actions/download-artifact@v3
+ with:
+ name: firmware-release
+
+ # create draft release and upload artifacts
+ - name: Create draft release
+ uses: softprops/action-gh-release@v1
+ with:
+ name: HyperSerialPico ${{ env.VERSION }}
+ tag_name: ${{ env.TAG }}
+ files: |
+ *.uf2
+ draft: true
+ prerelease: ${{ env.preRelease }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..97bdfaf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,355 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+sdk/pico
+build/*
+generated/
+firmwares/
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..35e1430
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "sdk/pico"]
+ path = sdk/pico
+ url = https://github.com/RaspberryPi/pico-sdk
+[submodule "sdk/freertos"]
+ path = sdk/freertos
+ url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git
+ branch = smp
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c3121a7
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,85 @@
+# User configuration section starts here
+
+# Default output data pin for the LED strip
+set(OUTPUT_DATA_PIN 2)
+
+# Default output data pin for the LED strip (SPI leds only, not for sk6812/ws2812b)
+set(OUTPUT_CLOCK_PIN 3)
+
+# Use multi-segment, starting index of second led strip or OFF to disable
+set(SECOND_SEGMENT_INDEX OFF)
+
+# If multi-segment is used and it's reversed, set this option to ON to enable reversing
+set(SECOND_SEGMENT_REVERSED OFF)
+
+# User configuration section ends here
+# Usually you don't need to change anything belowe this section
+
+cmake_minimum_required(VERSION 3.13)
+
+# initialize the SDK based on PICO_SDK_PATH
+# note: this must happen before project()
+set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/sdk/pico)
+set(FREERTOS_KERNEL_PATH ${CMAKE_SOURCE_DIR}/sdk/freertos)
+
+include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
+include(${FREERTOS_KERNEL_PATH}/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake)
+
+project(HyperSerialPico C CXX ASM)
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+
+IF(CMAKE_COMPILER_IS_GNUCC)
+ string(REGEX REPLACE "(\-O[011123456789])" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
+ string(REGEX REPLACE "(\-O[011123456789])" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Og")
+ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Og")
+ENDIF(CMAKE_COMPILER_IS_GNUCC)
+
+# initialize the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# generic HyperSerialPico settings
+set(HyperSerialPicoCompanionLibs FreeRTOS-Kernel FreeRTOS-Kernel-Heap1 pico_stdlib pico_multicore hardware_pio hardware_dma hardware_spi)
+set(HyperSerialPicoCompanionIncludes ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sdk/config)
+file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/generated)
+file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/firmwares)
+
+macro(HyperSerialPicoTarget HyperSerialPicoTargetName)
+ add_executable(${HyperSerialPicoTargetName} ${CMAKE_SOURCE_DIR}/source/main.cpp)
+ target_include_directories(${HyperSerialPicoTargetName} PRIVATE ${HyperSerialPicoCompanionIncludes})
+ target_link_libraries(${HyperSerialPicoTargetName} ${HyperSerialPicoCompanionLibs})
+ pico_add_extra_outputs(${HyperSerialPicoTargetName})
+ pico_enable_stdio_usb(${HyperSerialPicoTargetName} 1)
+ pico_enable_stdio_uart(${HyperSerialPicoTargetName} 0)
+ pico_generate_pio_header(${HyperSerialPicoTargetName} ${CMAKE_SOURCE_DIR}/pio/neopixel.pio OUTPUT_DIR ${CMAKE_SOURCE_DIR}/generated)
+ add_custom_command(TARGET ${HyperSerialPicoTargetName} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/${HyperSerialPicoTargetName}.uf2 ${CMAKE_SOURCE_DIR}/firmwares)
+endmacro()
+
+# targets for different LED strips
+IF(NOT SECOND_SEGMENT_INDEX)
+ HyperSerialPicoTarget("HyperSerialPico_Spi")
+ target_compile_definitions(HyperSerialPico_Spi PRIVATE -DSPILED_APA102 -DDATA_PIN=${OUTPUT_DATA_PIN} -DCLOCK_PIN=${OUTPUT_CLOCK_PIN})
+ HyperSerialPicoTarget("HyperSerialPico_sk6812Cold")
+ target_compile_definitions(HyperSerialPico_sk6812Cold PRIVATE -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=${OUTPUT_DATA_PIN})
+ HyperSerialPicoTarget("HyperSerialPico_sk6812Neutral")
+ target_compile_definitions(HyperSerialPico_sk6812Neutral PRIVATE -DNEOPIXEL_RGBW -DDATA_PIN=${OUTPUT_DATA_PIN})
+ HyperSerialPicoTarget("HyperSerialPico_ws2812")
+ target_compile_definitions(HyperSerialPico_ws2812 PRIVATE -DNEOPIXEL_RGB -DDATA_PIN=${OUTPUT_DATA_PIN})
+ELSE()
+ IF(NOT SECOND_SEGMENT_REVERSED)
+ HyperSerialPicoTarget("HyperSerialPico_sk6812Cold_multisegment_at_${SECOND_SEGMENT_INDEX}")
+ target_compile_definitions("HyperSerialPico_sk6812Cold_multisegment_at_${SECOND_SEGMENT_INDEX}" PRIVATE -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=${OUTPUT_DATA_PIN} -DSECOND_SEGMENT_START_INDEX=${SECOND_SEGMENT_INDEX})
+ HyperSerialPicoTarget("HyperSerialPico_sk6812Neutral_multisegment_at_${SECOND_SEGMENT_INDEX}")
+ target_compile_definitions("HyperSerialPico_sk6812Neutral_multisegment_at_${SECOND_SEGMENT_INDEX}" PRIVATE -DNEOPIXEL_RGBW -DDATA_PIN=${OUTPUT_DATA_PIN} -DSECOND_SEGMENT_START_INDEX=${SECOND_SEGMENT_INDEX})
+ HyperSerialPicoTarget("HyperSerialPico_ws2812_multisegment_at_${SECOND_SEGMENT_INDEX}")
+ target_compile_definitions("HyperSerialPico_ws2812_multisegment_at_${SECOND_SEGMENT_INDEX}" PRIVATE -DNEOPIXEL_RGB -DDATA_PIN=${OUTPUT_DATA_PIN} -DSECOND_SEGMENT_START_INDEX=${SECOND_SEGMENT_INDEX})
+ ELSE()
+ HyperSerialPicoTarget("HyperSerialPico_sk6812Cold_rev_multisegment_at_${SECOND_SEGMENT_INDEX}")
+ target_compile_definitions("HyperSerialPico_sk6812Cold_rev_multisegment_at_${SECOND_SEGMENT_INDEX}" PRIVATE -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=${OUTPUT_DATA_PIN} -DSECOND_SEGMENT_START_INDEX=${SECOND_SEGMENT_INDEX} -DSECOND_SEGMENT_REVERSED)
+ HyperSerialPicoTarget("HyperSerialPico_sk6812Neutral_rev_multisegment_at_${SECOND_SEGMENT_INDEX}")
+ target_compile_definitions("HyperSerialPico_sk6812Neutral_rev_multisegment_at_${SECOND_SEGMENT_INDEX}" PRIVATE -DNEOPIXEL_RGBW -DDATA_PIN=${OUTPUT_DATA_PIN} -DSECOND_SEGMENT_START_INDEX=${SECOND_SEGMENT_INDEX} -DSECOND_SEGMENT_REVERSED)
+ HyperSerialPicoTarget("HyperSerialPico_ws2812_rev_multisegment_at_${SECOND_SEGMENT_INDEX}")
+ target_compile_definitions("HyperSerialPico_ws2812_rev_multisegment_at_${SECOND_SEGMENT_INDEX}" PRIVATE -DNEOPIXEL_RGB -DDATA_PIN=${OUTPUT_DATA_PIN} -DSECOND_SEGMENT_START_INDEX=${SECOND_SEGMENT_INDEX} -DSECOND_SEGMENT_REVERSED)
+ ENDIF()
+ENDIF()
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ee4f13b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+MIT License
+
+Copyright (c) 2023 awawa-dev
+
+https://github.com/awawa-dev/HyperSerialPico
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3d4cc4a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# HyperSerialPico
+HyperHDR's highspeed adalight serial port LED driver implementation for Raspberry Pi Pico RP2040.
+
+# Example of supported boards using Rp2040
+
+
+# Supported LED strips
+| LED strip / Device | Single lane | Multi-segment |
+|--------------------------------|:----------------:|:------------------:|
+| SK6812 cold white | yes | yes |
+| SK6812 neutral white | yes | yes |
+| WS281x | yes | yes |
+| SPI (APA102, SK9812, HD107...) | yes | no* |
+
+`*` you don't need parallel mode for SPI LEDs, they are already using high speed 10Mb internal data line
+
+# How to flash it?
+It's very easy and you don't need any special flasher.
+Download the firmware from the [Release folder](https://github.com/awawa-dev/HyperSerialPico/releases).
+* If you Pico board has only one button (`boot`) then press & hold it and connect the board to the USB port. Then you can release the button.
+* If you Pico board has two buttons, connect it to the USB port. Then press & hold `boot` and `reset` buttons, then release `reset` and next release `boot` button.
+
+In the system file explorer you should find new drive (e.g. called `RPI-RP2` drive) exposed by the Pico board. Drap & drop the selected fimrware.
+The Pico will reset automaticly after the upload and after few seconds it will be ready to use by HyperHDR as a serial port device using Adalight driver.
+
+# HyperHDR configuration
+Make sure you have enabled `AWA protocol` and `Esp8266/ESP32/Rp2040 handshake` options.
+You can leave the speed at `2000000` because the CDC driver should use the highest possible speed automatically (at least it happens on Windows).
+![obraz](https://user-images.githubusercontent.com/69086569/236870662-12f67d14-c2ca-4ba1-b6a3-e34c27949d19.png)
+
+# Default pinout
+
+**LED output (SK6812/WS281x):** GPIO2 for Data
+**LED output (SPI LEDs):** GPIO2 for Data, GPIO3 for Clock
+
+Pinout can be changed, but you need to make changes to CMakeList.txt (`OUTPUT_DATA_PIN`/`OUTPUT_CLOCK_PIN`) and recompile the project. Also multi-segment mode can be enabled in this file: `SECOND_SEGMENT_INDEX` option at the beginning and optionally `SECOND_SEGMENT_REVERSED`.
+Once compiled, the results can be found in the `firmwares` folder.
+
+# Some benchmark results
+
+| Single RGBW LED strip | Max. refresh rate |
+|------------------------------------------------|-------------------|
+| 300LEDs RGBW
Refresh rate/continues output | 83 |
+| 600LEDs RGBW
Refresh rate/continues output | 42 |
+| 900LEDs RGBW
Refresh rate/continues output | 28 |
+
+| Parallel multi-segment mode | Max. refresh rate |
+|---------------------------------------------------------------------------|----------------------|
+| 300LEDs RGBW
Refresh rate/continues output
SECOND_SEGMENT_INDEX=150 | 100 |
+| 600LEDs RGBW
Refresh rate/continues output
SECOND_SEGMENT_INDEX=300 | 83 |
+| 900LEDs RGBW
Refresh rate/continues output
SECOND_SEGMENT_INDEX=450 | 55 |
diff --git a/include/base.h b/include/base.h
new file mode 100644
index 0000000..d5a148d
--- /dev/null
+++ b/include/base.h
@@ -0,0 +1,172 @@
+/* base.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef BASE_H
+#define BASE_H
+
+class Base
+{
+ // LED strip number
+ int ledsNumber = 0;
+ // NeoPixelBusLibrary primary object
+ LED_DRIVER* ledStrip1 = nullptr;
+ // NeoPixelBusLibrary second object
+ LED_DRIVER2* ledStrip2 = nullptr;
+ // frame is set and ready to render
+ bool readyToRender = false;
+
+ public:
+ // static data buffer for the loop
+ volatile uint8_t buffer[MAX_BUFFER + 1] = {0};
+ // handle to tasks
+ TaskHandle_t processDataHandle = nullptr;
+ TaskHandle_t processSerialHandle = nullptr;
+ // semaphore to synchronize them
+ semaphore_t serialSemaphore;
+ semaphore_t receiverSemaphore;
+ // current queue position
+ volatile int queueCurrent = 0;
+ // queue end position
+ volatile int queueEnd = 0;
+
+ inline int getLedsNumber()
+ {
+ return ledsNumber;
+ }
+
+ inline LED_DRIVER* getLedStrip1()
+ {
+ return ledStrip1;
+ }
+
+ inline LED_DRIVER2* getLedStrip2()
+ {
+ return ledStrip2;
+ }
+
+ void initLedStrip(int count)
+ {
+ if (ledStrip1 != nullptr)
+ {
+ delete ledStrip1;
+ ledStrip1 = nullptr;
+ }
+
+ if (ledStrip2 != nullptr)
+ {
+ delete ledStrip2;
+ ledStrip2 = nullptr;
+ }
+
+ ledsNumber = count;
+
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ if (ledsNumber > SECOND_SEGMENT_START_INDEX)
+ {
+ #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB)
+ ledStrip1 = new LED_DRIVER(SECOND_SEGMENT_START_INDEX, DATA_PIN);
+ ledStrip2 = new LED_DRIVER2(ledsNumber - SECOND_SEGMENT_START_INDEX, DATA_PIN);
+ #else
+ ledStrip1 = new LED_DRIVER(SECOND_SEGMENT_START_INDEX);
+ ledStrip1->Begin(CLOCK_PIN, 12, DATA_PIN, 15);
+ ledStrip2 = new LED_DRIVER2(ledsNumber - SECOND_SEGMENT_START_INDEX);
+ ledStrip2->Begin(SECOND_SEGMENT_CLOCK_PIN, 12, SECOND_SEGMENT_DATA_PIN, 15);
+ #endif
+ }
+ #endif
+
+ if (ledStrip1 == nullptr)
+ {
+ #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB)
+ ledStrip1 = new LED_DRIVER(ledsNumber, DATA_PIN);
+ #else
+ ledStrip1 = new LED_DRIVER(ledsNumber, DATA_PIN, CLOCK_PIN);
+ #endif
+ }
+ }
+
+ /**
+ * @brief Check if there is already prepared frame to display
+ *
+ * @return true
+ * @return false
+ */
+ inline bool hasLateFrameToRender()
+ {
+ return readyToRender;
+ }
+
+ inline void dropLateFrame()
+ {
+ readyToRender = false;
+ }
+
+ inline void renderLeds(bool newFrame)
+ {
+ if (newFrame)
+ readyToRender = true;
+
+ if (readyToRender &&
+ (ledStrip1 != nullptr && ledStrip1->isReadyBlocking()))
+ {
+ statistics.increaseShow();
+ readyToRender = false;
+
+ // display segments
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ ledStrip1->renderAllLanes();
+ #else
+ ledStrip1->renderSingleLane();
+ #endif
+ }
+ }
+
+ inline bool setStripPixel(uint16_t pix, ColorDefinition &inputColor)
+ {
+ if (pix < ledsNumber)
+ {
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ if (pix < SECOND_SEGMENT_START_INDEX)
+ ledStrip1->SetPixel(pix, inputColor);
+ else
+ {
+ #if defined(SECOND_SEGMENT_REVERSED)
+ ledStrip2->SetPixel(ledsNumber - pix - 1, inputColor);
+ #else
+ ledStrip2->SetPixel(pix - SECOND_SEGMENT_START_INDEX, inputColor);
+ #endif
+ }
+ #else
+ ledStrip1->SetPixel(pix, inputColor);
+ #endif
+ }
+
+ return (pix + 1 < ledsNumber);
+ }
+} base;
+
+#endif
\ No newline at end of file
diff --git a/include/calibration.h b/include/calibration.h
new file mode 100644
index 0000000..154ba57
--- /dev/null
+++ b/include/calibration.h
@@ -0,0 +1,134 @@
+/* calibration.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifdef NEOPIXEL_RGBW
+ typedef ColorGrbw ColorDefinition;
+#elif NEOPIXEL_RGB
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ typedef ColorGrb ColorDefinition;
+ #else
+ typedef ColorGrb32 ColorDefinition;
+ #endif
+#elif SPILED_APA102
+ typedef ColorDotstartBgr ColorDefinition;
+#endif
+
+
+#if !defined(CALIBRATION_H) && (defined(NEOPIXEL_RGBW) || defined(HYPERSERIAL_TESTING))
+#define CALIBRATION_H
+
+#include
+#include
+
+#define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom))
+
+struct
+{
+ uint8_t white[256];
+ uint8_t red[256];
+ uint8_t green[256];
+ uint8_t blue[256];
+} channelCorrection;
+
+class CalibrationConfig
+{
+ // calibration parameters
+ uint8_t gain = 0xFF;
+ uint8_t red = 0xA0;
+ uint8_t green = 0xA0;
+ uint8_t blue = 0xA0;
+
+ /**
+ * @brief Build the LUT table using provided parameters
+ *
+ */
+ void prepareCalibration()
+ {
+ // prepare LUT calibration table, cold white is much better than "neutral" white
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ // color calibration
+ uint32_t _gain = gain * i; // adjust gain
+ uint32_t _red = red * i; // adjust red
+ uint32_t _green = green * i; // adjust green
+ uint32_t _blue = blue * i; // adjust blue
+
+ channelCorrection.white[i] = (uint8_t)std::min(ROUND_DIVIDE(_gain, 0xFF), (uint32_t)0xFF);
+ channelCorrection.red[i] = (uint8_t)std::min(ROUND_DIVIDE(_red, 0xFF), (uint32_t)0xFF);
+ channelCorrection.green[i] = (uint8_t)std::min(ROUND_DIVIDE(_green,0xFF), (uint32_t)0xFF);
+ channelCorrection.blue[i] = (uint8_t)std::min(ROUND_DIVIDE(_blue, 0xFF), (uint32_t)0xFF);
+ }
+ }
+
+ public:
+ CalibrationConfig()
+ {
+ prepareCalibration();
+ }
+
+ /**
+ * @brief Compare base calibration settings
+ *
+ */
+ bool compareCalibrationSettings(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue)
+ {
+ return _gain == gain && _red == red && _green == green && _blue == blue;
+ }
+
+ /**
+ * @brief Set the parameters that define RGB to RGBW transformation
+ *
+ * @param _gain
+ * @param _red
+ * @param _green
+ * @param _blue
+ */
+ void setParamsAndPrepareCalibration(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue)
+ {
+ if (gain != _gain || red != _red || green != _green || blue != _blue)
+ {
+ gain = _gain;
+ red = _red;
+ green = _green;
+ blue = _blue;
+ prepareCalibration();
+ }
+ }
+
+ /**
+ * @brief print RGBW calibration parameters when no data is received
+ *
+ */
+ void printCalibration()
+ {
+ char output[128];
+ snprintf(output, sizeof(output),"RGBW => Gain: %i/255, red: %i, green: %i, blue: %i\r\n", gain, red, green, blue);
+ printf(output);
+ }
+} calibrationConfig;
+#endif
+
diff --git a/include/framestate.h b/include/framestate.h
new file mode 100644
index 0000000..ba8d514
--- /dev/null
+++ b/include/framestate.h
@@ -0,0 +1,261 @@
+/* framestate.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef FRAMESTATE_H
+#define FRAMESTATE_H
+
+/**
+ * @brief my AWA frame protocol definition
+ *
+ */
+enum class AwaProtocol
+{
+ HEADER_A,
+ HEADER_w,
+ HEADER_a,
+ HEADER_HI,
+ HEADER_LO,
+ HEADER_CRC,
+ VERSION2_GAIN,
+ VERSION2_RED,
+ VERSION2_GREEN,
+ VERSION2_BLUE,
+ RED,
+ GREEN,
+ BLUE,
+ FLETCHER1,
+ FLETCHER2,
+ FLETCHER_EXT
+};
+
+/**
+ * @brief Contains current state of the incoming frame
+ *
+ */
+class
+{
+ volatile AwaProtocol state = AwaProtocol::HEADER_A;
+ bool protocolVersion2 = false;
+ uint8_t CRC = 0;
+ uint16_t count = 0;
+ uint16_t currentLed = 0;
+ uint16_t fletcher1 = 0;
+ uint16_t fletcher2 = 0;
+ uint16_t fletcherExt = 0;
+ uint8_t position = 0;
+
+ public:
+ ColorDefinition color;
+
+ /**
+ * @brief Reset statistics for new frame
+ *
+ * @param input
+ */
+ inline void init(uint8_t input)
+ {
+ currentLed = 0;
+ count = input * 0x100;
+ CRC = input;
+ fletcher1 = 0;
+ fletcher2 = 0;
+ fletcherExt = 0;
+ position = 0;
+ base.dropLateFrame();
+ }
+
+ /**
+ * @brief get computed CRC
+ *
+ * @return uint8_t
+ */
+ inline uint8_t getCRC()
+ {
+ return CRC;
+ }
+
+ /**
+ * @brief Get the color count reported by the frame
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getCount()
+ {
+ return count;
+ }
+
+ /**
+ * @brief Get the Fletcher1 total sum
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getFletcher1()
+ {
+ return fletcher1;
+ }
+
+ /**
+ * @brief Get the Fletcher2 total sum
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getFletcher2()
+ {
+ return fletcher2;
+ }
+
+ /**
+ * @brief Get the FletcherExt total sum
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getFletcherExt()
+ {
+ return (fletcherExt != 0x41) ? fletcherExt : 0xaa;
+ }
+
+ /**
+ * @brief Get and increase the current Led index
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getCurrentLedIndex()
+ {
+ return currentLed++;
+ }
+
+ /**
+ * @brief Set if frame protocol version 2 (contains calibration data)
+ *
+ * @param newVer
+ */
+ inline void setProtocolVersion2(bool newVer)
+ {
+ protocolVersion2 = newVer;
+ }
+
+ /**
+ * @brief Verify if frame protocol version 2 (contains calibration data)
+ *
+ * @return true
+ * @return false
+ */
+ inline bool isProtocolVersion2()
+ {
+ return protocolVersion2;
+ }
+
+ /**
+ * @brief Set new AWA frame state
+ *
+ * @param newState
+ */
+ inline void setState(AwaProtocol newState)
+ {
+ state = newState;
+ }
+
+ /**
+ * @brief Get current AWA frame state
+ *
+ * @return AwaProtocol
+ */
+ inline AwaProtocol getState()
+ {
+ return state;
+ }
+
+ /**
+ * @brief Update CRC based on current and previuos input
+ *
+ * @param input
+ */
+ inline void computeCRC(uint8_t input)
+ {
+ count += input;
+ CRC = CRC ^ input ^ 0x55;
+ }
+
+ /**
+ * @brief Update Fletcher checksumn for incoming input
+ *
+ * @param input
+ */
+ inline void addFletcher(uint8_t input)
+ {
+ fletcher1 = (fletcher1 + (uint16_t)input) % 255;
+ fletcher2 = (fletcher2 + fletcher1) % 255;
+ fletcherExt = (fletcherExt + (input ^ (position++))) % 255;
+ }
+
+ /**
+ * @brief Check if the calibration data was updated and calculate new one
+ *
+ */
+ inline void updateIncomingCalibration()
+ {
+ #ifdef NEOPIXEL_RGBW
+ if (protocolVersion2)
+ {
+ calibrationConfig.setParamsAndPrepareCalibration(calibration.gain, calibration.red, calibration.green, calibration.blue);
+ }
+ #endif
+ }
+
+
+ #ifdef NEOPIXEL_RGBW
+ /**
+ * @brief Compute && correct the white channel
+ *
+ */
+ inline void rgb2rgbw()
+ {
+ color.W = std::min(channelCorrection.red[color.R],
+ std::min(channelCorrection.green[color.G],
+ channelCorrection.blue[color.B]));
+ color.R -= channelCorrection.red[color.W];
+ color.G -= channelCorrection.green[color.W];
+ color.B -= channelCorrection.blue[color.W];
+ color.W = channelCorrection.white[color.W];
+ }
+ #endif
+
+ /**
+ * @brief Incoming calibration data
+ *
+ */
+ struct
+ {
+ uint8_t gain = 0;
+ uint8_t red = 0;
+ uint8_t green = 0;
+ uint8_t blue = 0;
+ } calibration;
+
+} frameState;
+
+#endif
\ No newline at end of file
diff --git a/include/leds.h b/include/leds.h
new file mode 100644
index 0000000..ec178af
--- /dev/null
+++ b/include/leds.h
@@ -0,0 +1,565 @@
+#pragma once
+
+/* leds.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+/*
+ HyperSerialPico led (aka PicoLada) library features:
+ - neopixel (rgb: ws2812b, ws2813..., rgbw: sk6812b) and dotstar (rgb: apa102, hd107, sk9822...) led strip support
+ - single and up to 8 lines parallel (neopixel) mode
+ - DMA
+ - PIO neopixel hardware processing
+ - using LUT tables for preparing PIO DMA parallel buffer
+ - SPI dotstar hardware support
+ - non-blocking rendering (check isReady if it's finished)
+
+ Usage for sk6812 rgbw single lane:
+ ledStrip1 = new sk6812(ledsNumber, DATA_PIN);
+ ledStrip1->SetPixel(index, ColorGrbw(255));
+ ledStrip1->renderSingleLane();
+
+ Usage for ws2812 rgb single lane:
+ ledStrip1 = new ws2812(ledsNumber, DATA_PIN);
+ ledStrip1->SetPixel(index, ColorGrb32(255));
+ ledStrip1->renderSingleLane();
+
+ Usage for sk6812 rgbw multi lanes:
+ ledStrip1 = new sk6812(ledsNumber, DATA_PIN); // using DATA_PIN output
+ ledStrip2 = new sk6812(ledsNumber, DATA_PIN); // using DATA_PIN + 1 output
+ ledStrip1->SetPixel(index, ColorGrbw(255));
+ ledStrip2->SetPixel(index, ColorGrbw(255));
+ ledStrip1->renderAllLanes(); // renders ledStrip1 and ledStrip2 simoultaneusly
+
+ Usage for ws2812 rgb multi lanes:
+ ledStrip1 = new ws2812(ledsNumber, DATA_PIN); // using DATA_PIN output
+ ledStrip2 = new ws2812(ledsNumber, DATA_PIN); // using DATA_PIN + 1 output
+ ledStrip1->SetPixel(index, ColorGrb(255));
+ ledStrip2->SetPixel(index, ColorGrb(255));
+ ledStrip1->renderAllLanes(); // renders ledStrip1 and ledStrip2 simoultaneusly
+
+ Usage for dotstar rgb single line:
+ ledStrip1 = new apa102(ledsNumber, DATA_PIN, CLOCK_PIN);
+ ledStrip1->SetPixel(index, ColorDotstartBgr(255));
+ ledStrip1->renderSingleLane();
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct ColorGrb32
+{
+ uint8_t notUsed;
+ uint8_t B;
+ uint8_t R;
+ uint8_t G;
+
+ ColorGrb32(uint8_t gray) :
+ R(gray), G(gray), B(gray)
+ {
+ };
+
+ ColorGrb32() : R(0), G(0), B(0)
+ {
+ };
+
+ static bool isAlignedTo24()
+ {
+ return true;
+ };
+};
+
+struct ColorGrb
+{
+ uint8_t B;
+ uint8_t R;
+ uint8_t G;
+
+
+ ColorGrb(uint8_t gray) :
+ R(gray), G(gray), B(gray)
+ {
+ };
+
+ ColorGrb() : R(0), G(0), B(0)
+ {
+ };
+};
+
+struct ColorGrbw
+{
+ uint8_t W;
+ uint8_t B;
+ uint8_t R;
+ uint8_t G;
+
+ ColorGrbw(uint8_t gray) :
+ R(gray), G(gray), B(gray), W(gray)
+ {
+ };
+
+ ColorGrbw() : R(0), G(0), B(0), W(0)
+ {
+ };
+
+ static bool isAlignedTo24()
+ {
+ return false;
+ };
+};
+
+struct ColorDotstartBgr
+{
+ uint8_t brightness;
+ uint8_t B;
+ uint8_t G;
+ uint8_t R;
+
+ ColorDotstartBgr(uint8_t gray) :
+ R(gray), G(gray), B(gray), brightness(gray | 0b11100000)
+ {
+ };
+
+ ColorDotstartBgr() : R(0), G(0), B(0), brightness(0xff)
+ {
+ };
+};
+
+class LedDriver
+{
+ protected:
+
+ int ledsNumber;
+ int pin;
+ int clockPin;
+ int dmaSize;
+ uint8_t* buffer;
+ uint8_t* dma;
+
+ public:
+
+ LedDriver(int _ledsNumber, int _pin, int _dmaSize): LedDriver(_ledsNumber, _pin, 0, _dmaSize)
+ {
+
+ }
+
+ LedDriver(int _ledsNumber, int _pin, int _clockPin, int _dmaSize)
+ {
+ LedDriverDmaReceiver = this;
+ ledsNumber = _ledsNumber;
+ pin = _pin;
+ clockPin = _clockPin;
+ dmaSize = _dmaSize;
+ if (dmaSize % 4)
+ dmaSize += (4 - (_dmaSize % 4));
+ buffer = reinterpret_cast(calloc(dmaSize, 1));
+ dma = reinterpret_cast(calloc(dmaSize, 1));
+ }
+
+ ~LedDriver()
+ {
+ free(buffer);
+ free(dma);
+ if (LedDriverDmaReceiver == this)
+ LedDriverDmaReceiver = nullptr;
+ }
+
+ static LedDriver* LedDriverDmaReceiver;
+};
+
+LedDriver* LedDriver::LedDriverDmaReceiver = nullptr;
+
+class DmaClient
+{
+ protected:
+
+ PIO selectedPIO;
+ uint stateIndex;
+
+ static uint PICO_DMA_CHANNEL;
+ static volatile uint64_t lastRenderTime;
+ static volatile bool isDmaBusy;
+
+
+ DmaClient()
+ {
+ PICO_DMA_CHANNEL = dma_claim_unused_channel(true);
+ isDmaBusy = false;
+ lastRenderTime = 0;
+ };
+
+ ~DmaClient()
+ {
+ for(int i = 0; i < 10 && isDmaBusy; i++)
+ busy_wait_us(500);
+
+ dma_channel_abort(PICO_DMA_CHANNEL);
+ dma_channel_set_irq0_enabled(PICO_DMA_CHANNEL, false);
+ irq_set_enabled(DMA_IRQ_0, false);
+
+ dma_channel_unclaim(PICO_DMA_CHANNEL);
+ };
+
+ void dmaConfigure(PIO _selectedPIO, uint _sm)
+ {
+ selectedPIO = _selectedPIO;
+ stateIndex = _sm;
+ };
+
+ void initDmaPio(uint dataLenDword32)
+ {
+ dma_channel_config dmaConfig = dma_channel_get_default_config(PICO_DMA_CHANNEL);
+ channel_config_set_dreq(&dmaConfig, pio_get_dreq(selectedPIO, stateIndex, true));
+ channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_32);
+ channel_config_set_read_increment(&dmaConfig, true);
+ dma_channel_configure(PICO_DMA_CHANNEL, &dmaConfig, &selectedPIO->txf[stateIndex], NULL, dataLenDword32, false);
+
+ assignDmaIrq();
+ };
+
+ void initDmaSpi(uint dataLenByte8)
+ {
+ dma_channel_config dmaConfig = dma_channel_get_default_config(PICO_DMA_CHANNEL);
+ channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_8);
+ channel_config_set_dreq(&dmaConfig, spi_get_dreq(spi_default, true));
+ dma_channel_configure(PICO_DMA_CHANNEL, &dmaConfig,&spi_get_hw(spi_default)->dr, NULL, dataLenByte8, false);
+
+ assignDmaIrq();
+ };
+
+ void assignDmaIrq()
+ {
+ irq_set_exclusive_handler(DMA_IRQ_0, dmaFinishReceiver);
+ dma_channel_set_irq0_enabled(PICO_DMA_CHANNEL, true);
+ irq_set_enabled(DMA_IRQ_0, true);
+ };
+
+ public:
+
+ bool isReadyBlocking()
+ {
+ int wait = 200;
+ while(isDmaBusy && wait-- > 0)
+ busy_wait_us(50);
+
+ return !isDmaBusy;
+ }
+
+ bool isReady()
+ {
+ return !isDmaBusy;
+ }
+
+ static void dmaFinishReceiver()
+ {
+ if (dma_hw->ints0 & (1u<ints0 = (1u<= 1)
+ {
+ programAddress = pio_add_program(selectedPIO, &neopixel_parallel_program);
+ for(uint i=_pin; i<_pin + lanes; i++){
+ pio_gpio_init(selectedPIO, i);
+ }
+ smConfig = neopixel_parallel_program_get_default_config(programAddress);
+ sm_config_set_out_pins(&smConfig, _pin, lanes);
+ sm_config_set_set_pins(&smConfig, _pin, lanes);
+ }
+ else
+ {
+ programAddress = pio_add_program(selectedPIO, &neopixel_program);
+ pio_gpio_init(selectedPIO, _pin);
+ smConfig = neopixel_program_get_default_config(programAddress);
+ sm_config_set_sideset_pins(&smConfig, _pin);
+ }
+
+ pio_sm_set_consecutive_pindirs(selectedPIO, stateIndex, _pin, std::max(lanes, 1), true);
+ sm_config_set_out_shift(&smConfig, false, true, (alignTo24) ? 24: 32);
+ sm_config_set_fifo_join(&smConfig, PIO_FIFO_JOIN_TX);
+ float div = clock_get_hz(clk_sys) / (800000 * 12);
+ sm_config_set_clkdiv(&smConfig, div);
+ pio_sm_init(selectedPIO, stateIndex, programAddress, &smConfig);
+ pio_sm_set_enabled(selectedPIO, stateIndex, true);
+
+ initDmaPio(dmaSize / 4);
+ }
+
+ uint8_t* getBufferMemory()
+ {
+ return buffer;
+ }
+
+ protected:
+
+ void renderDma(bool resetBuffer)
+ {
+ if (isDmaBusy)
+ return;
+
+ isDmaBusy = true;
+
+ uint64_t currentTime = time_us_64();
+ if (currentTime < resetTime + lastRenderTime)
+ busy_wait_us(std::min(resetTime + lastRenderTime - currentTime, resetTime));
+
+ memcpy(dma, buffer, dmaSize);
+
+ dma_channel_set_read_addr(PICO_DMA_CHANNEL, dma, true);
+
+ if (resetBuffer)
+ memset(buffer, 0, dmaSize);
+ }
+};
+
+template
+class NeopixelType : public Neopixel
+{
+ public:
+
+ NeopixelType(int _ledsNumber, int _pin) : Neopixel(0, RESET_TIME, _ledsNumber, _pin, _ledsNumber * sizeof(colorData), colorData::isAlignedTo24())
+ {
+ }
+
+ void SetPixel(int index, colorData color)
+ {
+ if (index >= ledsNumber)
+ return;
+
+ *(reinterpret_cast(buffer)+index) = color;
+ }
+
+ void renderSingleLane()
+ {
+ renderDma(false);
+ }
+};
+
+
+class NeopixelParallel
+{
+ static Neopixel *muxer;
+ static int instances;
+ static int maxLeds;
+
+ protected:
+ const uint8_t myLaneMask;
+ static uint8_t* buffer;
+
+ public:
+
+ NeopixelParallel(size_t pixelSize, uint64_t _resetTime, int _ledsNumber, int _pin):
+ myLaneMask(1 << (instances++))
+ {
+ maxLeds = std::max(maxLeds, _ledsNumber);
+
+ delete muxer;
+ muxer = new Neopixel(instances, _resetTime, maxLeds, _pin, maxLeds * 8 * pixelSize );
+ buffer = muxer->getBufferMemory();
+ }
+
+ ~NeopixelParallel()
+ {
+ if (instances > 0)
+ instances--;
+
+ if (instances == 0)
+ {
+ delete muxer;
+ muxer = nullptr;
+ buffer = nullptr;
+ maxLeds = 0;
+ }
+ }
+
+ bool isReadyBlocking()
+ {
+ return muxer->isReadyBlocking();
+ }
+
+ bool isReady()
+ {
+ return muxer->isReady();
+ }
+
+ void renderAllLanes()
+ {
+ muxer->renderDma(true);
+ }
+};
+
+template
+class NeopixelParallelType : public NeopixelParallel
+{
+ uint32_t lut[16];
+
+ public:
+
+ NeopixelParallelType(int _ledsNumber, int _basePinForLanes) : NeopixelParallel(sizeof(colorData), RESET_TIME,
+ _ledsNumber, _basePinForLanes)
+ {
+ for (uint8_t a = 0; a < 16; a++)
+ {
+ uint8_t* target = reinterpret_cast(&(lut[a]));
+ for (uint8_t b = 0; b < 4; b++)
+ *(target++) = (uint8_t) ((a & (0b00000001 << b)) ? myLaneMask : 0);
+ }
+ }
+
+ void SetPixel(int index, colorData color)
+ {
+ if (index >= maxLeds)
+ return;
+
+ uint8_t* source = reinterpret_cast(&color);
+ uint32_t* target = reinterpret_cast(&(buffer[(index + 1) * 8 * sizeof(colorData)]));
+
+ for(int i = 0; i < sizeof(colorData); i++)
+ {
+ *(--target) |= lut[ *(source) & 0b00001111];
+ *(--target) |= lut[ *(source++) >> 4];
+ }
+ }
+};
+
+class Dotstar : public LedDriver, public DmaClient
+{
+ uint64_t resetTime;
+
+ friend class NeopixelParallel;
+
+ public:
+ Dotstar(uint64_t _resetTime, int _ledsNumber, uint32_t _datapin, uint32_t _clockpin, int _dmaSize):
+ LedDriver(_ledsNumber, _datapin, _clockpin, _dmaSize)
+ {
+ dmaConfigure(pio0, 0);
+ resetTime = _resetTime;
+
+ spi_init(spi_default, 10000000);
+ gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI);
+ gpio_init(PICO_DEFAULT_SPI_CSN_PIN);
+ gpio_set_function(_clockpin, GPIO_FUNC_SPI);
+ gpio_set_function(_datapin, GPIO_FUNC_SPI);
+ bi_decl(bi_3pins_with_func(PICO_DEFAULT_SPI_RX_PIN, _datapin, _clockpin, GPIO_FUNC_SPI));
+ bi_decl(bi_1pin_with_name(PICO_DEFAULT_SPI_CSN_PIN, "SPI CS"));
+
+ initDmaSpi(_dmaSize);
+ }
+
+ uint8_t* getBufferMemory()
+ {
+ return buffer;
+ }
+
+ protected:
+
+ void renderDma()
+ {
+ if (isDmaBusy)
+ return;
+
+ isDmaBusy = true;
+
+ uint64_t currentTime = time_us_64();
+ if (currentTime < resetTime + lastRenderTime)
+ busy_wait_us(std::min(resetTime + lastRenderTime - currentTime, resetTime));
+
+ memcpy(dma, buffer, dmaSize);
+
+ dma_channel_set_read_addr(PICO_DMA_CHANNEL, dma, true);
+ }
+};
+
+template
+class DotstarType : public Dotstar
+{
+ public:
+
+ DotstarType(int _ledsNumber, int _dataPin, int _clockPin) : Dotstar(RESET_TIME, _ledsNumber, _dataPin, _clockPin, (_ledsNumber + 2) * sizeof(colorData))
+ {
+ }
+
+ void SetPixel(int index, colorData color)
+ {
+ if (index >= ledsNumber)
+ return;
+
+ *(reinterpret_cast(buffer)+index+1) = color;
+ }
+
+ void renderSingleLane()
+ {
+ memset(buffer,0 ,4);
+ *(reinterpret_cast(buffer)+ledsNumber+1) = colorData(0xff);
+ renderDma();
+ }
+};
+
+Neopixel* NeopixelParallel::muxer = nullptr;
+uint8_t* NeopixelParallel::buffer = nullptr;
+int NeopixelParallel::instances = 0;
+int NeopixelParallel::maxLeds = 0;
+uint DmaClient::PICO_DMA_CHANNEL = 0;
+volatile uint64_t DmaClient::lastRenderTime = 0;
+volatile bool DmaClient::isDmaBusy = false;
+
+
+// API classes
+typedef NeopixelType<650, ColorGrb32> ws2812;
+typedef NeopixelType<450, ColorGrbw> sk6812;
+typedef NeopixelParallelType<300, ColorGrb> ws2812p;
+typedef NeopixelParallelType<80, ColorGrbw> sk6812p;
+typedef DotstarType<100, ColorDotstartBgr> apa102;
+
diff --git a/include/main.h b/include/main.h
new file mode 100644
index 0000000..f114808
--- /dev/null
+++ b/include/main.h
@@ -0,0 +1,264 @@
+/* main.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#define MAX_BUFFER (3013 * 3 + 1)
+#define HELLO_MESSAGE "\r\nWelcome!\r\nAwa driver 9.\r\n"
+
+#include "calibration.h"
+#include "statistics.h"
+#include "base.h"
+#include "framestate.h"
+
+void updateMainStatistics(unsigned long currentTime, unsigned long deltaTime, bool hasData)
+{
+ if (hasData && deltaTime >= 1000 && deltaTime <= 1025 && statistics.getGoodFrames() > 3)
+ statistics.update(currentTime);
+ else if (deltaTime > 1025)
+ statistics.lightReset(currentTime, hasData);
+}
+
+/**
+ * @brief process received data on core 0
+ *
+ */
+void processData()
+{
+ // update and print statistics
+ unsigned long currentTime = millis();
+ unsigned long deltaTime = currentTime - statistics.getStartTime();
+
+ updateMainStatistics(currentTime, deltaTime, base.queueCurrent != base.queueEnd);
+
+ if (statistics.getStartTime() + 5000 < millis())
+ {
+ frameState.setState(AwaProtocol::HEADER_A);
+ }
+
+ // render waiting frame if available
+ if (base.hasLateFrameToRender())
+ base.renderLeds(false);
+
+ // process received data
+ while (base.queueCurrent != base.queueEnd)
+ {
+ uint8_t input = base.buffer[base.queueCurrent++];
+
+ if (base.queueCurrent >= MAX_BUFFER)
+ {
+ base.queueCurrent = 0;
+ yield();
+ }
+
+ switch (frameState.getState())
+ {
+ case AwaProtocol::HEADER_A:
+ // assume it's protocol version 1, verify it later
+ frameState.setProtocolVersion2(false);
+ if (input == 'A')
+ frameState.setState(AwaProtocol::HEADER_w);
+ break;
+
+ case AwaProtocol::HEADER_w:
+ if (input == 'w')
+ frameState.setState(AwaProtocol::HEADER_a);
+ else
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+
+ case AwaProtocol::HEADER_a:
+ // detect protocol version
+ if (input == 'a')
+ frameState.setState(AwaProtocol::HEADER_HI);
+ else if (input == 'A')
+ {
+ frameState.setState(AwaProtocol::HEADER_HI);
+ frameState.setProtocolVersion2(true);
+ }
+ else
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+
+ case AwaProtocol::HEADER_HI:
+ // initialize new frame properties
+ statistics.increaseTotal();
+ frameState.init(input);
+ frameState.setState(AwaProtocol::HEADER_LO);
+ break;
+
+ case AwaProtocol::HEADER_LO:
+ frameState.computeCRC(input);
+ frameState.setState(AwaProtocol::HEADER_CRC);
+ break;
+
+ case AwaProtocol::HEADER_CRC:
+ // verify CRC and create/update LED driver if neccesery
+ if (frameState.getCRC() == input)
+ {
+ uint16_t ledSize = frameState.getCount() + 1;
+
+ // sanity check
+ if (ledSize > 4096)
+ frameState.setState(AwaProtocol::HEADER_A);
+ else
+ {
+ if (ledSize != base.getLedsNumber())
+ base.initLedStrip(ledSize);
+
+ frameState.setState(AwaProtocol::RED);
+ }
+ }
+ else if (frameState.getCount() == 0x2aa2 && (input == 0x15 || input == 0x35))
+ {
+ statistics.print(currentTime, base.processDataHandle, base.processSerialHandle);
+
+ if (input == 0x15)
+ printf(HELLO_MESSAGE);
+ delay(10);
+
+ currentTime = millis();
+ statistics.reset(currentTime);
+ frameState.setState(AwaProtocol::HEADER_A);
+ }
+ else
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+
+ case AwaProtocol::RED:
+ frameState.color.R = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::GREEN);
+ break;
+
+ case AwaProtocol::GREEN:
+ frameState.color.G = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::BLUE);
+ break;
+
+ case AwaProtocol::BLUE:
+ frameState.color.B = input;
+ frameState.addFletcher(input);
+
+ #ifdef NEOPIXEL_RGBW
+ // calculate RGBW from RGB using provided calibration data
+ frameState.rgb2rgbw();
+ #endif
+
+ // set pixel, increase the index and check if it was the last LED color to come
+ if (base.setStripPixel(frameState.getCurrentLedIndex(), frameState.color))
+ {
+ frameState.setState(AwaProtocol::RED);
+ }
+ else
+ {
+ if (frameState.isProtocolVersion2())
+ frameState.setState(AwaProtocol::VERSION2_GAIN);
+ else
+ frameState.setState(AwaProtocol::FLETCHER1);
+ }
+
+ break;
+
+ case AwaProtocol::VERSION2_GAIN:
+ frameState.calibration.gain = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::VERSION2_RED);
+ break;
+
+ case AwaProtocol::VERSION2_RED:
+ frameState.calibration.red = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::VERSION2_GREEN);
+ break;
+
+ case AwaProtocol::VERSION2_GREEN:
+ frameState.calibration.green = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::VERSION2_BLUE);
+ break;
+
+ case AwaProtocol::VERSION2_BLUE:
+ frameState.calibration.blue = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::FLETCHER1);
+ break;
+
+ case AwaProtocol::FLETCHER1:
+ // initial frame data integrity check
+ if (input != frameState.getFletcher1())
+ frameState.setState(AwaProtocol::HEADER_A);
+ else
+ frameState.setState(AwaProtocol::FLETCHER2);
+ break;
+
+ case AwaProtocol::FLETCHER2:
+ // initial frame data integrity check
+ if (input != frameState.getFletcher2())
+ frameState.setState(AwaProtocol::HEADER_A);
+ else
+ frameState.setState(AwaProtocol::FLETCHER_EXT);
+ break;
+
+ case AwaProtocol::FLETCHER_EXT:
+ // final frame data integrity check
+ if (input == frameState.getFletcherExt())
+ {
+ statistics.increaseGood();
+
+ base.renderLeds(true);
+
+ #ifdef NEOPIXEL_RGBW
+ // if received the calibration data, update it now
+ if (frameState.isProtocolVersion2())
+ {
+ frameState.updateIncomingCalibration();
+ }
+ #endif
+
+ currentTime = millis();
+ deltaTime = currentTime - statistics.getStartTime();
+ updateMainStatistics(currentTime, deltaTime, true);
+
+ yield();
+ }
+
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/include/statistics.h b/include/statistics.h
new file mode 100644
index 0000000..1558014
--- /dev/null
+++ b/include/statistics.h
@@ -0,0 +1,166 @@
+/* stats.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef STATISTICS_H
+#define STATISTICS_H
+
+// statistics (stats sent only when there is no communication)
+class
+{
+ unsigned long startTime = 0;
+ uint16_t goodFrames = 0;
+ uint16_t showFrames = 0;
+ uint16_t totalFrames = 0;
+ uint16_t finalGoodFrames = 0;
+ uint16_t finalShowFrames = 0;
+ uint16_t finalTotalFrames = 0;
+
+ public:
+ /**
+ * @brief Get the start time of the current period
+ *
+ * @return unsigned long
+ */
+ inline unsigned long getStartTime()
+ {
+ return startTime;
+ }
+
+ /**
+ * @brief Detected new frame
+ *
+ */
+ inline void increaseTotal()
+ {
+ totalFrames++;
+ }
+
+ /**
+ * @brief The frame is received and shown
+ *
+ */
+ inline void increaseShow()
+ {
+ showFrames++;
+ }
+
+ /**
+ * @brief The frame is received correctly (not yet displayed)
+ *
+ */
+ inline void increaseGood()
+ {
+ goodFrames++;
+ }
+
+ /**
+ * @brief Get number of correctly received frames
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getGoodFrames()
+ {
+ return goodFrames;
+ }
+
+ /**
+ * @brief Period restart, save current statistics ans send them later if there is no incoming communication
+ *
+ * @param currentTime
+ */
+ void update(unsigned long currentTime)
+ {
+ if (totalFrames > 0)
+ {
+ finalShowFrames = showFrames;
+ finalGoodFrames = std::min(goodFrames, totalFrames);
+ finalTotalFrames = totalFrames;
+ }
+
+ startTime = currentTime;
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+ }
+
+ /**
+ * @brief Print last saved statistics to the serial port
+ *
+ * @param curTime
+ * @param taskHandle
+ */
+ void print(unsigned long curTime, TaskHandle_t taskHandle1, TaskHandle_t taskHandle2)
+ {
+ char output[128];
+
+ startTime = curTime;
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+
+ snprintf(output, sizeof(output), "HyperHDR frames: %u (FPS), receiv.: %u, good: %u, incompl.: %u, mem1: %i, mem2: %i, heap: %zu\r\n",
+ finalShowFrames, finalTotalFrames,finalGoodFrames,(finalTotalFrames - finalGoodFrames),
+ (taskHandle1 != nullptr) ? uxTaskGetStackHighWaterMark(taskHandle1) : 0,
+ (taskHandle2 != nullptr) ? uxTaskGetStackHighWaterMark(taskHandle2) : 0,
+ xPortGetFreeHeapSize());
+ printf(output);
+
+ #if defined(NEOPIXEL_RGBW)
+ calibrationConfig.printCalibration();
+ #endif
+ }
+
+ /**
+ * @brief Reset statistics
+ *
+ */
+ void reset(unsigned long currentTime)
+ {
+ startTime = currentTime;
+
+ finalShowFrames = 0;
+ finalGoodFrames = 0;
+ finalTotalFrames = 0;
+
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+ }
+
+ void lightReset(unsigned long curTime, bool hasData)
+ {
+ if (hasData)
+ startTime = curTime;
+
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+ }
+
+} statistics;
+
+#endif
\ No newline at end of file
diff --git a/pio/neopixel.pio b/pio/neopixel.pio
new file mode 100644
index 0000000..d737bb4
--- /dev/null
+++ b/pio/neopixel.pio
@@ -0,0 +1,46 @@
+; MIT License
+;
+; Copyright (c) 2023 awawa-dev
+;
+; https://github.com/awawa-dev/HyperSerialPico
+;
+; Permission is hereby granted, free of charge, to any person obtaining a copy
+; of this software and associated documentation files (the "Software"), to deal
+; in the Software without restriction, including without limitation the rights
+; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+; copies of the Software, and to permit persons to whom the Software is
+; furnished to do so, subject to the following conditions:
+;
+; The above copyright notice and this permission notice shall be included in all
+; copies or substantial portions of the Software.
+;
+; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+; SOFTWARE.
+
+
+.program neopixel
+.side_set 1
+
+.wrap_target
+bitloop:
+ out x, 1 side 0 [4]
+ jmp !x do_zero side 1 [2]
+do_one:
+ jmp bitloop side 1 [3]
+do_zero:
+ nop side 0 [3]
+.wrap
+
+.program neopixel_parallel
+
+.wrap_target
+ out x, 8
+ mov pins, !null [2]
+ mov pins, x [3]
+ mov pins, null [3]
+.wrap
\ No newline at end of file
diff --git a/sdk/config/FreeRTOSConfig.h b/sdk/config/FreeRTOSConfig.h
new file mode 100644
index 0000000..1d90800
--- /dev/null
+++ b/sdk/config/FreeRTOSConfig.h
@@ -0,0 +1,139 @@
+/*
+ * FreeRTOS V202107.00
+ * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * http://www.FreeRTOS.org
+ * http://aws.amazon.com/freertos
+ *
+ * 1 tab == 4 spaces!
+ */
+
+#ifndef FREERTOS_CONFIG_H
+#define FREERTOS_CONFIG_H
+
+/*-----------------------------------------------------------
+ * Application specific definitions.
+ *
+ * These definitions should be adjusted for your particular hardware and
+ * application requirements.
+ *
+ * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
+ * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
+ *
+ * See http://www.freertos.org/a00110.html
+ *----------------------------------------------------------*/
+
+/* Scheduler Related */
+#define configUSE_PREEMPTION 1
+#define configUSE_TICKLESS_IDLE 0
+#define configUSE_IDLE_HOOK 0
+#define configUSE_TICK_HOOK 0
+#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
+#define configMAX_PRIORITIES 32
+#define configMINIMAL_STACK_SIZE ( configSTACK_DEPTH_TYPE ) 256
+#define configUSE_16_BIT_TICKS 0
+
+#define configIDLE_SHOULD_YIELD 1
+
+/* Synchronization Related */
+#define configUSE_MUTEXES 1
+#define configUSE_RECURSIVE_MUTEXES 1
+#define configUSE_APPLICATION_TASK_TAG 0
+#define configUSE_COUNTING_SEMAPHORES 1
+#define configQUEUE_REGISTRY_SIZE 8
+#define configUSE_QUEUE_SETS 1
+#define configUSE_TIME_SLICING 1
+#define configUSE_NEWLIB_REENTRANT 0
+#define configENABLE_BACKWARD_COMPATIBILITY 0
+#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
+
+/* System */
+#define configSTACK_DEPTH_TYPE uint32_t
+#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
+
+/* Memory allocation related definitions. */
+#define configSUPPORT_STATIC_ALLOCATION 0
+#define configSUPPORT_DYNAMIC_ALLOCATION 1
+#define configTOTAL_HEAP_SIZE (128*1024)
+#define configAPPLICATION_ALLOCATED_HEAP 0
+
+/* Hook function related definitions. */
+#define configCHECK_FOR_STACK_OVERFLOW 0
+#define configUSE_MALLOC_FAILED_HOOK 0
+#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
+
+/* Run time and task stats gathering related definitions. */
+#define configGENERATE_RUN_TIME_STATS 0
+#define configUSE_TRACE_FACILITY 1
+#define configUSE_STATS_FORMATTING_FUNCTIONS 0
+
+/* Co-routine related definitions. */
+#define configUSE_CO_ROUTINES 0
+#define configMAX_CO_ROUTINE_PRIORITIES 1
+
+/* Software timer related definitions. */
+#define configUSE_TIMERS 1
+#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
+#define configTIMER_QUEUE_LENGTH 10
+#define configTIMER_TASK_STACK_DEPTH 1024
+
+/* Interrupt nesting behaviour configuration. */
+/*
+#define configKERNEL_INTERRUPT_PRIORITY [dependent of processor]
+#define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and application]
+#define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on processor and application]
+*/
+
+/* SMP port only */
+#define configNUM_CORES 1
+#define configTICK_CORE 1
+#define configRUN_MULTIPLE_PRIORITIES 1
+
+/* RP2040 specific */
+#define configSUPPORT_PICO_SYNC_INTEROP 1
+#define configSUPPORT_PICO_TIME_INTEROP 1
+
+#include
+/* Define to trap errors during development. */
+#define configASSERT(x) assert(x)
+
+/* Set the following definitions to 1 to include the API function, or zero
+to exclude the API function. */
+#define INCLUDE_vTaskPrioritySet 1
+#define INCLUDE_uxTaskPriorityGet 1
+#define INCLUDE_vTaskDelete 1
+#define INCLUDE_vTaskSuspend 1
+#define INCLUDE_vTaskDelayUntil 1
+#define INCLUDE_vTaskDelay 1
+#define INCLUDE_xTaskGetSchedulerState 1
+#define INCLUDE_xTaskGetCurrentTaskHandle 1
+#define INCLUDE_uxTaskGetStackHighWaterMark 1
+#define INCLUDE_xTaskGetIdleTaskHandle 1
+#define INCLUDE_eTaskGetState 1
+#define INCLUDE_xTimerPendFunctionCall 1
+#define INCLUDE_xTaskAbortDelay 1
+#define INCLUDE_xTaskGetHandle 1
+#define INCLUDE_xTaskResumeFromISR 1
+#define INCLUDE_xQueueGetMutexHolder 1
+
+/* A header file that defines trace macro can be included here. */
+
+#endif /* FREERTOS_CONFIG_H */
+
diff --git a/sdk/freertos b/sdk/freertos
new file mode 160000
index 0000000..570ade4
--- /dev/null
+++ b/sdk/freertos
@@ -0,0 +1 @@
+Subproject commit 570ade4001e50adbf06a074582ea993af562e0e1
diff --git a/sdk/pico b/sdk/pico
new file mode 160000
index 0000000..f396d05
--- /dev/null
+++ b/sdk/pico
@@ -0,0 +1 @@
+Subproject commit f396d05f8252d4670d4ea05c8b7ac938ef0cd381
diff --git a/source/main.cpp b/source/main.cpp
new file mode 100644
index 0000000..a3b8a85
--- /dev/null
+++ b/source/main.cpp
@@ -0,0 +1,177 @@
+/* main.cpp
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialPico
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#define TUD_OPT_HIGH_SPEED
+
+
+#include "FreeRTOS.h"
+#include "task.h"
+#include
+#include
+#include "pico/stdio/driver.h"
+#include "pico/stdlib.h"
+#include "pico/stdio.h"
+#include "pico/stdio_usb.h"
+#include "pico/multicore.h"
+#include "pico/sem.h"
+#include "leds.h"
+
+
+///////////////////////////////////////////////////////////////////////////
+// DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE CmakeList.txt //
+///////////////////////////////////////////////////////////////////////////
+
+#define _STR(x) #x
+#define _XSTR(x) _STR(x)
+#define VAR_NAME_VALUE(var) #var " = " _XSTR(var)
+#define _XSTR2(x,y) _STR(x) _STR(y)
+#define VAR_NAME_VALUE2(var) #var " = " _XSTR2(var)
+
+#ifdef NEOPIXEL_RGBW
+ #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGBW))
+#endif
+#ifdef NEOPIXEL_RGB
+ #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGB))
+#endif
+#ifdef COLD_WHITE
+ #pragma message(VAR_NAME_VALUE(COLD_WHITE))
+#endif
+#ifdef SPILED_APA102
+ #pragma message(VAR_NAME_VALUE(SPILED_APA102))
+#endif
+
+#ifdef NEOPIXEL_RGBW
+ #define LED_DRIVER sk6812
+#elif NEOPIXEL_RGB
+ #define LED_DRIVER ws2812
+#endif
+
+#ifdef SPILED_APA102
+ #define LED_DRIVER apa102
+#endif
+
+#pragma message(VAR_NAME_VALUE(DATA_PIN))
+#ifdef CLOCK_PIN
+ #pragma message(VAR_NAME_VALUE(CLOCK_PIN))
+#endif
+
+#if defined(SECOND_SEGMENT_START_INDEX)
+ #pragma message("Using parallel mode for segments")
+
+ #ifdef NEOPIXEL_RGBW
+ #undef LED_DRIVER
+ #define LED_DRIVER sk6812p
+ #define LED_DRIVER2 sk6812p
+ #elif NEOPIXEL_RGB
+ #undef LED_DRIVER
+ #define LED_DRIVER ws2812p
+ #define LED_DRIVER2 ws2812p
+ #else
+ #error "Parallel mode is unsupportd for selected LEDs configuration"
+ #endif
+
+ #pragma message(VAR_NAME_VALUE(LED_DRIVER))
+ #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_START_INDEX))
+ #pragma message(VAR_NAME_VALUE(LED_DRIVER2))
+ #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_REVERSED))
+#else
+ #pragma message(VAR_NAME_VALUE(LED_DRIVER))
+
+ typedef LedDriver LED_DRIVER2;
+#endif
+
+/////////////////////////////////////////////////////////////////////////
+#define delay(x) sleep_ms(x)
+#define yield() busy_wait_us(100)
+#define millis xTaskGetTickCount
+
+#include "main.h"
+
+static void core1()
+{
+ for( ;; )
+ {
+ if (sem_acquire_timeout_us(&base.serialSemaphore, portMAX_DELAY))
+ {
+ //printf("Core %d: Start index: %i, End index: %i, (%u)\n", get_core_num(), base.queueCurrent, base.queueEnd, millis());
+ processData();
+ //printf("Core %d: Start index: %i, End index: %i, (%u)\n", get_core_num(), base.queueCurrent, base.queueEnd, millis());
+ }
+ }
+}
+
+static void core0( void *pvParameters )
+{
+ for( ;; )
+ {
+ if (sem_acquire_timeout_us(&base.receiverSemaphore, portMAX_DELAY))
+ {
+ int total, toRead;
+ do
+ {
+ toRead = MAX_BUFFER - base.queueEnd;
+ total = stdio_usb.in_chars((char*)(&(base.buffer[base.queueEnd])), MAX_BUFFER - base.queueEnd);
+ if (total > 0)
+ base.queueEnd = (base.queueEnd + total) % (MAX_BUFFER);
+ }while(toRead==total);
+
+ sem_release(&base.serialSemaphore);
+ }
+ }
+}
+
+static void serialEvent(void *)
+{
+ sem_release(&base.receiverSemaphore);
+}
+
+int main(void)
+{
+ stdio_init_all();
+
+ sem_init(&base.serialSemaphore, 0, 1);
+
+ sem_init(&base.receiverSemaphore, 0, 1);
+
+ multicore_launch_core1(core1);
+
+ stdio_set_chars_available_callback(serialEvent, nullptr);
+
+ xTaskCreate(core0,
+ "HyperSerialPico:core0",
+ configMINIMAL_STACK_SIZE,
+ NULL,
+ (configMAX_PRIORITIES - 1),
+ &base.processSerialHandle);
+
+ vTaskStartScheduler();
+ panic_unsupported();
+}
+
+
+
+