mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 19:13:00 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 561143cfb0 | |||
| 678ee2963e | |||
| bae3854d27 | |||
| 0645f1897a | |||
| da0a5c3e73 | |||
| 22932e636d | |||
| 2b7302e659 | |||
| 60ce89021a | |||
| 25556428a7 | |||
| 584587d814 | |||
| a0179117b1 | |||
| ad577a1c66 | |||
| b9958a689b | |||
| 3d8bc308ee | |||
| 9ca36394d0 | |||
| f489d43259 | |||
| 6b663894b0 | |||
| b8ca356ce3 | |||
| 3a54b112ec | |||
| b013e68b17 | |||
| b2bf0f2459 | |||
| 56adca3bf6 |
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Make build folder
|
||||
run: mkdir ${{ github.workspace }}/build
|
||||
- name: Run the Docker image
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev ${{ matrix.cmake_args }}
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev ninja ${{ matrix.cmake_args }}
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Prepare Firmware ZIP
|
||||
run: |
|
||||
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
|
||||
mkdir -p flashing/firmware && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/firmware/hackrf_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/firmware/hackrf_usb.bin
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
|
||||
@@ -103,6 +103,8 @@ jobs:
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
@@ -129,14 +131,42 @@ jobs:
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
# Combine all 3 device firmware bins into a single zip with device selector bat
|
||||
- name: Create Combined Firmware ZIP
|
||||
run: |
|
||||
mkdir -p firmware_combined
|
||||
unzip artifacts/firmware-default/firmware.zip -d firmware_combined
|
||||
mv firmware_combined/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hackrf.bin
|
||||
unzip -j artifacts/firmware-portarf/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_portarf.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hpro.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip firmware/hackrf_usb.dfu firmware/hackrf_usb.bin -d firmware_combined_hpro
|
||||
mv firmware_combined_hpro/hackrf_usb.dfu firmware_combined/firmware/hackrf_hpro_usb.dfu
|
||||
mv firmware_combined_hpro/hackrf_usb.bin firmware_combined/firmware/hackrf_hpro_usb.bin
|
||||
cd firmware_combined && zip -r ../firmware_all_devices.zip . && cd ..
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
VER="${{ steps.version_date.outputs.date }}"
|
||||
mkdir -p release_assets
|
||||
# OCI tar assets
|
||||
cp artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hackrf_mayhem_nightly_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_portarf_mayhem_nightly_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hpro_mayhem_nightly_${VER}.ppfw.tar"
|
||||
# Combined firmware
|
||||
cp firmware_all_devices.zip "release_assets/FIRMWARE_mayhem_nightly_${VER}.zip"
|
||||
# SD card assets
|
||||
cp artifacts/sdcard-default/sdcard.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_nightly_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-default/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_nightly_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-portarf/sdcard.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_nightly_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-portarf/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_nightly_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-hpro/sdcard.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_nightly_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-hpro/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_nightly_${VER}-no-world-map.zip"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: nightly-tag-${{ steps.date.outputs.date }}
|
||||
release_name: Nightly Release - ${{ steps.date.outputs.date }}
|
||||
name: Nightly Release - ${{ steps.date.outputs.date }}
|
||||
body: |
|
||||
**Nightly release - ${{ steps.date.outputs.date }}**
|
||||
This build is the latest and greatest, although may not be the most stable as this is a nightly release.
|
||||
@@ -145,114 +175,4 @@ jobs:
|
||||
${{ steps.changelog.outputs.content }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
# Default device assets
|
||||
- name: Upload Default Firmware TAR Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: OCI_hackrf_mayhem_nightly_${{ steps.version_date.outputs.date }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Default Firmware Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/firmware-default/firmware.zip
|
||||
asset_name: FIRMWARE_hackrf_mayhem_nightly_${{ steps.version_date.outputs.date }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload Default SD Card Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-default/sdcard.zip
|
||||
asset_name: COPY_TO_SDCARD_hackrf_mayhem_nightly_${{ steps.version_date.outputs.date }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload Default SD Card Asset - No Map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-no-map-default/sdcard-no-map.zip
|
||||
asset_name: COPY_TO_SDCARD_hackrf_mayhem_nightly_${{ steps.version_date.outputs.date }}-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
# PortaRF device assets
|
||||
- name: Upload PortaRF Firmware TAR Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: OCI_portarf_mayhem_nightly_${{ steps.version_date.outputs.date }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload PortaRF Firmware Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/firmware-portarf/firmware.zip
|
||||
asset_name: FIRMWARE_portarf_mayhem_nightly_${{ steps.version_date.outputs.date }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload PortaRF SD Card Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-portarf/sdcard.zip
|
||||
asset_name: COPY_TO_SDCARD_portarf_mayhem_nightly_${{ steps.version_date.outputs.date }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload PortaRF SD Card Asset - No Map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-no-map-portarf/sdcard-no-map.zip
|
||||
asset_name: COPY_TO_SDCARD_portarf_mayhem_nightly_${{ steps.version_date.outputs.date }}-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
# HPro device assets
|
||||
- name: Upload HPro Firmware TAR Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: OCI_hpro_mayhem_nightly_${{ steps.version_date.outputs.date }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload HPro Firmware Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/firmware-hpro/firmware.zip
|
||||
asset_name: FIRMWARE_hpro_mayhem_nightly_${{ steps.version_date.outputs.date }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload HPro SD Card Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-hpro/sdcard.zip
|
||||
asset_name: COPY_TO_SDCARD_hpro_mayhem_nightly_${{ steps.version_date.outputs.date }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload HPro SD Card Asset - No Map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-no-map-hpro/sdcard-no-map.zip
|
||||
asset_name: COPY_TO_SDCARD_hpro_mayhem_nightly_${{ steps.version_date.outputs.date }}-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
files: release_assets/*
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Make build folder
|
||||
run: mkdir ${{ github.workspace }}/build
|
||||
- name: Run the Docker image
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev ${{ matrix.cmake_args }}
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev ninja ${{ matrix.cmake_args }}
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
- name: Prepare Firmware ZIP
|
||||
run: |
|
||||
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
|
||||
mkdir -p flashing/firmware && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/firmware/hackrf_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/firmware/hackrf_usb.bin
|
||||
- name: Create Firmware ZIP
|
||||
run: |
|
||||
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
|
||||
@@ -86,6 +86,8 @@ jobs:
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
@@ -115,14 +117,42 @@ jobs:
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
# Combine all 3 device firmware bins into a single zip with device selector bat
|
||||
- name: Create Combined Firmware ZIP
|
||||
run: |
|
||||
mkdir -p firmware_combined
|
||||
unzip artifacts/firmware-default/firmware.zip -d firmware_combined
|
||||
mv firmware_combined/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hackrf.bin
|
||||
unzip -j artifacts/firmware-portarf/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_portarf.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
|
||||
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hpro.bin
|
||||
unzip -j artifacts/firmware-hpro/firmware.zip firmware/hackrf_usb.dfu firmware/hackrf_usb.bin -d firmware_combined_hpro
|
||||
mv firmware_combined_hpro/hackrf_usb.dfu firmware_combined/firmware/hackrf_hpro_usb.dfu
|
||||
mv firmware_combined_hpro/hackrf_usb.bin firmware_combined/firmware/hackrf_hpro_usb.bin
|
||||
cd firmware_combined && zip -r ../firmware_all_devices.zip . && cd ..
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
VER="${{ steps.version.outputs.version }}"
|
||||
mkdir -p release_assets
|
||||
# OCI tar assets
|
||||
cp artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hackrf_mayhem_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_portarf_mayhem_${VER}.ppfw.tar"
|
||||
cp artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hpro_mayhem_${VER}.ppfw.tar"
|
||||
# Combined firmware
|
||||
cp firmware_all_devices.zip "release_assets/FIRMWARE_mayhem_${VER}.zip"
|
||||
# SD card assets
|
||||
cp artifacts/sdcard-default/sdcard.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-default/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-portarf/sdcard.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-portarf/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_${VER}-no-world-map.zip"
|
||||
cp artifacts/sdcard-hpro/sdcard.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_${VER}.zip"
|
||||
cp artifacts/sdcard-no-map-hpro/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_${VER}-no-world-map.zip"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.version }}
|
||||
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||
name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||
body: |
|
||||
**Stable release - ${{ steps.version.outputs.version }}**
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/portapack-mayhem/mayhem-firmware/blob/master/README.md) for details.
|
||||
@@ -141,114 +171,4 @@ jobs:
|
||||
For certain functionality, like external apps, the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
|
||||
draft: true
|
||||
prerelease: false
|
||||
# Default device assets
|
||||
- name: Upload Default Firmware TAR Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: OCI_hackrf_mayhem_${{ steps.version.outputs.version }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Default Firmware Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/firmware-default/firmware.zip
|
||||
asset_name: FIRMWARE_hackrf_mayhem_${{ steps.version.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload Default SD Card Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-default/sdcard.zip
|
||||
asset_name: COPY_TO_SDCARD_hackrf_mayhem_${{ steps.version.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload Default SD Card Asset - No Map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-no-map-default/sdcard-no-map.zip
|
||||
asset_name: COPY_TO_SDCARD_hackrf_mayhem_${{ steps.version.outputs.version }}-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
# PortaRF device assets
|
||||
- name: Upload PortaRF Firmware TAR Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: OCI_portarf_mayhem_${{ steps.version.outputs.version }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload PortaRF Firmware Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/firmware-portarf/firmware.zip
|
||||
asset_name: FIRMWARE_portarf_mayhem_${{ steps.version.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload PortaRF SD Card Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-portarf/sdcard.zip
|
||||
asset_name: COPY_TO_SDCARD_portarf_mayhem_${{ steps.version.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload PortaRF SD Card Asset - No Map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-no-map-portarf/sdcard-no-map.zip
|
||||
asset_name: COPY_TO_SDCARD_portarf_mayhem_${{ steps.version.outputs.version }}-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
# HPro device assets
|
||||
- name: Upload HPro Firmware TAR Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: OCI_hpro_mayhem_${{ steps.version.outputs.version }}.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload HPro Firmware Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/firmware-hpro/firmware.zip
|
||||
asset_name: FIRMWARE_hpro_mayhem_${{ steps.version.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload HPro SD Card Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-hpro/sdcard.zip
|
||||
asset_name: COPY_TO_SDCARD_hpro_mayhem_${{ steps.version.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload HPro SD Card Asset - No Map
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: artifacts/sdcard-no-map-hpro/sdcard-no-map.zip
|
||||
asset_name: COPY_TO_SDCARD_hpro_mayhem_${{ steps.version.outputs.version }}-no-world-map.zip
|
||||
asset_content_type: application/zip
|
||||
files: release_assets/*
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
|
||||
[<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
[<img src="https://camo.githubusercontent.com/c1f7dd1e7672324f60a513f0de23de76da6a669e63896a9de535d8c8093fc3c7/68747470733a2f2f7261772e6769746875622e636f6d2f7368617265627261696e65642f706f7274617061636b2d6861636b72662f6d61737465722f646f632f696d616765732f68617264776172652f706f7274617061636b5f68315f6f7065726174696e672e6a7067" height="310">]([https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h1r1r2))
|
||||
[<img src="https://github-production-user-asset-6210df.s3.amazonaws.com/4393979/295532718-8e5b661c-f946-4cee-8b2d-0a615f7715f4.png" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
|
||||
|
||||
|
||||
# What is this?
|
||||
|
||||
@@ -28,9 +28,9 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/DF2BZS) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/QT5JMB), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/24T3TO), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
|
||||
|
||||
+2
-2
@@ -29,5 +29,5 @@ RUN mkdir /opt/build \
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
# replace make with ninja temporarily while building your image if you prefer to use that by default
|
||||
CMD ["make"]
|
||||
# replace ninja with make if you prefer to use that by default
|
||||
CMD ["ninja"]
|
||||
|
||||
@@ -205,20 +205,42 @@ SPECOptionsView::SPECOptionsView(
|
||||
PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) {
|
||||
set_parent_rect(parent_rect);
|
||||
add_children({&label_sr, &options_sr, &label_dc, &options_dc,
|
||||
&label_qi, &options_qi, &label_qs, &options_qs,
|
||||
&label_dec, &options_dec});
|
||||
&label_qi, &options_qi, &label_qs, &options_qs});
|
||||
|
||||
options_sr.set_by_value(receiver_model.sampling_rate());
|
||||
options_dc.set_by_value(1);
|
||||
options_qi.set_by_value(1);
|
||||
options_qs.set_by_value(0);
|
||||
options_dec.set_by_value(0);
|
||||
// 1. READ the current state from hardware
|
||||
fpga_reg_1 = radio::debug::fpga::register_read(1);
|
||||
uint32_t fpga_reg_2 = radio::debug::fpga::register_read(2);
|
||||
|
||||
options_sr.on_change = [this](size_t, OptionsField::value_t v) { receiver_model.set_sampling_rate(v); };
|
||||
options_dc.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x01; else fpga_reg_1 &= ~0x01; update_fpga_ctrl(); };
|
||||
options_qi.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x02; else fpga_reg_1 &= ~0x02; update_fpga_ctrl(); };
|
||||
options_qs.on_change = [this](size_t, OptionsField::value_t v) { if (v) fpga_reg_1 |= 0x04; else fpga_reg_1 &= ~0x04; update_fpga_ctrl(); };
|
||||
options_dec.on_change = [this](size_t, OptionsField::value_t v) { radio::debug::fpga::register_write(2, v); };
|
||||
// 2. INITIALIZE UI WIDGETS based on read bits
|
||||
options_dc.set_by_value((fpga_reg_1 & 0x01) ? 1 : 0); // Bit 0
|
||||
options_qi.set_by_value((fpga_reg_1 & 0x02) ? 1 : 0); // Bit 1
|
||||
options_qs.set_by_value((fpga_reg_1 & 0x04) ? 1 : 0); // Bit 2
|
||||
options_sr.set_value(receiver_model.sampling_rate());
|
||||
|
||||
options_sr.on_change = [this](int32_t v) { receiver_model.set_sampling_rate(static_cast<uint32_t>(v)); };
|
||||
options_dc.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v)
|
||||
fpga_reg_1 |= 0x01;
|
||||
else
|
||||
fpga_reg_1 &= ~0x01;
|
||||
update_fpga_ctrl();
|
||||
};
|
||||
|
||||
options_qi.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v)
|
||||
fpga_reg_1 |= 0x02;
|
||||
else
|
||||
fpga_reg_1 &= ~0x02;
|
||||
update_fpga_ctrl();
|
||||
};
|
||||
|
||||
options_qs.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (v)
|
||||
fpga_reg_1 |= 0x04;
|
||||
else
|
||||
fpga_reg_1 &= ~0x04;
|
||||
update_fpga_ctrl();
|
||||
};
|
||||
}
|
||||
|
||||
void PralineOptionsView::update_fpga_ctrl() {
|
||||
@@ -249,7 +271,6 @@ AnalogAudioView::AnalogAudioView(
|
||||
&button_pro
|
||||
#else
|
||||
&waterfall
|
||||
|
||||
#endif
|
||||
});
|
||||
|
||||
@@ -549,13 +570,8 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis);
|
||||
receiver_model.set_modulation(modulation);
|
||||
|
||||
#ifdef PRALINE
|
||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 4000000);
|
||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 2000000);
|
||||
#else
|
||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
|
||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
|
||||
#endif
|
||||
|
||||
receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
|
||||
|
||||
|
||||
@@ -45,21 +45,26 @@ class PralineOptionsView : public View {
|
||||
private:
|
||||
// Layout: SR (Sample Rate), DC (DC Block), QI (Q-Invert), QS (Quarter Shift), D (Decim)
|
||||
Text label_sr{{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "SR"};
|
||||
OptionsField options_sr{{UI_POS_X(3), UI_POS_Y(0)}, 4, {{"0.25M", 250000}, {"0.5M", 500000}, {"0.75M", 750000}, {"1.0M", 1000000}, {"1.25M", 1250000}, {"1.5M", 1500000}, {"1.75M", 1750000}, {"2.0M", 2000000}, {"2.25M", 2250000}, {"2.5M", 2500000}, {"2.75M", 2750000}, {"3.0M", 3000000}, {"3.05M", 3050000}, {"3.072M", 3072000}, {"3.10M", 3100000}, {"3.15M", 3150000}, {"3.20M", 3200000}, {"3.25M", 3250000}, {"3.30M", 3300000}, {"3.35M", 3350000}, {"3.40M", 3400000}, {"3.45M", 3450000}, {"3.5M", 3500000}, {"3.51M", 3510000}, {"3.52M", 3520000}, {"3.53M", 3530000}, {"3.54M", 3540000}, {"3.55M", 3550000}, {"3.56M", 3560000}, {"3.57M", 3570000}, {"3.58M", 3580000}, {"3.59M", 3590000}, {"3.60M", 3600000}, {"3.61M", 3610000}, {"3.62M", 3620000}, {"3.63M", 3630000}, {"3.64M", 3640000}, {"3.65M", 3650000}, {"3.66M", 3660000}, {"3.67M", 3670000}, {"3.68M", 3680000}, {"3.69M", 3690000}, {"3.70M", 3700000}, {"3.71M", 3710000}, {"3.72M", 3720000}, {"3.73M", 3730000}, {"3.74M", 3740000}, {"3.75M", 3750000}, {"3.76M", 3760000}, {"3.77M", 3770000}, {"3.78M", 3780000}, {"3.79M", 3790000}, {"3.80M", 3800000}, {"3.81M", 3810000}, {"3.82M", 3820000}, {"3.83M", 3830000}, {"3.84M", 3840000}, {"3.85M", 3850000}, {"3.86M", 3860000}, {"3.87M", 3870000}, {"3.88M", 3880000}, {"3.89M", 3890000}, {"3.90M", 3900000}, {"3.91M", 3910000}, {"3.92M", 3920000}, {"3.93M", 3930000}, {"3.94M", 3940000}, {"3.95M", 3950000}, {"3.96M", 3960000}, {"3.97M", 3970000}, {"3.98M", 3980000}, {"3.99M", 3990000}, {"4.0M", 4000000}, {"5.0M", 5000000}}};
|
||||
|
||||
Text label_dc{{UI_POS_X(8), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "DC"};
|
||||
OptionsField options_dc{{UI_POS_X(11), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
// Parameters: Position, Length (in chars), Range, Step, and default value
|
||||
NumberField options_sr{
|
||||
{UI_POS_X(3), UI_POS_Y(0)},
|
||||
8, // Number of digits to show
|
||||
{0, 40000000}, // Range: 0 to 40,000 kHz
|
||||
100, // Step: 100 Hz
|
||||
' ' // Fix: Single quotes for char
|
||||
};
|
||||
|
||||
Text label_qi{{UI_POS_X(14), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QI"};
|
||||
OptionsField options_qi{{UI_POS_X(17), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
Text label_dc{{UI_POS_X(14), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "DC"};
|
||||
OptionsField options_dc{{UI_POS_X(16), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
|
||||
Text label_qs{{UI_POS_X(20), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QS"};
|
||||
OptionsField options_qs{{UI_POS_X(23), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
Text label_qi{{UI_POS_X(20), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QI"};
|
||||
OptionsField options_qi{{UI_POS_X(22), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
|
||||
Text label_dec{{UI_POS_X(26), UI_POS_Y(0), UI_POS_WIDTH(1), UI_POS_HEIGHT(1)}, "D"};
|
||||
OptionsField options_dec{{UI_POS_X(28), UI_POS_Y(0)}, 2, {{" 1", 0}, {" 2", 1}, {" 4", 2}, {" 8", 3}, {"16", 4}}};
|
||||
Text label_qs{{UI_POS_X(26), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QS"};
|
||||
OptionsField options_qs{{UI_POS_X(28), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
|
||||
|
||||
uint8_t fpga_reg_1{0x03}; // Tracks DC, QI, QS bits
|
||||
uint8_t fpga_reg_1{0x00}; // Tracks DC, QI, QS bits
|
||||
void update_fpga_ctrl();
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -590,6 +590,187 @@ void PralineRadioDebugView::refresh() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* WFMAudioDebugView *************************************************/
|
||||
|
||||
WFMAudioDebugView::WFMAudioDebugView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&text_lbl_clk0,
|
||||
&text_clk0,
|
||||
&text_lbl_fpga_dec,
|
||||
&text_fpga_dec,
|
||||
&text_lbl_post_fpga,
|
||||
&text_post_fpga,
|
||||
&text_section1,
|
||||
&text_lbl_reg8,
|
||||
&text_reg8,
|
||||
&text_lbl_lpf_bw,
|
||||
&text_lpf_bw,
|
||||
&text_section2,
|
||||
&text_lbl_fpga_r1,
|
||||
&text_fpga_r1,
|
||||
&text_lbl_dc_q,
|
||||
&text_dc_q,
|
||||
&text_section3,
|
||||
&text_lbl_expected,
|
||||
&text_expected,
|
||||
&text_lbl_deemph,
|
||||
&text_deemph,
|
||||
&text_status,
|
||||
&text_status2,
|
||||
&button_refresh,
|
||||
&button_toggle_q,
|
||||
&button_done,
|
||||
});
|
||||
|
||||
text_title.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_section1.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_section2.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_section3.set_style(Theme::getInstance()->fg_yellow);
|
||||
|
||||
button_refresh.on_select = [this](Button&) {
|
||||
refresh();
|
||||
};
|
||||
|
||||
button_toggle_q.on_select = [this](Button&) {
|
||||
uint32_t current = radio::debug::fpga::register_read(1);
|
||||
uint8_t new_val = current ^ 0x02; // Toggle Q_INVERT bit
|
||||
radio::debug::fpga::register_write(1, new_val);
|
||||
radio::invalidate_spi_config();
|
||||
refresh();
|
||||
};
|
||||
|
||||
button_done.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void WFMAudioDebugView::focus() {
|
||||
button_refresh.focus();
|
||||
}
|
||||
|
||||
void WFMAudioDebugView::refresh() {
|
||||
// === Si5351 CLK0 Sample Rate ===
|
||||
// Read MS0 parameters to calculate frequency
|
||||
uint8_t reg44 = portapack::clock_manager.si5351_read_register(44);
|
||||
uint8_t reg45 = portapack::clock_manager.si5351_read_register(45);
|
||||
uint8_t reg46 = portapack::clock_manager.si5351_read_register(46);
|
||||
|
||||
uint8_t r_div_encoded = (reg44 >> 4) & 0x07;
|
||||
uint32_t r_div = 1 << r_div_encoded;
|
||||
uint32_t p1 = ((uint32_t)(reg44 & 0x03) << 16) | ((uint32_t)reg45 << 8) | reg46;
|
||||
uint32_t ms_div = (p1 + 512) / 128;
|
||||
|
||||
// PLL A is 800 MHz
|
||||
uint32_t clk0_khz = 800000 / ms_div / r_div;
|
||||
uint32_t clk0_mhz_int = clk0_khz / 1000;
|
||||
uint32_t clk0_khz_frac = clk0_khz % 1000;
|
||||
|
||||
text_clk0.set(to_string_dec_uint(clk0_mhz_int) + "." +
|
||||
to_string_dec_uint(clk0_khz_frac / 100) +
|
||||
to_string_dec_uint((clk0_khz_frac / 10) % 10) +
|
||||
to_string_dec_uint(clk0_khz_frac % 10) + " MHz");
|
||||
|
||||
if (clk0_khz >= 3000 && clk0_khz <= 3200) {
|
||||
text_clk0.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_clk0.set_style(Theme::getInstance()->fg_red);
|
||||
}
|
||||
|
||||
// === FPGA Decimation ===
|
||||
uint8_t fpga_decim = radio::debug::fpga::register_read(2);
|
||||
uint32_t fpga_div = 1 << fpga_decim;
|
||||
text_fpga_dec.set("/" + to_string_dec_uint(fpga_div) + " (n=" + to_string_dec_uint(fpga_decim) + ")");
|
||||
|
||||
// === Post-FPGA Rate ===
|
||||
uint32_t post_fpga_khz = clk0_khz / fpga_div;
|
||||
text_post_fpga.set(to_string_dec_uint(post_fpga_khz) + " kHz");
|
||||
|
||||
// For WFM, post-FPGA should be >= 384 kHz for proper audio decimation
|
||||
if (post_fpga_khz >= 384) {
|
||||
text_post_fpga.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_post_fpga.set_style(Theme::getInstance()->fg_orange);
|
||||
}
|
||||
|
||||
// === MAX2831 LPF ===
|
||||
uint32_t reg8 = radio::debug::second_if::register_read(8);
|
||||
text_reg8.set("0x" + to_string_hex(reg8, 4));
|
||||
|
||||
uint8_t lpf_coarse = reg8 & 0x03;
|
||||
const char* lpf_names[] = {"7.5 MHz", "8.5 MHz", "15 MHz", "18 MHz"};
|
||||
text_lpf_bw.set(lpf_names[lpf_coarse]);
|
||||
|
||||
// 7.5 MHz is minimum, OK for mono WFM but tight for stereo
|
||||
if (lpf_coarse >= 1) {
|
||||
text_lpf_bw.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_lpf_bw.set_style(Theme::getInstance()->fg_orange);
|
||||
}
|
||||
|
||||
// === FPGA Control Register ===
|
||||
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
|
||||
text_fpga_r1.set("0x" + to_string_hex(fpga_ctrl, 2));
|
||||
|
||||
bool dc_block = fpga_ctrl & 0x01;
|
||||
bool q_invert = fpga_ctrl & 0x02;
|
||||
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
|
||||
|
||||
text_dc_q.set(std::string(dc_block ? "DC:ON" : "DC:OFF") +
|
||||
" Q:" + std::string(q_invert ? "INV" : "NOR") +
|
||||
" QS:" + to_string_dec_uint(quarter_shift));
|
||||
|
||||
if (dc_block) {
|
||||
text_dc_q.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_dc_q.set_style(Theme::getInstance()->fg_orange);
|
||||
}
|
||||
|
||||
// === Expected Audio Rate ===
|
||||
// WFM typically: 3072 kHz / 64 = 48 kHz audio
|
||||
// Or: 3072 kHz → /8 (channel) → 384 kHz → /8 (audio) → 48 kHz
|
||||
uint32_t expected_audio = post_fpga_khz / 64; // Simplified assumption
|
||||
text_expected.set(to_string_dec_uint(expected_audio) + " kHz (est)");
|
||||
|
||||
if (expected_audio >= 44 && expected_audio <= 50) {
|
||||
text_expected.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_expected.set_style(Theme::getInstance()->fg_red);
|
||||
}
|
||||
|
||||
// === De-emphasis Status ===
|
||||
// We can't directly read the M4 de-emphasis config, but we can indicate what SHOULD be set
|
||||
// 75µs for USA, 50µs for Europe
|
||||
text_deemph.set("Chk M4 cfg.");
|
||||
text_deemph.set_style(Theme::getInstance()->fg_orange);
|
||||
|
||||
// === Status Summary ===
|
||||
bool sample_rate_ok = (clk0_khz >= 3000 && clk0_khz <= 3200);
|
||||
bool lpf_ok = (lpf_coarse <= 0x0F); // check against the 4-bit max
|
||||
bool dc_ok = dc_block;
|
||||
|
||||
if (sample_rate_ok && lpf_ok && dc_ok) {
|
||||
text_status.set("Hardware config looks OK.");
|
||||
text_status.set_style(Theme::getInstance()->fg_green);
|
||||
text_status2.set("If ringy: Chk d-emph in M4!");
|
||||
text_status2.set_style(Theme::getInstance()->fg_orange);
|
||||
} else {
|
||||
std::string issues = "Issues: ";
|
||||
if (!sample_rate_ok) issues += "SampleRate ";
|
||||
if (!lpf_ok) issues += "LPF ";
|
||||
if (!dc_ok) issues += "DC_Block ";
|
||||
text_status.set(issues);
|
||||
text_status.set_style(Theme::getInstance()->fg_red);
|
||||
text_status2.set("Fix before checking audio.");
|
||||
text_status2.set_style(Theme::getInstance()->fg_red);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* BasebandStatusView ******************************************************/
|
||||
|
||||
BasebandStatusView::BasebandStatusView(NavigationView& nav)
|
||||
@@ -1531,8 +1712,9 @@ void Si5351DebugView::refresh_status() {
|
||||
// Expected for 8 MHz: P1=5888 (0x1700), a=50
|
||||
uint32_t ms_div = (p1 + 512) / 128; // Integer divider value
|
||||
|
||||
// Calculate frequency: f_out = 800 MHz / ms_div / r_div
|
||||
uint32_t freq_khz = 800000 / ms_div / r_div; // Result in kHz
|
||||
// Calculate frequency: f_out = 800 or 800 MHz / ms_div / r_div
|
||||
uint32_t vco_khz = (clk0_ctrl & 0x20) ? 800000 : 800000; // PLLB vs PLLA
|
||||
uint32_t freq_khz = vco_khz / ms_div / r_div; // Result in kHz
|
||||
|
||||
// Show P1 value and R45 for debugging
|
||||
text_clk0_freq_value.set("F:" + to_string_dec_uint(freq_khz / 1000) + "MHz (P1:" + to_string_hex(p1, 4) + ")");
|
||||
@@ -2001,6 +2183,483 @@ void SystemDiagnosticsView::refresh() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Si5351PLLADebugView *******************************************************/
|
||||
|
||||
Si5351PLLADebugView::Si5351PLLADebugView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&text_title, &text_lbl_raw, &text_r26_27, &text_r28_30, &text_r31_33,
|
||||
&text_lbl_decoded, &text_lbl_p1, &text_p1, &text_lbl_p2, &text_p2,
|
||||
&text_lbl_p3, &text_p3, &text_lbl_calc, &text_lbl_mult, &text_mult,
|
||||
&text_lbl_vco, &text_vco, &text_status, &button_refresh, &button_done});
|
||||
|
||||
text_title.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_raw.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_calc.set_style(Theme::getInstance()->fg_yellow);
|
||||
|
||||
button_refresh.on_select = [this](Button&) { refresh(); };
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Si5351PLLADebugView::focus() {
|
||||
button_refresh.focus();
|
||||
}
|
||||
|
||||
void Si5351PLLADebugView::refresh() {
|
||||
// Read PLL A parameters
|
||||
uint8_t r26 = portapack::clock_manager.si5351_read_register(26);
|
||||
uint8_t r27 = portapack::clock_manager.si5351_read_register(27);
|
||||
uint8_t r28 = portapack::clock_manager.si5351_read_register(28);
|
||||
uint8_t r29 = portapack::clock_manager.si5351_read_register(29);
|
||||
uint8_t r30 = portapack::clock_manager.si5351_read_register(30);
|
||||
uint8_t r31 = portapack::clock_manager.si5351_read_register(31);
|
||||
uint8_t r32 = portapack::clock_manager.si5351_read_register(32);
|
||||
uint8_t r33 = portapack::clock_manager.si5351_read_register(33);
|
||||
|
||||
// Display Raw registers
|
||||
text_r26_27.set("R26-27 (P3 LO): " + to_string_hex(r26, 2) + " " + to_string_hex(r27, 2));
|
||||
text_r28_30.set("R28-30 (P1): " + to_string_hex(r28, 2) + " " + to_string_hex(r29, 2) + " " + to_string_hex(r30, 2));
|
||||
text_r31_33.set("R31-33 (P3H|P2):" + to_string_hex(r31, 2) + " " + to_string_hex(r32, 2) + " " + to_string_hex(r33, 2));
|
||||
|
||||
// Decode parameters
|
||||
uint32_t pll_p1 = ((uint32_t)(r28 & 0x03) << 16) | ((uint32_t)r29 << 8) | r30;
|
||||
uint32_t pll_p2 = ((uint32_t)(r31 & 0x0F) << 16) | ((uint32_t)r32 << 8) | r33;
|
||||
uint32_t pll_p3 = ((uint32_t)(r31 >> 4) << 16) | ((uint32_t)r26 << 8) | r27;
|
||||
|
||||
text_p1.set(to_string_dec_uint(pll_p1) + " (0x" + to_string_hex(pll_p1, 5) + ")");
|
||||
text_p2.set(to_string_dec_uint(pll_p2) + " (0x" + to_string_hex(pll_p2, 5) + ")");
|
||||
text_p3.set(to_string_dec_uint(pll_p3) + " (0x" + to_string_hex(pll_p3, 5) + ")");
|
||||
|
||||
// Calculate Multiplier: M = (128 * P3 + P1 * 128 + 512 + P2) / (128 * P3)
|
||||
// Simplified as: Multiplier = ((P1 + 512) / 128) + (P2 / P3)
|
||||
uint32_t a = (pll_p1 + 512) / 128;
|
||||
|
||||
if (pll_p3 > 0) {
|
||||
text_mult.set(to_string_dec_uint(a) + " + " + to_string_dec_uint(pll_p2) + "/" + to_string_dec_uint(pll_p3));
|
||||
} else {
|
||||
text_mult.set("ERR: P3=0");
|
||||
}
|
||||
|
||||
// Calculate VCO Frequency (f_vco = f_xtal * Multiplier)
|
||||
// The HackRF crystal (f_xtal) is 25 MHz.
|
||||
if (pll_p3 > 0) {
|
||||
uint64_t vco_num = (uint64_t)pll_p2 + (uint64_t)pll_p3 * (pll_p1 + 512);
|
||||
uint64_t vco_den = 128ULL * pll_p3;
|
||||
uint32_t vco_khz = (uint32_t)((25000ULL * vco_num) / vco_den);
|
||||
|
||||
text_vco.set(to_string_dec_uint(vco_khz / 1000) + "." + to_string_dec_uint(vco_khz % 1000, 3) + " MHz");
|
||||
|
||||
// Si5351 VCO range is 600-900 MHz
|
||||
if (vco_khz >= 600000 && vco_khz <= 900000) {
|
||||
text_vco.set_style(Theme::getInstance()->fg_green);
|
||||
text_status.set("VCO within valid range.");
|
||||
} else {
|
||||
text_vco.set_style(Theme::getInstance()->fg_red);
|
||||
text_status.set("VCO OUT OF RANGE (600-900)!");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
Si5351PLLBDebugView::Si5351PLLBDebugView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&text_title,
|
||||
&text_lbl_raw, &text_r34_35, &text_r36_38, &text_r39_41,
|
||||
&text_lbl_decoded, &text_lbl_p1, &text_p1, &text_lbl_p2, &text_p2, &text_lbl_p3, &text_p3,
|
||||
&text_lbl_calc, &text_lbl_vco, &text_vco,
|
||||
&text_status, &button_refresh, &button_done});
|
||||
|
||||
text_title.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_raw.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_calc.set_style(Theme::getInstance()->fg_yellow);
|
||||
|
||||
button_refresh.on_select = [this](Button&) { refresh(); };
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Si5351PLLBDebugView::focus() {
|
||||
button_refresh.focus();
|
||||
}
|
||||
|
||||
void Si5351PLLBDebugView::refresh() {
|
||||
uint8_t r34 = portapack::clock_manager.si5351_read_register(34); // P3[15:8]
|
||||
uint8_t r35 = portapack::clock_manager.si5351_read_register(35); // P3[7:0]
|
||||
uint8_t r36 = portapack::clock_manager.si5351_read_register(36); // P1[17:16]
|
||||
uint8_t r37 = portapack::clock_manager.si5351_read_register(37); // P1[15:8]
|
||||
uint8_t r38 = portapack::clock_manager.si5351_read_register(38); // P1[7:0]
|
||||
uint8_t r39 = portapack::clock_manager.si5351_read_register(39); // P3[19:16] | P2[19:16]
|
||||
uint8_t r40 = portapack::clock_manager.si5351_read_register(40); // P2[15:8]
|
||||
uint8_t r41 = portapack::clock_manager.si5351_read_register(41); // P2[7:0]
|
||||
|
||||
text_r34_35.set("R34-35: " + to_string_hex(r34, 2) + " " + to_string_hex(r35, 2));
|
||||
text_r36_38.set("R36-38: " + to_string_hex(r36, 2) + " " + to_string_hex(r37, 2) + " " + to_string_hex(r38, 2));
|
||||
text_r39_41.set("R39-41: " + to_string_hex(r39, 2) + " " + to_string_hex(r40, 2) + " " + to_string_hex(r41, 2));
|
||||
|
||||
uint32_t p1 = ((uint32_t)(r36 & 0x03) << 16) | ((uint32_t)r37 << 8) | r38;
|
||||
uint32_t p2 = ((uint32_t)(r39 & 0x0F) << 16) | ((uint32_t)r40 << 8) | r41;
|
||||
uint32_t p3 = ((uint32_t)(r39 >> 4) << 16) | ((uint32_t)r34 << 8) | r35;
|
||||
|
||||
text_p1.set(to_string_dec_uint(p1) + " (0x" + to_string_hex(p1, 5) + ")");
|
||||
text_p2.set(to_string_dec_uint(p2) + " (0x" + to_string_hex(p2, 5) + ")");
|
||||
text_p3.set(to_string_dec_uint(p3) + " (0x" + to_string_hex(p3, 5) + ")");
|
||||
|
||||
if (p3 > 0) {
|
||||
uint64_t vco_num = (uint64_t)p2 + (uint64_t)p3 * (p1 + 512);
|
||||
uint64_t vco_den = 128ULL * p3;
|
||||
uint32_t vco_khz = (uint32_t)((25000ULL * vco_num) / vco_den);
|
||||
text_vco.set(to_string_dec_uint(vco_khz / 1000) + "." + to_string_dec_uint(vco_khz % 1000, 3) + " MHz");
|
||||
|
||||
bool ok = (vco_khz >= 600000 && vco_khz <= 900000);
|
||||
text_vco.set_style(ok ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
|
||||
text_status.set(ok ? "PLL B VCO OK" : "VCO OUT OF RANGE!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Si5351MultiSynthDebugView *************************************************/
|
||||
|
||||
Si5351MultiSynthDebugView::Si5351MultiSynthDebugView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({
|
||||
&text_title,
|
||||
&text_lbl_clk_ctrl,
|
||||
&text_clk_ctrl,
|
||||
&text_lbl_ms_int,
|
||||
&text_ms_int,
|
||||
&text_lbl_raw,
|
||||
&text_lbl_r42_43,
|
||||
&text_r42_43,
|
||||
&text_lbl_r44_46,
|
||||
&text_r44_46,
|
||||
&text_lbl_r47_49,
|
||||
&text_r47_49,
|
||||
&text_lbl_decoded,
|
||||
&text_lbl_p1,
|
||||
&text_p1,
|
||||
&text_lbl_p2,
|
||||
&text_p2,
|
||||
&text_lbl_p3,
|
||||
&text_p3,
|
||||
&text_lbl_rdiv,
|
||||
&text_rdiv,
|
||||
&text_lbl_calc,
|
||||
&text_lbl_div,
|
||||
&text_div,
|
||||
&text_lbl_freq,
|
||||
&text_freq,
|
||||
&text_status,
|
||||
&button_refresh,
|
||||
&button_reset,
|
||||
&button_frac,
|
||||
&button_done,
|
||||
});
|
||||
|
||||
text_title.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_raw.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_decoded.set_style(Theme::getInstance()->fg_yellow);
|
||||
text_lbl_calc.set_style(Theme::getInstance()->fg_yellow);
|
||||
|
||||
button_refresh.on_select = [this](Button&) {
|
||||
refresh();
|
||||
};
|
||||
|
||||
button_reset.on_select = [this](Button&) {
|
||||
force_pll_reset();
|
||||
};
|
||||
|
||||
button_frac.on_select = [this](Button&) {
|
||||
force_fractional_mode();
|
||||
};
|
||||
|
||||
button_done.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Si5351MultiSynthDebugView::focus() {
|
||||
button_refresh.focus();
|
||||
}
|
||||
|
||||
void Si5351MultiSynthDebugView::force_pll_reset() {
|
||||
// Reset PLL A (bit 5)
|
||||
portapack::clock_manager.si5351_write_register(177, 0x20);
|
||||
|
||||
// Wait for PLL to settle
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Si5351MultiSynthDebugView::force_fractional_mode() {
|
||||
// Force CLK0 to fractional mode
|
||||
// Read current control register
|
||||
uint8_t clk0_ctrl = portapack::clock_manager.si5351_read_register(16);
|
||||
|
||||
// Clear MS_INT bit (bit 6) to enable fractional mode
|
||||
clk0_ctrl &= ~0x40;
|
||||
|
||||
// Write back
|
||||
portapack::clock_manager.si5351_write_register(16, clk0_ctrl);
|
||||
|
||||
// Reset PLL to apply
|
||||
portapack::clock_manager.si5351_write_register(177, 0x20);
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Si5351MultiSynthDebugView::refresh() {
|
||||
// === Clock Control Register 16 (CLK0) ===
|
||||
uint8_t clk0_ctrl = portapack::clock_manager.si5351_read_register(16);
|
||||
text_clk_ctrl.set("0x" + to_string_hex(clk0_ctrl, 2) +
|
||||
" (" + to_string_bin(clk0_ctrl, 8) + ")");
|
||||
|
||||
// Decode MS_INT bit (bit 6)
|
||||
bool ms_int = (clk0_ctrl >> 6) & 1;
|
||||
if (ms_int) {
|
||||
text_ms_int.set("1:INT MODE!");
|
||||
text_ms_int.set_style(Theme::getInstance()->fg_red);
|
||||
} else {
|
||||
text_ms_int.set("0:Fract Mode");
|
||||
text_ms_int.set_style(Theme::getInstance()->fg_green);
|
||||
}
|
||||
|
||||
// === Read Raw MS0 Registers (42-49) ===
|
||||
uint8_t r42 = portapack::clock_manager.si5351_read_register(42);
|
||||
uint8_t r43 = portapack::clock_manager.si5351_read_register(43);
|
||||
uint8_t r44 = portapack::clock_manager.si5351_read_register(44);
|
||||
uint8_t r45 = portapack::clock_manager.si5351_read_register(45);
|
||||
uint8_t r46 = portapack::clock_manager.si5351_read_register(46);
|
||||
uint8_t r47 = portapack::clock_manager.si5351_read_register(47);
|
||||
uint8_t r48 = portapack::clock_manager.si5351_read_register(48);
|
||||
uint8_t r49 = portapack::clock_manager.si5351_read_register(49);
|
||||
|
||||
// Display raw registers
|
||||
text_r42_43.set(to_string_hex(r42, 2) + " " + to_string_hex(r43, 2) +
|
||||
" (P3[15:0])");
|
||||
text_r44_46.set(to_string_hex(r44, 2) + " " + to_string_hex(r45, 2) +
|
||||
" " + to_string_hex(r46, 2) + " (R|P1)");
|
||||
text_r47_49.set(to_string_hex(r47, 2) + " " + to_string_hex(r48, 2) +
|
||||
" " + to_string_hex(r49, 2) + " (P3|P2)");
|
||||
|
||||
// === Decode P1, P2, P3 ===
|
||||
// Si5351 MS Register Layout:
|
||||
// Reg 42: P3[15:8]
|
||||
// Reg 43: P3[7:0]
|
||||
// Reg 44: bits 6:4 = R_DIV[2:0], bits 1:0 = P1[17:16]
|
||||
// Reg 45: P1[15:8]
|
||||
// Reg 46: P1[7:0]
|
||||
// Reg 47: bits 7:4 = P3[19:16], bits 3:0 = P2[19:16]
|
||||
// Reg 48: P2[15:8]
|
||||
// Reg 49: P2[7:0]
|
||||
|
||||
// Decode R_DIV
|
||||
uint8_t r_div_encoded = (r44 >> 4) & 0x07;
|
||||
uint32_t r_div = 1 << r_div_encoded;
|
||||
text_rdiv.set("/" + to_string_dec_uint(r_div) + " (enc=" + to_string_dec_uint(r_div_encoded) + ")");
|
||||
|
||||
// Decode P1 (18-bit)
|
||||
uint32_t p1 = ((uint32_t)(r44 & 0x03) << 16) | ((uint32_t)r45 << 8) | r46;
|
||||
text_p1.set(to_string_dec_uint(p1) + " (0x" + to_string_hex(p1, 5) + ")");
|
||||
|
||||
// Decode P2 (20-bit)
|
||||
uint32_t p2 = ((uint32_t)(r47 & 0x0F) << 16) | ((uint32_t)r48 << 8) | r49;
|
||||
text_p2.set(to_string_dec_uint(p2) + " (0x" + to_string_hex(p2, 5) + ")");
|
||||
|
||||
// Decode P3 (20-bit)
|
||||
uint32_t p3 = ((uint32_t)(r47 >> 4) << 16) | ((uint32_t)r42 << 8) | r43;
|
||||
text_p3.set(to_string_dec_uint(p3) + " (0x" + to_string_hex(p3, 5) + ")");
|
||||
|
||||
// Color code P2/P3 based on whether fractional is being used
|
||||
if (p2 == 0 && p3 == 1) {
|
||||
text_p2.set_style(Theme::getInstance()->fg_orange);
|
||||
text_p3.set_style(Theme::getInstance()->fg_orange);
|
||||
} else if (p3 > 1) {
|
||||
text_p2.set_style(Theme::getInstance()->fg_green);
|
||||
text_p3.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
// Default/neutral style when P2/P3 don't match known patterns
|
||||
text_p2.set_style(Theme::getInstance()->fg_light);
|
||||
text_p3.set_style(Theme::getInstance()->fg_light);
|
||||
}
|
||||
|
||||
// === Calculate Output Frequency ===
|
||||
|
||||
// === Calculate Multisynth Divider ===
|
||||
// Correct Si5351 formula:
|
||||
// MS_DIV = (P2+P3 × (P1 + 512)) / (128 × P3)
|
||||
// MS_DIV = P2/(128*P3) + P1+512/(128*P3)
|
||||
// x = (P1 + 512) / 128
|
||||
// y = P2/128
|
||||
// z = P3
|
||||
// f_out = f_vco / MS_DIV / R_DIV
|
||||
// a = floor((P1 + 512) / 128)
|
||||
// k = (P1 + 512) - 128*a
|
||||
// b = b = (P2 + c*k) / 128
|
||||
// c = P3
|
||||
// In the case of integer division: b=0, c=1, so MS_DIV = a
|
||||
|
||||
uint64_t ms_div_numerator = (uint64_t)p2 + (uint64_t)p3 * (p1 + 512);
|
||||
uint64_t ms_div_denominator = 128ULL * p3;
|
||||
|
||||
uint32_t x = (p1 + 512) / 128;
|
||||
uint32_t y = p2 / 128;
|
||||
uint32_t z = p3;
|
||||
|
||||
// For display, show the full fractional value
|
||||
// MS_DIV = (a+b)/c
|
||||
if (p3 > 1 && p2 > 0) {
|
||||
std::string div_str = "(" + to_string_dec_uint(x) + "+" + to_string_dec_uint(y) + ")/" + to_string_dec_uint(z);
|
||||
text_div.set(div_str);
|
||||
} else {
|
||||
std::string div_str = to_string_dec_uint(x);
|
||||
text_div.set(div_str);
|
||||
}
|
||||
|
||||
// === Calculate Output Frequency ===
|
||||
|
||||
// f_vco = 800,000,000 (PLL A)
|
||||
// f_out in kHz = 800,000,000 / MS_DIV / r_div / 1000
|
||||
// = 800,000 × ms_div_denominator / ms_div_numerator / r_div
|
||||
|
||||
uint32_t freq_khz = 0;
|
||||
if (ms_div_numerator > 0) {
|
||||
freq_khz = (uint32_t)((800000ULL * ms_div_denominator) / ms_div_numerator / r_div);
|
||||
}
|
||||
|
||||
uint32_t freq_mhz = freq_khz / 1000;
|
||||
uint32_t freq_frac = freq_khz % 1000;
|
||||
|
||||
text_freq.set(to_string_dec_uint(freq_mhz) + "." +
|
||||
to_string_dec_uint(freq_frac / 100) +
|
||||
to_string_dec_uint((freq_frac / 10) % 10) +
|
||||
to_string_dec_uint(freq_frac % 10) + " MHz");
|
||||
|
||||
// Color code based on expected ~24.576 MHz for WFM stereo
|
||||
if (freq_khz >= 24500 && freq_khz <= 24700) {
|
||||
text_freq.set_style(Theme::getInstance()->fg_green);
|
||||
} else if (freq_khz >= 24000 && freq_khz <= 26000) {
|
||||
text_freq.set_style(Theme::getInstance()->fg_orange);
|
||||
} else {
|
||||
text_freq.set_style(Theme::getInstance()->fg_red);
|
||||
}
|
||||
|
||||
// === Status Summary ===
|
||||
// Expected values for 24.576 MHz (3.072 MHz * 8 decimation):
|
||||
// VCO = 800 MHz
|
||||
// Target freq = 49.152 MHz (before R_DIV=/2)
|
||||
// MS_DIV = 800M / 49.152M = 16.276...
|
||||
// a = 16, b = 53, c = 192
|
||||
// P1 = 128*16 + floor(128*53/192) - 512 = 2048 + 35 - 512 = 1571
|
||||
// P2 = 128*53 - 192*35 = 6784 - 6720 = 64
|
||||
// P3 = 192
|
||||
|
||||
if (ms_int) {
|
||||
text_status.set("ERROR: Integer mode! P2/P3 ignored!");
|
||||
text_status.set_style(Theme::getInstance()->fg_red);
|
||||
} else if (p2 == 0 && p3 == 1) {
|
||||
text_status.set("WARN P2:0,P3:1 INT Equiv");
|
||||
text_status.set_style(Theme::getInstance()->fg_orange);
|
||||
} else if (p3 == 192 && p2 == 64) {
|
||||
text_status.set("GOOD Exp 3.072M values!");
|
||||
text_status.set_style(Theme::getInstance()->fg_green);
|
||||
} else if (freq_khz >= 24500 && freq_khz <= 24700) {
|
||||
text_status.set("OK: Freq in range");
|
||||
text_status.set_style(Theme::getInstance()->fg_green);
|
||||
} else {
|
||||
text_status.set("CHECK: P2=" + to_string_dec_uint(p2) +
|
||||
" P3=" + to_string_dec_uint(p3));
|
||||
text_status.set_style(Theme::getInstance()->fg_orange);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
PralineClockDebugView::PralineClockDebugView(NavigationView& nav)
|
||||
: View(),
|
||||
rows{
|
||||
{&t0_id, &t0_ma, &t0_mode, &t0_src, &t0_ph, &t0_st},
|
||||
{&t1_id, &t1_ma, &t1_mode, &t1_src, &t1_ph, &t1_st},
|
||||
{&t2_id, &t2_ma, &t2_mode, &t2_src, &t2_ph, &t2_st},
|
||||
{&t3_id, &t3_ma, &t3_mode, &t3_src, &t3_ph, &t3_st},
|
||||
{&t4_id, &t4_ma, &t4_mode, &t4_src, &t4_ph, &t4_st},
|
||||
{&t5_id, &t5_ma, &t5_mode, &t5_src, &t5_ph, &t5_st}} {
|
||||
add_children({&text_title, &text_lbl_pll, &text_pll_status,
|
||||
&text_lbl_afe, &text_afe_rate, &text_lbl_n, &text_n_val,
|
||||
&text_header,
|
||||
&t0_id, &t0_ma, &t0_mode, &t0_src, &t0_ph, &t0_st,
|
||||
&t1_id, &t1_ma, &t1_mode, &t1_src, &t1_ph, &t1_st,
|
||||
&t2_id, &t2_ma, &t2_mode, &t2_src, &t2_ph, &t2_st,
|
||||
&t3_id, &t3_ma, &t3_mode, &t3_src, &t3_ph, &t3_st,
|
||||
&t4_id, &t4_ma, &t4_mode, &t4_src, &t4_ph, &t4_st,
|
||||
&t5_id, &t5_ma, &t5_mode, &t5_src, &t5_ph, &t5_st,
|
||||
&button_refresh, &button_done});
|
||||
|
||||
button_refresh.on_select = [this](Button&) { this->refresh(); };
|
||||
button_done.on_select = [&nav](Button&) { nav.pop(); };
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void PralineClockDebugView::focus() {
|
||||
button_refresh.focus();
|
||||
}
|
||||
|
||||
void PralineClockDebugView::refresh() {
|
||||
// 1. System Status
|
||||
uint8_t status = portapack::clock_manager.si5351_read_status();
|
||||
bool pll_a = !(status & 0x20);
|
||||
bool pll_b = !(status & 0x40);
|
||||
text_pll_status.set(std::string(pll_a ? "A:OK " : "A:ERR ") + (pll_b ? "B:OK" : "B:ERR"));
|
||||
text_pll_status.set_style((pll_a && pll_b) ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
|
||||
|
||||
// 2. AFE & Decimation Info
|
||||
uint32_t base_rate = portapack::clock_manager.get_sampling_frequency();
|
||||
uint8_t n = portapack::clock_manager.get_resampling_n();
|
||||
text_afe_rate.set(to_string_dec_uint(base_rate << n) + " Hz");
|
||||
text_n_val.set(to_string_dec_uint(n));
|
||||
|
||||
// 3. Clock Table Decoding
|
||||
uint8_t output_en = portapack::clock_manager.si5351_read_register(3);
|
||||
const char* ma_lookup[] = {"2m", "4m", "6m", "8m"};
|
||||
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
uint8_t ctrl = portapack::clock_manager.si5351_read_register(16 + i);
|
||||
|
||||
// mA (Bits 1:0)
|
||||
rows[i].ma->set(ma_lookup[ctrl & 0x03]);
|
||||
|
||||
// Mode (Bit 6: 1=Integer, 0=Fractional)
|
||||
rows[i].mode->set((ctrl & 0x40) ? "INT" : "FRAC");
|
||||
rows[i].mode->set_style((ctrl & 0x40) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_yellow);
|
||||
|
||||
// PLL Source (Bit 5: 0=PLLA, 1=PLLB)
|
||||
rows[i].src->set((ctrl & 0x20) ? "PLLB" : "PLLA");
|
||||
rows[i].src->set_style((ctrl & 0x20) ? Theme::getInstance()->fg_blue : Theme::getInstance()->fg_green);
|
||||
|
||||
// Phase (Bit 4: 1=Inverted, 0=Normal)
|
||||
// Use 0x10 (Bit 4)
|
||||
rows[i].phase->set((ctrl & 0x10) ? "INVRT" : "NORM ");
|
||||
rows[i].phase->set_style((ctrl & 0x10) ? Theme::getInstance()->fg_orange : Theme::getInstance()->fg_light);
|
||||
|
||||
// Status (Powered On and Output Enabled)
|
||||
bool is_on = !(ctrl & 0x80) && !(output_en & (1 << i));
|
||||
rows[i].stat->set(is_on ? "ON" : "OFF");
|
||||
rows[i].stat->set_style(is_on ? Theme::getInstance()->fg_green : Theme::getInstance()->fg_red);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* GPIODebugView *************************************************/
|
||||
GPIODebugView::GPIODebugView(NavigationView& nav) {
|
||||
@@ -2821,10 +3480,15 @@ void DebugMenuView::on_populate() {
|
||||
}
|
||||
add_items({
|
||||
#ifdef PRALINE
|
||||
{"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SystemDiagnosticsView>(); }},
|
||||
{"WFM Audio", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<WFMAudioDebugView>(); }},
|
||||
{"MSynth Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351MultiSynthDebugView>(); }},
|
||||
{"Radio Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RadioDiagnosticsView>(); }},
|
||||
{"ProRadio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<PralineRadioDebugView>(); }},
|
||||
{"Radio Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<PralineRadioDebugView>(); }},
|
||||
{"Signal Path", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SignalPathStatusView>(); }},
|
||||
{"System Diag", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<SystemDiagnosticsView>(); }},
|
||||
{"Clocks", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<ui::PralineClockDebugView>(); }},
|
||||
{"PLL A Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351PLLADebugView>(); }},
|
||||
{"PLL B Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<Si5351PLLBDebugView>(); }},
|
||||
{"GPIO Debug", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<GPIODebugView>(); }},
|
||||
{"RFFC Status", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RFFC5072StatusView>(); }},
|
||||
{"RFFC Tuning", ui::Theme::getInstance()->fg_yellow->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<RFFCTuningDebugView>(); }},
|
||||
|
||||
@@ -482,6 +482,69 @@ class PralineRadioDebugView : public View {
|
||||
};
|
||||
#endif
|
||||
|
||||
/* WFMAudioDebugView ***************************************************/
|
||||
|
||||
#ifdef PRALINE
|
||||
class WFMAudioDebugView : public View {
|
||||
public:
|
||||
WFMAudioDebugView(NavigationView& nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "WFM Audio Debug"; }
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_title{{0, 0, 240, 16}, "=== WFM Audio Debug ==="};
|
||||
|
||||
// Sample rates section
|
||||
Text text_lbl_clk0{{0, 20, 140, 16}, "Si5351 CLK0 Rate:"};
|
||||
Text text_clk0{{150, 20, 90, 16}, "---"};
|
||||
|
||||
Text text_lbl_fpga_dec{{0, 36, 140, 16}, "FPGA Decimation:"};
|
||||
Text text_fpga_dec{{150, 36, 90, 16}, "---"};
|
||||
|
||||
Text text_lbl_post_fpga{{0, 52, 140, 16}, "Post-FPGA Rate:"};
|
||||
Text text_post_fpga{{150, 52, 90, 16}, "---"};
|
||||
|
||||
// MAX2831 section
|
||||
Text text_section1{{0, 72, 240, 16}, "--- MAX2831 LPF ---"};
|
||||
|
||||
Text text_lbl_reg8{{0, 88, 140, 16}, "Reg8 (LPF RX):"};
|
||||
Text text_reg8{{150, 88, 90, 16}, "---"};
|
||||
|
||||
Text text_lbl_lpf_bw{{0, 104, 140, 16}, "LPF Bandwidth:"};
|
||||
Text text_lpf_bw{{150, 104, 90, 16}, "---"};
|
||||
|
||||
// FPGA section
|
||||
Text text_section2{{0, 124, 240, 16}, "--- FPGA Control ---"};
|
||||
|
||||
Text text_lbl_fpga_r1{{0, 140, 140, 16}, "Reg1 (Ctrl):"};
|
||||
Text text_fpga_r1{{150, 140, 90, 16}, "---"};
|
||||
|
||||
Text text_lbl_dc_q{{0, 156, 70, 16}, "DC/Q/QS:"};
|
||||
Text text_dc_q{{72, 156, 168, 16}, "---"};
|
||||
|
||||
// Audio section
|
||||
Text text_section3{{0, 176, 240, 16}, "--- Audio Path ---"};
|
||||
|
||||
Text text_lbl_expected{{0, 192, 96, 16}, "Expctd Aud:"};
|
||||
Text text_expected{{98, 192, 142, 16}, "---"};
|
||||
|
||||
Text text_lbl_deemph{{0, 208, 96, 16}, "D-emph Cfg:"};
|
||||
Text text_deemph{{98, 208, 132, 16}, "---"};
|
||||
|
||||
// Status
|
||||
Text text_status{{0, 228, 240, 16}, "---"};
|
||||
Text text_status2{{0, 244, 240, 16}, "---"};
|
||||
|
||||
Button button_refresh{{10, 268, 70, 24}, "Refresh"};
|
||||
Button button_toggle_q{{90, 268, 70, 24}, "Toggle Q"};
|
||||
Button button_done{{170, 268, 60, 24}, "Done"};
|
||||
};
|
||||
#endif
|
||||
|
||||
/* BasebandStatusView ***************************************************/
|
||||
|
||||
class BasebandStatusView : public View {
|
||||
@@ -1062,6 +1125,249 @@ class SystemDiagnosticsView : public View {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Si5351PLLADebugView - Debug fractional parameters for PLL A */
|
||||
class Si5351PLLADebugView : public View {
|
||||
public:
|
||||
Si5351PLLADebugView(NavigationView& nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "Si5351 PLL A Debug"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void refresh();
|
||||
|
||||
Text text_title{{0, 0, 240, 16}, "===PLL A Fractional Debug==="};
|
||||
|
||||
// Raw register values
|
||||
Text text_lbl_raw{{0, 24, 240, 16}, "---Raw Registers (26-33)---"};
|
||||
Text text_r26_27{{8, 40, 224, 16}, "---"};
|
||||
Text text_r28_30{{8, 56, 224, 16}, "---"};
|
||||
Text text_r31_33{{8, 72, 224, 16}, "---"};
|
||||
|
||||
// Decoded values
|
||||
Text text_lbl_decoded{{0, 96, 240, 16}, "---Decoded Parameters---"};
|
||||
Text text_lbl_p1{{0, 112, 48, 16}, "P1:"};
|
||||
Text text_p1{{50, 112, 190, 16}, "---"};
|
||||
Text text_lbl_p2{{0, 128, 48, 16}, "P2:"};
|
||||
Text text_p2{{50, 128, 190, 16}, "---"};
|
||||
Text text_lbl_p3{{0, 144, 48, 16}, "P3:"};
|
||||
Text text_p3{{50, 144, 190, 16}, "---"};
|
||||
|
||||
// Calculated VCO
|
||||
Text text_lbl_calc{{0, 168, 240, 16}, "---Calculated VCO---"};
|
||||
Text text_lbl_mult{{0, 184, 80, 16}, "Multiplier:"};
|
||||
Text text_mult{{82, 184, 158, 16}, "---"};
|
||||
Text text_lbl_vco{{0, 200, 80, 16}, "VCO Freq:"};
|
||||
Text text_vco{{82, 200, 158, 16}, "---"};
|
||||
|
||||
Text text_status{{0, 224, 240, 32}, ""};
|
||||
|
||||
Button button_refresh{{8, 280, 100, 24}, "Refresh"};
|
||||
Button button_done{{132, 280, 100, 24}, "Done"};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
class Si5351PLLBDebugView : public View {
|
||||
public:
|
||||
Si5351PLLBDebugView(NavigationView& nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "Si5351 PLL B Debug"; };
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_title{{0, 0, 240, 16}, "=== PLL B Fractional Debug ==="};
|
||||
|
||||
Text text_lbl_raw{{0, 24, 240, 16}, "--- Raw Registers (34-41) ---"};
|
||||
Text text_r34_35{{8, 40, 224, 16}, "---"};
|
||||
Text text_r36_38{{8, 56, 224, 16}, "---"};
|
||||
Text text_r39_41{{8, 72, 224, 16}, "---"};
|
||||
|
||||
Text text_lbl_decoded{{0, 96, 240, 16}, "--- Decoded Parameters ---"};
|
||||
Text text_lbl_p1{{0, 112, 48, 16}, "P1:"};
|
||||
Text text_p1{{50, 112, 190, 16}, "---"};
|
||||
Text text_lbl_p2{{0, 128, 48, 16}, "P2:"};
|
||||
Text text_p2{{50, 128, 190, 16}, "---"};
|
||||
Text text_lbl_p3{{0, 144, 48, 16}, "P3:"};
|
||||
Text text_p3{{50, 144, 190, 16}, "---"};
|
||||
|
||||
Text text_lbl_calc{{0, 168, 240, 16}, "--- Calculated VCO ---"};
|
||||
Text text_lbl_vco{{0, 200, 80, 16}, "VCO Freq:"};
|
||||
Text text_vco{{82, 200, 158, 16}, "---"};
|
||||
|
||||
Text text_status{{0, 224, 240, 32}, ""};
|
||||
Button button_refresh{{8, 280, 100, 24}, "Refresh"};
|
||||
Button button_done{{132, 280, 100, 24}, "Done"};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Si5351MultiSynthDebugView - Debug fractional register values for MS0 */
|
||||
class Si5351MultiSynthDebugView : public View {
|
||||
public:
|
||||
Si5351MultiSynthDebugView(NavigationView& nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "Si5351 MS0 Debug"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void refresh();
|
||||
void force_pll_reset();
|
||||
void force_fractional_mode();
|
||||
|
||||
Text text_title{{0, 0, 240, 16}, "===MS0 Fractional Debug==="};
|
||||
|
||||
// Clock Control Register 16 (CLK0)
|
||||
Text text_lbl_clk_ctrl{{0, 18, 100, 16}, "CLK0 Ctrl:"};
|
||||
Text text_clk_ctrl{{102, 18, 138, 16}, "---"};
|
||||
|
||||
Text text_lbl_ms_int{{0, 34, 100, 16}, "MS_INT bit:"};
|
||||
Text text_ms_int{{102, 34, 138, 16}, "---"};
|
||||
|
||||
// Raw register values
|
||||
Text text_lbl_raw{{0, 54, 240, 16}, "---Raw Registers (42-49)---"};
|
||||
|
||||
Text text_lbl_r42_43{{0, 70, 80, 16}, "R42-43:"};
|
||||
Text text_r42_43{{82, 70, 158, 16}, "---"};
|
||||
|
||||
Text text_lbl_r44_46{{0, 86, 80, 16}, "R44-46:"};
|
||||
Text text_r44_46{{82, 86, 158, 16}, "---"};
|
||||
|
||||
Text text_lbl_r47_49{{0, 102, 80, 16}, "R47-49:"};
|
||||
Text text_r47_49{{82, 102, 158, 16}, "---"};
|
||||
|
||||
// Decoded values
|
||||
Text text_lbl_decoded{{0, 122, 240, 16}, "---Decoded Values---"};
|
||||
|
||||
Text text_lbl_p1{{0, 138, 48, 16}, "P1:"};
|
||||
Text text_p1{{50, 138, 190, 16}, "---"};
|
||||
|
||||
Text text_lbl_p2{{0, 154, 48, 16}, "P2:"};
|
||||
Text text_p2{{50, 154, 190, 16}, "---"};
|
||||
|
||||
Text text_lbl_p3{{0, 170, 48, 16}, "P3:"};
|
||||
Text text_p3{{50, 170, 190, 16}, "---"};
|
||||
|
||||
Text text_lbl_rdiv{{0, 186, 48, 16}, "R_DIV:"};
|
||||
Text text_rdiv{{50, 186, 190, 16}, "---"};
|
||||
|
||||
// Calculated frequency
|
||||
Text text_lbl_calc{{0, 206, 240, 16}, "---Calculated Output---"};
|
||||
|
||||
Text text_lbl_div{{0, 222, 80, 16}, "MS Div:"};
|
||||
Text text_div{{82, 222, 158, 16}, "---"};
|
||||
|
||||
Text text_lbl_freq{{0, 238, 80, 16}, "CLK0 Out:"};
|
||||
Text text_freq{{82, 238, 158, 16}, "---"};
|
||||
|
||||
// Status
|
||||
Text text_status{{0, 258, 240, 16}, ""};
|
||||
|
||||
Button button_refresh{{2, 280, 56, 24}, "Refresh"};
|
||||
Button button_reset{{62, 280, 56, 24}, "PLL Rst"};
|
||||
Button button_frac{{122, 280, 56, 24}, "Frac"};
|
||||
Button button_done{{182, 280, 56, 24}, "Done"};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
class PralineClockDebugView : public View {
|
||||
public:
|
||||
PralineClockDebugView(NavigationView& nav);
|
||||
void focus() override;
|
||||
std::string title() const override { return "Pro Clock Status"; };
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
|
||||
Text text_title{{0, 0, 240, 16}, "=== Pro Clock Dashboard ==="};
|
||||
|
||||
// System Status
|
||||
Text text_lbl_pll{{0, 20, 80, 16}, "PLL Lock:"};
|
||||
Text text_pll_status{{88, 20, 152, 16}, "---"};
|
||||
|
||||
Text text_lbl_afe{{0, 36, 80, 16}, "AFE Rate:"};
|
||||
Text text_afe_rate{{88, 36, 152, 16}, "---"};
|
||||
|
||||
Text text_lbl_n{{0, 52, 80, 16}, "Decim (n):"};
|
||||
Text text_n_val{{88, 52, 152, 16}, "-"};
|
||||
|
||||
// Table Header (Width 240px, 30 characters)
|
||||
Text text_header{{0, 72, 240, 16}, "ID mA Mode Src Phase Stat"};
|
||||
|
||||
// Helper structure to group row widgets for CLK0-CLK5
|
||||
struct ClockRow {
|
||||
Text* id;
|
||||
Text* ma;
|
||||
Text* mode;
|
||||
Text* src; // PLL A, or B
|
||||
Text* phase;
|
||||
Text* stat;
|
||||
};
|
||||
std::vector<ClockRow> rows;
|
||||
|
||||
// We define the actual widgets for 6 clocks
|
||||
// Note: Layout uses 16px vertical spacing per row
|
||||
|
||||
// Full list of clock row entries (C0 through C5)
|
||||
// Layout: Y starts at 88, increments by 16 per row.
|
||||
|
||||
// C0: MAX5864 (AFE/Sampling)
|
||||
Text t0_id{{0, 88, 24, 16}, "C0:"}; // Column 0
|
||||
Text t0_ma{{32, 88, 24, 16}, "-"}; // Column 4 (mA)
|
||||
Text t0_mode{{64, 88, 48, 16}, "-"}; // Column 8 (Mode)
|
||||
Text t0_src{{112, 88, 32, 16}, "-"}; // Column 14 (Src)
|
||||
Text t0_ph{{160, 88, 48, 16}, "-"}; // Column 20 (Phase)
|
||||
Text t0_st{{208, 88, 32, 16}, "-"}; // Column 26 (Stat)
|
||||
|
||||
// C1: iCE40 FPGA Timing
|
||||
Text t1_id{{0, 104, 24, 16}, "C1:"};
|
||||
Text t1_ma{{32, 104, 24, 16}, "-"};
|
||||
Text t1_mode{{64, 104, 48, 16}, "-"};
|
||||
Text t1_src{{112, 104, 32, 16}, "-"};
|
||||
Text t1_ph{{160, 104, 48, 16}, "-"};
|
||||
Text t1_st{{208, 104, 32, 16}, "-"};
|
||||
|
||||
// C2: LPC43xx MCU Input
|
||||
Text t2_id{{0, 120, 24, 16}, "C2:"};
|
||||
Text t2_ma{{32, 120, 24, 16}, "-"};
|
||||
Text t2_mode{{64, 120, 48, 16}, "-"};
|
||||
Text t2_src{{112, 120, 32, 16}, "-"};
|
||||
Text t2_ph{{160, 120, 48, 16}, "-"};
|
||||
Text t2_st{{208, 120, 32, 16}, "-"};
|
||||
|
||||
// C3: SG_CLK (Switching Regulator/Logic Sync)
|
||||
Text t3_id{{0, 136, 24, 16}, "C3:"};
|
||||
Text t3_ma{{32, 136, 24, 16}, "-"};
|
||||
Text t3_mode{{64, 136, 48, 16}, "-"};
|
||||
Text t3_src{{112, 136, 32, 16}, "-"};
|
||||
Text t3_ph{{160, 136, 48, 16}, "-"};
|
||||
Text t3_st{{208, 136, 32, 16}, "-"};
|
||||
|
||||
// C4: P_CLK (Peripheral/Expansion Clock)
|
||||
Text t4_id{{0, 152, 24, 16}, "C4:"};
|
||||
Text t4_ma{{32, 152, 24, 16}, "-"};
|
||||
Text t4_mode{{64, 152, 48, 16}, "-"};
|
||||
Text t4_src{{112, 152, 32, 16}, "-"};
|
||||
Text t4_ph{{160, 152, 48, 16}, "-"};
|
||||
Text t4_st{{208, 152, 32, 16}, "-"};
|
||||
|
||||
// C5: AUX_CLK (Auxiliary Reference)
|
||||
Text t5_id{{0, 168, 24, 16}, "C5:"};
|
||||
Text t5_ma{{32, 168, 24, 16}, "-"};
|
||||
Text t5_mode{{64, 168, 48, 16}, "-"};
|
||||
Text t5_src{{112, 168, 32, 16}, "-"};
|
||||
Text t5_ph{{160, 168, 48, 16}, "-"};
|
||||
Text t5_st{{208, 168, 32, 16}, "-"};
|
||||
|
||||
Button button_refresh{{8, 260, 100, 24}, "Refresh"};
|
||||
Button button_done{{132, 260, 100, 24}, "Done"};
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
class DebugPeripheralsMenuView : public BtnGridView {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_sd_card_debug.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
@@ -446,7 +447,11 @@ void SetUIView::focus() {
|
||||
/* SetSDCardView *********************************************/
|
||||
|
||||
SetSDCardView::SetSDCardView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
add_children({&status_labels,
|
||||
&text_card_status,
|
||||
&text_filesystem_type,
|
||||
&button_more_info,
|
||||
&labels,
|
||||
&checkbox_sdcard_speed,
|
||||
&button_test_sdcard_high_speed,
|
||||
&text_sdcard_test_status,
|
||||
@@ -455,6 +460,10 @@ SetSDCardView::SetSDCardView(NavigationView& nav) {
|
||||
|
||||
checkbox_sdcard_speed.set_value(pmem::config_sdcard_high_speed_io());
|
||||
|
||||
button_more_info.on_select = [&nav, this](Button&) {
|
||||
nav.push<SDCardDebugView>();
|
||||
};
|
||||
|
||||
button_test_sdcard_high_speed.on_select = [&nav, this](Button&) {
|
||||
pmem::set_config_sdcard_high_speed_io(true, false);
|
||||
text_sdcard_test_status.set("!! HIGH SPEED MODE ON !!");
|
||||
@@ -472,7 +481,79 @@ SetSDCardView::SetSDCardView(NavigationView& nav) {
|
||||
}
|
||||
|
||||
void SetSDCardView::focus() {
|
||||
button_save.focus();
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
void SetSDCardView::on_show() {
|
||||
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status) {
|
||||
update_sd_card_status();
|
||||
};
|
||||
|
||||
update_sd_card_status();
|
||||
}
|
||||
|
||||
void SetSDCardView::on_hide() {
|
||||
sd_card::status_signal -= sd_card_status_signal_token;
|
||||
}
|
||||
|
||||
void SetSDCardView::update_sd_card_status() {
|
||||
using sd_card::Status;
|
||||
|
||||
const auto status = sd_card::status();
|
||||
|
||||
// Update card status text
|
||||
switch (status) {
|
||||
case Status::NotPresent:
|
||||
text_card_status.set("Not Inserted");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::Present:
|
||||
text_card_status.set("Inserted");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::Mounted:
|
||||
text_card_status.set("Mounted");
|
||||
// Determine filesystem type
|
||||
{
|
||||
const auto fs_type = sd_card::fs.fs_type;
|
||||
std::string fs_name;
|
||||
switch (fs_type) {
|
||||
case FS_FAT12:
|
||||
fs_name = "FAT12";
|
||||
break;
|
||||
case FS_FAT16:
|
||||
fs_name = "FAT16";
|
||||
break;
|
||||
case FS_FAT32:
|
||||
fs_name = "FAT32";
|
||||
break;
|
||||
case FS_EXFAT:
|
||||
fs_name = "exFAT";
|
||||
break;
|
||||
default:
|
||||
fs_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
text_filesystem_type.set(fs_name);
|
||||
}
|
||||
break;
|
||||
case Status::ConnectError:
|
||||
text_card_status.set("Connect Error");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::MountError:
|
||||
text_card_status.set("Mount Error");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
case Status::IOError:
|
||||
text_card_status.set("I/O Error");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
default:
|
||||
text_card_status.set("Unknown");
|
||||
text_filesystem_type.set("---");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* SetConverterSettingsView ******************************/
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ff.h"
|
||||
#include "sd_card.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
@@ -424,36 +425,60 @@ class SetSDCardView : public View {
|
||||
public:
|
||||
SetSDCardView(NavigationView& nav);
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "SD Card"; };
|
||||
|
||||
private:
|
||||
SignalToken sd_card_status_signal_token{};
|
||||
// Status section (top half)
|
||||
Labels status_labels{
|
||||
{{10, 24}, "Card Status:", Theme::getInstance()->fg_light->foreground},
|
||||
{{10, 48}, "Filesystem:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_card_status{
|
||||
{120, 24, 110, 16},
|
||||
""};
|
||||
|
||||
Text text_filesystem_type{
|
||||
{120, 48, 110, 16},
|
||||
""};
|
||||
|
||||
Button button_more_info{
|
||||
{UI_POS_X_CENTER(20), 90, UI_POS_WIDTH(20), UI_POS_HEIGHT(2)},
|
||||
"More Info"};
|
||||
|
||||
// Settings section (bottom half)
|
||||
Labels labels{
|
||||
// 01234567890123456789012345678
|
||||
{{UI_POS_X_CENTER(26), 120 - 48}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(26), 120 - 32}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
|
||||
{{UI_POS_X_CENTER(26), 140}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X_CENTER(26), 156}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Checkbox checkbox_sdcard_speed{
|
||||
{UI_POS_X_CENTER(26), 120},
|
||||
{UI_POS_X_CENTER(26), 180},
|
||||
20,
|
||||
"enable high speed IO"};
|
||||
|
||||
Button button_test_sdcard_high_speed{
|
||||
{UI_POS_X_CENTER(27), 152, UI_POS_WIDTH(27), UI_POS_HEIGHT(2)},
|
||||
{UI_POS_X_CENTER(27), 210, UI_POS_WIDTH(27), UI_POS_HEIGHT(2)},
|
||||
"TEST BUTTON (NO PMEM SAVE)"};
|
||||
|
||||
Text text_sdcard_test_status{
|
||||
{UI_POS_X_CENTER(28), 198, UI_POS_WIDTH(28), UI_POS_HEIGHT(1)},
|
||||
{UI_POS_X_CENTER(28), 256, UI_POS_WIDTH(28), UI_POS_HEIGHT(1)},
|
||||
""};
|
||||
|
||||
Button button_save{
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
|
||||
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
|
||||
"Cancel"};
|
||||
|
||||
void update_sd_card_status();
|
||||
};
|
||||
|
||||
class SetConverterSettingsView : public View {
|
||||
|
||||
@@ -334,12 +334,22 @@ void set_rds_data(const uint16_t message_length) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_spectrum(const size_t sampling_rate, const size_t trigger) {
|
||||
void set_spectrum(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger) {
|
||||
const WidebandSpectrumConfigMessage message{
|
||||
sampling_rate, trigger};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_time_sink(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger) {
|
||||
const TimeSinkConfigMessage message{
|
||||
sampling_rate, trigger};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
|
||||
const WeFaxRxConfigureMessage message{lpm, ioc};
|
||||
send_message(&message);
|
||||
|
||||
@@ -97,7 +97,12 @@ void set_pocsag(int8_t baud_config = -1);
|
||||
void set_adsb();
|
||||
void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed);
|
||||
void set_rds_data(const uint16_t message_length);
|
||||
void set_spectrum(const size_t sampling_rate, const size_t trigger);
|
||||
void set_spectrum(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger);
|
||||
void set_time_sink(
|
||||
const size_t sampling_rate,
|
||||
const size_t trigger);
|
||||
void set_siggen_tone(const uint32_t tone);
|
||||
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||
|
||||
@@ -41,6 +41,10 @@ extern "C" {
|
||||
|
||||
constexpr uint32_t si5351_vco_f = 800000000;
|
||||
|
||||
#ifdef PRALINE
|
||||
constexpr uint32_t si5351_vco_afe_f = 800000000; // Optimal for 3.072 MHz sample frequencies commonly used by apps
|
||||
#endif
|
||||
|
||||
constexpr si5351::Inputs si5351_inputs{
|
||||
.f_xtal = si5351_xtal_f,
|
||||
.f_clkin = si5351_clkin_f,
|
||||
@@ -62,8 +66,24 @@ constexpr si5351::PLL si5351_pll_xtal_25m{
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
};
|
||||
|
||||
// PLL A registers (Base 26)
|
||||
constexpr auto si5351_pll_a_xtal_reg = si5351_pll_xtal_25m.reg(0);
|
||||
|
||||
#ifdef PRALINE
|
||||
// PLL A: 800 MHz VCO (32x Multiplier for jitter-free 3.072 MHz sampling)
|
||||
constexpr si5351::PLL si5351_pll_xtal_800m{
|
||||
.f_in = si5351_inputs.f_xtal,
|
||||
.a = 32,
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
};
|
||||
constexpr auto si5351_pll_a_800_reg = si5351_pll_xtal_800m.reg(0); // Base 26
|
||||
|
||||
// PLL B: registers (Base 34) 800 MHz VCO (32x Multiplier for stable Digital/SGPIO bus)
|
||||
constexpr auto si5351_pll_b_800_reg = si5351_pll_xtal_25m.reg(1); // Base 34
|
||||
constexpr auto si5351_pll_b_xtal_reg = si5351_pll_xtal_25m.reg(1);
|
||||
|
||||
static_assert(si5351_pll_xtal_25m.f_vco() == si5351_vco_f, "PLL XTAL frequency wrong");
|
||||
static_assert(si5351_pll_xtal_25m.p1() == 3584, "PLL XTAL P1 wrong");
|
||||
static_assert(si5351_pll_xtal_25m.p2() == 0, "PLL XTAL P2 wrong");
|
||||
@@ -99,16 +119,16 @@ constexpr si5351::MultisynthFractional si5351_ms_18m432 {
|
||||
.r_div = 1,
|
||||
};
|
||||
*/
|
||||
/*
|
||||
constexpr si5351::MultisynthFractional si5351_ms_0_20m {
|
||||
.f_src = si5351_vco_f,
|
||||
.a = 20,
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
.r_div = 1,
|
||||
// constexpr auto si5351_ms_0_20m_reg = si5351_ms_0_20m.reg(0);
|
||||
|
||||
constexpr si5351::MultisynthFractional si5351_ms_0_4m{
|
||||
.f_src = si5351_vco_f, // 800,000,000 Hz
|
||||
.a = 100, // Integer divider 100
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
.r_div = 1 // Final R-divider: 2^1 = 2
|
||||
};
|
||||
constexpr auto si5351_ms_0_20m_reg = si5351_ms_0_20m.reg(0);
|
||||
*/
|
||||
|
||||
constexpr si5351::MultisynthFractional si5351_ms_0_8m{
|
||||
.f_src = si5351_vco_f,
|
||||
.a = 50,
|
||||
@@ -116,6 +136,42 @@ constexpr si5351::MultisynthFractional si5351_ms_0_8m{
|
||||
.c = 1,
|
||||
.r_div = 1,
|
||||
};
|
||||
|
||||
#ifdef PRALINE
|
||||
// (initial 4 MHz from 800 MHz VCO: 800 / 200 = 4)
|
||||
constexpr si5351::MultisynthFractional si5351_ms_afe_4m{
|
||||
.f_src = si5351_vco_afe_f, // 800 MHz
|
||||
.a = 200,
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
.r_div = 0};
|
||||
|
||||
// (10 MHz from 800 MHz VCO: 800 / 80 = 10)
|
||||
constexpr si5351::MultisynthFractional si5351_ms_afe_10m{
|
||||
.f_src = si5351_vco_afe_f, // 800 MHz
|
||||
.a = 80,
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
.r_div = 0};
|
||||
// (40 MHz from 800 MHz VCO: 800 / 20 = 40)
|
||||
constexpr si5351::MultisynthFractional si5351_ms_afe_40m{
|
||||
.f_src = si5351_vco_afe_f, // 800 MHz
|
||||
.a = 20,
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
.r_div = 0};
|
||||
|
||||
// (20 MHz from 800 MHz VCO: 800 / 40 = 20)
|
||||
constexpr si5351::MultisynthFractional si5351_ms_20m{
|
||||
.f_src = si5351_vco_f,
|
||||
.a = 40,
|
||||
.b = 0,
|
||||
.c = 1,
|
||||
.r_div = 0,
|
||||
};
|
||||
// constexpr auto si5351_ms_20m_reg = si5351_ms_20m.reg(0);
|
||||
#endif
|
||||
|
||||
constexpr auto si5351c_ms_0_8m_reg = si5351_ms_0_8m.reg(clock_generator_output_og_codec);
|
||||
|
||||
#ifdef PRALINE
|
||||
@@ -238,21 +294,20 @@ constexpr ClockControls si5351c_clock_control_common{{
|
||||
|
||||
constexpr ClockControls si5351a_clock_control_common{{
|
||||
#ifdef PRALINE
|
||||
// CLK0: MAX5864 (ADC)
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK1: SCT_CLK - iCE40 FPGA timing clock
|
||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK2: LPC43xx MCU
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK3: CLKOUT (optional) SMA Port P1
|
||||
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK4: PRALINE MAX2831 reference (40 MHz) - INVERTED per hackrf_usb, 4mA, Integer mode
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK5: PRALINE RFFC5072 reference (40 MHz) - INVERTED, 6mA, Integer mode
|
||||
// This matches HackRF One OG configuration for RFFC5072
|
||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK6: SMA Port P2
|
||||
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
// CLK0: MAX5864 (ADC) - 4mA, Normal PLLA Integer (Standard for Praline sync)
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||
// CLK1: SCT_CLK (iCE40 FPGA) - 6mA, PLLA Normal Integer
|
||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||
// CLK2: LPC43xx MCU - 4mA, Normal PLLB (Must be Integer for MCU stability)
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||
// CLK3: CLKOUT SMA Port P1 - 8mA, Normal PLLB Integer
|
||||
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||
// CLK4: MAX2831 reference (40 MHz) - Inverted PLLA Integer (Required for mixer lock)
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||
// CLK5: RFFC5072 reference (40 MHz) - Inverted PLLA Integer (Required for mixer lock)
|
||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
|
||||
// CLK6: Not used (disabled) 2mA, Normal PLLB, Power_Off
|
||||
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
#else
|
||||
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_Off},
|
||||
@@ -265,7 +320,6 @@ constexpr ClockControls si5351a_clock_control_common{{
|
||||
// CLK6: Not used
|
||||
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
#endif
|
||||
|
||||
// CLK7: Not used
|
||||
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
|
||||
|
||||
@@ -398,58 +452,69 @@ void ClockManager::init_clock_generator() {
|
||||
#endif
|
||||
|
||||
#ifdef PRALINE
|
||||
/* PRALINE uses Si5351A with:
|
||||
* CLK0 = AFE_CLK (codec/FPGA sample clock)
|
||||
* CLK1 = SCT_CLK (FPGA timing clock at 2x sample rate)
|
||||
* CLK4 = second IF (MAX2831)
|
||||
* CLK5 = first IF (RFFC5072)
|
||||
* Uses PLLA on XTAL only (no CLKIN support).
|
||||
|
||||
/* * Praline HackRF Pro Clock Assignments (800 MHz VCO Configuration)
|
||||
* VCO Frequency: 800,000,000 Hz (Master Reference)
|
||||
* * CLK0: AFE_CLK (MAX5864 Codec & FPGA ADC Interface)
|
||||
* - Note: Defines hardware sample rate. Essential for WFM purity.
|
||||
* * CLK1: SCT_CLK (iCE40 FPGA System/Timing Clock)
|
||||
* - Note: Timing for SGPIO data bus; scales to 2x SR in wideband modes.
|
||||
* * CLK2: MCU_CLKIN (LPC43xx MCU External Clock Input)
|
||||
* - Note: Synchronizes MCU processing to the RF clock tree.
|
||||
* * CLK3: SG_CLK (Switching Regulator/Internal Logic Sync)
|
||||
* - Note: Used for internal FPGA logic/gateware synchronization.
|
||||
* * CLK4: P_CLK (Peripheral/Expansion Clock)
|
||||
* - Note: Routed to expansion headers for external hardware sync.
|
||||
* * CLK5: AUX_CLK (Auxiliary reference for secondary logic)
|
||||
* - Note: Provides additional timing flexibility for the iCE40 FPGA.
|
||||
* * CLK6/7: Unused / Power-Down
|
||||
* - State: Disabled (si5351a_ms6_7_off_reg)
|
||||
* - Note: Kept OFF to reduce EMI/RFI near the RF front-end.
|
||||
* * CLKOUT: Optional external clock output on the header.
|
||||
*/
|
||||
|
||||
/* Step 1: Write PLL A configuration (800 MHz VCO from 25 MHz XTAL) */
|
||||
/* Step 1: Write PLL A (800 MHz for RF) and PLL B (800 MHz for Digital) */
|
||||
/* Use single-byte writes to debug I2C issues */
|
||||
{
|
||||
const auto& pll_regs = si5351_pll_a_xtal_reg;
|
||||
const uint8_t base_reg = pll_regs[0];
|
||||
for (size_t i = 1; i < pll_regs.size(); i++) {
|
||||
clock_generator.write_register(base_reg + i - 1, pll_regs[i]);
|
||||
// Write PLLA (Registers 26-33)
|
||||
/* Write PLL A configuration (Base 26) */
|
||||
const auto& pll_a = si5351_pll_a_800_reg;
|
||||
for (size_t i = 1; i < pll_a.size(); i++) {
|
||||
clock_generator.write_register(pll_a[0] + i - 1, pll_a[i]);
|
||||
}
|
||||
|
||||
// Write PLLB (Registers 34-41)
|
||||
const auto& pll_b = si5351_pll_b_800_reg;
|
||||
for (size_t i = 1; i < pll_b.size(); i++) {
|
||||
clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 2: Write multisynth configurations using single-byte writes */
|
||||
clock_generator.write_ms_single_byte(0, si5351_ms_0_8m); // MS0 = divider 50, r_div=1 for 8 MHz
|
||||
clock_generator.write_ms_single_byte(1, si5351_ms_16m); // MS1 = divider 50, r_div=0 for 16 MHz
|
||||
|
||||
/* CLK4 and CLK5 - use single-byte writes too */
|
||||
{
|
||||
const auto& ms4_regs = si5351c_ms_4_reg;
|
||||
const uint8_t base_reg = ms4_regs[0];
|
||||
for (size_t i = 1; i < ms4_regs.size(); i++) {
|
||||
clock_generator.write_register(base_reg + i - 1, ms4_regs[i]);
|
||||
}
|
||||
}
|
||||
{
|
||||
const auto& ms5_regs = si5351c_ms_5_reg;
|
||||
const uint8_t base_reg = ms5_regs[0];
|
||||
for (size_t i = 1; i < ms5_regs.size(); i++) {
|
||||
clock_generator.write_register(base_reg + i - 1, ms5_regs[i]);
|
||||
}
|
||||
}
|
||||
clock_generator.write(si5351a_ms6_7_off_reg); // MS6/7 off - short write is OK
|
||||
// These cover all active channels on the Praline board
|
||||
clock_generator.write_ms_single_byte(0, si5351_ms_afe_40m); // CLK0: PLL A Codec (40 MHz)
|
||||
clock_generator.write_ms_single_byte(1, si5351_ms_afe_40m); // CLK1: PLL A FPGA Timing (40 MHz)
|
||||
clock_generator.write_ms_single_byte(2, si5351_ms_40m); // CLK2: PLL B MCU Input (40 MHz)
|
||||
clock_generator.write_ms_single_byte(3, si5351_ms_10m); // CLK3: PLL B Logic Sync (10 MHz)
|
||||
clock_generator.write_ms_single_byte(4, si5351_ms_afe_40m); // CLK4: PLL A Second IF (40 MHz)
|
||||
clock_generator.write_ms_single_byte(5, si5351_ms_afe_40m); // CLK5: PLL A First IF (40 MHz)
|
||||
|
||||
/* Step 3: NOW set clock control registers (AFTER multisynths per HackRF reference) */
|
||||
const auto ref_pll = ClockControl::MultiSynthSource::PLLA;
|
||||
const auto ref_pll_a = ClockControl::MultiSynthSource::PLLA;
|
||||
const auto ref_pll_b = ClockControl::MultiSynthSource::PLLB;
|
||||
const ClockControls si5351_clock_control = ClockControls{{
|
||||
si5351a_clock_control_common[0].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[1].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[2].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[3].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[4].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[5].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[6].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[7].ms_src(ref_pll),
|
||||
si5351a_clock_control_common[0].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[1].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[2].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[3].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[4].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[5].ms_src(ref_pll_a),
|
||||
si5351a_clock_control_common[6].ms_src(ref_pll_b),
|
||||
si5351a_clock_control_common[7].ms_src(ref_pll_b),
|
||||
}};
|
||||
clock_generator.set_clock_control(si5351_clock_control);
|
||||
// clock_generator.set_clock_control(si5351_clock_control);
|
||||
// Use single-byte writes instead of multi-byte
|
||||
clock_generator.set_clock_control_single_byte(si5351_clock_control);
|
||||
#else
|
||||
if (hackrf_r9) {
|
||||
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
|
||||
@@ -477,8 +542,8 @@ void ClockManager::init_clock_generator() {
|
||||
|
||||
// Wait for PLL(s) to lock.
|
||||
#ifdef PRALINE
|
||||
// PRALINE: Wait for PLLA to lock (0x20 = LOL_A bit)
|
||||
uint8_t device_status_mask = 0x20;
|
||||
// PRALINE: Wait for 0x60 (0x20 | 0x40), PLLA and PLLB to lock (0x20 = LOL_A bit, 0x40 = LOL_B bit)
|
||||
uint8_t device_status_mask = 0x60;
|
||||
uint32_t pll_timeout = 100000;
|
||||
while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) {
|
||||
pll_timeout--;
|
||||
@@ -487,23 +552,6 @@ void ClockManager::init_clock_generator() {
|
||||
static volatile uint32_t pll_lock_timeout = pll_timeout;
|
||||
(void)pll_lock_timeout;
|
||||
|
||||
// CRITICAL: Add delay to ensure Si5351 writes complete before I2C bus stops
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
// ===== ADD THIS SAFETY BLOCK =====
|
||||
// Pre-configure CLK4/CLK5 with correct settings including inversion.
|
||||
// This ensures the registers have correct values even if enable_if_clocks()
|
||||
// is not called or fails. The clocks will still be powered off until
|
||||
// enable_if_clocks() enables the outputs.
|
||||
|
||||
// CLK5: OFF (for now), Integer, PLLA, INVERTED, MS_Self, 6mA = 0xDE
|
||||
// (Same as 0x5E but with bit 7 set for power off)
|
||||
clock_generator.write_register(21, 0xDE);
|
||||
|
||||
// CLK4: OFF (for now), Integer, PLLA, INVERTED, MS_Self, 4mA = 0xDD
|
||||
clock_generator.write_register(20, 0xDD);
|
||||
// ===== END SAFETY BLOCK =====
|
||||
|
||||
#else
|
||||
// Wait for PLL(s) to lock - with timeout to prevent hang
|
||||
uint8_t device_status_mask = hackrf_r9
|
||||
@@ -642,27 +690,6 @@ void ClockManager::disable_codec_clocks() {
|
||||
|
||||
void ClockManager::enable_if_clocks() {
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: CLK4=MAX2831, CLK5=RFFC5072
|
||||
*
|
||||
* Force-write complete configuration to guarantee correct setup.
|
||||
* Added force-write to verify correct registers are applying necessary register clock settings.
|
||||
*/
|
||||
|
||||
// Configure and enable CLK4 (MAX2831)
|
||||
// Register 20: ON, Integer, PLLA, INVERTED, MS_Self, 4mA = 0x5D
|
||||
clock_generator.write_register(20, 0x5D);
|
||||
|
||||
// Configure and enable CLK5 (RFFC5072) - CRITICAL!
|
||||
// Register 21: ON, Integer, PLLA, INVERTED, MS_Self, 6mA = 0x5E
|
||||
clock_generator.write_register(21, 0x5E);
|
||||
|
||||
// Enable outputs (register 3, bits 4 and 5 = 0)
|
||||
uint8_t reg3 = clock_generator.read_register(3);
|
||||
reg3 &= ~0x30;
|
||||
clock_generator.write_register(3, reg3);
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
/* PRALINE uses CLK5 (first IF) and CLK4 (second IF) */
|
||||
clock_generator.enable_clock(clock_generator_output_og_first_if);
|
||||
clock_generator.enable_output_mask(1U << clock_generator_output_og_first_if);
|
||||
@@ -702,23 +729,43 @@ void ClockManager::disable_if_clocks() {
|
||||
|
||||
void ClockManager::set_sampling_frequency(const uint32_t frequency) {
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: CLK0=AFE_CLK runs at sample rate (VCO/divider/2)
|
||||
* CLK1=SCT_CLK runs at 2x sample rate (VCO/divider/1)
|
||||
* Reference: hackrf_core.c sample_rate_frac_set() lines 580-582
|
||||
/*
|
||||
* PRALINE sample rate strategy:
|
||||
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
|
||||
* 2. Use FPGA decimation to achieve desired output rate
|
||||
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
|
||||
*/
|
||||
|
||||
/* PRALINE: Match HackRF USB sample_rate_frac_set()
|
||||
* Reference: hackrf_usb radio.c lines 29-91, hackrf_core.c lines 501-685 */
|
||||
constexpr uint32_t MAX_AFE_RATE = 40000000; // Use 40 MHz per GSG reference
|
||||
constexpr uint8_t MAX_N = 5;
|
||||
|
||||
_base_band_frequency = frequency;
|
||||
|
||||
// Set FPGA decimation to 0 (no decimation) for direct passthrough
|
||||
fpga_debug_register_write(2, 0);
|
||||
uint8_t n = 0;
|
||||
uint32_t afe_rate = frequency;
|
||||
|
||||
// Find the largest n where AFE rate stays within limit
|
||||
// Start at n=0 and work up
|
||||
while (n < MAX_N) {
|
||||
uint32_t next_rate = afe_rate << 1;
|
||||
if (next_rate > MAX_AFE_RATE) break;
|
||||
afe_rate = next_rate;
|
||||
n++;
|
||||
}
|
||||
|
||||
_resampling_n = n;
|
||||
|
||||
// Set FPGA RX decimation register
|
||||
fpga_debug_register_write(2, n);
|
||||
radio::invalidate_spi_config();
|
||||
|
||||
// The following was originally from @kitty. Adopting for testing radio.
|
||||
clock_generator.set_ms_frequency(0, frequency * 2, si5351_vco_f, 1); // CLK0: r_div=1 (÷2)
|
||||
clock_generator.set_ms_frequency(1, frequency * 2, si5351_vco_f, 0); // CLK1: r_div=0 (÷1)
|
||||
// Configure Si5351 clocks
|
||||
// CLK0: AFE_CLK (with r_div=1 for ÷2)
|
||||
// CLK1: SCT_CLK (with r_div=0 for ÷1, runs at 2× AFE for FPGA timing)
|
||||
// Configure Si5351 clocks using the correct AFE VCO
|
||||
clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1);
|
||||
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
|
||||
|
||||
#else
|
||||
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
|
||||
* twice the frequency, and derived from the MS0 synth. So it's only
|
||||
@@ -742,7 +789,18 @@ void ClockManager::set_reference_ppb(const int32_t ppb) {
|
||||
if (hackrf_r9 && reference.source != ReferenceSource::Xtal) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
// On Praline, only apply if we aren't locked to a superior external 10MHz source
|
||||
// (Assuming you have a way to detect the 10MHz presence on Praline)
|
||||
if (reference.source == ReferenceSource::External) {
|
||||
return;
|
||||
}
|
||||
constexpr uint32_t pll_multiplier = si5351_pll_xtal_800m.a;
|
||||
#else
|
||||
constexpr uint32_t pll_multiplier = si5351_pll_xtal_25m.a;
|
||||
#endif
|
||||
|
||||
constexpr uint32_t denominator = 1000000 / pll_multiplier;
|
||||
const uint32_t new_a = (ppb >= 0) ? pll_multiplier : (pll_multiplier - 1);
|
||||
const uint32_t new_b = (ppb >= 0) ? (ppb / 1000) : (denominator + (ppb / 1000));
|
||||
@@ -754,8 +812,14 @@ void ClockManager::set_reference_ppb(const int32_t ppb) {
|
||||
.b = new_b,
|
||||
.c = new_c,
|
||||
};
|
||||
|
||||
#ifdef PRALINE
|
||||
clock_generator.write_pll_single_byte(0, pll);
|
||||
clock_generator.reset_plls();
|
||||
#else
|
||||
const auto pll_a_reg = pll.reg(0);
|
||||
clock_generator.write(pll_a_reg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClockManager::start_frequency_monitor_measurement(const cgu::CLK_SEL clk_sel) {
|
||||
@@ -791,33 +855,40 @@ uint32_t ClockManager::get_frequency_monitor_measurement_in_hertz() {
|
||||
|
||||
void ClockManager::start_audio_pll() {
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: Use 12MHz XTAL for audio PLL
|
||||
* For 12MHz XTAL input, 48kHz audio rate, 256Fs MCLK:
|
||||
* Fout=12.288MHz, Fcco=491.52MHz
|
||||
* 12MHz * 1024 / 25 = 491.52MHz
|
||||
* MSEL=1024, NSEL=25, PSEL=20
|
||||
*/
|
||||
// Comprehensive init matches non-PRALINE for stability and source selection
|
||||
cgu::pll0audio::ctrl({
|
||||
.pd = 1,
|
||||
.bypass = 0,
|
||||
.directi = 0,
|
||||
.directo = 0,
|
||||
.clken = 0,
|
||||
.frm = 0,
|
||||
.autoblock = 1,
|
||||
.pllfract_req = 0,
|
||||
.sel_ext = 1,
|
||||
.mod_pd = 1,
|
||||
.clk_sel = cgu::CLK_SEL::XTAL,
|
||||
.pd = 1, // Start powered down
|
||||
.bypass = 0, // Use the PLL
|
||||
.directi = 0, // Enable N-divider
|
||||
.directo = 0, // Enable P-divider
|
||||
.clken = 0, // Disable output initially
|
||||
.frm = 0, // Normal mode
|
||||
.autoblock = 1, // Glitchless switching
|
||||
.pllfract_req = 0, // Integer mode
|
||||
.sel_ext = 1, // MUST BE 1 to use clk_sel GP_CLKIN
|
||||
.mod_pd = 1, // Power down modulator (Reduces noise/hiss)
|
||||
.clk_sel = cgu::CLK_SEL::GP_CLKIN,
|
||||
});
|
||||
|
||||
cgu::pll0audio::mdiv({
|
||||
.mdec = 22625UL, // MDEC for MSEL=1024
|
||||
});
|
||||
cgu::pll0audio::np_div({
|
||||
.pdec = 31, // PSEL=20
|
||||
.ndec = 69, // NDEC for NSEL=25
|
||||
});
|
||||
cgu::pll0audio::mdiv({.mdec = 30542UL});
|
||||
cgu::pll0audio::np_div({.pdec = 31, .ndec = 45});
|
||||
cgu::pll0audio::frac({.pllfract_ctrl = 0});
|
||||
|
||||
cgu::pll0audio::power_up();
|
||||
|
||||
// Safety timeout prevents boot hang if Si5351 clock is missing
|
||||
{
|
||||
uint32_t timeout = 100000;
|
||||
while (!cgu::pll0audio::is_locked() && timeout > 0) {
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
|
||||
cgu::pll0audio::clock_enable();
|
||||
set_base_audio_clock_divider(1);
|
||||
|
||||
LPC_CGU->BASE_AUDIO_CLK.AUTOBLOCK = 1;
|
||||
LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IDIVD);
|
||||
#else
|
||||
cgu::pll0audio::ctrl({
|
||||
.pd = 1,
|
||||
@@ -847,30 +918,21 @@ void ClockManager::start_audio_pll() {
|
||||
.pdec = 31,
|
||||
.ndec = 45,
|
||||
});
|
||||
#endif
|
||||
|
||||
cgu::pll0audio::frac({
|
||||
.pllfract_ctrl = 0,
|
||||
});
|
||||
|
||||
cgu::pll0audio::power_up();
|
||||
#ifndef PRALINE
|
||||
while (!cgu::pll0audio::is_locked());
|
||||
#else
|
||||
// PRALINE FIX: Add timeout to prevent infinite hang if GP_CLKIN not present
|
||||
{
|
||||
uint32_t timeout = 100000;
|
||||
while (!cgu::pll0audio::is_locked() && timeout > 0) {
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
cgu::pll0audio::clock_enable();
|
||||
|
||||
while (!cgu::pll0audio::is_locked());
|
||||
|
||||
cgu::pll0audio::clock_enable();
|
||||
set_base_audio_clock_divider(1);
|
||||
|
||||
LPC_CGU->BASE_AUDIO_CLK.AUTOBLOCK = 1;
|
||||
LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IDIVD);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClockManager::set_base_audio_clock_divider(const size_t divisor) {
|
||||
@@ -879,9 +941,24 @@ void ClockManager::set_base_audio_clock_divider(const size_t divisor) {
|
||||
}
|
||||
|
||||
void ClockManager::stop_audio_pll() {
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: Gracefully switch audio peripherals away from the PLL branch */
|
||||
LPC_CGU->BASE_AUDIO_CLK.PD = 1; // Power down the branch first
|
||||
LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IRC); // Reset to safe IRC source
|
||||
#endif
|
||||
|
||||
cgu::pll0audio::clock_disable();
|
||||
cgu::pll0audio::power_down();
|
||||
|
||||
#ifdef PRALINE
|
||||
/* PRALINE: Add a safety timeout to the unlock check to prevent potential hangs */
|
||||
uint32_t timeout = 100000;
|
||||
while (cgu::pll0audio::is_locked() && timeout > 0) {
|
||||
timeout--;
|
||||
}
|
||||
#else
|
||||
while (cgu::pll0audio::is_locked());
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClockManager::enable_clock_output(bool enable) {
|
||||
|
||||
@@ -77,6 +77,8 @@ class ClockManager {
|
||||
void set_reference_ppb(const int32_t ppb);
|
||||
|
||||
#ifdef PRALINE
|
||||
uint8_t get_resampling_n() const { return _resampling_n; }
|
||||
|
||||
// Si5351 diagnostic methods
|
||||
uint8_t si5351_read_status() { return clock_generator.device_status(); }
|
||||
uint8_t si5351_read_register(uint8_t reg) { return clock_generator.read_register(reg); }
|
||||
@@ -112,6 +114,10 @@ class ClockManager {
|
||||
ReferenceSource detect_reference_source();
|
||||
Reference choose_reference();
|
||||
bool loss_of_signal();
|
||||
|
||||
#ifdef PRALINE
|
||||
uint8_t _resampling_n{0}; // Current decimation factor (log2)
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /*__CLOCK_MANAGER_H__*/
|
||||
|
||||
+11
-1
@@ -103,6 +103,10 @@ set(EXTCPPSRC
|
||||
external/sstvtx/main.cpp
|
||||
external/sstvtx/ui_sstvtx.cpp
|
||||
|
||||
#same_tx
|
||||
external/same_tx/main.cpp
|
||||
external/same_tx/ui_same_tx.cpp
|
||||
|
||||
#sstvrx
|
||||
external/sstvrx/main.cpp
|
||||
external/sstvrx/ui_sstvrx.cpp
|
||||
@@ -312,6 +316,10 @@ set(EXTCPPSRC
|
||||
#pocsag_tx
|
||||
external/pocsag_tx/main.cpp
|
||||
external/pocsag_tx/ui_pocsag_tx.cpp
|
||||
|
||||
#time_sink
|
||||
external/time_sink/main.cpp
|
||||
external/time_sink/ui_time_sink.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -339,6 +347,7 @@ set(EXTAPPLIST
|
||||
adsbtx
|
||||
#morse_tx
|
||||
sstvtx
|
||||
same_tx
|
||||
sstvrx
|
||||
random_password
|
||||
acars_rx
|
||||
@@ -386,10 +395,11 @@ set(EXTAPPLIST
|
||||
siggen
|
||||
morse_radio
|
||||
morseradiotx
|
||||
keeloqtx
|
||||
keeloqtx
|
||||
rtty_rx
|
||||
rtty_tx
|
||||
pocsag_tx
|
||||
time_sink
|
||||
)
|
||||
|
||||
# sdusb has type conflicts with PRALINE (HackRF Pro) - add only for non-PRALINE builds
|
||||
|
||||
+15
@@ -99,6 +99,9 @@ MEMORY
|
||||
ram_external_app_rtty_tx (rwx) : org = 0xADFA0000, len = 32k
|
||||
ram_external_app_tpmstx (rwx) : org = 0xADFB0000, len = 32k
|
||||
ram_external_app_pocsag_tx (rwx) : org = 0xADFC0000, len = 32k
|
||||
ram_external_app_time_sink (rwx) : org = 0xADFD0000, len = 32k
|
||||
ram_external_app_same_tx (rwx) : org = 0xADFE0000, len = 32k
|
||||
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -559,5 +562,17 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_pocsag_tx.application_information));
|
||||
*(*ui*external_app*pocsag_tx*);
|
||||
} > ram_external_app_pocsag_tx
|
||||
|
||||
.external_app_time_sink : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_time_sink.application_information));
|
||||
*(*ui*external_app*time_sink*);
|
||||
} > ram_external_app_time_sink
|
||||
|
||||
.external_app_same_tx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_same_tx.application_information));
|
||||
*(*ui*external_app*same_tx*);
|
||||
} > ram_external_app_same_tx
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -165,7 +165,7 @@ FmRadioView::FmRadioView(NavigationView& nav)
|
||||
&gr});
|
||||
|
||||
txt_save_help.set_focusable(false);
|
||||
txt_save_help.visible(false);
|
||||
txt_save_help.hidden(true);
|
||||
for (uint8_t i = 0; i < 12; ++i) {
|
||||
if (freq_fav_list[i].frequency == 0) {
|
||||
freq_fav_list[i].frequency = 87000000;
|
||||
@@ -195,7 +195,7 @@ FmRadioView::FmRadioView(NavigationView& nav)
|
||||
btn_fav_save.on_select = [this](Button&) {
|
||||
save_fav = !save_fav;
|
||||
txt_save_help.set_text(save_fav ? "Select slot" : "");
|
||||
txt_save_help.visible(save_fav);
|
||||
txt_save_help.hidden(!save_fav);
|
||||
txt_save_help.set_dirty();
|
||||
};
|
||||
|
||||
@@ -229,9 +229,9 @@ void FmRadioView::on_btn_clicked(uint8_t i) {
|
||||
freq_fav_list[i].modulation = field_modulation.selected_index_value();
|
||||
freq_fav_list[i].bandwidth = radio_bw;
|
||||
update_fav_btn_texts();
|
||||
txt_save_help.visible(save_fav);
|
||||
txt_save_help.hidden(true);
|
||||
txt_save_help.set_text("");
|
||||
txt_save_help.set_dirty();
|
||||
set_dirty();
|
||||
return;
|
||||
}
|
||||
field_frequency.set_value(freq_fav_list[i].frequency);
|
||||
@@ -266,7 +266,7 @@ FmRadioView::~FmRadioView() {
|
||||
}
|
||||
|
||||
void FmRadioView::on_audio_spectrum() {
|
||||
if (gr.visible() && audio_spectrum_data) gr.update_audio_spectrum(*audio_spectrum_data);
|
||||
if (gr.drawn() && audio_spectrum_data) gr.update_audio_spectrum(*audio_spectrum_data);
|
||||
if (audio_spectrum_data && audio_spectrum_data->db.size() <= 128) {
|
||||
for (size_t i = 0; i < audio_spectrum_data->db.size(); ++i) {
|
||||
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* SAME TX - Specific Area Message Encoding Transmitter
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_same_tx.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::same_tx {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<SameTxView>();
|
||||
}
|
||||
} // namespace ui::external_app::same_tx
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_same_tx.application_information"), used)) application_information_t _application_information_same_tx = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::same_tx::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "SAME TX",
|
||||
/*.bitmap_data = */ {
|
||||
// 16x16 icon - simple "!" alert symbol
|
||||
0x00,
|
||||
0x00,
|
||||
0x18,
|
||||
0x00,
|
||||
0x3C,
|
||||
0x00,
|
||||
0x3C,
|
||||
0x00,
|
||||
0x3C,
|
||||
0x00,
|
||||
0x3C,
|
||||
0x00,
|
||||
0x18,
|
||||
0x00,
|
||||
0x18,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x18,
|
||||
0x00,
|
||||
0x3C,
|
||||
0x00,
|
||||
0x3C,
|
||||
0x00,
|
||||
0x18,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#include "ui_same_tx.hpp"
|
||||
#include "theme.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::same_tx {
|
||||
|
||||
static constexpr uint32_t SAME_SAMPLES_PER_BIT = 2949;
|
||||
static constexpr uint32_t SAME_MARK_FREQ = 2083;
|
||||
static constexpr uint32_t SAME_SPACE_FREQ = 1563;
|
||||
static constexpr uint32_t SAME_FM_DEVIATION = 5000;
|
||||
static constexpr uint8_t SAME_REPEAT = 3;
|
||||
|
||||
static constexpr char SAME_ORG_CODES[][4] = {"WXR", "EAS", "CIV", "PEP"};
|
||||
static constexpr char SAME_EVT_CODES[][4] = {
|
||||
"RWT",
|
||||
"RMT",
|
||||
"NPT",
|
||||
"NST",
|
||||
"NMT",
|
||||
"EAN",
|
||||
"EAT",
|
||||
"NIC",
|
||||
"ADR",
|
||||
"AVA",
|
||||
"AVW",
|
||||
"BZW",
|
||||
"CFW",
|
||||
"CFS",
|
||||
"DSW",
|
||||
"EQW",
|
||||
"EVI",
|
||||
"FFW",
|
||||
"FFS",
|
||||
"FFH",
|
||||
"FRW",
|
||||
"HLS",
|
||||
"HUW",
|
||||
"HUH",
|
||||
"SVR",
|
||||
"TOR",
|
||||
};
|
||||
|
||||
static uint8_t bitrev8(uint8_t b) {
|
||||
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
||||
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
||||
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
||||
return b;
|
||||
}
|
||||
|
||||
static void fmt2d(char* buf, int v) {
|
||||
buf[0] = '0' + v / 10;
|
||||
buf[1] = '0' + v % 10;
|
||||
}
|
||||
static void fmt3d(char* buf, int v) {
|
||||
buf[0] = '0' + v / 100;
|
||||
buf[1] = '0' + (v / 10) % 10;
|
||||
buf[2] = '0' + v % 10;
|
||||
}
|
||||
|
||||
void SameTxView::update_msg_preview() {
|
||||
const char* org = SAME_ORG_CODES[field_org.selected_index()];
|
||||
const char* evt = SAME_EVT_CODES[field_event_idx.value()];
|
||||
int ss = field_fips_state.value();
|
||||
int ccc = field_fips_county.value();
|
||||
int dh = field_dur_h.value();
|
||||
int dm = field_dur_m.value();
|
||||
char buf[48];
|
||||
char* p = buf;
|
||||
// "WXR-RWT-SS0CCC+HHmm" (preview, no ZCZC header)
|
||||
for (const char* s = org; *s; s++) *p++ = *s;
|
||||
*p++ = '-';
|
||||
for (const char* s = evt; *s; s++) *p++ = *s;
|
||||
*p++ = '-';
|
||||
fmt2d(p, ss);
|
||||
p += 2;
|
||||
*p++ = '0';
|
||||
fmt3d(p, ccc);
|
||||
p += 3;
|
||||
*p++ = '+';
|
||||
fmt2d(p, dh);
|
||||
p += 2;
|
||||
fmt2d(p, dm);
|
||||
p += 2;
|
||||
*p = '\0';
|
||||
text_msg.set(buf);
|
||||
}
|
||||
|
||||
void SameTxView::start_tx() {
|
||||
const char* org = SAME_ORG_CODES[field_org.selected_index()];
|
||||
const char* evt = SAME_EVT_CODES[field_event_idx.value()];
|
||||
int ss = field_fips_state.value();
|
||||
int ccc = field_fips_county.value();
|
||||
int dh = field_dur_h.value();
|
||||
int dm = field_dur_m.value();
|
||||
|
||||
// Build full SAME message: "ZCZC-ORG-EVT-SS0CCC+HHMM-0010000-STATION-"
|
||||
char msg[64];
|
||||
char* p = msg;
|
||||
const char* hdr = "ZCZC-";
|
||||
for (const char* s = hdr; *s; s++) *p++ = *s;
|
||||
for (const char* s = org; *s; s++) *p++ = *s;
|
||||
*p++ = '-';
|
||||
for (const char* s = evt; *s; s++) *p++ = *s;
|
||||
*p++ = '-';
|
||||
fmt2d(p, ss);
|
||||
p += 2;
|
||||
*p++ = '0';
|
||||
fmt3d(p, ccc);
|
||||
p += 3;
|
||||
*p++ = '+';
|
||||
fmt2d(p, dh);
|
||||
p += 2;
|
||||
fmt2d(p, dm);
|
||||
p += 2;
|
||||
*p++ = '-';
|
||||
// Issue time: fixed 0010000 (placeholder), station: SAME-TX
|
||||
const char* tail = "0010000-SAMETX--";
|
||||
for (const char* s = tail; *s; s++) *p++ = *s;
|
||||
*p = '\0';
|
||||
|
||||
auto* words = reinterpret_cast<uint16_t*>(shared_memory.bb_data.data);
|
||||
size_t idx = 0;
|
||||
for (uint8_t i = 0; i < 16; i++)
|
||||
words[idx++] = bitrev8(0xAB);
|
||||
for (const char* c = msg; *c; c++)
|
||||
words[idx++] = bitrev8(static_cast<uint8_t>(*c));
|
||||
words[idx] = 0;
|
||||
|
||||
tx_active_ = true;
|
||||
text_status.set("Transmitting...");
|
||||
transmitter_model.enable();
|
||||
baseband::set_afsk_data(
|
||||
SAME_SAMPLES_PER_BIT,
|
||||
SAME_MARK_FREQ,
|
||||
SAME_SPACE_FREQ,
|
||||
SAME_REPEAT,
|
||||
SAME_FM_DEVIATION,
|
||||
8);
|
||||
}
|
||||
|
||||
void SameTxView::stop_tx() {
|
||||
baseband::kill_afsk();
|
||||
transmitter_model.disable();
|
||||
tx_active_ = false;
|
||||
text_status.set("Ready");
|
||||
tx_view.set_transmitting(false);
|
||||
}
|
||||
|
||||
void SameTxView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
if (done) {
|
||||
stop_tx();
|
||||
text_status.set("Done!");
|
||||
} else {
|
||||
(void)progress;
|
||||
text_status.set("TX...");
|
||||
}
|
||||
}
|
||||
|
||||
void SameTxView::focus() {
|
||||
field_org.focus();
|
||||
}
|
||||
|
||||
SameTxView::SameTxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_afsk);
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_org,
|
||||
&field_event_idx,
|
||||
&text_evt,
|
||||
&field_fips_state,
|
||||
&field_fips_county,
|
||||
&field_dur_h,
|
||||
&field_dur_m,
|
||||
&text_msg,
|
||||
&text_status,
|
||||
&tx_view,
|
||||
});
|
||||
|
||||
// Defaults: Ohio, county 007 (Ashtabula), 30 min
|
||||
field_fips_state.set_value(39);
|
||||
field_fips_county.set_value(7);
|
||||
field_dur_h.set_value(0);
|
||||
field_dur_m.set_value(30);
|
||||
|
||||
auto refresh = [this](int32_t) {
|
||||
text_evt.set(SAME_EVT_CODES[field_event_idx.value()]);
|
||||
update_msg_preview();
|
||||
};
|
||||
field_event_idx.on_change = refresh;
|
||||
field_org.on_change = [this](size_t, int32_t) { update_msg_preview(); };
|
||||
field_fips_state.on_change = [this](int32_t) { update_msg_preview(); };
|
||||
field_fips_county.on_change = [this](int32_t) { update_msg_preview(); };
|
||||
field_dur_h.on_change = [this](int32_t) { update_msg_preview(); };
|
||||
field_dur_m.on_change = [this](int32_t) { update_msg_preview(); };
|
||||
|
||||
update_msg_preview();
|
||||
|
||||
tx_view.on_start = [this]() {
|
||||
if (!tx_active_) {
|
||||
start_tx();
|
||||
tx_view.set_transmitting(true);
|
||||
}
|
||||
};
|
||||
tx_view.on_stop = [this]() { stop_tx(); };
|
||||
}
|
||||
|
||||
SameTxView::~SameTxView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::same_tx
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifndef __UI_SAME_TX_H__
|
||||
#define __UI_SAME_TX_H__
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "message.hpp"
|
||||
#include "modems.hpp"
|
||||
|
||||
namespace ui::external_app::same_tx {
|
||||
|
||||
class SameTxView : public View {
|
||||
public:
|
||||
SameTxView(NavigationView& nav);
|
||||
~SameTxView();
|
||||
SameTxView(const SameTxView&) = delete;
|
||||
SameTxView& operator=(const SameTxView&) = delete;
|
||||
void focus() override;
|
||||
std::string title() const override { return "SAME TX"; }
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
bool tx_active_{false};
|
||||
void start_tx();
|
||||
void stop_tx();
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
void update_msg_preview();
|
||||
|
||||
TxRadioState radio_state_{162550000, 1750000, AFSK_TX_SAMPLERATE};
|
||||
app_settings::SettingsManager settings_{"tx_same", app_settings::Mode::TX};
|
||||
|
||||
Labels labels{{
|
||||
{{0 * 8, 0 * 16}, "Org:", ui::Theme::getInstance()->fg_light->foreground},
|
||||
{{8 * 8, 0 * 16}, "Evt:", ui::Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 1 * 16}, "FIPS:", ui::Theme::getInstance()->fg_light->foreground},
|
||||
{{16 * 8, 1 * 16}, "Dur:", ui::Theme::getInstance()->fg_light->foreground},
|
||||
}};
|
||||
OptionsField field_org{
|
||||
{4 * 8, 0 * 16},
|
||||
3,
|
||||
{{"WXR", 0}, {"EAS", 1}, {"CIV", 2}, {"PEP", 3}}};
|
||||
NumberField field_event_idx{{12 * 8, 0 * 16}, 2, {0, 25}, 1, '0', true};
|
||||
Text text_evt{{15 * 8, 0 * 16, 3 * 8, 16}, "RWT"};
|
||||
NumberField field_fips_state{{5 * 8, 1 * 16}, 2, {0, 99}, 1, '0'};
|
||||
NumberField field_fips_county{{7 * 8 + 4, 1 * 16}, 3, {0, 999}, 1, '0'};
|
||||
NumberField field_dur_h{{20 * 8, 1 * 16}, 2, {0, 99}, 1, '0'};
|
||||
NumberField field_dur_m{{22 * 8, 1 * 16}, 2, {0, 59}, 1, '0'};
|
||||
Text text_msg{{0, 2 * 16, 240, 16}, ""};
|
||||
Text text_status{{0, 3 * 16, 240, 16}, "Ready"};
|
||||
TransmitterView tx_view{(int16_t)UI_POS_Y_BOTTOM(4), 1000, 0};
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto msg = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_tx_progress(msg.progress, msg.done);
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::same_tx
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_time_sink.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::time_sink {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<TimeSinkView>();
|
||||
}
|
||||
} // namespace ui::external_app::time_sink
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_time_sink.application_information"), used)) application_information_t _application_information_time_sink = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::time_sink::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Time Sink",
|
||||
/*.bitmap_data = */ {
|
||||
0x18,
|
||||
0x00,
|
||||
0x24,
|
||||
0x00,
|
||||
0x42,
|
||||
0x00,
|
||||
0x42,
|
||||
0x00,
|
||||
0x42,
|
||||
0x00,
|
||||
0x42,
|
||||
0x00,
|
||||
0x81,
|
||||
0x00,
|
||||
0xAB,
|
||||
0x6A,
|
||||
0x80,
|
||||
0x40,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'T', 'S', 'K'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* Copyleft zxkmm (>) 2026
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_time_sink.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::time_sink {
|
||||
|
||||
TimeSinkWaveformWidget::TimeSinkWaveformWidget(
|
||||
Rect parent_rect,
|
||||
const int16_t* data,
|
||||
size_t length,
|
||||
Color color)
|
||||
: Widget{parent_rect},
|
||||
data_{data},
|
||||
length_{length},
|
||||
color_{color} {
|
||||
reset_cache();
|
||||
}
|
||||
|
||||
void TimeSinkWaveformWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||
Widget::set_parent_rect(new_parent_rect);
|
||||
reset_cache();
|
||||
}
|
||||
|
||||
void TimeSinkWaveformWidget::on_show() {
|
||||
reset_cache();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TimeSinkWaveformWidget::reset_cache() {
|
||||
history_count_ = 0;
|
||||
history_head_ = 0;
|
||||
needs_clear_ = true;
|
||||
}
|
||||
|
||||
void TimeSinkWaveformWidget::set_persistence_frames(uint8_t frames) {
|
||||
const auto clamped = static_cast<uint8_t>(std::clamp<size_t>(
|
||||
frames,
|
||||
1,
|
||||
max_persistence_frames));
|
||||
if (clamped != persistence_frames_) {
|
||||
persistence_frames_ = clamped;
|
||||
reset_cache();
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
Coord TimeSinkWaveformWidget::sample_to_y(const Rect& r, int16_t sample) const {
|
||||
const int32_t y_center = r.top() + (r.height() / 2);
|
||||
const int32_t y_span = std::max<int32_t>(1, r.height() - 1);
|
||||
const int32_t y = y_center - (static_cast<int32_t>(sample) * y_span) / 65536;
|
||||
return static_cast<Coord>(std::clamp<int32_t>(y, r.top(), r.bottom() - 1));
|
||||
}
|
||||
|
||||
void TimeSinkWaveformWidget::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
const auto background = Theme::getInstance()->bg_darkest->background;
|
||||
|
||||
if (!r || !data_ || !length_) {
|
||||
if (needs_clear_) {
|
||||
painter.fill_rectangle_unrolled8(r, background);
|
||||
needs_clear_ = false;
|
||||
}
|
||||
history_count_ = 0;
|
||||
history_head_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t columns = std::min<size_t>({
|
||||
length_,
|
||||
max_columns,
|
||||
static_cast<size_t>(r.width()),
|
||||
});
|
||||
if (!columns) {
|
||||
history_count_ = 0;
|
||||
history_head_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (needs_clear_) {
|
||||
painter.fill_rectangle_unrolled8(r, background);
|
||||
needs_clear_ = false;
|
||||
history_count_ = 0;
|
||||
history_head_ = 0;
|
||||
}
|
||||
|
||||
for (size_t x = 0; x < columns; ++x) {
|
||||
const size_t src_index = (x * length_) / columns;
|
||||
current_y_[x] = sample_to_y(r, data_[src_index]);
|
||||
}
|
||||
|
||||
// Draw first, then erase stale pixels so the trace never disappears mid-refresh.
|
||||
for (size_t x = 0; x < columns; ++x) {
|
||||
display.draw_pixel(
|
||||
{static_cast<Coord>(r.left() + x), current_y_[x]},
|
||||
color_);
|
||||
}
|
||||
|
||||
if (history_count_ >= persistence_frames_) {
|
||||
const size_t expired_slot = history_head_;
|
||||
|
||||
for (size_t x = 0; x < columns; ++x) {
|
||||
const auto expired_y = history_y_[expired_slot][x];
|
||||
bool keep = (expired_y == current_y_[x]);
|
||||
|
||||
if (!keep) {
|
||||
for (size_t i = 1; i < history_count_; ++i) {
|
||||
const size_t slot = (history_head_ + i) % max_persistence_frames;
|
||||
if (history_y_[slot][x] == expired_y) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
display.draw_pixel(
|
||||
{static_cast<Coord>(r.left() + x), expired_y},
|
||||
background);
|
||||
}
|
||||
}
|
||||
|
||||
history_head_ = (history_head_ + 1) % max_persistence_frames;
|
||||
--history_count_;
|
||||
}
|
||||
|
||||
const size_t tail_slot = (history_head_ + history_count_) % max_persistence_frames;
|
||||
std::copy_n(current_y_.begin(), columns, history_y_[tail_slot].begin());
|
||||
++history_count_;
|
||||
}
|
||||
|
||||
TimeSinkView::TimeSinkView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
&field_frequency,
|
||||
&field_frequency_step,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&options_sample_rate,
|
||||
&field_trigger,
|
||||
&options_persistence,
|
||||
&options_trigger_mode,
|
||||
&field_trigger_level,
|
||||
&waveform,
|
||||
});
|
||||
|
||||
field_frequency_step.set_by_value(receiver_model.frequency_step());
|
||||
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_frequency_step(v);
|
||||
field_frequency.set_step(v);
|
||||
};
|
||||
|
||||
options_sample_rate.set_by_nearest_value(sampling_rate);
|
||||
sampling_rate = options_sample_rate.selected_index_value();
|
||||
options_sample_rate.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
sampling_rate = v;
|
||||
apply_spectrum_config();
|
||||
};
|
||||
|
||||
field_trigger.set_value(trigger);
|
||||
field_trigger.on_change = [this](int32_t v) {
|
||||
trigger = static_cast<uint8_t>(v);
|
||||
apply_spectrum_config();
|
||||
};
|
||||
|
||||
options_persistence.set_by_nearest_value(persistence_frames);
|
||||
persistence_frames = options_persistence.selected_index_value();
|
||||
waveform.set_persistence_frames(persistence_frames);
|
||||
options_persistence.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
persistence_frames = static_cast<uint8_t>(v);
|
||||
waveform.set_persistence_frames(persistence_frames);
|
||||
};
|
||||
|
||||
options_trigger_mode.set_by_nearest_value(trigger_mode);
|
||||
trigger_mode = static_cast<uint8_t>(options_trigger_mode.selected_index_value());
|
||||
options_trigger_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
trigger_mode = static_cast<uint8_t>(v);
|
||||
trigger_lock_valid = false;
|
||||
};
|
||||
|
||||
field_trigger_level.set_value(trigger_level);
|
||||
field_trigger_level.on_change = [this](int32_t v) {
|
||||
trigger_level = v;
|
||||
trigger_lock_valid = false;
|
||||
};
|
||||
|
||||
receiver_model.set_squelch_level(0);
|
||||
receiver_model.enable();
|
||||
apply_spectrum_config();
|
||||
}
|
||||
|
||||
TimeSinkView::~TimeSinkView() {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void TimeSinkView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void TimeSinkView::on_show() {
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
void TimeSinkView::on_hide() {
|
||||
baseband::spectrum_streaming_stop();
|
||||
}
|
||||
|
||||
void TimeSinkView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
waveform.set_parent_rect({0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height});
|
||||
}
|
||||
|
||||
void TimeSinkView::apply_spectrum_config() {
|
||||
receiver_model.set_sampling_rate(sampling_rate);
|
||||
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(sampling_rate));
|
||||
|
||||
baseband::set_time_sink(
|
||||
sampling_rate,
|
||||
trigger);
|
||||
}
|
||||
|
||||
size_t TimeSinkView::find_stable_trigger_index(const ChannelSpectrum& spectrum) {
|
||||
if (spectrum.db.size() < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
TriggerMode mode = TriggerMode::Rising;
|
||||
switch (trigger_mode) {
|
||||
case static_cast<uint8_t>(TriggerMode::Off):
|
||||
mode = TriggerMode::Off;
|
||||
break;
|
||||
case static_cast<uint8_t>(TriggerMode::Rising):
|
||||
mode = TriggerMode::Rising;
|
||||
break;
|
||||
case static_cast<uint8_t>(TriggerMode::Falling):
|
||||
mode = TriggerMode::Falling;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode == TriggerMode::Off) {
|
||||
trigger_lock_valid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr int32_t hysteresis = 2;
|
||||
const int32_t threshold = std::clamp<int32_t>(
|
||||
128 + trigger_level,
|
||||
hysteresis,
|
||||
255 - hysteresis);
|
||||
const size_t center = spectrum.db.size() / 2;
|
||||
const size_t reference_index = trigger_lock_valid ? trigger_lock_index : center;
|
||||
|
||||
bool found = false;
|
||||
size_t best_index = 0;
|
||||
size_t best_distance = spectrum.db.size();
|
||||
const auto circular_distance = [count = spectrum.db.size()](size_t a, size_t b) -> size_t {
|
||||
const size_t linear =
|
||||
(a > b) ? (a - b) : (b - a);
|
||||
return std::min(linear, count - linear);
|
||||
};
|
||||
|
||||
for (size_t i = 1; i < spectrum.db.size(); ++i) {
|
||||
const int32_t prev = spectrum.db[i - 1];
|
||||
const int32_t curr = spectrum.db[i];
|
||||
|
||||
bool crossing = false;
|
||||
if (mode == TriggerMode::Rising) {
|
||||
crossing =
|
||||
(prev <= (threshold - hysteresis)) &&
|
||||
(curr >= (threshold + hysteresis));
|
||||
} else {
|
||||
crossing =
|
||||
(prev >= (threshold + hysteresis)) &&
|
||||
(curr <= (threshold - hysteresis));
|
||||
}
|
||||
|
||||
if (!crossing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t distance = circular_distance(i, reference_index);
|
||||
if (!found || (distance < best_distance)) {
|
||||
found = true;
|
||||
best_index = i;
|
||||
best_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
trigger_lock_index = best_index;
|
||||
trigger_lock_valid = true;
|
||||
return best_index;
|
||||
}
|
||||
|
||||
return trigger_lock_valid ? trigger_lock_index : 0;
|
||||
}
|
||||
|
||||
void TimeSinkView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
|
||||
const size_t trigger_index = find_stable_trigger_index(spectrum);
|
||||
const size_t source_count = spectrum.db.size();
|
||||
const size_t window_size = source_count;
|
||||
|
||||
for (size_t x = 0; x < waveform_points; x++) {
|
||||
const size_t offset = (x * window_size) / waveform_points;
|
||||
const size_t src_index =
|
||||
(trigger_index + offset) % source_count;
|
||||
const int32_t centered = static_cast<int32_t>(spectrum.db[src_index]) - 128;
|
||||
const int32_t scaled = centered * 256;
|
||||
waveform_buffer[x] = static_cast<int16_t>(std::clamp<int32_t>(scaled, -32768, 32767));
|
||||
}
|
||||
|
||||
waveform.set_dirty();
|
||||
}
|
||||
|
||||
void TimeSinkView::on_freqchg(int64_t freq) {
|
||||
field_frequency.set_value(freq);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::time_sink
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyleft zxkmm (>) 2026
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_TIME_SINK_APP_H__
|
||||
#define __UI_TIME_SINK_APP_H__
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "message.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace ui::external_app::time_sink {
|
||||
|
||||
constexpr size_t time_sink_waveform_points = 240;
|
||||
|
||||
class TimeSinkWaveformWidget : public Widget {
|
||||
public:
|
||||
TimeSinkWaveformWidget(Rect parent_rect, const int16_t* data, size_t length, Color color);
|
||||
TimeSinkWaveformWidget(const TimeSinkWaveformWidget&) = delete;
|
||||
TimeSinkWaveformWidget(TimeSinkWaveformWidget&&) = delete;
|
||||
TimeSinkWaveformWidget& operator=(const TimeSinkWaveformWidget&) = delete;
|
||||
TimeSinkWaveformWidget& operator=(TimeSinkWaveformWidget&&) = delete;
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void on_show() override;
|
||||
void paint(Painter& painter) override;
|
||||
void set_persistence_frames(uint8_t frames);
|
||||
|
||||
private:
|
||||
static constexpr size_t max_columns = time_sink_waveform_points;
|
||||
static constexpr size_t max_persistence_frames = 16; // this is sad that we cant have 32 histories in ext app due to memory constraints
|
||||
|
||||
void reset_cache();
|
||||
Coord sample_to_y(const Rect& r, int16_t sample) const;
|
||||
|
||||
const int16_t* data_;
|
||||
size_t length_;
|
||||
Color color_;
|
||||
std::array<Coord, max_columns> current_y_{};
|
||||
std::array<std::array<Coord, max_columns>, max_persistence_frames> history_y_{};
|
||||
size_t history_count_{0};
|
||||
size_t history_head_{0};
|
||||
uint8_t persistence_frames_{1};
|
||||
bool needs_clear_{true};
|
||||
};
|
||||
|
||||
class TimeSinkView : public View {
|
||||
public:
|
||||
TimeSinkView(NavigationView& nav);
|
||||
~TimeSinkView();
|
||||
|
||||
TimeSinkView(const TimeSinkView&) = delete;
|
||||
TimeSinkView(TimeSinkView&&) = delete;
|
||||
TimeSinkView& operator=(const TimeSinkView&) = delete;
|
||||
TimeSinkView& operator=(TimeSinkView&&) = delete;
|
||||
|
||||
std::string title() const override { return "Time Sink"; };
|
||||
void focus() override;
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
private:
|
||||
enum class TriggerMode : uint8_t {
|
||||
Off = 0,
|
||||
Rising = 1,
|
||||
Falling = 2,
|
||||
};
|
||||
|
||||
static constexpr Dim header_height = 3 * 16;
|
||||
static constexpr size_t waveform_points = time_sink_waveform_points;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
433'920'000,
|
||||
1'750'000,
|
||||
2'000'000,
|
||||
ReceiverModel::Mode::SpectrumAnalysis};
|
||||
|
||||
uint32_t sampling_rate{2'000'000};
|
||||
uint8_t trigger{0};
|
||||
uint8_t persistence_frames{1};
|
||||
uint8_t trigger_mode{static_cast<uint8_t>(TriggerMode::Rising)};
|
||||
int32_t trigger_level{0};
|
||||
size_t trigger_lock_index{0};
|
||||
bool trigger_lock_valid{false};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_time_sink"sv,
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"sampling_rate"sv, &sampling_rate},
|
||||
{"trigger"sv, &trigger},
|
||||
{"persistence_frames"sv, &persistence_frames},
|
||||
{"trigger_mode"sv, &trigger_mode},
|
||||
{"trigger_level"sv, &trigger_level},
|
||||
}};
|
||||
|
||||
int16_t waveform_buffer[waveform_points]{0};
|
||||
ChannelSpectrumFIFO* fifo = nullptr;
|
||||
|
||||
Labels labels{
|
||||
{{UI_POS_X(0), UI_POS_Y(1)}, "SR:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(11), UI_POS_Y(1)}, "DEC:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(2)}, "PST:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(9), UI_POS_Y(2)}, "TRM:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(19), UI_POS_Y(2)}, "LVL:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
nav_};
|
||||
|
||||
FrequencyStepView field_frequency_step{
|
||||
{10 * 8, UI_POS_Y(0)}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{16 * 8, UI_POS_Y(0)}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{21 * 8, UI_POS_Y(0)}};
|
||||
|
||||
OptionsField options_sample_rate{
|
||||
{UI_POS_X(3), UI_POS_Y(1)},
|
||||
6,
|
||||
{
|
||||
{"1.0M ", 1'000'000},
|
||||
{"2.0M ", 2'000'000},
|
||||
{"5.0M ", 5'000'000},
|
||||
{"10.0M ", 10'000'000},
|
||||
{"20.0M ", 20'000'000},
|
||||
}};
|
||||
|
||||
NumberField field_trigger{
|
||||
{UI_POS_X(17), UI_POS_Y(1)},
|
||||
3,
|
||||
{0, 128},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField options_persistence{
|
||||
{UI_POS_X(3), UI_POS_Y(2)},
|
||||
3,
|
||||
{
|
||||
{"1 ", 1},
|
||||
{"2 ", 2},
|
||||
{"4 ", 4},
|
||||
{"8 ", 8},
|
||||
{"16 ", 16},
|
||||
}};
|
||||
|
||||
OptionsField options_trigger_mode{
|
||||
{UI_POS_X(13), UI_POS_Y(2)},
|
||||
4,
|
||||
{
|
||||
{"Off ", static_cast<int32_t>(TriggerMode::Off)},
|
||||
{"Rise", static_cast<int32_t>(TriggerMode::Rising)},
|
||||
{"Fall", static_cast<int32_t>(TriggerMode::Falling)},
|
||||
}};
|
||||
|
||||
NumberField field_trigger_level{
|
||||
{UI_POS_X(23), UI_POS_Y(2)},
|
||||
4,
|
||||
{-127, 127},
|
||||
1,
|
||||
' '};
|
||||
|
||||
TimeSinkWaveformWidget waveform{
|
||||
{0, header_height, screen_width, screen_height - header_height},
|
||||
waveform_buffer,
|
||||
waveform_points,
|
||||
Theme::getInstance()->fg_light->foreground};
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config{
|
||||
Message::ID::ChannelSpectrumConfig,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
||||
this->fifo = message.fifo;
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
if (this->fifo) {
|
||||
ChannelSpectrum spectrum{};
|
||||
bool has_spectrum = false;
|
||||
while (fifo->out(spectrum)) {
|
||||
has_spectrum = true;
|
||||
}
|
||||
if (has_spectrum) {
|
||||
this->on_channel_spectrum(spectrum);
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_freqchg{
|
||||
Message::ID::FreqChangeCommand,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
|
||||
this->on_freqchg(message->freq);
|
||||
}};
|
||||
|
||||
void apply_spectrum_config();
|
||||
size_t find_stable_trigger_index(const ChannelSpectrum& spectrum);
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
void on_freqchg(int64_t freq);
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::time_sink
|
||||
|
||||
#endif // __UI_TIME_SINK_APP_H__
|
||||
@@ -373,6 +373,16 @@ class Si5351 {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef PRALINE
|
||||
void set_clock_control_single_byte(const ClockControls& clock_control) {
|
||||
_clock_control = clock_control;
|
||||
// Use single-byte writes for PRALINE (multi-byte I2C fails)
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
write_register(Register::CLKControl_Base + i, _clock_control[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool plla_loss_of_signal() {
|
||||
return (device_status() >> 5) & 1;
|
||||
}
|
||||
@@ -413,6 +423,7 @@ class Si5351 {
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Write multisynth config using single-byte writes for debugging */
|
||||
|
||||
void write_ms_single_byte(const size_t ms_number, const MultisynthFractional& config) {
|
||||
const auto regs = config.reg(ms_number);
|
||||
// regs[0] is the base register address, regs[1-8] are the data bytes
|
||||
@@ -421,6 +432,15 @@ class Si5351 {
|
||||
write_register(base_reg + i - 1, regs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void write_pll_single_byte(const uint8_t pll_n, const PLL& pll_config) {
|
||||
const auto regs = pll_config.reg(pll_n);
|
||||
// regs[0] is base register address, regs[1-8] are data
|
||||
const uint8_t base_reg = regs[0];
|
||||
for (size_t i = 1; i < regs.size(); i++) {
|
||||
write_register(base_reg + i - 1, regs[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void set_ms_frequency(
|
||||
|
||||
@@ -589,25 +589,6 @@ init_status_t init() {
|
||||
|
||||
clock_manager.init_clock_generator();
|
||||
|
||||
#ifdef PRALINE
|
||||
// Force CLK4/CLK5 configuration BEFORE I2C bus stops
|
||||
// This ensures the inversion bits are written while I2C is still active
|
||||
|
||||
// CLK4 (MAX2831): ON, Integer, PLLA, INVERTED, MS_Self, 4mA = 0x5D
|
||||
clock_manager.si5351_write_register(20, 0x5D);
|
||||
|
||||
// CLK5 (RFFC5072): ON, Integer, PLLA, INVERTED, MS_Self, 6mA = 0x5E
|
||||
clock_manager.si5351_write_register(21, 0x5E);
|
||||
|
||||
// Enable CLK4 and CLK5 outputs NOW (before I2C stops)
|
||||
uint8_t reg3 = clock_manager.si5351_read_register(3);
|
||||
reg3 &= ~0x30; // Clear bits 4 and 5 to enable
|
||||
clock_manager.si5351_write_register(3, reg3);
|
||||
|
||||
// Wait for clocks to stabilize
|
||||
chThdSleepMilliseconds(10);
|
||||
#endif
|
||||
|
||||
i2c0.stop();
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
@@ -79,6 +79,15 @@ static constexpr SPIConfig ssp_config_max283x = {
|
||||
CR0_CLOCKRATE(ssp_scr(ssp1_pclk_f, ssp1_cpsr, max283x_spi_f) + 3) | CR0_FRFSPI | CR0_DSS9BIT,
|
||||
.cpsr = ssp1_cpsr,
|
||||
};
|
||||
|
||||
static max283x::MAX283x* transceiver = nullptr;
|
||||
|
||||
void set_rx_buff_vcm(const size_t v) {
|
||||
if (transceiver) {
|
||||
transceiver->set_rx_buff_vcm(v);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
/* MAX2837/MAX2839 use 16-bit SPI transfers */
|
||||
static constexpr SPIConfig ssp_config_max283x = {
|
||||
@@ -154,11 +163,26 @@ void init() {
|
||||
baseband_codec.init();
|
||||
|
||||
#ifdef PRALINE
|
||||
|
||||
/* Praline-Specific Bus and Gateware Configuration */
|
||||
|
||||
// SYNC SGPIO TO FPGA CLOCK:
|
||||
// Configure all 16 SGPIO slices to use the external clock (SGPIO8)
|
||||
// provided by the FPGA. This allows the MCU to stay at 40MHz
|
||||
// while the data bus scales to the RF sample rate.
|
||||
// Bit 2:1 of SGPIO_MUX_CFG = 01 (External clock from SGPIO8)
|
||||
// SYNC SGPIO TO FPGA CLOCK WITH FALLING EDGE LATCH
|
||||
for (int i = 0; i < 16; i++) {
|
||||
// (1 << 1) = External clock from SGPIO8
|
||||
// (1 << 3) = Sample on the FALLING edge of the clock
|
||||
LPC_SGPIO->SGPIO_MUX_CFG[i] = (1 << 1) | (1 << 3);
|
||||
}
|
||||
|
||||
/* Initialize FPGA registers - DC_BLOCK must be enabled for RX */
|
||||
// debug::fpga::init();
|
||||
// These FPGA registers control DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
|
||||
fpga_debug_register_write(1, 0x03); // DC_BLOCK=1, QUARTER_SHIFT=1, Q_INVERT=0
|
||||
fpga_debug_register_write(2, 0x03); // RX_DECIM=8 (2^3 decimation for testing 20 MHz -> 2.5 MHz with audio for now)
|
||||
fpga_debug_register_write(1, 0x00); // DC_BLOCK=1, QUARTER_SHIFT=0, Q_INVERT=0
|
||||
fpga_debug_register_write(2, 0x00); // RX_DECIM=No Decim
|
||||
fpga_debug_register_write(3, 0x00); // TX_CTRL=0
|
||||
fpga_debug_register_write(4, 0x00); // TX_INTRP=0
|
||||
fpga_debug_register_write(5, 0x00); // TX_PSTEP=0
|
||||
@@ -210,10 +234,6 @@ void set_direction(const rf::Direction new_direction) {
|
||||
|
||||
#ifdef PRALINE
|
||||
|
||||
// This FPGA registers fix DC_BLOCK, Q-Inv, QUARTER SHIFT, and Decimation.
|
||||
fpga_debug_register_write(1, 0x03); // DC_BLOCK, Q-Inv, no-QUARTER_SHIFT.
|
||||
fpga_debug_register_write(2, 0x03); // RX_DECIM=8 (2^3 decimation for testing 20 MHz -> 2.5 MHz with audio for now)
|
||||
|
||||
// Q inversion controlled by GPIO0[13] (SGPIO12), not FPGA register
|
||||
bool q_invert = mixer_invert ^ baseband_invert;
|
||||
if (q_invert) {
|
||||
@@ -284,11 +304,6 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
mixer_invert = tuning_config.mixer_invert;
|
||||
|
||||
#ifdef PRALINE
|
||||
// TEST: Force baseband invert for Praline (like r9)
|
||||
// baseband_invert = (direction == rf::Direction::Receive);
|
||||
|
||||
// CORRECT: FPGA register 1 only controls DC_BLOCK
|
||||
fpga_debug_register_write(1, 0x01); // DC_BLOCK only, no QUARTER_SHIFT!
|
||||
|
||||
// Q inversion controlled by GPIO0[13] (SGPIO12), not FPGA register
|
||||
bool q_invert = mixer_invert ^ baseband_invert;
|
||||
@@ -506,7 +521,7 @@ void register_write(const size_t register_number, uint32_t value) {
|
||||
void init() {
|
||||
// Initialize FPGA registers after bitstream load
|
||||
// DC_BLOCK (bit 0) must be enabled for RX to work
|
||||
fpga_debug_register_write(1, 0x01); // CTRL: DC_BLOCK=1
|
||||
fpga_debug_register_write(1, 0x00); // CTRL: DC_BLOCK=1
|
||||
fpga_debug_register_write(2, 0x00); // RX_DECIM: no decimation
|
||||
fpga_debug_register_write(3, 0x00); // TX_CTRL: NCO disabled
|
||||
fpga_debug_register_write(4, 0x00); // TX_INTRP: no interpolation
|
||||
|
||||
@@ -64,6 +64,7 @@ void disable();
|
||||
|
||||
#ifdef PRALINE
|
||||
void invalidate_spi_config();
|
||||
void set_rx_buff_vcm(const size_t v);
|
||||
#endif
|
||||
|
||||
namespace debug {
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#ifdef PRALINE
|
||||
extern "C" {
|
||||
#include "fpga_bridge.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace hackrf::one;
|
||||
using namespace portapack;
|
||||
|
||||
@@ -248,6 +242,14 @@ void ReceiverModel::set_normalized_headphone_volume(uint8_t v) {
|
||||
void ReceiverModel::enable() {
|
||||
enabled_ = true;
|
||||
radio::set_direction(rf::Direction::Receive);
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Anchor the Common Mode Voltage (VCM) to 1.2V.
|
||||
* This stabilizes the electrical floor of the I/Q signals.
|
||||
*/
|
||||
radio::set_rx_buff_vcm(1);
|
||||
#endif
|
||||
|
||||
update_tuning_frequency();
|
||||
update_antenna_bias();
|
||||
update_rf_amp();
|
||||
@@ -312,17 +314,6 @@ void ReceiverModel::update_tuning_frequency() {
|
||||
// TODO: use positive offset if freq < offset.
|
||||
if (enabled_) {
|
||||
radio::set_tuning_frequency(target_frequency() + hidden_offset + tuning_offset());
|
||||
|
||||
#ifdef PRALINE
|
||||
/* Praline: Must re-apply baseband filter after frequency change
|
||||
* Reference: hackrf_usb radio.c radio_set_frequency()
|
||||
*
|
||||
* Different frequency ranges may use different quarter-shift modes,
|
||||
* which affects the required LPF bandwidth. For now we just
|
||||
* recalculate the filter to be safe.
|
||||
*/
|
||||
update_baseband_bandwidth();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,20 +325,47 @@ void ReceiverModel::set_hidden_offset(rf::Frequency offset) {
|
||||
void ReceiverModel::update_baseband_bandwidth() {
|
||||
if (enabled_) {
|
||||
#ifdef PRALINE
|
||||
/* Praline: LPF bandwidth calculation
|
||||
* Reference: hackrf_usb radio.c radio_set_filter()
|
||||
/*
|
||||
* PRALINE LPF bandwidth calculation from GSG hackrf_usb radio.c
|
||||
*
|
||||
* LPF = (sample_rate * 3) / 8
|
||||
* Plus additional offset if quarter-shift is enabled (not implemented yet)
|
||||
* The LPF should be set to capture the desired signal bandwidth
|
||||
* while the FPGA decimation filter handles anti-aliasing.
|
||||
*
|
||||
* For most modes: LPF = (output_sample_rate * 3) / 8
|
||||
* For quarter-shift: add offset for shifted spectrum
|
||||
*
|
||||
* Note: MAX2831 minimum LPF is 11.6 MHz, so for narrow sample rates
|
||||
* the hardware limit applies and FPGA filter does the real work.
|
||||
*/
|
||||
uint32_t lpf_bandwidth = (sampling_rate() * 3) / 8;
|
||||
|
||||
// For now, quarter-shift is disabled, so no offset added
|
||||
// When quarter-shift is implemented:
|
||||
// if (quarter_shift_enabled) {
|
||||
// uint32_t offset = (sampling_rate() << decimation_n) / 8;
|
||||
// lpf_bandwidth += offset * 2;
|
||||
// }
|
||||
uint32_t sample_rate = sampling_rate();
|
||||
uint8_t resampling_n = portapack::clock_manager.get_resampling_n();
|
||||
uint32_t afe_rate = sample_rate << resampling_n;
|
||||
|
||||
// Base LPF: enough to capture desired bandwidth
|
||||
uint32_t lpf_bandwidth = (sample_rate * 3) / 8;
|
||||
|
||||
// Check if quarter-shift is enabled (FPGA register 1, bits 2-3)
|
||||
uint32_t fpga_ctrl = radio::debug::fpga::register_read(1);
|
||||
uint8_t quarter_shift = (fpga_ctrl >> 2) & 0x03;
|
||||
|
||||
if (quarter_shift != 0) {
|
||||
// Quarter-shift moves spectrum by AFE_rate/4, need wider LPF
|
||||
uint32_t offset = afe_rate / 8;
|
||||
lpf_bandwidth += offset * 2;
|
||||
}
|
||||
|
||||
// For best anti-alias performance, also consider AFE Nyquist
|
||||
// If our calculated LPF is below MAX2831 minimum, it doesn't matter
|
||||
// But if we can set LPF to just below AFE Nyquist, that's optimal
|
||||
uint32_t afe_nyquist = afe_rate / 2;
|
||||
|
||||
// Use the larger of: signal bandwidth requirement OR Nyquist protection
|
||||
// (but MAX2831 driver will clamp to its available settings anyway)
|
||||
if (lpf_bandwidth < afe_nyquist) {
|
||||
// Set LPF close to Nyquist for maximum alias rejection
|
||||
lpf_bandwidth = (afe_nyquist * 9) / 10; // 90% of Nyquist
|
||||
}
|
||||
|
||||
radio::set_baseband_filter_bandwidth_rx(lpf_bandwidth);
|
||||
#else
|
||||
@@ -366,6 +384,12 @@ void ReceiverModel::update_sampling_rate() {
|
||||
radio::set_baseband_rate(sampling_rate());
|
||||
}
|
||||
update_tuning_frequency();
|
||||
|
||||
#ifdef PRALINE
|
||||
// GSG reference: re-apply frequency after sample rate change
|
||||
// This reconfigures LPF bandwidth based on new decimation
|
||||
update_baseband_bandwidth();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ReceiverModel::update_lna() {
|
||||
|
||||
@@ -36,7 +36,7 @@ bool card_present = false;
|
||||
Status status_{Status::NotPresent};
|
||||
|
||||
FRESULT mount() {
|
||||
return f_mount(&fs, reinterpret_cast<const TCHAR*>(_T("")), 0);
|
||||
return f_mount(&fs, reinterpret_cast<const TCHAR*>(_T("")), 1);
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||
}
|
||||
|
||||
void GraphEq::paint(Painter& painter) {
|
||||
if (!visible()) return;
|
||||
if (!drawn()) return;
|
||||
if (!is_calculated) { // calc positions first
|
||||
calculate_params();
|
||||
is_calculated = true;
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "ui_btngrid.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -42,19 +44,12 @@ BtnGridView::BtnGridView(
|
||||
}
|
||||
button_pgup.set_focusable(false);
|
||||
button_pgup.on_select = [this](Button&) {
|
||||
if (arrow_up_enabled) {
|
||||
if (((int64_t)highlighted_item - displayed_max) > 0)
|
||||
set_highlighted(highlighted_item - displayed_max);
|
||||
else
|
||||
set_highlighted(0);
|
||||
}
|
||||
page_up();
|
||||
};
|
||||
|
||||
button_pgdown.set_focusable(false);
|
||||
button_pgdown.on_select = [this](Button&) {
|
||||
if (arrow_down_enabled) {
|
||||
set_highlighted(highlighted_item + displayed_max);
|
||||
}
|
||||
page_down();
|
||||
};
|
||||
|
||||
button_pgup.set_style(Theme::getInstance()->bg_darkest_small);
|
||||
@@ -143,6 +138,7 @@ void BtnGridView::clear() {
|
||||
// clear vector and release memory, not using swap since it's causing capture to glitch/fault
|
||||
menu_items.clear();
|
||||
|
||||
// TODO(u-foka): Clean up my mess, move this somewhere to clear memory when the view is not visible, but not to be confused with clearing the menu items...
|
||||
for (auto& item : menu_item_views)
|
||||
remove_child(item.get());
|
||||
|
||||
@@ -186,6 +182,13 @@ void BtnGridView::insert_item(const GridItem& new_item, size_t position, bool in
|
||||
}
|
||||
|
||||
void BtnGridView::show_hide_arrows() {
|
||||
// if there are no menu items, disable both arrows and avoid size-1 underflow
|
||||
if (menu_items.empty()) {
|
||||
set_arrow_up_enabled(false);
|
||||
set_arrow_down_enabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (highlighted_item == 0) {
|
||||
set_arrow_up_enabled(false);
|
||||
} else {
|
||||
@@ -198,6 +201,15 @@ void BtnGridView::show_hide_arrows() {
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::reload_items() {
|
||||
menu_items.clear();
|
||||
on_populate();
|
||||
set_highlighted(highlighted_item, true);
|
||||
show_hide_arrows();
|
||||
|
||||
set_dirty(); // Redraw the now potentially empty space as well
|
||||
}
|
||||
|
||||
void BtnGridView::update_items() {
|
||||
size_t i = 0;
|
||||
|
||||
@@ -237,9 +249,20 @@ void BtnGridView::show_arrows_enabled(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
bool BtnGridView::set_highlighted(int32_t new_value, bool force_update) {
|
||||
int32_t item_count = (int32_t)menu_items.size();
|
||||
|
||||
// nothing to highlight when the list is empty
|
||||
if (item_count == 0) {
|
||||
highlighted_item = 0;
|
||||
offset = 0;
|
||||
show_hide_arrows();
|
||||
if (force_update) {
|
||||
update_items();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_value < 0)
|
||||
return false;
|
||||
|
||||
@@ -247,13 +270,15 @@ bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
new_value = item_count - 1;
|
||||
}
|
||||
|
||||
bool needs_update = false;
|
||||
|
||||
if (((uint32_t)new_value > offset) && ((new_value - offset) >= displayed_max)) {
|
||||
// Shift BtnGridView up
|
||||
highlighted_item = new_value;
|
||||
// rounding up new offset to next multiple of rows
|
||||
offset = new_value - displayed_max + rows_;
|
||||
offset -= (offset % rows_);
|
||||
update_items();
|
||||
needs_update = true;
|
||||
// refresh whole screen (display flickers) only if scrolling last row up and a blank button is needed at the bottom
|
||||
if ((new_value + rows_ >= item_count) && (item_count % rows_) != 0)
|
||||
set_dirty();
|
||||
@@ -261,15 +286,32 @@ bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
// Shift BtnGridView down
|
||||
highlighted_item = new_value;
|
||||
offset = (new_value / rows_) * rows_;
|
||||
update_items();
|
||||
needs_update = true;
|
||||
// no need to set_dirty() here since all buttons have been repainted
|
||||
} else {
|
||||
// Just update highlight
|
||||
highlighted_item = new_value;
|
||||
}
|
||||
|
||||
if (visible())
|
||||
item_view(highlighted_item - offset)->focus();
|
||||
// Normalize offset to show maximum items when count decreased
|
||||
if (offset + displayed_max > item_count && item_count > 0) {
|
||||
if (item_count >= displayed_max) {
|
||||
offset = item_count - displayed_max;
|
||||
} else {
|
||||
offset = 0;
|
||||
}
|
||||
needs_update = true;
|
||||
}
|
||||
|
||||
if (needs_update || force_update) {
|
||||
update_items();
|
||||
}
|
||||
|
||||
if (drawn()) {
|
||||
size_t idx = highlighted_item - offset;
|
||||
if (idx < menu_item_views.size())
|
||||
item_view(idx)->focus();
|
||||
}
|
||||
|
||||
show_hide_arrows();
|
||||
|
||||
@@ -281,7 +323,11 @@ uint32_t BtnGridView::highlighted_index() {
|
||||
}
|
||||
|
||||
void BtnGridView::on_focus() {
|
||||
item_view(highlighted_item - offset)->focus();
|
||||
if (!menu_items.empty()) {
|
||||
size_t idx = highlighted_item - offset;
|
||||
if (idx < menu_item_views.size())
|
||||
item_view(idx)->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::on_blur() {
|
||||
@@ -292,12 +338,18 @@ void BtnGridView::on_blur() {
|
||||
}
|
||||
|
||||
void BtnGridView::on_show() {
|
||||
on_populate();
|
||||
View::on_show();
|
||||
set_highlighted(highlighted_item);
|
||||
|
||||
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status /*status*/) {
|
||||
this->reload_items();
|
||||
};
|
||||
|
||||
reload_items();
|
||||
}
|
||||
|
||||
void BtnGridView::on_hide() {
|
||||
sd_card::status_signal -= sd_card_status_signal_token;
|
||||
|
||||
View::on_hide();
|
||||
clear();
|
||||
set_arrow_up_enabled(false);
|
||||
@@ -319,8 +371,10 @@ bool BtnGridView::on_key(const KeyEvent key) {
|
||||
return set_highlighted(highlighted_item - 1);
|
||||
|
||||
case KeyEvent::Select:
|
||||
if (menu_items[highlighted_item].on_select) {
|
||||
menu_items[highlighted_item].on_select();
|
||||
if (!menu_items.empty() && highlighted_item < menu_items.size()) {
|
||||
if (menu_items[highlighted_item].on_select) {
|
||||
menu_items[highlighted_item].on_select();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -370,4 +424,80 @@ bool BtnGridView::blacklisted_app(GridItem new_item) {
|
||||
return std::search(blacklist_ptr.get(), blacklist_ptr.get() + blacklist_len, app_name.begin(), app_name.end()) < blacklist_ptr.get() + blacklist_len;
|
||||
}
|
||||
|
||||
void BtnGridView::page_up() {
|
||||
if (arrow_up_enabled) {
|
||||
size_t item_count = menu_items.size();
|
||||
if (item_count == 0)
|
||||
return;
|
||||
|
||||
size_t new_offset;
|
||||
if (offset > displayed_max) {
|
||||
new_offset = offset - displayed_max;
|
||||
} else {
|
||||
new_offset = 0;
|
||||
}
|
||||
|
||||
// If we can't move further, just move highlight to start
|
||||
if (new_offset == offset) {
|
||||
set_highlighted(0);
|
||||
return;
|
||||
}
|
||||
|
||||
bool was_visible = (highlighted_item >= new_offset && highlighted_item < new_offset + displayed_max);
|
||||
|
||||
offset = new_offset;
|
||||
update_items();
|
||||
|
||||
if (was_visible) {
|
||||
if (drawn()) {
|
||||
size_t idx = highlighted_item - offset;
|
||||
if (idx < menu_item_views.size())
|
||||
item_view(idx)->focus();
|
||||
}
|
||||
} else {
|
||||
// focus last item on the new page (clamp to last item overall)
|
||||
size_t last_on_page = std::min(new_offset + displayed_max, item_count) - 1;
|
||||
set_highlighted((int)last_on_page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::page_down() {
|
||||
if (arrow_down_enabled) {
|
||||
size_t item_count = menu_items.size();
|
||||
if (item_count == 0)
|
||||
return;
|
||||
|
||||
size_t max_offset;
|
||||
if (item_count > displayed_max) {
|
||||
max_offset = item_count - displayed_max;
|
||||
} else {
|
||||
max_offset = 0;
|
||||
}
|
||||
size_t new_offset = std::min(offset + displayed_max, max_offset);
|
||||
|
||||
// If we can't move further, just move highlight to last
|
||||
if (new_offset == offset) {
|
||||
set_highlighted((int)(item_count - 1));
|
||||
return;
|
||||
}
|
||||
|
||||
bool was_visible = (highlighted_item >= new_offset && highlighted_item < new_offset + displayed_max);
|
||||
|
||||
offset = new_offset;
|
||||
update_items();
|
||||
|
||||
if (was_visible) {
|
||||
if (drawn()) {
|
||||
size_t idx = highlighted_item - offset;
|
||||
if (idx < menu_item_views.size())
|
||||
item_view(idx)->focus();
|
||||
}
|
||||
} else {
|
||||
// focus first item on the new page
|
||||
set_highlighted((int)new_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -71,7 +71,7 @@ class BtnGridView : public View {
|
||||
bool show_arrows{true}; // flag used to hide arrows in main menu
|
||||
void show_arrows_enabled(bool enabled);
|
||||
|
||||
bool set_highlighted(int32_t new_value);
|
||||
bool set_highlighted(int32_t new_value, bool force_update = false);
|
||||
uint32_t highlighted_index();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
@@ -88,11 +88,15 @@ class BtnGridView : public View {
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool blacklisted_app(GridItem new_item);
|
||||
|
||||
void reload_items();
|
||||
void update_items();
|
||||
void set_btn_height_fixed(uint8_t h) {
|
||||
button_h = h;
|
||||
}
|
||||
|
||||
void page_up();
|
||||
void page_down();
|
||||
|
||||
protected:
|
||||
virtual void on_populate() = 0;
|
||||
|
||||
@@ -116,6 +120,7 @@ class BtnGridView : public View {
|
||||
size_t displayed_max{0};
|
||||
size_t highlighted_item{0};
|
||||
size_t offset{0};
|
||||
SignalToken sd_card_status_signal_token{};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -301,6 +301,10 @@ SystemStatusView::SystemStatusView(
|
||||
this->on_clk();
|
||||
};
|
||||
|
||||
sd_card_status_view.on_select = [this](ImageButton&) {
|
||||
this->on_sd_card();
|
||||
};
|
||||
|
||||
// Initialize toggle buttons
|
||||
toggle_speaker.set_value(pmem::config_speaker_disable());
|
||||
toggle_mute.set_value(pmem::config_audio_mute());
|
||||
@@ -507,6 +511,16 @@ void SystemStatusView::on_clk() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SystemStatusView::on_sd_card() {
|
||||
if (!nav_.is_valid()) return;
|
||||
if (sd_info_up) return;
|
||||
sd_info_up = true;
|
||||
nav_.push<SetSDCardView>();
|
||||
nav_.set_on_pop([this]() {
|
||||
sd_info_up = false;
|
||||
});
|
||||
}
|
||||
|
||||
void SystemStatusView::on_title() {
|
||||
if (nav_.is_top())
|
||||
nav_.push<AboutView>();
|
||||
@@ -771,8 +785,6 @@ void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
true);
|
||||
}
|
||||
};
|
||||
|
||||
grid.update_items();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
@@ -789,7 +801,7 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
"Check SD card\n"
|
||||
"Update SD card content\n");
|
||||
}},
|
||||
error_tile_pos);
|
||||
error_tile_pos, true);
|
||||
} else {
|
||||
std::sort(externalItems.begin(), externalItems.end(), [](const auto &a, const auto &b)
|
||||
{
|
||||
@@ -804,18 +816,10 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
grid.update_items();
|
||||
}
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
bool verify_sdcard_format() {
|
||||
FATFS* fs = &sd_card::fs;
|
||||
return (fs->fs_type == FS_FAT32 || fs->fs_type == FS_EXFAT) || !(sd_card::status() == sd_card::Status::Mounted);
|
||||
/* ^ to satisfy those users that not use an sd*/
|
||||
}
|
||||
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav)
|
||||
@@ -824,7 +828,8 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav)
|
||||
void ReceiversMenuView::on_populate() {
|
||||
bool return_icon = pmem::show_gui_return_icon();
|
||||
if (return_icon) {
|
||||
add_item({"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }});
|
||||
add_item({"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }},
|
||||
true);
|
||||
}
|
||||
add_apps(nav_, *this, RX);
|
||||
add_external_items(nav_, app_location_t::RX, *this, return_icon ? 1 : 0);
|
||||
@@ -913,11 +918,6 @@ SystemMenuView::SystemMenuView(NavigationView& nav)
|
||||
}
|
||||
|
||||
void SystemMenuView::on_populate() {
|
||||
if (!verify_sdcard_format()) {
|
||||
add_item({"SDCard Error", Theme::getInstance()->error_dark->foreground, nullptr, [this]() {
|
||||
nav_.display_modal("Error", "SD Card is not exFAT/FAT32");
|
||||
}});
|
||||
}
|
||||
add_apps(nav_, *this, HOME);
|
||||
add_external_items(nav_, app_location_t::HOME, *this, 0);
|
||||
add_item({"HackRF", Theme::getInstance()->fg_cyan->foreground, &bitmap_icon_hackrf, [this]() { hackrf_mode(nav_); }});
|
||||
|
||||
@@ -62,7 +62,6 @@ namespace ui {
|
||||
|
||||
void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc);
|
||||
void add_external_items(NavigationView& nav, app_location_t location, BtnGridView& grid, uint8_t error_tile_pos, bool show_error_tile = true);
|
||||
bool verify_sdcard_format();
|
||||
|
||||
enum modal_t {
|
||||
INFO = 0,
|
||||
@@ -202,6 +201,7 @@ class SystemStatusView : public View {
|
||||
static constexpr auto default_title = "";
|
||||
bool batt_was_inited = false; // if the battery was off on tart, but later turned on.
|
||||
bool batt_info_up = false; // to prevent show multiple batt info dialog
|
||||
bool sd_info_up = false; // to prevent show multiple sd info dialog
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -303,6 +303,7 @@ class SystemStatusView : public View {
|
||||
void on_title();
|
||||
void refresh();
|
||||
void on_clk();
|
||||
void on_sd_card();
|
||||
void on_tx_disabled();
|
||||
void rtc_battery_workaround();
|
||||
void on_battery_data(const BatteryStateMessage* msg);
|
||||
|
||||
@@ -83,7 +83,7 @@ const Color color_sd_card(const sd_card::Status status) {
|
||||
|
||||
SDCardStatusView::SDCardStatusView(
|
||||
const Rect parent_rect)
|
||||
: Image{parent_rect, &bitmap_sd_card_unknown, detail::color_sd_card_unknown, Theme::getInstance()->bg_dark->background} {
|
||||
: ImageButton{parent_rect, &bitmap_sd_card_unknown, detail::color_sd_card_unknown, Theme::getInstance()->bg_dark->background} {
|
||||
}
|
||||
|
||||
void SDCardStatusView::on_show() {
|
||||
@@ -101,7 +101,7 @@ void SDCardStatusView::paint(Painter& painter) {
|
||||
set_bitmap(&detail::bitmap_sd_card(status));
|
||||
set_foreground(detail::color_sd_card(status));
|
||||
|
||||
Image::paint(painter);
|
||||
ImageButton::paint(painter);
|
||||
}
|
||||
|
||||
void SDCardStatusView::on_status(const sd_card::Status) {
|
||||
|
||||
@@ -26,10 +26,11 @@
|
||||
#include "theme.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include <functional>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SDCardStatusView : public Image {
|
||||
class SDCardStatusView : public ImageButton {
|
||||
public:
|
||||
SDCardStatusView(const Rect parent_rect);
|
||||
|
||||
|
||||
@@ -467,6 +467,7 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PSPE wideband_spectrum)
|
||||
|
||||
|
||||
### WFM Audio
|
||||
|
||||
set(MODE_CPPSRC
|
||||
@@ -705,6 +706,13 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PMRT morsetx)
|
||||
|
||||
### Time Sink
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_time_sink.cpp
|
||||
)
|
||||
DeclareTargets(PTSK time_sink)
|
||||
|
||||
set(MODE_CPPSRC
|
||||
sd_over_usb/proc_sd_over_usb.cpp
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyleft zxkmm (>) 2026
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "proc_time_sink.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
void TimeSinkProcessor::execute(const buffer_c8_t& buffer) {
|
||||
// 2048 complex8_t samples per buffer.
|
||||
// 102.4us per buffer. 20480 instruction cycles per buffer.
|
||||
|
||||
if (!configured) {
|
||||
return;
|
||||
}
|
||||
|
||||
execute_time_domain(buffer);
|
||||
}
|
||||
|
||||
void TimeSinkProcessor::execute_time_domain(const buffer_c8_t& buffer) {
|
||||
if (!time_streaming || buffer.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (phase < trigger) {
|
||||
phase++;
|
||||
return;
|
||||
}
|
||||
|
||||
phase = 0;
|
||||
|
||||
if (time_domain_request_update) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t stride = std::max<size_t>(1, buffer.count / time_domain_spectrum.db.size());
|
||||
time_domain_spectrum.sampling_rate = buffer.sampling_rate / stride;
|
||||
time_domain_spectrum.channel_filter_low_frequency = 0;
|
||||
time_domain_spectrum.channel_filter_high_frequency = 0;
|
||||
time_domain_spectrum.channel_filter_transition = 0;
|
||||
|
||||
for (size_t i = 0; i < time_domain_spectrum.db.size(); i++) {
|
||||
const size_t sample_index = std::min(i * stride, buffer.count - 1);
|
||||
const int32_t normalized = std::clamp<int32_t>(
|
||||
static_cast<int32_t>(buffer.p[sample_index].real()) + 128,
|
||||
0,
|
||||
255);
|
||||
time_domain_spectrum.db[i] = static_cast<uint8_t>(normalized);
|
||||
}
|
||||
|
||||
time_domain_request_update = true;
|
||||
EventDispatcher::events_flag(EVT_MASK_SPECTRUM);
|
||||
}
|
||||
|
||||
void TimeSinkProcessor::set_time_streaming_state(const SpectrumStreamingConfigMessage& message) {
|
||||
if (message.mode == SpectrumStreamingConfigMessage::Mode::Running) {
|
||||
time_streaming = true;
|
||||
ChannelSpectrumConfigMessage fifo_message{&fifo};
|
||||
shared_memory.application_queue.push(fifo_message);
|
||||
} else {
|
||||
time_streaming = false;
|
||||
time_domain_request_update = false;
|
||||
fifo.reset_in();
|
||||
}
|
||||
}
|
||||
|
||||
void TimeSinkProcessor::update_time_domain() {
|
||||
if (time_streaming && time_domain_request_update) {
|
||||
fifo.in(time_domain_spectrum);
|
||||
}
|
||||
|
||||
time_domain_request_update = false;
|
||||
}
|
||||
|
||||
void TimeSinkProcessor::on_message(const Message* const msg) {
|
||||
switch (msg->id) {
|
||||
case Message::ID::UpdateSpectrum:
|
||||
update_time_domain();
|
||||
break;
|
||||
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
set_time_streaming_state(*reinterpret_cast<const SpectrumStreamingConfigMessage*>(msg));
|
||||
break;
|
||||
|
||||
case Message::ID::TimeSinkConfig: {
|
||||
const auto& message = *reinterpret_cast<const TimeSinkConfigMessage*>(msg);
|
||||
baseband_fs = message.sampling_rate;
|
||||
trigger = message.trigger;
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
phase = 0;
|
||||
time_domain_request_update = false;
|
||||
configured = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<TimeSinkProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyleft zxkmm (>) 2026
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __PROC_TIME_SINK_H__
|
||||
#define __PROC_TIME_SINK_H__
|
||||
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
class TimeSinkProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
bool configured = false;
|
||||
size_t baseband_fs = 20000000;
|
||||
|
||||
void execute_time_domain(const buffer_c8_t& buffer);
|
||||
void set_time_streaming_state(const SpectrumStreamingConfigMessage& message);
|
||||
void update_time_domain();
|
||||
ChannelSpectrum time_domain_spectrum{};
|
||||
ChannelSpectrum fifo_data[1 << ChannelSpectrumConfigMessage::fifo_k]{};
|
||||
ChannelSpectrumFIFO fifo{fifo_data, ChannelSpectrumConfigMessage::fifo_k};
|
||||
|
||||
size_t phase = 0, trigger = 127;
|
||||
bool time_streaming = false;
|
||||
volatile bool time_domain_request_update = false;
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
};
|
||||
|
||||
#endif /*__PROC_TIME_SINK_H__*/
|
||||
@@ -113,13 +113,13 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
|
||||
fft_c_preswapped(audio_spectrum, fft_step, fft_step + 1);
|
||||
fft_step++;
|
||||
} else {
|
||||
constexpr float mag_scale = 5.0f;
|
||||
const size_t spectrum_end = spectrum.db.size();
|
||||
for (size_t i = 0; i < spectrum_end; i++) {
|
||||
// const auto corrected_sample = spectrum_window_hamming_3(audio_spectrum, i);
|
||||
const auto corrected_sample = audio_spectrum[i];
|
||||
const auto mag2 = magnitude_squared(corrected_sample * (1.0f / 32768.0f));
|
||||
const float db = mag2_to_dbv_norm(mag2);
|
||||
constexpr float mag_scale = 5.0f;
|
||||
const unsigned int v = (db * mag_scale) + 255.0f;
|
||||
spectrum.db[i] = std::max(0U, std::min(255U, v));
|
||||
}
|
||||
|
||||
@@ -81,11 +81,7 @@ class WidebandFMAudio : public BasebandProcessor {
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
#ifdef PRALINE
|
||||
static constexpr size_t baseband_fs = 2000000;
|
||||
#else
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
#endif
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
|
||||
@@ -77,7 +77,8 @@ void wait_for_device() {
|
||||
do {
|
||||
device_id = get_device_id();
|
||||
} while (device_id != W25Q80BV_DEVICE_ID_RES &&
|
||||
device_id != W25Q16DV_DEVICE_ID_RES);
|
||||
device_id != W25Q16DV_DEVICE_ID_RES &&
|
||||
device_id != W25Q32JV_DEVICE_ID_RES);
|
||||
}
|
||||
|
||||
void wait_not_busy() {
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
|
||||
#define W25Q80BV_DEVICE_ID_RES 0x13 /* Expected device_id for W25Q80BV */
|
||||
#define W25Q16DV_DEVICE_ID_RES 0x14 /* Expected device_id for W25Q16DV */
|
||||
#define W25Q32JV_DEVICE_ID_RES 0x15 /* Expected device_id for W25Q32JV (Praline) */
|
||||
|
||||
#define SSP_CR1(port) MMIO32(port + 0x004)
|
||||
#define PERIPH_BASE_APB0 0x40080000
|
||||
|
||||
@@ -155,6 +155,7 @@ class Message {
|
||||
StreamTXConfiguration = 97,
|
||||
RTTYData = 98,
|
||||
NotificationData = 99,
|
||||
TimeSinkConfig = 100,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -309,6 +310,20 @@ class WidebandSpectrumConfigMessage : public Message {
|
||||
size_t trigger{0};
|
||||
};
|
||||
|
||||
class TimeSinkConfigMessage : public Message {
|
||||
public:
|
||||
constexpr TimeSinkConfigMessage(
|
||||
size_t sampling_rate,
|
||||
size_t trigger)
|
||||
: Message{ID::TimeSinkConfig},
|
||||
sampling_rate{sampling_rate},
|
||||
trigger{trigger} {
|
||||
}
|
||||
|
||||
size_t sampling_rate{0};
|
||||
size_t trigger{0};
|
||||
};
|
||||
|
||||
struct AudioSpectrum {
|
||||
std::array<uint8_t, 128> db{{0}};
|
||||
// uint32_t sampling_rate { 0 };
|
||||
|
||||
@@ -96,6 +96,7 @@ constexpr image_tag_t image_tag_sonde{'P', 'S', 'O', 'N'};
|
||||
constexpr image_tag_t image_tag_tpms{'P', 'T', 'P', 'M'};
|
||||
constexpr image_tag_t image_tag_wfm_audio{'P', 'W', 'F', 'M'};
|
||||
constexpr image_tag_t image_tag_wideband_spectrum{'P', 'S', 'P', 'E'};
|
||||
constexpr image_tag_t image_tag_time_sink{'P', 'T', 'S', 'K'};
|
||||
constexpr image_tag_t image_tag_test{'P', 'T', 'S', 'T'};
|
||||
|
||||
constexpr image_tag_t image_tag_adsb_tx{'P', 'A', 'D', 'T'};
|
||||
|
||||
@@ -171,7 +171,7 @@ void FocusManager::update(Widget* const top_widget, const KeyEvent event) {
|
||||
const auto focus_screen_rect = focus_widget()->screen_rect();
|
||||
|
||||
const auto test_fn = [&focus_screen_rect, event](ui::Widget* const w) -> test_result_t {
|
||||
// if( w->visible() && w->focusable() ) {
|
||||
// if( w->drawn() && w->focusable() ) {
|
||||
if (w->focusable()) {
|
||||
const auto distance = rect_distances(event, focus_screen_rect, w->screen_rect());
|
||||
if (distance >= 0) {
|
||||
|
||||
@@ -121,11 +121,11 @@ void Painter::paint_widget_tree(Widget* w) {
|
||||
|
||||
void Painter::paint_widget(Widget* w) {
|
||||
if (w->hidden()) {
|
||||
// Mark widget (and all children) as invisible.
|
||||
w->visible(false);
|
||||
// Mark widget (and all children) as not drawn.
|
||||
w->drawn(false);
|
||||
} else {
|
||||
// Mark this widget as visible and recurse.
|
||||
w->visible(true);
|
||||
// Mark this widget as drawn and recurse.
|
||||
w->drawn(true);
|
||||
|
||||
if (w->dirty()) {
|
||||
w->paint(*this);
|
||||
|
||||
@@ -91,9 +91,9 @@ void Widget::set_parent(Widget* const widget) {
|
||||
}
|
||||
|
||||
if (parent_ && !widget) {
|
||||
// We have a parent, but are losing it. Update visible status.
|
||||
// We have a parent, but are losing it. Update drawn status.
|
||||
dirty_overlapping_children_in_rect(screen_rect());
|
||||
visible(false);
|
||||
drawn(false);
|
||||
}
|
||||
|
||||
if (widget == nullptr)
|
||||
@@ -209,9 +209,9 @@ const Style& Widget::style() const {
|
||||
return style_ ? *style_ : parent()->style();
|
||||
}
|
||||
|
||||
void Widget::visible(bool v) {
|
||||
if (v != flags.visible) {
|
||||
flags.visible = v;
|
||||
void Widget::drawn(bool v) {
|
||||
if (v != flags.drawn) {
|
||||
flags.drawn = v;
|
||||
|
||||
/* TODO: This on_show/on_hide implementation seems inelegant.
|
||||
* But I need *some* way to take/configure resources when
|
||||
@@ -224,9 +224,9 @@ void Widget::visible(bool v) {
|
||||
} else {
|
||||
on_hide();
|
||||
|
||||
// Set all children invisible too.
|
||||
// Set all children not drawn too.
|
||||
for (const auto child : children()) {
|
||||
child->visible(false);
|
||||
child->drawn(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -694,7 +694,7 @@ void Console::clear(bool clear_buffer = false) {
|
||||
if (clear_buffer)
|
||||
buffer.clear();
|
||||
|
||||
if (!hidden() && visible()) {
|
||||
if (!hidden() && drawn()) {
|
||||
display.fill_rectangle(
|
||||
screen_rect(),
|
||||
Theme::getInstance()->bg_darkest->background);
|
||||
@@ -706,7 +706,7 @@ void Console::clear(bool clear_buffer = false) {
|
||||
void Console::write(std::string message) {
|
||||
bool escape = false;
|
||||
|
||||
if (!hidden() && visible()) {
|
||||
if (!hidden() && drawn()) {
|
||||
const Style& s = style();
|
||||
const Font& font = s.font;
|
||||
auto rect = screen_rect();
|
||||
@@ -797,7 +797,7 @@ void Console::on_hide() {
|
||||
}
|
||||
|
||||
void Console::crlf() {
|
||||
if (hidden() || !visible()) return;
|
||||
if (hidden() || !drawn()) return;
|
||||
|
||||
const auto& s = style();
|
||||
auto sr = screen_rect();
|
||||
@@ -3232,7 +3232,7 @@ void GraphEq::update_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||
}
|
||||
|
||||
void GraphEq::paint(Painter& painter) {
|
||||
if (!visible()) return;
|
||||
if (!drawn()) return;
|
||||
if (!is_calculated) { // calc positions first
|
||||
calculate_params();
|
||||
is_calculated = true;
|
||||
|
||||
@@ -126,8 +126,8 @@ class Widget {
|
||||
bool dirty() const;
|
||||
void set_clean();
|
||||
|
||||
void visible(bool v);
|
||||
bool visible() { return flags.visible; };
|
||||
void drawn(bool v);
|
||||
bool drawn() { return flags.drawn; };
|
||||
|
||||
bool highlighted() const;
|
||||
void set_highlighted(const bool value);
|
||||
@@ -146,7 +146,7 @@ class Widget {
|
||||
bool hidden : 1; // Hide widget and children.
|
||||
bool focusable : 1; // Widget can receive focus.
|
||||
bool highlighted : 1; // Show in a highlighted style.
|
||||
bool visible : 1; // Object was visible during last paint.
|
||||
bool drawn : 1; // Object was drawn during last paint.
|
||||
};
|
||||
|
||||
flags_t flags{
|
||||
@@ -154,7 +154,7 @@ class Widget {
|
||||
.hidden = false,
|
||||
.focusable = false,
|
||||
.highlighted = false,
|
||||
.visible = false,
|
||||
.drawn = false,
|
||||
};
|
||||
|
||||
static const std::vector<Widget*> no_children;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
// DO NOT EDIT: IT IS AUTO GENERATED BY CMAKE!!!!
|
||||
|
||||
// clang-format off
|
||||
//Allowed fw size in MB
|
||||
#define FLASH_SIZE_MB 2
|
||||
//Current compiled fw size in MB
|
||||
#define FLASH_SIZE_LIMIT_MB 1
|
||||
// clang-format on
|
||||
@@ -160,7 +160,7 @@ void FocusManager::update(
|
||||
const auto focus_screen_rect = focus_widget()->screen_rect();
|
||||
|
||||
const auto test_fn = [&focus_screen_rect, event](ui::Widget* const w) -> test_result_t {
|
||||
// if( w->visible() && w->focusable() ) {
|
||||
// if( w->drawn() && w->focusable() ) {
|
||||
if (w->focusable()) {
|
||||
const auto distance = rect_distances(event, focus_screen_rect, w->screen_rect());
|
||||
if (distance >= 0) {
|
||||
|
||||
@@ -118,11 +118,11 @@ void Painter::paint_widget_tree(Widget* w) {
|
||||
|
||||
void Painter::paint_widget(Widget* w) {
|
||||
if (w->hidden()) {
|
||||
// Mark widget (and all children) as invisible.
|
||||
w->visible(false);
|
||||
// Mark widget (and all children) as not drawn.
|
||||
w->drawn(false);
|
||||
} else {
|
||||
// Mark this widget as visible and recurse.
|
||||
w->visible(true);
|
||||
// Mark this widget as drawn and recurse.
|
||||
w->drawn(true);
|
||||
|
||||
if (w->dirty()) {
|
||||
w->paint(*this);
|
||||
|
||||
@@ -91,9 +91,9 @@ void Widget::set_parent(Widget* const widget) {
|
||||
}
|
||||
|
||||
if (parent_ && !widget) {
|
||||
// We have a parent, but are losing it. Update visible status.
|
||||
// We have a parent, but are losing it. Update drawn status.
|
||||
dirty_overlapping_children_in_rect(screen_rect());
|
||||
visible(false);
|
||||
drawn(false);
|
||||
}
|
||||
|
||||
parent_ = widget;
|
||||
@@ -212,9 +212,9 @@ const Style& Widget::style() const {
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::visible(bool v) {
|
||||
if (v != flags.visible) {
|
||||
flags.visible = v;
|
||||
void Widget::drawn(bool v) {
|
||||
if (v != flags.drawn) {
|
||||
flags.drawn = v;
|
||||
|
||||
/* TODO: This on_show/on_hide implementation seems inelegant.
|
||||
* But I need *some* way to take/configure resources when
|
||||
@@ -227,9 +227,9 @@ void Widget::visible(bool v) {
|
||||
} else {
|
||||
on_hide();
|
||||
|
||||
// Set all children invisible too.
|
||||
// Mark all children as not drawn too.
|
||||
for (const auto child : children()) {
|
||||
child->visible(false);
|
||||
child->drawn(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -701,7 +701,7 @@ void Console::clear(bool clear_buffer = false) {
|
||||
if (clear_buffer)
|
||||
buffer.clear();
|
||||
|
||||
if (!hidden() && visible()) {
|
||||
if (!hidden() && drawn()) {
|
||||
_api->fill_rectangle(screen_rect().left(), screen_rect().top(), screen_rect().width(), screen_rect().height(), Theme::getInstance()->bg_darkest->background.v);
|
||||
}
|
||||
|
||||
@@ -711,7 +711,7 @@ void Console::clear(bool clear_buffer = false) {
|
||||
void Console::write(std::string message) {
|
||||
bool escape = false;
|
||||
|
||||
if (!hidden() && visible()) {
|
||||
if (!hidden() && drawn()) {
|
||||
const Style& s = style();
|
||||
const Font& font = s.font;
|
||||
auto rect = screen_rect();
|
||||
@@ -803,7 +803,7 @@ void Console::on_hide() {
|
||||
}
|
||||
|
||||
void Console::crlf() {
|
||||
if (hidden() || !visible())
|
||||
if (hidden() || !drawn())
|
||||
return;
|
||||
|
||||
const auto& s = style();
|
||||
|
||||
@@ -127,8 +127,8 @@ class Widget {
|
||||
bool dirty() const;
|
||||
void set_clean();
|
||||
|
||||
void visible(bool v);
|
||||
bool visible() { return flags.visible; };
|
||||
void drawn(bool v);
|
||||
bool drawn() const { return flags.drawn; };
|
||||
|
||||
bool highlighted() const;
|
||||
void set_highlighted(const bool value);
|
||||
@@ -147,7 +147,7 @@ class Widget {
|
||||
bool hidden : 1; // Hide widget and children.
|
||||
bool focusable : 1; // Widget can receive focus.
|
||||
bool highlighted : 1; // Show in a highlighted style.
|
||||
bool visible : 1; // Object was visible during last paint.
|
||||
bool drawn : 1; // Object was drawn during last paint.
|
||||
};
|
||||
|
||||
flags_t flags{
|
||||
@@ -155,7 +155,7 @@ class Widget {
|
||||
.hidden = false,
|
||||
.focusable = false,
|
||||
.highlighted = false,
|
||||
.visible = false,
|
||||
.drawn = false,
|
||||
};
|
||||
|
||||
static const std::vector<Widget*> no_children;
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
Plug HackRF+Portapack, set it in HackRF mode, launch flash_portapack_mayhem.bat
|
||||
Run mayhem_flasher.bat and follow the prompts to select your device and action (flash Mayhem, DFU unbrick, or restore factory firmware).
|
||||
|
||||
Read https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware for more detailed information
|
||||
Read https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware for more detailed information.
|
||||
@@ -1,19 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo *** Run HackRF firmware in RAM via LPC DFU ***
|
||||
echo.
|
||||
echo This is used to "unbrick" your HackRF, if you are no longer able to use
|
||||
echo HackRF tools to flash or operate your HackRF.
|
||||
echo.
|
||||
echo Connect your HackRF One to a USB port on your computer.
|
||||
echo.
|
||||
echo Hold down both the DFU and RESET buttons on the HackRF.
|
||||
echo Then release the RESET button (closest to the edge).
|
||||
echo Then release the DFU button.
|
||||
echo.
|
||||
pause
|
||||
|
||||
echo.
|
||||
"utils/dfu-util-static.exe" --device 1fc9:000c --download "utils/hackrf_one_usb.dfu" --reset
|
||||
echo.
|
||||
pause
|
||||
@@ -1,32 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo *** Re-flash the HackRF with PortaPack firmware ***
|
||||
echo.
|
||||
echo Connect your HackRF One to a USB port on your computer.
|
||||
echo.
|
||||
echo If using a PortaPack, put the PortaPack in HackRF mode by selecting
|
||||
echo the "HackRF" option from the main menu.
|
||||
echo.
|
||||
pause
|
||||
|
||||
echo.
|
||||
|
||||
REM Check if the firmware file exists
|
||||
if not exist portapack-mayhem-firmware.bin (
|
||||
echo The firmware file "portapack-mayhem-firmware.bin" does not exist.
|
||||
echo Please ensure that you have downloaded the latest release from:
|
||||
echo https://github.com/portapack-mayhem/mayhem-firmware/releases/
|
||||
echo.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
"utils/hackrf_update.exe" portapack-mayhem-firmware.bin
|
||||
echo.
|
||||
echo If your device never boot after flashing, please refer to won't boot article"
|
||||
echo.
|
||||
echo "click-to-open url: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won%%27t-boot"
|
||||
echo "or
|
||||
echo "copy-and-paste url: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot"
|
||||
echo.
|
||||
pause
|
||||
@@ -0,0 +1,310 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
echo =========================================================
|
||||
echo PortaPack Mayhem - Device Flasher
|
||||
echo =========================================================
|
||||
echo.
|
||||
echo Connect your device to a USB port on your computer.
|
||||
echo.
|
||||
|
||||
REM ── Step 1: Select device ─────────────────────────────────
|
||||
echo What is your device?
|
||||
echo.
|
||||
echo 1. HackRF One / PortaPack
|
||||
echo 2. PortaRF
|
||||
echo 3. HackRF Pro / PortaPack
|
||||
echo.
|
||||
set /p DEVICE_CHOICE="Enter your choice (1, 2 or 3): "
|
||||
|
||||
if "%DEVICE_CHOICE%"=="1" set DEVICE_NAME=HackRF One / PortaPack
|
||||
if "%DEVICE_CHOICE%"=="2" set DEVICE_NAME=PortaRF
|
||||
if "%DEVICE_CHOICE%"=="3" set DEVICE_NAME=HackRF Pro / PortaPack
|
||||
|
||||
if not defined DEVICE_NAME (
|
||||
echo.
|
||||
echo Invalid choice. Please run the script again and enter 1, 2, or 3.
|
||||
echo.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Device: %DEVICE_NAME%
|
||||
echo.
|
||||
|
||||
REM ── Step 2: Select action ──────────────────────────────────
|
||||
echo What would you like to do?
|
||||
echo.
|
||||
echo 1. Flash Mayhem firmware
|
||||
echo 2. Flash DFU then Mayhem (DFU unbrick followed by Mayhem flash in one go)
|
||||
echo 3. Flash via DFU (unbrick - run HackRF firmware from RAM)
|
||||
echo 4. Flash factory HackRF firmware (HackRF only - removes PortaPack support)
|
||||
echo.
|
||||
set /p ACTION_CHOICE="Enter your choice (1, 2, 3 or 4): "
|
||||
|
||||
if "%ACTION_CHOICE%"=="1" goto :flash_mayhem
|
||||
if "%ACTION_CHOICE%"=="2" goto :flash_dfu_then_mayhem
|
||||
if "%ACTION_CHOICE%"=="3" goto :flash_dfu
|
||||
if "%ACTION_CHOICE%"=="4" goto :flash_factory
|
||||
|
||||
echo.
|
||||
echo Invalid choice. Please run the script again and enter 1, 2, 3, or 4.
|
||||
echo.
|
||||
pause
|
||||
exit /b
|
||||
|
||||
|
||||
REM ── Action 1: Flash Mayhem ─────────────────────────────────
|
||||
:flash_mayhem
|
||||
echo.
|
||||
call :set_firmware
|
||||
if "%DEVICE_CHOICE%"=="1" (
|
||||
echo If your device has a PortaPack attached, switch it to HackRF mode first by
|
||||
echo selecting the "HackRF" option from the main menu.
|
||||
echo.
|
||||
)
|
||||
echo Firmware: %FIRMWARE%
|
||||
echo.
|
||||
call :check_file_exists "%FIRMWARE%" "firmware file" || (pause & exit /b)
|
||||
call :do_flash
|
||||
if "!FLASH_OK!"=="1" call :print_success
|
||||
pause
|
||||
exit /b
|
||||
|
||||
|
||||
REM ── Action 2: DFU unbrick ──────────────────────────────────
|
||||
:flash_dfu
|
||||
echo.
|
||||
echo *** Load HackRF firmware into RAM via LPC DFU ***
|
||||
echo.
|
||||
echo Use this to unbrick your device if you can no longer use HackRF tools
|
||||
echo to flash or communicate with it.
|
||||
echo.
|
||||
echo Before pressing any key, put your device into DFU mode:
|
||||
echo 1. Hold down both the DFU and RESET buttons.
|
||||
echo 2. Release the RESET button first (the one closest to the edge).
|
||||
echo 3. Then release the DFU button.
|
||||
echo.
|
||||
call :set_dfu_file
|
||||
call :check_file_exists "%DFU_FILE%" "DFU file" || (pause & exit /b)
|
||||
echo.
|
||||
call :do_dfu
|
||||
echo.
|
||||
pause
|
||||
exit /b
|
||||
|
||||
|
||||
REM ── Action 3: DFU then flash Mayhem ─────────────────────────
|
||||
:flash_dfu_then_mayhem
|
||||
echo.
|
||||
echo *** DFU unbrick followed by Mayhem firmware flash ***
|
||||
echo.
|
||||
echo This will first load HackRF firmware into RAM via DFU, then flash Mayhem.
|
||||
echo.
|
||||
echo Before pressing any key, put your device into DFU mode:
|
||||
echo 1. Hold down both the DFU and RESET buttons.
|
||||
echo 2. Release the RESET button first (the one closest to the edge).
|
||||
echo 3. Then release the DFU button.
|
||||
echo.
|
||||
call :set_dfu_file
|
||||
call :check_file_exists "%DFU_FILE%" "DFU file" || (pause & exit /b)
|
||||
call :set_firmware
|
||||
call :check_file_exists "%FIRMWARE%" "firmware file" || (pause & exit /b)
|
||||
|
||||
echo Step 1 of 2: Loading HackRF firmware via DFU...
|
||||
echo.
|
||||
call :do_dfu
|
||||
|
||||
echo.
|
||||
echo DFU complete. Waiting 5 seconds for device to reconnect...
|
||||
timeout /t 5 /nobreak >nul
|
||||
|
||||
echo.
|
||||
echo Step 2 of 2: Flashing Mayhem firmware (%FIRMWARE%)...
|
||||
echo.
|
||||
call :do_flash
|
||||
if "!FLASH_OK!"=="1" call :print_success
|
||||
pause
|
||||
exit /b
|
||||
|
||||
|
||||
REM ── Serial fallback: boot device into HackRF mode via serial ──
|
||||
:serial_fallback
|
||||
set SERIAL_OK=0
|
||||
set COM_PORT=
|
||||
echo.
|
||||
|
||||
REM Enumerate available COM ports into a temp file
|
||||
powershell -NoProfile -Command "[System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object | ForEach-Object { $_ }" > "%TEMP%\mayhem_comports.txt" 2>nul
|
||||
|
||||
REM Count and index ports
|
||||
set PORT_COUNT=0
|
||||
for /f "usebackq tokens=*" %%P in ("%TEMP%\mayhem_comports.txt") do (
|
||||
set /a PORT_COUNT+=1
|
||||
set PORT_!PORT_COUNT!=%%P
|
||||
)
|
||||
|
||||
if !PORT_COUNT! equ 0 (
|
||||
goto :eof
|
||||
)
|
||||
|
||||
if !PORT_COUNT! equ 1 (
|
||||
set COM_PORT=!PORT_1!
|
||||
) else (
|
||||
echo Multiple COM ports detected. Select the one your device is on:
|
||||
echo.
|
||||
for /l %%I in (1,1,!PORT_COUNT!) do (
|
||||
echo %%I. !PORT_%%I!
|
||||
)
|
||||
echo.
|
||||
set /p PORT_CHOICE="Enter your choice (1-!PORT_COUNT!): "
|
||||
set COM_PORT=
|
||||
for /l %%I in (1,1,!PORT_COUNT!) do (
|
||||
if "!PORT_CHOICE!"=="%%I" set COM_PORT=!PORT_%%I!
|
||||
)
|
||||
if not defined COM_PORT (
|
||||
echo Invalid selection.
|
||||
goto :eof
|
||||
)
|
||||
)
|
||||
|
||||
echo Connecting to !COM_PORT!...
|
||||
powershell -NoProfile -Command "$p = New-Object System.IO.Ports.SerialPort('!COM_PORT!', 115200, [System.IO.Ports.Parity]::None, 8, [System.IO.Ports.StopBits]::One); try { $p.Open(); $p.Write('hackrf' + [char]13 + [char]10); Start-Sleep -Milliseconds 500 } catch { Write-Host ('Serial error: ' + $_.Exception.Message); exit 1 }; try { $p.Close() } catch {}; exit 0"
|
||||
if %ERRORLEVEL% equ 0 (
|
||||
set SERIAL_OK=1
|
||||
echo Switching to HackRF mode, waiting for device...
|
||||
timeout /t 7 /nobreak >nul
|
||||
) else (
|
||||
echo Could not open !COM_PORT!. Make sure the port is not in use.
|
||||
)
|
||||
goto :eof
|
||||
|
||||
|
||||
REM ── Action 4: Flash factory HackRF firmware ────────────────
|
||||
:flash_factory
|
||||
echo.
|
||||
echo *** Restore factory HackRF firmware ***
|
||||
echo.
|
||||
echo This will remove Mayhem and restore the original HackRF firmware.
|
||||
echo PortaPack functionality will no longer be available after this.
|
||||
echo.
|
||||
|
||||
if "%DEVICE_CHOICE%"=="3" (
|
||||
set FACTORY_BIN=firmware\hackrf_hpro_usb.bin
|
||||
) else (
|
||||
set FACTORY_BIN=firmware\hackrf_usb.bin
|
||||
)
|
||||
|
||||
call :check_file_exists "%FACTORY_BIN%" "factory firmware" || (pause & exit /b)
|
||||
echo.
|
||||
"utils/hackrf_spiflash.exe" -R -w "%FACTORY_BIN%"
|
||||
echo.
|
||||
pause
|
||||
exit /b
|
||||
|
||||
|
||||
REM ── Helper: Set FIRMWARE based on DEVICE_CHOICE ─────────────
|
||||
:set_firmware
|
||||
if "%DEVICE_CHOICE%"=="1" set FIRMWARE=firmware\firmware_hackrf.bin
|
||||
if "%DEVICE_CHOICE%"=="2" set FIRMWARE=firmware\firmware_portarf.bin
|
||||
if "%DEVICE_CHOICE%"=="3" set FIRMWARE=firmware\firmware_hpro.bin
|
||||
goto :eof
|
||||
|
||||
|
||||
REM ── Helper: Set DFU_FILE based on DEVICE_CHOICE ─────────────
|
||||
:set_dfu_file
|
||||
if "%DEVICE_CHOICE%"=="3" (
|
||||
set DFU_FILE=firmware\hackrf_hpro_usb.dfu
|
||||
) else (
|
||||
set DFU_FILE=firmware\hackrf_usb.dfu
|
||||
)
|
||||
goto :eof
|
||||
|
||||
|
||||
REM ── Helper: Check a required file exists ─────────────────────
|
||||
REM %1 = quoted file path %2 = human-readable label
|
||||
:check_file_exists
|
||||
if not exist %1 (
|
||||
echo ERROR: The %~2 file %1 was not found.
|
||||
echo Please ensure you have downloaded the latest release from:
|
||||
echo https://release.hackrf.app/
|
||||
echo.
|
||||
exit /b 1
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
|
||||
REM ── Helper: Flash FIRMWARE with serial fallback ───────────────
|
||||
:do_flash
|
||||
set FLASH_OK=0
|
||||
"utils/hackrf_spiflash.exe" -R -w "%FIRMWARE%"
|
||||
if %ERRORLEVEL% equ 0 (
|
||||
set FLASH_OK=1
|
||||
goto :eof
|
||||
)
|
||||
call :serial_fallback
|
||||
if "!SERIAL_OK!"=="1" (
|
||||
echo.
|
||||
echo Device should now be in HackRF mode. Retrying flash...
|
||||
echo.
|
||||
"utils/hackrf_spiflash.exe" -R -w "%FIRMWARE%"
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
set FLASH_OK=1
|
||||
) else (
|
||||
echo.
|
||||
call :print_error
|
||||
)
|
||||
) else (
|
||||
echo.
|
||||
call :print_error
|
||||
)
|
||||
goto :eof
|
||||
|
||||
|
||||
REM ── Helper: Run DFU flash ────────────────────────────────────
|
||||
:do_dfu
|
||||
"utils/dfu-util-static.exe" --device 1fc9:000c --download "%DFU_FILE%"
|
||||
goto :eof
|
||||
|
||||
|
||||
REM ── Helper: Print error banner ──────────────────────────────
|
||||
:print_error
|
||||
echo.
|
||||
echo $$$$$$\
|
||||
echo $$ __$$\
|
||||
echo $$ / $$ ^| $$$$$$\ $$$$$$\ $$$$$$$\
|
||||
echo $$ ^| $$ ^|$$ __$$\ $$ __$$\ $$ _____^|
|
||||
echo $$ ^| $$ ^|$$ / $$ ^|$$ / $$ ^|\$$$$$$\
|
||||
echo $$ ^| $$ ^|$$ ^| $$ ^|$$ ^| $$ ^| \____$$\
|
||||
echo $$$$$$ ^|\$$$$$$ ^|$$$$$$$ ^|$$$$$$$ ^|
|
||||
echo \______/ \______/ $$ ____/ \_______/
|
||||
echo $$ ^|
|
||||
echo $$ ^|
|
||||
echo \__^|
|
||||
echo.
|
||||
echo ERROR: Device not found. Make sure your device is connected,
|
||||
echo powered on, and in HackRF mode, then try again.
|
||||
echo.
|
||||
goto :eof
|
||||
|
||||
|
||||
REM ── Helper: Print success banner ─────────────────────────────
|
||||
:print_success
|
||||
echo.
|
||||
echo ______ __ __ ______ ______ ________ ______ ______
|
||||
echo / \ ^| \ ^| \ / \ / \ ^| \ / \ / \
|
||||
echo ^| $$$$$$\^| $$ ^| $$^| $$$$$$\^| $$$$$$\^| $$$$$$$$^| $$$$$$\^| $$$$$$\
|
||||
echo ^| $$___\$$^| $$ ^| $$^| $$ \$$^| $$ \$$^| $$__ ^| $$___\$$^| $$___\$$
|
||||
echo \$$ \ ^| $$ ^| $$^| $$ ^| $$ ^| $$ \ \$$ \ \$$ \
|
||||
echo _\$$$$$$\^| $$ ^| $$^| $$ __ ^| $$ __ ^| $$$$$ _\$$$$$$\ _\$$$$$$\
|
||||
echo ^| \__^| $$^| $$__/ $$^| $$__/ \^| $$__/ \^| $$_____ ^| \__^| $$^| \__^| $$
|
||||
echo \$$ $$ \$$ $$ \$$ $$ \$$ $$^| $$ \ \$$ $$ \$$ $$
|
||||
echo \$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$ \$$$$$$$$ \$$$$$$ \$$$$$$
|
||||
echo.
|
||||
echo Firmware flashed successfully. Your device is rebooting...
|
||||
echo.
|
||||
echo If your device does not boot after flashing, see the troubleshooting wiki:
|
||||
echo https://github.com/portapack-mayhem/mayhem-firmware/wiki/Wont-boot
|
||||
echo.
|
||||
goto :eof
|
||||
@@ -1,15 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo *** Re-flash the HackRF with HackRF firmware ***
|
||||
echo.
|
||||
echo Connect your HackRF One to a USB port on your computer.
|
||||
echo.
|
||||
echo If using a PortaPack, put the PortaPack in HackRF mode by selecting
|
||||
echo the "HackRF" option from the main menu.
|
||||
echo.
|
||||
pause
|
||||
|
||||
echo.
|
||||
"utils/hackrf_update.exe" "utils/hackrf_one_usb.bin"
|
||||
echo.
|
||||
pause
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
Submodule hackrf updated: 7d16b954ad...38e082b939
Reference in New Issue
Block a user