diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 7fbabb12e..4b02659f4 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -135,7 +135,9 @@ set(CPPSRC touch_adc.cpp encoder.cpp audio.cpp + adsb.cpp afsk.cpp + rds.cpp ${COMMON}/lcd_ili9341.cpp ${COMMON}/ui.cpp ${COMMON}/ui_text.cpp @@ -170,7 +172,8 @@ set(CPPSRC ui_rds.cpp ui_lcr.cpp ui_xylos.cpp - # ui_freqman.cpp + ui_numbers.cpp + ui_freqman.cpp ui_encoders.cpp ui_jammer.cpp # ui_loadmodule.cpp diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 5e619c849..b4bb4d0c2 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -2478,6 +2478,30 @@ __/common/wm8731.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/__/common/wm8731.cpp.s .PHONY : __/common/wm8731.cpp.s +adsb.obj: adsb.cpp.obj +.PHONY : adsb.obj + +# target to build an object file +adsb.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/adsb.cpp.obj +.PHONY : adsb.cpp.obj + +adsb.i: adsb.cpp.i +.PHONY : adsb.i + +# target to preprocess a source file +adsb.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/adsb.cpp.i +.PHONY : adsb.cpp.i + +adsb.s: adsb.cpp.s +.PHONY : adsb.s + +# target to generate assembly for a file +adsb.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/adsb.cpp.s +.PHONY : adsb.cpp.s + afsk.obj: afsk.cpp.obj .PHONY : afsk.obj @@ -3150,6 +3174,30 @@ radio.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/radio.cpp.s .PHONY : radio.cpp.s +rds.obj: rds.cpp.obj +.PHONY : rds.obj + +# target to build an object file +rds.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/rds.cpp.obj +.PHONY : rds.cpp.obj + +rds.i: rds.cpp.i +.PHONY : rds.i + +# target to preprocess a source file +rds.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/rds.cpp.i +.PHONY : rds.cpp.i + +rds.s: rds.cpp.s +.PHONY : rds.s + +# target to generate assembly for a file +rds.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/rds.cpp.s +.PHONY : rds.cpp.s + receiver_model.obj: receiver_model.cpp.obj .PHONY : receiver_model.obj @@ -3798,6 +3846,30 @@ ui_font_fixed_8x16.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_font_fixed_8x16.cpp.s .PHONY : ui_font_fixed_8x16.cpp.s +ui_freqman.obj: ui_freqman.cpp.obj +.PHONY : ui_freqman.obj + +# target to build an object file +ui_freqman.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_freqman.cpp.obj +.PHONY : ui_freqman.cpp.obj + +ui_freqman.i: ui_freqman.cpp.i +.PHONY : ui_freqman.i + +# target to preprocess a source file +ui_freqman.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_freqman.cpp.i +.PHONY : ui_freqman.cpp.i + +ui_freqman.s: ui_freqman.cpp.s +.PHONY : ui_freqman.s + +# target to generate assembly for a file +ui_freqman.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_freqman.cpp.s +.PHONY : ui_freqman.cpp.s + ui_handwrite.obj: ui_handwrite.cpp.obj .PHONY : ui_handwrite.obj @@ -3918,6 +3990,30 @@ ui_navigation.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_navigation.cpp.s .PHONY : ui_navigation.cpp.s +ui_numbers.obj: ui_numbers.cpp.obj +.PHONY : ui_numbers.obj + +# target to build an object file +ui_numbers.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_numbers.cpp.obj +.PHONY : ui_numbers.cpp.obj + +ui_numbers.i: ui_numbers.cpp.i +.PHONY : ui_numbers.i + +# target to preprocess a source file +ui_numbers.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_numbers.cpp.i +.PHONY : ui_numbers.cpp.i + +ui_numbers.s: ui_numbers.cpp.s +.PHONY : ui_numbers.s + +# target to generate assembly for a file +ui_numbers.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_numbers.cpp.s +.PHONY : ui_numbers.cpp.s + ui_nuoptix.obj: ui_nuoptix.cpp.obj .PHONY : ui_nuoptix.obj @@ -4534,6 +4630,9 @@ help: @echo "... __/common/wm8731.obj" @echo "... __/common/wm8731.i" @echo "... __/common/wm8731.s" + @echo "... adsb.obj" + @echo "... adsb.i" + @echo "... adsb.s" @echo "... afsk.obj" @echo "... afsk.i" @echo "... afsk.s" @@ -4618,6 +4717,9 @@ help: @echo "... radio.obj" @echo "... radio.i" @echo "... radio.s" + @echo "... rds.obj" + @echo "... rds.i" + @echo "... rds.s" @echo "... receiver_model.obj" @echo "... receiver_model.i" @echo "... receiver_model.s" @@ -4699,6 +4801,9 @@ help: @echo "... ui_font_fixed_8x16.obj" @echo "... ui_font_fixed_8x16.i" @echo "... ui_font_fixed_8x16.s" + @echo "... ui_freqman.obj" + @echo "... ui_freqman.i" + @echo "... ui_freqman.s" @echo "... ui_handwrite.obj" @echo "... ui_handwrite.i" @echo "... ui_handwrite.s" @@ -4714,6 +4819,9 @@ help: @echo "... ui_navigation.obj" @echo "... ui_navigation.i" @echo "... ui_navigation.s" + @echo "... ui_numbers.obj" + @echo "... ui_numbers.i" + @echo "... ui_numbers.s" @echo "... ui_nuoptix.obj" @echo "... ui_nuoptix.i" @echo "... ui_nuoptix.s" diff --git a/firmware/application/adsb.cpp b/firmware/application/adsb.cpp new file mode 100644 index 000000000..d1e8ccabd --- /dev/null +++ b/firmware/application/adsb.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 "adsb.hpp" + +#include "portapack_persistent_memory.hpp" + +namespace adsb { + +void make_frame_mode_s(uint8_t * adsb_frame, uint32_t ICAO_address) { + adsb_frame[0] = 0x8D; // DF and CA + adsb_frame[1] = ICAO_address >> 16; + adsb_frame[2] = (ICAO_address >> 8) & 0xFF; + adsb_frame[3] = ICAO_address & 0xFF; +} + +void generate_frame_id(uint8_t * adsb_frame, uint32_t ICAO_address, char * callsign) { + uint8_t c, s; + char ch; + std::string callsign_formatted(8, '_'); + uint64_t callsign_coded = 0; + + make_frame_mode_s(adsb_frame, ICAO_address); + + adsb_frame[4] = 0x20; // TC + + // Translate and code callsign + for (c = 0; c < 8; c++) { + ch = callsign[c]; + for (s = 0; s < 64; s++) { + if (ch == icao_id_lut[s]) break; + } + if (s < 64) { + ch = icao_id_lut[s]; + } else { + ch = ' '; + s = 32; + } + callsign_coded |= ((uint64_t)s << ((7 - c) * 6)); + callsign_formatted[c] = ch; + } + + // Insert callsign in frame + for (c = 0; c < 6; c++) + adsb_frame[c + 5] = (callsign_coded >> ((5 - c) * 8)) & 0xFF; + + ADSB_generate_CRC(adsb_frame); +} + +void generate_frame_pos(uint8_t * adsb_frame, uint32_t ICAO_address, uint32_t altitude, float latitude, float longitude) { + uint8_t c, time_parity; + uint32_t altitude_coded; + uint32_t LAT, LON; + float delta_lat, yz, rlat, delta_lon, xz; + + LAT = 0; + + make_frame_mode_s(adsb_frame, ICAO_address); + + adsb_frame[4] = 0x58; // TC, SS and NICsb + + altitude_coded = (altitude + 1000) / 25; // Can be coded in 100ft steps also + + // LAT: + // index j = floor(59*latcprE-60*latcprO+0.50) + // latE = DlatE*(mod(j,60)+latcprE) + // latO = DlatO*(mod(j,59)+latcprO) + // if latE >= 270 -> latE -= 360 + // if latO >= 270 -> latO -= 360 + //time_parity = 0; // 0~1 + //delta_lat = 90.0 / (60.0 - (time_parity / 4.0)); + //yz = 524288.0 * (mod(lat, delta_lat) / delta_lat); // Round to int ! + //rlat = delta_lat * ((yz / 524288.0) + int(lat / delta_lat)); + //delta_lon = 360.0 / (NL(rlat) - time_parity); + //xz = 524288.0 * (mod(lon, delta_lon) / delta_lon); // Round to int ! + /*if (time_parity) { + A = sign(rlat0)[NL(rlat0) - NL(rlat1)]; + }*/ + // int xz and yz, then: + // xz >>= 2; + // yz >>= 2; + // To get 17 bits + + // aaaaaaa Q bbbb + adsb_frame[5] = ((altitude_coded & 0x7F0) >> 3) | 1; + adsb_frame[6] = ((altitude_coded & 0x00F) << 4) | (LAT >> 15); // Then 0, even/odd, and the 2 LAT-CPR MSBs +} + +void ADSB_generate_CRC(uint8_t * in_frame) { + uint8_t adsb_crc[14]; // Temp buffer + uint8_t b, c, s, bitn; + const uint32_t crc_poly = 0x1205FFF; + + in_frame[11] = 0x00; // Clear CRC + in_frame[12] = 0x00; + in_frame[13] = 0x00; + + // Compute CRC + memcpy(adsb_crc, in_frame, 14); + for (c = 0; c < 11; c++) { + for (b = 0; b < 8; b++) { + if ((adsb_crc[c] << b) & 0x80) { + for (s = 0; s < 25; s++) { + bitn = (c * 8) + b + s; + if ((crc_poly >> s) & 1) adsb_crc[bitn >> 3] ^= (0x80 >> (bitn & 7)); + } + } + } + } + // Insert CRC in frame + for (c = 0; c < 3; c++) + in_frame[c + 11] = adsb_crc[c + 11]; + +} + +} /* namespace adsb */ diff --git a/firmware/application/adsb.hpp b/firmware/application/adsb.hpp new file mode 100644 index 000000000..b2f134d1b --- /dev/null +++ b/firmware/application/adsb.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 +#include + +#ifndef __ADSB_H__ +#define __ADSB_H__ + +namespace adsb { + + const char icao_id_lut[65] = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"; + + void make_frame_mode_s(uint8_t * adsb_frame, uint32_t ICAO_address); + + void generate_frame_id(uint8_t * adsb_frame, uint32_t ICAO_address, char * callsign); + void generate_frame_pos(uint8_t * adsb_frame, uint32_t ICAO_address, uint32_t altitude, float latitude, float longitude); + + void ADSB_generate_CRC(uint8_t * in_message); + +} /* namespace adsb */ + +#endif/*__ADSB_H__*/ diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index 0719f239e..04131f28e 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -151,6 +151,13 @@ void set_adsb() { send_message(&message); } +void set_rds_data(const uint16_t message_length) { + const RDSConfigureMessage message { + message_length + }; + send_message(&message); +} + void set_dtmf_data(const uint32_t bw, const uint32_t tone_length, const uint32_t pause_length) { const DTMFTXConfigMessage message { bw, diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index 59a3fdb7c..6b329f4c4 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -63,6 +63,7 @@ void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t pause_symbols); void set_pocsag(); void set_adsb(); +void set_rds_data(const uint16_t message_length); void set_dtmf_data(const uint32_t bw, const uint32_t tone_length, const uint32_t pause_length); void run_image(const portapack::spi_flash::image_tag_t image_tag); diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 68d21301e..cedaed355 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -23,9 +23,14 @@ // Bitmaps generated with: // Gimp image > indexed colors (16), then "xxd -i *.bmp" +//TEST: Numbers +//TEST: Jammer +//TEST: RDS + //BUG: Unistroke text entry screen doesn't care about string max length parameter //BUG (fixed ?): No audio in about when shown second time //BUG: POCSAG RX sometimes misses the first codeword after SYNC +//BUG: Soundboard crashes on exit if no wav files on sd card //TODO: Use ModalMessageView with yes/no for TX //TODO: Show address/data bit fields in OOK TX @@ -33,7 +38,6 @@ //TODO: Check more OOK encoders //TODO: POCSAG 512 and 2400 (all 3 at the same time, or parameter ?) //TODO: Check AFSK transmit end, skips last bits ? -//TODO: Check jammer bandwidths //TODO: Use msgpack for settings, lists... on sd card //TODO: Frequency manager //TODO: Morse coder diff --git a/firmware/application/rds.cpp b/firmware/application/rds.cpp new file mode 100644 index 000000000..151ff82b2 --- /dev/null +++ b/firmware/application/rds.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 "rds.hpp" + +#include "portapack_shared_memory.hpp" + +namespace rds { + +uint32_t makeblock(uint32_t blockdata, uint16_t offset) { + uint16_t CRC = 0; + uint8_t doinv; + + for (uint8_t i = 0; i < 16; i++) { + doinv = (((blockdata << i) & 0x8000) >> 15) ^ (CRC >> 9); + if (doinv) CRC ^= 0b0011011100; + CRC = ((CRC << 1) | doinv) & 0x3FF; + } + + return (blockdata << 10) | (CRC ^ offset); +} + +// Todo: +// Make PI +// TA/TP flags +// Group selection + +// Boolean to binary +uint8_t b2b(const bool in) { + if (in) + return 1; + else + return 0; +} + +void make_0B_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, + const bool MS, const bool DI, const uint8_t C, const char * chars) { + + group[0] = PI_code; + group[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | (PTY << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3); + group[2] = PI_code; + group[3] = (chars[0] << 8) | chars[1]; +} + +void make_2A_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, + const uint8_t segment, const char * chars) { + + group[0] = PI_code; + group[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | (PTY << 5) | (b2b(AB) << 4) | (segment & 15); + group[2] = (chars[0] << 8) | chars[1]; + group[3] = (chars[2] << 8) | chars[3]; +} + +uint16_t gen_PSN(const char * psname, const uint8_t pty) { + uint8_t c; + uint32_t group[4][4] = { 0 }; + + // 4 groups with 2 PSN characters in each + make_0B_group(&group[0][0], 0xF849, true, pty, false, true, false, 0, &psname[0]); + make_0B_group(&group[1][0], 0xF849, true, pty, false, true, false, 1, &psname[2]); + make_0B_group(&group[2][0], 0xF849, true, pty, false, true, false, 2, &psname[4]); + make_0B_group(&group[3][0], 0xF849, true, pty, false, true, false, 3, &psname[6]); + + /*uint32_t group[4][4] = { + { + 0b1111100001001001, //PI + 0b0000110011101000, //Address + 0b1111100001001001, //PI + 0b0000000000000000 //Replaced + }, + + { + 0b1111100001001001, //PI + 0b0000110011101001, //Address + 0b1111100001001001, //PI + 0b0000000000000000 //Replaced + }, + + { + 0b1111100001001001, //PI + 0b0000110011101010, //Address + 0b1111100001001001, //PI + 0b0000000000000000 //Replaced + }, + + { + 0b1111100001001001, //PI + 0b0000110011101011, //Address + 0b1111100001001001, //PI + 0b0000000000000000 //Replaced + }, + }; + + //Insert PSN data in groups + group[0][3] = (psname[0] << 8) | psname[1]; + group[1][3] = (psname[2] << 8) | psname[3]; + group[2][3] = (psname[4] << 8) | psname[5]; + group[3][3] = (psname[6] << 8) | psname[7]; + */ + + // Generate checkbits for all blocks + for (c = 0; c < 4; c++) { + group[c][0] = makeblock(group[c][0], RDS_OFFSET_A); + group[c][1] = makeblock(group[c][1], RDS_OFFSET_B); + group[c][2] = makeblock(group[c][2], RDS_OFFSET_Cp); // C' ! + group[c][3] = makeblock(group[c][3], RDS_OFFSET_D); + } + + // Todo + //for (c = 0; c < 16; c++) + // shared_memory.radio_data[c] = group[c >> 2][c & 3]; + + return 4 * 4 * 26; +} + +uint16_t gen_RadioText(const char * radiotext, const uint8_t pty) { + size_t c, i; + uint32_t * group; + char radiotext_buffer[65] = { 0 }; + uint8_t rtlen, groups; + + strcpy(radiotext_buffer, radiotext); + + rtlen = strlen(radiotext_buffer); + + radiotext_buffer[rtlen] = 0x0D; + + // Pad to multiple of 4 + while (rtlen & 3) + radiotext_buffer[rtlen++] = ' '; + + groups = rtlen >> 2; // 4 characters per group + + group = (uint32_t*)chHeapAlloc(0x0, 4 * groups * sizeof(uint32_t)); + + for (c = 0; c < groups; c++) + make_2A_group(&group[c << 2], 0xF849, true, pty, false, c, &radiotext_buffer[c * 4]); + + // Generate checkbits + for (c = 0; c < groups; c++) { + i = c * 4; + group[i + 0] = makeblock(group[i + 0], RDS_OFFSET_A); + group[i + 1] = makeblock(group[i + 1], RDS_OFFSET_B); + group[i + 2] = makeblock(group[i + 2], RDS_OFFSET_C); + group[i + 3] = makeblock(group[i + 3], RDS_OFFSET_D); + } + + uint32_t * tx_data_u32 = (uint32_t*)shared_memory.tx_data; + + for (c = 0; c < (groups * 4); c++) + tx_data_u32[c] = group[c]; + + return groups * 4 * 26; +} + +} /* namespace rds */ diff --git a/firmware/application/rds.hpp b/firmware/application/rds.hpp new file mode 100644 index 000000000..f5fcc7f9c --- /dev/null +++ b/firmware/application/rds.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 +#include +#include "ch.h" + +#ifndef __RDS_H__ +#define __RDS_H__ + +namespace rds { + +#define RDS_OFFSET_A 0b0011111100 +#define RDS_OFFSET_B 0b0110011000 +#define RDS_OFFSET_C 0b0101101000 +#define RDS_OFFSET_Cp 0b1101010000 +#define RDS_OFFSET_D 0b0110110100 + +uint32_t makeblock(uint32_t blockdata, uint16_t offset); +uint8_t b2b(const bool in); +void make_0B_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, + const bool MS, const bool DI, const uint8_t C, const char * chars); +void make_2A_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, + const uint8_t segment, const char * chars); +uint16_t gen_PSN(const char * psname, const uint8_t pty); +uint16_t gen_RadioText(const char * radiotext, const uint8_t pty); + +} /* namespace rds */ + +#endif/*__RDS_H__*/ diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp index 0a3a94000..53a9d76f0 100644 --- a/firmware/application/string_format.cpp +++ b/firmware/application/string_format.cpp @@ -55,6 +55,21 @@ static char* to_string_dec_uint_pad_internal( return q; } +std::string to_string_bin( + const uint32_t n, + const uint8_t l) +{ + char p[33]; + for (uint8_t c = 0; c < l; c++) { + if (n & (1 << (l - c))) + p[c] = '1'; + else + p[c] = '0'; + } + p[l] = 0; + return p; +} + std::string to_string_dec_uint( const uint32_t n, const int32_t l, diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp index 75f5457cc..3fa962773 100644 --- a/firmware/application/string_format.hpp +++ b/firmware/application/string_format.hpp @@ -30,6 +30,7 @@ using namespace lpc43xx; // TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp... +std::string to_string_bin(const uint32_t n, const uint8_t l = 0); std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0); std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0); std::string to_string_hex(const uint64_t n, const int32_t l = 0); diff --git a/firmware/application/ui_adsbtx.cpp b/firmware/application/ui_adsbtx.cpp index deb93ac36..e94e1114e 100644 --- a/firmware/application/ui_adsbtx.cpp +++ b/firmware/application/ui_adsbtx.cpp @@ -23,6 +23,7 @@ #include "ui_adsbtx.hpp" #include "ui_alphanum.hpp" +#include "adsb.hpp" #include "string_format.hpp" #include "portapack.hpp" #include "baseband_api.hpp" @@ -31,6 +32,7 @@ #include #include +using namespace adsb; using namespace portapack; namespace ui { @@ -45,149 +47,27 @@ ADSBTxView::~ADSBTxView() { } void ADSBTxView::paint(Painter& painter) { + (void)painter; button_callsign.set_text(callsign); } -void ADSBTxView::generate_frame_pos() { - uint8_t b, c, s, time_parity, bitn; - char ch; +void ADSBTxView::generate_frame() { + uint8_t c; std::string str_debug; - uint16_t altitude_coded = (38000 + 1000) / 25; - uint32_t LAT, LON; - float delta_lat, yz, rlat, delta_lon, xz; - uint8_t adsb_crc[14]; // Temp buffer - uint32_t crc_poly = 0x1205FFF; - - LAT = 0; - - adsb_frame[0] = (options_format.selected_index_value() << 3) | 5; // DF and CA - adsb_frame[1] = 0x48; // ICAO24 - adsb_frame[2] = 0x40; - adsb_frame[3] = 0xD6; - adsb_frame[4] = 0x58; // TC, SS and NICsb - - // altitude = (feet + 1000) / 25 - - // LAT: - // index j = floor(59*latcprE-60*latcprO+0.50) - // latE = DlatE*(mod(j,60)+latcprE) - // latO = DlatO*(mod(j,59)+latcprO) - // if latE >= 270 -> latE -= 360 - // if latO >= 270 -> latO -= 360 - //time_parity = 0; // 0~1 - //delta_lat = 90.0 / (60.0 - (time_parity / 4.0)); - //yz = 524288.0 * (mod(lat, delta_lat) / delta_lat); // Round to int ! - //rlat = delta_lat * ((yz / 524288.0) + int(lat / delta_lat)); - //delta_lon = 360.0 / (NL(rlat) - time_parity); - //xz = 524288.0 * (mod(lon, delta_lon) / delta_lon); // Round to int ! - /*if (time_parity) { - A = sign(rlat0)[NL(rlat0) - NL(rlat1)]; - }*/ - // int xz and yz, then: - // xz >>= 2; - // yz >>= 2; - // To get 17 bits - - // aaaaaaa Q bbbb - adsb_frame[5] = ((altitude_coded & 0x7F0) >> 3) | 1; - adsb_frame[6] = ((altitude_coded & 0x00F) << 4) | (LAT >> 15); // Then 0, even/odd, and the 2 LAT-CPR MSBs - - adsb_frame[11] = 0x00; // Clear CRC - adsb_frame[12] = 0x00; - adsb_frame[13] = 0x00; - - // Compute CRC - memcpy(adsb_crc, adsb_frame, 14); - for (c = 0; c < 11; c++) { - for (b = 0; b < 8; b++) { - if ((adsb_crc[c] << b) & 0x80) { - for (s = 0; s < 25; s++) { - bitn = (c * 8) + b + s; - if ((crc_poly >> s) & 1) adsb_crc[bitn >> 3] ^= (0x80 >> (bitn & 7)); - } - } - } - } - // Insert CRC in frame - for (c = 0; c < 3; c++) - adsb_frame[c + 11] = adsb_crc[c + 11]; - - // Convert to binary - for (c = 0; c < 112; c++) - adsb_bin[c] = (adsb_frame[c >> 3] >> (7 - (c & 7))) & 1; - - // Display for debug - str_debug = ""; - for (c = 0; c < 7; c++) - str_debug += to_string_hex(adsb_frame[c], 2); - text_frame_a.set(str_debug); - str_debug = ""; - for (c = 0; c < 7; c++) - str_debug += to_string_hex(adsb_frame[c + 7], 2); - text_frame_b.set(str_debug); -} -void ADSBTxView::generate_frame_id() { - uint8_t b, c, s, bitn; - char ch; - std::string str_debug; - std::string callsign_formatted(8, '_'); - uint64_t callsign_coded = 0; - uint8_t adsb_crc[14]; // Temp buffer - uint32_t crc_poly = 0x1205FFF; + if (options_format.selected_index() == 2) + button_transmit.hidden(true); - // Init frame - //memset(adsb_frame, 0, 120); - - adsb_frame[0] = (options_format.selected_index_value() << 3) | 5; // DF and CA - adsb_frame[1] = 0x48; // ICAO24 - adsb_frame[2] = 0x40; - adsb_frame[3] = 0xD6; - adsb_frame[4] = 0x20; // TC - - adsb_frame[11] = 0x00; // Clear CRC - adsb_frame[12] = 0x00; - adsb_frame[13] = 0x00; - - // Translate and code callsign - for (c = 0; c < 8; c++) { - ch = callsign[c]; - for (s = 0; s < 64; s++) { - if (ch == icao_id_lut[s]) break; - } - if (s < 64) { - ch = icao_id_lut[s]; - } else { - ch = ' '; - s = 32; - } - callsign_coded |= ((uint64_t)s << ((7 - c) * 6)); - callsign_formatted[c] = ch; - } - - // Insert callsign in frame - for (c = 0; c < 6; c++) - adsb_frame[c + 5] = (callsign_coded >> ((5 - c) * 8)) & 0xFF; - - // Compute CRC - memcpy(adsb_crc, adsb_frame, 14); - for (c = 0; c < 11; c++) { - for (b = 0; b < 8; b++) { - if ((adsb_crc[c] << b) & 0x80) { - for (s = 0; s < 25; s++) { - bitn = (c * 8) + b + s; - if ((crc_poly >> s) & 1) adsb_crc[bitn >> 3] ^= (0x80 >> (bitn & 7)); - } - } + generate_frame_id(adsb_frame, sym_icao.value_hex_u64(), callsign); + + memset(adsb_bin, 0, 112); + + // Convert to binary (1 bit per byte, faster for baseband code) + for (c = 0; c < 112; c++) { + if ((adsb_frame[c >> 3] << (c & 7)) & 0x80) { + adsb_bin[c] = 0xFF; } } - // Insert CRC in frame - for (c = 0; c < 3; c++) - adsb_frame[c + 11] = adsb_crc[c + 11]; - - // Convert to binary - for (c = 0; c < 112; c++) - adsb_bin[c] = (adsb_frame[c >> 3] >> (7 - (c & 7))) & 1; // Display for debug str_debug = ""; @@ -199,7 +79,7 @@ void ADSBTxView::generate_frame_id() { str_debug += to_string_hex(adsb_frame[c + 7], 2); text_frame_b.set(str_debug); - text_message.set(callsign_formatted); + //text_message.set(callsign_formatted); } void ADSBTxView::start_tx() { @@ -224,13 +104,13 @@ void ADSBTxView::on_txdone(const int n) { if (n == 200) { transmitter_model.disable(); - progress.set_value(0); + //progress.set_value(0); tx_mode = IDLE; button_transmit.set_style(&style_val); button_transmit.set_text("START"); } else { - progress.set_value(n); + //progress.set_value(n); } } @@ -245,32 +125,49 @@ ADSBTxView::ADSBTxView(NavigationView& nav) { &text_format, &options_format, &text_icaolabel, - &button_icao, + &sym_icao, &text_callsign, &button_callsign, + &text_altitude, + &field_altitude, + &text_latitude, + &field_lat_degrees, + &field_lat_minutes, + &field_lat_seconds, + &text_longitude, + &field_lon_degrees, + &field_lon_minutes, + &field_lon_seconds, &text_frame_a, &text_frame_b, - &progress, - &text_message, &button_transmit } }); options_format.set_by_value(17); // Mode S - progress.set_max(122); - options_format.on_change = [this](size_t i, int32_t v) { (void)i; (void)v; - generate_frame_id(); + generate_frame(); + }; + sym_icao.on_change = [this]() { + generate_frame(); }; button_callsign.on_select = [this, &nav](Button&) { textentry(nav, callsign, 9); }; + field_altitude.set_value(11000); + field_lat_degrees.set_value(0); + field_lat_minutes.set_value(0); + field_lat_seconds.set_value(0); + field_lon_degrees.set_value(0); + field_lon_minutes.set_value(0); + field_lon_seconds.set_value(0); + button_transmit.set_style(&style_val); - generate_frame_id(); + generate_frame(); // Single transmit button_transmit.on_select = [this, &nav](Button&) { @@ -278,7 +175,7 @@ ADSBTxView::ADSBTxView(NavigationView& nav) { tx_mode = SINGLE; button_transmit.set_style(&style_cancel); button_transmit.set_text("Wait"); - generate_frame_id(); + generate_frame(); start_tx(); } }; diff --git a/firmware/application/ui_adsbtx.hpp b/firmware/application/ui_adsbtx.hpp index 3e2631258..26ebf1417 100644 --- a/firmware/application/ui_adsbtx.hpp +++ b/firmware/application/ui_adsbtx.hpp @@ -57,10 +57,8 @@ private: uint8_t adsb_frame[14]; // 112 bit data block as 14 bytes uint8_t adsb_bin[112]; // 112 bit data block - const char icao_id_lut[65] = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"; - void start_tx(); - void generate_frame_id(); + void generate_frame(); void generate_frame_pos(); void on_txdone(const int n); @@ -74,18 +72,13 @@ private: .background = Color::red(), .foreground = Color::black(), }; - const Style style_grey { - .font = font::fixed_8x16, - .background = Color::black(), - .foreground = Color::grey(), - }; Text text_format { - { 4 * 8, 1 * 16, 7 * 8, 16 }, + { 2 * 8, 1 * 16, 7 * 8, 16 }, "Format:" }; OptionsField options_format { - { 12 * 8, 1 * 16 }, + { 10 * 8, 1 * 16 }, 10, { { "17: ADS-B", 17 }, @@ -95,40 +88,72 @@ private: }; Text text_icaolabel { - { 4 * 8, 3 * 16, 7 * 8, 16 }, + { 2 * 8, 2 * 16, 7 * 8, 16 }, "ICAO24:" }; - Button button_icao { - { 12 * 8, 2 * 16 + 12, 8 * 8, 24 }, - "012345" // 7277A9 + SymField sym_icao { + { 10 * 8, 2 * 16 }, + 6, + true // Hex }; Text text_callsign { - { 4 * 8, 4 * 16 + 8, 3 * 8, 16 }, + { 2 * 8, 3 * 16 + 8, 3 * 8, 16 }, "ID:" }; Button button_callsign { - { 8 * 8, 4 * 16 + 4, 10 * 8, 24 }, - "" // "KOR151 " + { 6 * 8, 3 * 16 + 4, 10 * 8, 24 }, + "" + }; + + Text text_altitude { + { 2 * 8, 5 * 16, 20 * 8, 16 }, + "Altitude: feet" + }; + NumberField field_altitude { + { 12 * 8, 5 * 16 }, + 5, + { -1000, 50000 }, + 250, + ' ' + }; + + Text text_latitude { + { 2 * 8, 6 * 16, 20 * 8, 16 }, + "Latitude: * ' \"" // No ° symbol in 8x16 font + }; + NumberField field_lat_degrees { + { 12 * 8, 6 * 16 }, 3, { -90, 90 }, 1, ' ' + }; + NumberField field_lat_minutes { + { 16 * 8, 6 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + NumberField field_lat_seconds { + { 19 * 8, 6 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + Text text_longitude { + { 2 * 8, 7 * 16, 20 * 8, 16 }, + "Longitude: * ' \"" // No ° symbol in 8x16 font + }; + NumberField field_lon_degrees { + { 12 * 8, 7 * 16 }, 3, { -90, 90 }, 1, ' ' + }; + NumberField field_lon_minutes { + { 16 * 8, 7 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + NumberField field_lon_seconds { + { 19 * 8, 7 * 16 }, 2, { 0, 59 }, 1, ' ' }; Text text_frame_a { - { 4 * 8, 10 * 16, 14 * 8, 16 }, + { 4 * 8, 12 * 16, 14 * 8, 16 }, "-" }; Text text_frame_b { - { 4 * 8, 11 * 16, 14 * 8, 16 }, + { 4 * 8, 13 * 16, 14 * 8, 16 }, "-" }; - ProgressBar progress { - { 5 * 8, 13 * 16, 20 * 8, 16 }, - }; - Text text_message { - { 5 * 8, 14 * 16, 20 * 8, 16 }, - "--------------------" - }; - Button button_transmit { { 2 * 8, 16 * 16, 64, 32 }, "START" diff --git a/firmware/application/ui_alphanum.cpp b/firmware/application/ui_alphanum.cpp index 6e49ce7d4..3c3c5cf32 100644 --- a/firmware/application/ui_alphanum.cpp +++ b/firmware/application/ui_alphanum.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek * * This file is part of PortaPack. * @@ -77,7 +78,7 @@ AlphanumView::AlphanumView( }; n = 0; - for(auto& button : buttons) { + for (auto& button : buttons) { button.on_select = button_fn; button.set_parent_rect({ static_cast((n % 5) * button_w), diff --git a/firmware/application/ui_alphanum.hpp b/firmware/application/ui_alphanum.hpp index 24aed4d01..f3fee6391 100644 --- a/firmware/application/ui_alphanum.hpp +++ b/firmware/application/ui_alphanum.hpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek * * This file is part of PortaPack. * @@ -54,7 +55,7 @@ private: bool _lowercase = false; static constexpr size_t button_w = 240 / 5; static constexpr size_t button_h = 28; - char txtinput[25] = {0}; + char txtinput[29] = { 0 }; // 28 chars max void char_add(const char c); void char_delete(); diff --git a/firmware/application/ui_freqman.cpp b/firmware/application/ui_freqman.cpp index cb8b6218f..1665f2421 100644 --- a/firmware/application/ui_freqman.cpp +++ b/firmware/application/ui_freqman.cpp @@ -23,11 +23,9 @@ #include "ui_freqman.hpp" #include "ch.h" - #include "ff.h" #include "portapack.hpp" #include "event_m0.hpp" -#include "hackrf_hal.hpp" #include "portapack_shared_memory.hpp" #include @@ -69,12 +67,4 @@ FreqManView::FreqManView( } -void FreqManView::on_show() { - -} - -void FreqManView::on_hide() { - -} - } diff --git a/firmware/application/ui_freqman.hpp b/firmware/application/ui_freqman.hpp index f18b385ae..d22208d7a 100644 --- a/firmware/application/ui_freqman.hpp +++ b/firmware/application/ui_freqman.hpp @@ -32,11 +32,12 @@ class FreqManView : public View { public: FreqManView(NavigationView& nav); //~FreqManView(); + std::string title() const override { return "Frequency list"; }; void paint(Painter& painter) override; - void on_show() override; - void on_hide() override; + //void on_show() override; + //void on_hide() override; private: std::array text_list; diff --git a/firmware/application/ui_jammer.cpp b/firmware/application/ui_jammer.cpp index 066953a1c..c48451da3 100644 --- a/firmware/application/ui_jammer.cpp +++ b/firmware/application/ui_jammer.cpp @@ -23,8 +23,6 @@ #include "ui_jammer.hpp" #include "ui_receiver.hpp" -//#include "ch.h" -//#include "evtimer.h" #include "baseband_api.hpp" #include "string_format.hpp" @@ -47,10 +45,12 @@ JammerView::~JammerView() { baseband::shutdown(); } -void JammerView::updfreq(uint8_t id, rf::Frequency f) { +void JammerView::update_text(uint8_t id, rf::Frequency f) { char finalstr[25] = {0}; + rf::Frequency center; + std::string bw; uint8_t c; - + auto mhz = to_string_dec_int(f / 1000000, 3); auto hz100 = to_string_dec_int((f / 100) % 10000, 4, '0'); @@ -59,64 +59,14 @@ void JammerView::updfreq(uint8_t id, rf::Frequency f) { strcat(finalstr, hz100.c_str()); strcat(finalstr, "M"); - while (strlen(finalstr) < 10) { + while (strlen(finalstr) < 10) strcat(finalstr, " "); - } - if (id == 0) { - range1_min = f; - this->button_setfreq1_min.set_text(finalstr); - } - if (id == 1) { - range1_max = f; - this->button_setfreq1_max.set_text(finalstr); - } - if (id == 2) { - range2_min = f; - this->button_setfreq2_min.set_text(finalstr); - } - if (id == 3) { - range2_max = f; - this->button_setfreq2_max.set_text(finalstr); - } - if (id == 4) { - range3_min = f; - this->button_setfreq3_min.set_text(finalstr); - } - if (id == 5) { - range3_max = f; - this->button_setfreq3_max.set_text(finalstr); - } - - rf::Frequency center; - std::string bw; - + buttons_freq[id].set_text(finalstr); + for (c = 0; c < 3; c++) { - if (c == 0) { - center = (range1_min + range1_max) / 2; - range1_center = center; - } - if (c == 1) { - center = (range2_min + range2_max) / 2; - range2_center = center; - } - if (c == 2) { - center = (range3_min + range3_max) / 2; - range3_center = center; - } - - if (c == 0) { - range1_width = abs(range1_max - range1_min) / 1000; - bw = to_string_dec_int(range1_width, 5); - } - if (c == 1) { - range2_width = abs(range2_max - range2_min) / 1000; - bw = to_string_dec_int(range2_width, 5); - } - if (c == 2) { - range3_width = abs(range3_max - range3_min) / 1000; - bw = to_string_dec_int(range3_width, 5); - } + center = (frequency_range[c].min + frequency_range[c].max) / 2; + bw = to_string_dec_int(abs(frequency_range[c].max - frequency_range[c].min) / 1000, 5); auto center_mhz = to_string_dec_int(center / 1000000, 4); auto center_hz100 = to_string_dec_int((center / 100) % 10000, 4, '0'); @@ -129,23 +79,23 @@ void JammerView::updfreq(uint8_t id, rf::Frequency f) { strcat(finalstr, bw.c_str()); strcat(finalstr, "kHz"); - while (strlen(finalstr) < 23) { + while (strlen(finalstr) < 23) strcat(finalstr, " "); - } - if (c == 0) this->text_info1.set(finalstr); - if (c == 1) this->text_info2.set(finalstr); - if (c == 2) this->text_info3.set(finalstr); + if (c == 0) text_info1.set(finalstr); + if (c == 1) text_info2.set(finalstr); + if (c == 2) text_info3.set(finalstr); } } void JammerView::on_retune(const int64_t freq) { - if (freq > 0) { - radio::set_tuning_frequency(freq); - } + if (freq > 0) + transmitter_model.set_tuning_frequency(freq); } JammerView::JammerView(NavigationView& nav) { + size_t n; + baseband::run_image(portapack::spi_flash::image_tag_jammer); static constexpr Style style_val { @@ -166,6 +116,8 @@ JammerView::JammerView(NavigationView& nav) { .foreground = Color::grey(), }; + JammerRange * jammer_ranges = (JammerRange*)shared_memory.tx_data; + add_children({ { &text_type, &options_modulation, @@ -178,158 +130,139 @@ JammerView::JammerView(NavigationView& nav) { &checkbox_range1, &checkbox_range2, &checkbox_range3, - &button_setfreq1_min, - &button_setfreq1_max, &text_info1, - &button_setfreq2_min, - &button_setfreq2_max, &text_info2, - &button_setfreq3_min, - &button_setfreq3_max, &text_info3, &button_transmit, &button_exit } }); + const auto button_freq_fn = [this, &nav](Button& button) { + uint16_t id = button.id; + rf::Frequency * value_ptr; + + if (id & 1) + value_ptr = &frequency_range[id].max; + else + value_ptr = &frequency_range[id].min; + auto new_view = nav.push(*value_ptr); + new_view->on_changed = [this, value_ptr](rf::Frequency f) { + *value_ptr = f; + }; + }; + + n = 0; + for (auto& button : buttons_freq) { + button.on_select = button_freq_fn; + button.set_parent_rect({ + static_cast(13 * 8), + static_cast((n * 52) + 91 + (17 * (n & 1))), + 88, 18 + }); + button.id = n; + button.set_text("----.----M"); + add_child(&button); + n++; + } + button_transmit.set_style(&style_val); text_info1.set_style(&style_info); text_info2.set_style(&style_info); text_info3.set_style(&style_info); - - options_preset.set_selected_index(8); options_preset.on_change = [this](size_t n, OptionsField::value_t v) { (void)n; - uint8_t c; + for (uint8_t c = 0; c < 3; c++) { + frequency_range[c].min = range_presets[v][c].min; + frequency_range[c].max = range_presets[v][c].max; + } + checkbox_range1.set_value(range_presets[v][0].enabled); + checkbox_range2.set_value(range_presets[v][1].enabled); + checkbox_range3.set_value(range_presets[v][2].enabled); + }; + + options_preset.set_selected_index(8); // Sigfox, because they deserve it + + button_transmit.on_select = [this, &nav, jammer_ranges](Button&) { + uint8_t c, i = 0; + size_t num_ranges; + rf::Frequency start_freq, range_bw, ch_width; + bool out_of_ranges = false; + + // Disable all ranges by default + for (i = 0; i < 9; i++) + jammer_ranges[i].enabled = false; + + // Generate jamming "channels", maximum: 9 + // Convert ranges min/max to center/bw for (c = 0; c < 3; c++) { - updfreq(c*2, range_presets[v][c].min); - updfreq((c*2)+1, range_presets[v][c].max); - if (c == 0) checkbox_range1.set_value(range_presets[v][c].active); - if (c == 1) checkbox_range2.set_value(range_presets[v][c].active); - if (c == 2) checkbox_range3.set_value(range_presets[v][c].active); - } - }; - - button_setfreq1_min.on_select = [this,&nav](Button&){ - auto new_view = nav.push(range1_min); - new_view->on_changed = [this](rf::Frequency f) { - updfreq(0, f); - }; - }; - button_setfreq1_max.on_select = [this,&nav](Button&){ - auto new_view = nav.push(range1_max); - new_view->on_changed = [this](rf::Frequency f) { - updfreq(0, f); - }; - }; - - button_transmit.on_select = [this](Button&) { - uint8_t i = 0; - rf::Frequency t, range_lower; - - for (i = 0; i < 16; i++) { - shared_memory.jammer_ranges[i].active = false; - } - - // Swap - if (range1_min > range1_max) { - t = range1_min; - range1_min = range1_max; - range1_max = t; - } - i = 0; - range_lower = range1_min; -// for (i = 0; i < 3; i++) { - - if (range1_max - range_lower > 1000000) { - shared_memory.jammer_ranges[i].center = range_lower + (1000000/2); - shared_memory.jammer_ranges[i].width = 1000000 / 10; - shared_memory.jammer_ranges[i].active = true; - shared_memory.jammer_ranges[i].duration = 2280000 / 10; - range_lower += 1000000; + range_bw = abs(frequency_range[c].max - frequency_range[c].min); // Total bw for range + if (frequency_range[c].min < frequency_range[c].max) + start_freq = frequency_range[c].min; + else + start_freq = frequency_range[c].max; + if (range_bw > 500000) { + // Example: 600kHz + // int(600000 / 500000) = 2 + // CH-BW = 600000 / 2 = 300000 + // Center-A = min + CH-BW / 2 = 150000 + // BW-A = CH-BW = 300000 + // Center-B = min + CH-BW + Center-A = 450000 + // BW-B = CH-BW = 300000 + num_ranges = 0; + while (range_bw > 500000) { + range_bw -= 500000; + num_ranges++; + } + ch_width = range_bw / num_ranges; + for (c = 0; c < num_ranges; c++) { + if (i >= 9) { + out_of_ranges = true; + break; + } + jammer_ranges[i].enabled = true; + jammer_ranges[i].width = ch_width; + jammer_ranges[i].center = start_freq + (ch_width / 2) + (ch_width * c); + jammer_ranges[i].duration = 2280000 / 10; // ? + i++; + } } else { - shared_memory.jammer_ranges[i].center = (range1_max + range_lower) / 2; - shared_memory.jammer_ranges[i].width = (range1_max - range_lower) / 10; // ? - shared_memory.jammer_ranges[i].active = true; - shared_memory.jammer_ranges[i].duration = 2280000 / 10; - //break; + if (i >= 9) { + out_of_ranges = true; + } else { + jammer_ranges[i].enabled = true; + jammer_ranges[i].width = range_bw; + jammer_ranges[i].center = start_freq + (range_bw / 2); + jammer_ranges[i].duration = 2280000 / 10; // ? + i++; + } } -// } - - // Swap - if (range2_min > range2_max) { - t = range2_min; - range2_min = range2_max; - range2_max = t; - } - i = 1; - range_lower = range2_min; -// for (i = 0; i < 3; i++) { - - if (range1_max - range_lower > 1000000) { - shared_memory.jammer_ranges[i].center = range_lower + (1000000/2); - shared_memory.jammer_ranges[i].width = 1000000 / 10; - shared_memory.jammer_ranges[i].active = true; - shared_memory.jammer_ranges[i].duration = 2280000 / 10; - range_lower += 1000000; - } else { - shared_memory.jammer_ranges[i].center = (range1_max + range_lower) / 2; - shared_memory.jammer_ranges[i].width = (range1_max - range_lower) / 10; // ? - shared_memory.jammer_ranges[i].active = true; - shared_memory.jammer_ranges[i].duration = 2280000 / 10; - //break; - } -// } - - // Swap - if (range3_min > range3_max) { - t = range3_min; - range3_min = range3_max; - range3_max = t; - } - i = 2; - range_lower = range3_min; -// for (i = 0; i < 3; i++) { - - if (range1_max - range_lower > 1000000) { - shared_memory.jammer_ranges[i].center = range_lower + (1000000/2); - shared_memory.jammer_ranges[i].width = 1000000 / 10; - shared_memory.jammer_ranges[i].active = true; - shared_memory.jammer_ranges[i].duration = 2280000 / 10; - range_lower += 1000000; - } else { - shared_memory.jammer_ranges[i].center = (range1_max + range_lower) / 2; - shared_memory.jammer_ranges[i].width = (range1_max - range_lower) / 10; // ? - shared_memory.jammer_ranges[i].active = true; - shared_memory.jammer_ranges[i].duration = 2280000 / 10; - //break; - } -// } - - if (jamming == true) { - jamming = false; - button_transmit.set_style(&style_val); - button_transmit.set_text("START"); - radio::disable(); - } else { - jamming = true; - button_transmit.set_style(&style_cancel); - button_transmit.set_text("STOP"); - - /*baseband::set_jammer_data( - - );*/ - //transmitter_model.set_tuning_frequency(433920000); // TODO - transmitter_model.set_baseband_configuration({ - .mode = 0, - .sampling_rate = 1536000U, - .decimation_factor = 1, - }); - transmitter_model.set_rf_amp(true); - transmitter_model.set_baseband_bandwidth(1750000); - transmitter_model.enable(); + if (!out_of_ranges) { + if (jamming == true) { + jamming = false; + button_transmit.set_style(&style_val); + button_transmit.set_text("START"); + radio::disable(); + } else { + jamming = true; + button_transmit.set_style(&style_cancel); + button_transmit.set_text("STOP"); + + //transmitter_model.set_tuning_frequency(433920000); // TODO + transmitter_model.set_baseband_configuration({ + .mode = 0, + .sampling_rate = 1536000U, + .decimation_factor = 1, + }); + transmitter_model.set_rf_amp(true); + transmitter_model.set_baseband_bandwidth(1750000); + transmitter_model.enable(); + } + } else { + nav.display_modal("Error", "Jamming bandwidth too high."); + } } }; diff --git a/firmware/application/ui_jammer.hpp b/firmware/application/ui_jammer.hpp index 6e14a2046..0a4ea5cd5 100644 --- a/firmware/application/ui_jammer.hpp +++ b/firmware/application/ui_jammer.hpp @@ -34,35 +34,26 @@ public: JammerView(NavigationView& nav); ~JammerView(); - void updfreq(uint8_t id, rf::Frequency f); void focus() override; std::string title() const override { return "Jammer"; }; private: - void on_retune(const int64_t freq); - - rf::Frequency range1_min; - rf::Frequency range1_max; - rf::Frequency range2_min; - rf::Frequency range2_max; - rf::Frequency range3_min; - rf::Frequency range3_max; - - rf::Frequency range1_center; - rf::Frequency range1_width; - rf::Frequency range2_center; - rf::Frequency range2_width; - rf::Frequency range3_center; - rf::Frequency range3_width; - - typedef struct rangepreset { - bool active; + // range_t from utility.hpp is const only + typedef struct freq_range { + bool enabled; rf::Frequency min; rf::Frequency max; - } rangepreset; + } freq_range_t; + + freq_range_t frequency_range[3]; - const rangepreset range_presets[10][3] = { + void update_text(uint8_t id, rf::Frequency f); + void on_retune(const int64_t freq); + + // TODO: TDD UMTS, voir doc Arcep + // TODO: Wifi, BT: 2 400 et 2 483,5 MHz + const freq_range_t range_presets[10][3] = { // Orange {{ true, 935000000, 945000000 }, // GSM 900 { true, 1808000000, 1832000000 }, // GSM 1800 @@ -80,7 +71,7 @@ private: // Free {{ true, 945000000, 950000000 }, // GSM 900 - { false, 0, 0 }, // GSM 1800 + { false, 0, 0 }, // GSM 1800 ? { true, 2144900000, 2149900000 }}, // UMTS // GSM-R @@ -88,9 +79,6 @@ private: { false, 0, 0 }, // GSM 1800 { false, 0, 0 }}, // UMTS - // TODO: TDD UMTS, voir doc Arcep - // TODO: Wifi, BT: 2 400 et 2 483,5 MHz - // DECT {{ true, 1880000000, 1900000000 }, // BW: 20MHz { false, 0, 0 }, @@ -102,12 +90,12 @@ private: { false, 0, 0 }}, // ISM 433 - {{ true, 433050000, 434790000 }, // BW: 0.2% + {{ true, 433050000, 434790000 }, // Center: 433.92MHz BW: 0.2% { false, 0, 0 }, { false, 0, 0 }}, // Sigfox - {{ true, 868150000, 868250000 }, // BW: 40kHz (50kHz) + {{ true, 868000000, 868220000 }, // Center: 868.2MHz BW: 40kHz { false, 0, 0 }, { false, 0, 0 }}, @@ -131,7 +119,7 @@ private: { { "Ramp ", 0 }, { "FM ", 1 }, - { "PSK ", 2 }, + { "Phase", 2 }, { "Tones", 3 } } }; @@ -209,40 +197,18 @@ private: "Range 3" }; - Button button_setfreq1_min { - { 13 * 8, 6 * 16 - 4 - 1, 11 * 8, 18 }, - "----.----M" - }; - Button button_setfreq1_max { - { 13 * 8, 7 * 16 - 4, 11 * 8, 18 }, - "----.----M" - }; + std::array buttons_freq; + Text text_info1 { { 3 * 8, 8 * 16 - 4 + 2, 25 * 8, 16 }, "C:----.----M W:-----kHz" }; - Button button_setfreq2_min { - { 13 * 8, 9 * 16 - 1, 11 * 8, 18 }, - "----.----M" - }; - Button button_setfreq2_max { - { 13 * 8, 10 * 16, 11 * 8, 18 }, - "----.----M" - }; Text text_info2 { { 3 * 8, 11 * 16 + 2, 25 * 8, 16 }, "C:----.----M W:-----kHz" }; - Button button_setfreq3_min { - { 13 * 8, 12 * 16 + 4 - 1, 11 * 8, 18 }, - "----.----M" - }; - Button button_setfreq3_max { - { 13 * 8, 13 * 16 + 4, 11 * 8, 18 }, - "----.----M" - }; Text text_info3 { { 3 * 8, 14 * 16 + 4 + 2, 25 * 8, 16 }, "C:----.----M W:-----kHz" diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 045a4307a..0c00aaddd 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -35,8 +35,9 @@ #include "ui_setup.hpp" #include "ui_debug.hpp" +#include "ui_numbers.hpp" //#include "ui_closecall.hpp" // DEBUG -//#include "ui_freqman.hpp" // DEBUG +#include "ui_freqman.hpp" #include "ui_nuoptix.hpp" #include "ui_soundboard.hpp" @@ -51,7 +52,6 @@ #include "ui_adsbtx.hpp" #include "ui_jammer.hpp" -#include "analog_audio_app.hpp" #include "ais_app.hpp" #include "ert_app.hpp" #include "tpms_app.hpp" @@ -143,7 +143,7 @@ void SystemStatusView::on_camera() { return; } - for(int i=0; i<320; i++) { + for (int i=0; i<320; i++) { std::array row; portapack::display.read_pixels({ 0, i, 240, 1 }, row); png.write_scanline(row); @@ -233,14 +233,14 @@ TranspondersMenuView::TranspondersMenuView(NavigationView& nav) { /* ReceiverMenuView ******************************************************/ ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { - add_items<7>({ { + add_items<6>({ { + // { "AFSK", ui::Color::grey(), [&nav](){ nav.push(); } }, // AFSKRXView { "Audio", ui::Color::green(), [&nav](){ nav.push(); } }, - { "Transponders", ui::Color::green(), [&nav](){ nav.push(); } }, - { "POCSAG 1200", ui::Color::cyan(), [&nav](){ nav.push(); } }, - { "Nordic/BTLE", ui::Color::grey(), [&nav](){ nav.push(); } }, - { "SIGFOX", ui::Color::grey(), [&nav](){ nav.push(); } }, // SIGFRXView { "CCIR", ui::Color::grey(), [&nav](){ nav.push(); } }, // XylosRXView - { "AFSK", ui::Color::grey(), [&nav](){ nav.push(); } }, // AFSKRXView + { "Nordic/BTLE", ui::Color::grey(), [&nav](){ nav.push(); } }, + { "POCSAG 1200", ui::Color::cyan(), [&nav](){ nav.push(); } }, + { "SIGFOX", ui::Color::grey(), [&nav](){ nav.push(); } }, // SIGFRXView + { "Transponders", ui::Color::green(), [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; } @@ -264,8 +264,8 @@ TransmitterCodedMenuView::TransmitterCodedMenuView(NavigationView& nav) { TransmitterAudioMenuView::TransmitterAudioMenuView(NavigationView& nav) { add_items<4>({ { - { "Soundboard", ui::Color::yellow(), [&nav](){ nav.push(); } }, - { "Numbers station", ui::Color::grey(), [&nav](){ nav.push(); } }, //nav.push(); + { "Soundboard", ui::Color::green(), [&nav](){ nav.push(); } }, + { "Numbers station", ui::Color::yellow(), [&nav](){ nav.push(); } }, { "Microphone", ui::Color::grey(), [&nav](){ nav.push(); } }, { "Whistle", ui::Color::grey(), [&nav](){ nav.push(); } }, } }); @@ -275,16 +275,15 @@ TransmitterAudioMenuView::TransmitterAudioMenuView(NavigationView& nav) { /* SystemMenuView ********************************************************/ SystemMenuView::SystemMenuView(NavigationView& nav) { - add_items<10>({ { + add_items<11>({ { { "Play dead", ui::Color::red(), [&nav](){ nav.push(false); } }, { "Receivers", ui::Color::cyan(), [&nav](){ nav.push(); } }, { "Capture", ui::Color::cyan(), [&nav](){ nav.push(); } }, - { "Code transmitters", ui::Color::purple(), [&nav](){ nav.push(); } }, - { "Audio transmitters", ui::Color::purple(), [&nav](){ nav.push(); } }, + { "Code transmitters", ui::Color::green(), [&nav](){ nav.push(); } }, + { "Audio transmitters", ui::Color::green(), [&nav](){ nav.push(); } }, //{ "Close Call RX", ui::Color::cyan(), [&nav](){ nav.push(); } }, { "Jammer", ui::Color::orange(), [&nav](){ nav.push(); } }, - //{ "Frequency manager", ui::Color::white(), [&nav](){ nav.push(); } }, - //{ "EPAR TX", ui::Color::green(), [&nav](){ nav.push(md5_baseband_tx, EPAR); } }, + { "Frequency manager", ui::Color::white(), [&nav](){ nav.push(); } }, //{ "Analyze", ui::Color::white(), [&nav](){ nav.push(); } }, { "Setup", ui::Color::white(), [&nav](){ nav.push(); } }, { "Debug", ui::Color::white(), [&nav](){ nav.push(); } }, diff --git a/firmware/application/ui_numbers.cpp b/firmware/application/ui_numbers.cpp index 54fe7843c..5da6f5de1 100644 --- a/firmware/application/ui_numbers.cpp +++ b/firmware/application/ui_numbers.cpp @@ -23,20 +23,15 @@ #include "ui_numbers.hpp" #include "ch.h" -#include "evtimer.h" - #include "ff.h" -#include "hackrf_gpio.hpp" #include "portapack.hpp" -#include "radio.hpp" - #include "hackrf_hal.hpp" #include "portapack_shared_memory.hpp" -#include "portapack_persistent_memory.hpp" #include #include +using namespace portapack; using namespace hackrf::one; namespace ui { @@ -47,25 +42,96 @@ void NumbersStationView::focus() { NumbersStationView::~NumbersStationView() { transmitter_model.disable(); + baseband::shutdown(); } void NumbersStationView::paint(Painter& painter) { (void)painter; } +void NumbersStationView::on_tuning_frequency_changed(rf::Frequency f) { + transmitter_model.set_tuning_frequency(f); +} + +void NumbersStationView::prepare_audio() { + if (cnt >= sample_duration) { + /*if (!check_loop.value()) { + transmitter_model.disable(); + return; + } else { + file.seek(44); + cnt = 0; + }*/ + + // DEBUG + file.seek(44); + cnt = 0; + + } + + file.read(audio_buffer, 1024); + + // Unsigned to signed, pretty stupid :/ + for (size_t n = 0; n < 1024; n++) + audio_buffer[n] -= 0x80; + + cnt += 1024; + + baseband::set_fifo_data(audio_buffer); +} + +void NumbersStationView::play_sound(uint16_t id) { + uint32_t divider; + + if (sounds[id].size == 0) return; + + auto error = file.open("/numbers/" + filenames[id] + ".wav"); + if (error.is_valid()) return; + + sample_duration = sounds[id].sample_duration; + + cnt = 0; + file.seek(44); // Skip header + + prepare_audio(); + + transmitter_model.set_baseband_configuration({ + .mode = 0, + .sampling_rate = 1536000, + .decimation_factor = 1, + }); + transmitter_model.set_rf_amp(true); + transmitter_model.set_lna(40); + transmitter_model.set_vga(40); + transmitter_model.set_baseband_bandwidth(1750000); + transmitter_model.enable(); + + divider = (1536000 / 44100) - 1; + + baseband::set_audiotx_data( + divider, + number_bw.value(), + false, + 0 + ); +} + NumbersStationView::NumbersStationView( - NavigationView& nav, - TransmitterModel& transmitter_model -) : transmitter_model(transmitter_model) -{ + NavigationView& nav +) { uint8_t m, d, dayofweek; uint16_t y; + baseband::run_image(portapack::spi_flash::image_tag_audio_tx); + add_children({ { &text_title, + &number_bw, &button_exit } }); - + + number_bw.set_value(15); + rtc::RTC datetime; rtcGetTime(&RTCD1, &datetime); diff --git a/firmware/application/ui_numbers.hpp b/firmware/application/ui_numbers.hpp index e380cac1a..7c7976324 100644 --- a/firmware/application/ui_numbers.hpp +++ b/firmware/application/ui_numbers.hpp @@ -20,34 +20,66 @@ * Boston, MA 02110-1301, USA. */ +#ifndef __UI_NUMBERS_H__ +#define __UI_NUMBERS_H__ + #include "ui.hpp" #include "ui_widget.hpp" #include "ui_painter.hpp" -#include "ui_menu.hpp" #include "ui_navigation.hpp" #include "ui_font_fixed_8x16.hpp" #include "clock_manager.hpp" #include "message.hpp" -#include "rf_path.hpp" -#include "max2837.hpp" -#include "volume.hpp" -#include "transmitter_model.hpp" +#include "baseband_api.hpp" +#include "file.hpp" namespace ui { class NumbersStationView : public View { public: - NumbersStationView(NavigationView& nav, TransmitterModel& transmitter_model); + NumbersStationView(NavigationView& nav); ~NumbersStationView(); void focus() override; void paint(Painter& painter) override; + std::string title() const override { return "Numbers station"; }; + private: - TransmitterModel& transmitter_model; + // Different from the one in ui_soundboard.hpp, simpler + struct sound { + uint32_t size = 0; + uint32_t sample_duration = 0; + }; + + sound sounds[11]; + + const std::string filenames[11] = { + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "anounce" + }; + const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; const char * day_of_week[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + File file; + uint32_t cnt; + uint32_t sample_duration; + int8_t audio_buffer[1024]; + + void on_tuning_frequency_changed(rf::Frequency f); + void prepare_audio(); + void play_sound(uint16_t id); + // Schedule: save on sd card // For each day of the week, max 8 messages ? // For each message: Normal, accent. Can chose accent on first or last digit @@ -63,10 +95,30 @@ private: "Schedule:" }; + NumberField number_bw { + { 11 * 8, 3 * 16 }, + 3, + {1, 150}, + 1, + ' ' + }; + Button button_exit { { 21 * 8, 16 * 16, 64, 32 }, "Exit" }; + + MessageHandlerRegistration message_handler_fifo_signal { + Message::ID::FIFOSignal, + [this](const Message* const p) { + const auto message = static_cast(p); + if (message->signaltype == 1) { + this->prepare_audio(); + } + } + }; }; } /* namespace ui */ + +#endif/*__UI_NUMBERS_H__*/ diff --git a/firmware/application/ui_rds.cpp b/firmware/application/ui_rds.cpp index 2bdf52523..cbda38ad5 100644 --- a/firmware/application/ui_rds.cpp +++ b/firmware/application/ui_rds.cpp @@ -22,7 +22,7 @@ #include "ui_rds.hpp" -#include "ch.h" +#include "rds.hpp" #include "ff.h" #include "hackrf_gpio.hpp" #include "portapack.hpp" @@ -35,6 +35,7 @@ #include using namespace portapack; +using namespace rds; namespace ui { @@ -42,186 +43,49 @@ void RDSView::focus() { button_editpsn.focus(); } +void RDSView::on_tuning_frequency_changed(rf::Frequency f) { + transmitter_model.set_tuning_frequency(f); +} + RDSView::~RDSView() { - radio::disable(); + transmitter_model.disable(); baseband::shutdown(); } -std::string to_string_bin(const uint32_t n, const uint8_t l) { - char p[33]; - for (uint8_t c = 0; c < l; c++) { - if ((n << c) & (1 << l)) - p[c] = '1'; - else - p[c] = '0'; - } - p[l] = 0; - return p; -} - -uint32_t makeblock(uint32_t blockdata, uint16_t offset) { - uint16_t CRC = 0; - uint8_t doinv; - - for (uint8_t i = 0; i < 16; i++) { - doinv = (((blockdata << i) & 0x8000) >> 15) ^ (CRC >> 9); - if (doinv) CRC ^= 0b0011011100; - CRC = ((CRC << 1) | doinv) & 0x3FF; - } - - return (blockdata << 10) | (CRC ^ offset); -} - -// Todo: -// Make PI -// Set frequency -// TA/TP flags -// Group selection - -uint8_t RDSView::b2b(const bool in) { - if (in) - return 1; - else - return 0; -} - -void RDSView::make_0B_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, - const bool MS, const bool DI, const uint8_t C, const char * chars) { - - group[0] = PI_code; - group[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | (PTY << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3); - group[2] = PI_code; - group[3] = (chars[0] << 8) | chars[1]; -} - -void RDSView::make_2A_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, - const bool segment, const char * chars) { - - group[0] = PI_code; - group[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | (PTY << 5) | (b2b(AB) << 4) | (segment & 15); - group[2] = (chars[0] << 8) | chars[1]; - group[3] = (chars[2] << 8) | chars[3]; -} - -void RDSView::gen_PSN(const char * psname) { - uint8_t c; - uint32_t group[4][4] = { 0 }; - - make_0B_group(&group[0][0], 0xF849, true, options_pty.selected_index(), false, true, false, 0, &psname[0]); - make_0B_group(&group[1][0], 0xF849, true, options_pty.selected_index(), false, true, false, 1, &psname[2]); - make_0B_group(&group[2][0], 0xF849, true, options_pty.selected_index(), false, true, false, 2, &psname[4]); - make_0B_group(&group[3][0], 0xF849, true, options_pty.selected_index(), false, true, false, 3, &psname[6]); +void RDSView::start_tx() { + transmitter_model.set_baseband_configuration({ + .mode = 0, + .sampling_rate = 2280000, + .decimation_factor = 1, + }); + transmitter_model.set_rf_amp(true); + transmitter_model.set_lna(40); + transmitter_model.set_vga(40); + transmitter_model.set_baseband_bandwidth(1750000); + transmitter_model.enable(); - /*uint32_t group[4][4] = { - { - 0b1111100001001001, //PI - 0b0000110011101000, //Address - 0b1111100001001001, //PI - 0b0000000000000000 //Replaced - }, - - { - 0b1111100001001001, //PI - 0b0000110011101001, //Address - 0b1111100001001001, //PI - 0b0000000000000000 //Replaced - }, - - { - 0b1111100001001001, //PI - 0b0000110011101010, //Address - 0b1111100001001001, //PI - 0b0000000000000000 //Replaced - }, - - { - 0b1111100001001001, //PI - 0b0000110011101011, //Address - 0b1111100001001001, //PI - 0b0000000000000000 //Replaced - }, - }; - - //Insert PSN data in groups - group[0][3] = (psname[0] << 8) | psname[1]; - group[1][3] = (psname[2] << 8) | psname[3]; - group[2][3] = (psname[4] << 8) | psname[5]; - group[3][3] = (psname[6] << 8) | psname[7]; - */ - - // Generate checkbits - for (c = 0; c < 4; c++) { - group[c][0] = makeblock(group[c][0], RDS_OFFSET_A); - group[c][1] = makeblock(group[c][1], RDS_OFFSET_B); - group[c][2] = makeblock(group[c][2], RDS_OFFSET_Cp); // C' ! - group[c][3] = makeblock(group[c][3], RDS_OFFSET_D); - } - - // Todo - //for (c = 0; c < 16; c++) - // shared_memory.radio_data[c] = group[c >> 2][c & 3]; - - shared_memory.bit_length = 4 * 4 * 26; -} - -void RDSView::gen_RadioText(const char * radiotext) { - size_t c, i; - uint32_t * group; - char radiotext_buffer[65] = { 0 }; - uint8_t rtlen, groups; - - strcpy(radiotext_buffer, radiotext); - - rtlen = strlen(radiotext_buffer); - - radiotext_buffer[rtlen] = 0x0D; - - // Pad to multiple of 4 - while(rtlen & 3) { - radiotext_buffer[rtlen] = ' '; - rtlen++; - } - - groups = rtlen >> 2; // 4 characters per group - - group = (uint32_t*)chHeapAlloc(0x0, 4 * groups * sizeof(uint32_t)); - - for (c = 0; c < groups; c++) - make_2A_group(&group[c << 2], 0xF849, true, options_pty.selected_index(), false, c, &radiotext_buffer[c << 2]); - - // Generate checkbits - for (c = 0; c < groups; c++) { - i = c * 4; - group[i + 0] = makeblock(group[i + 0], RDS_OFFSET_A); - group[i + 1] = makeblock(group[i + 1], RDS_OFFSET_B); - group[i + 2] = makeblock(group[i + 2], RDS_OFFSET_C); - group[i + 3] = makeblock(group[i + 3], RDS_OFFSET_D); - } - - // Todo - //for (c = 0; c < (groups * 4); c++) - // shared_memory.radio_data[c] = group[c]; - - shared_memory.bit_length = groups * 4 * 26; + baseband::set_rds_data(message_length); } void RDSView::paint(Painter& painter) { char RadioTextA[17]; - (void)painter; text_psn.set(PSN); + memcpy(RadioTextA, RadioText, 16); RadioTextA[16] = 0; text_radiotexta.set(RadioTextA); - text_radiotextb.set(&RadioText[16]); + memcpy(RadioTextA, RadioText + 16, 8); + RadioTextA[8] = 0; + text_radiotextb.set(RadioTextA); } RDSView::RDSView(NavigationView& nav) { baseband::run_image(portapack::spi_flash::image_tag_rds); strcpy(PSN, "TEST1234"); - strcpy(RadioText, "Radiotext test !"); + strcpy(RadioText, "Radiotext test ABCD1234"); add_children({ { &field_frequency, @@ -239,12 +103,16 @@ RDSView::RDSView(NavigationView& nav) { &button_exit } }); - rds_radio_config.tuning_frequency = tuning_frequency; - field_frequency.set_step(100000); + field_frequency.set_value(transmitter_model.tuning_frequency()); + field_frequency.set_step(50000); // 50kHz steps + field_frequency.on_change = [this](rf::Frequency f) { + this->on_tuning_frequency_changed(f); + }; field_frequency.on_edit = [this, &nav]() { - auto new_view = nav.push(tuning_frequency); + auto new_view = nav.push(field_frequency.value()); new_view->on_changed = [this](rf::Frequency f) { this->field_frequency.set_value(f); + this->on_tuning_frequency_changed(f); }; }; @@ -257,16 +125,15 @@ RDSView::RDSView(NavigationView& nav) { }; button_txpsn.on_select = [this](Button&){ if (txing) { - radio::disable(); button_txpsn.set_text("PSN"); button_txradiotext.set_text("Radiotext"); + transmitter_model.disable(); txing = false; } else { - gen_PSN(PSN); - rds_radio_config.tuning_frequency = tuning_frequency; + message_length = gen_PSN(PSN, options_pty.selected_index()); button_txpsn.set_text("STOP"); txing = true; - radio::disable(); + start_tx(); } }; @@ -275,16 +142,15 @@ RDSView::RDSView(NavigationView& nav) { }; button_txradiotext.on_select = [this](Button&){ if (txing) { - radio::disable(); button_txpsn.set_text("PSN"); button_txradiotext.set_text("Radiotext"); + transmitter_model.disable(); txing = false; } else { - gen_RadioText(RadioText); - rds_radio_config.tuning_frequency = tuning_frequency; + message_length = gen_RadioText(RadioText, options_pty.selected_index()); button_txradiotext.set_text("STOP"); txing = true; - radio::enable(rds_radio_config); + start_tx(); } }; diff --git a/firmware/application/ui_rds.hpp b/firmware/application/ui_rds.hpp index 0440465e7..b1f7cf115 100644 --- a/firmware/application/ui_rds.hpp +++ b/firmware/application/ui_rds.hpp @@ -33,12 +33,6 @@ #include "volume.hpp" #include "transmitter_model.hpp" -#define RDS_OFFSET_A 0b0011111100 -#define RDS_OFFSET_B 0b0110011000 -#define RDS_OFFSET_C 0b0101101000 -#define RDS_OFFSET_Cp 0b1101010000 -#define RDS_OFFSET_D 0b0110110100 - namespace ui { class RDSView : public View { @@ -54,28 +48,11 @@ private: char PSN[9]; char RadioText[25]; bool txing = false; - int64_t tuning_frequency = 92200000; // TODO: CHANGE ! - uint8_t b2b(const bool in); + uint16_t message_length; - void gen_PSN(const char * psname); - void gen_RadioText(const char * radiotext); - - void make_0B_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, - const bool MS, const bool DI, const uint8_t C, const char * chars); - void make_2A_group(uint32_t group[], const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, - const bool segment, const char * chars); - - radio::Configuration rds_radio_config = { - 0, - 2280000, // ? - 2500000, // ? - rf::Direction::Transmit, - true, - 0, - 0, - 1, - }; + void start_tx(); + void on_tuning_frequency_changed(rf::Frequency f); FrequencyField field_frequency { { 1 * 8, 1 * 16 }, diff --git a/firmware/application/ui_soundboard.cpp b/firmware/application/ui_soundboard.cpp index 72931fc0a..59df32723 100644 --- a/firmware/application/ui_soundboard.cpp +++ b/firmware/application/ui_soundboard.cpp @@ -25,8 +25,6 @@ #include "ui_soundboard.hpp" #include "ch.h" -#include "file.hpp" - #include "lfsr_random.hpp" #include "ui_alphanum.hpp" #include "portapack.hpp" @@ -258,6 +256,11 @@ SoundBoardView::SoundBoardView( } } + if (!c) { + nav.display_modal("No files", "No files in /wav/ directory"); + nav.pop(); + } + max_sound = c; max_page = max_sound / 21; // 21 buttons per page diff --git a/firmware/application/ui_xylos.cpp b/firmware/application/ui_xylos.cpp index 40a844a5f..22aa5f456 100644 --- a/firmware/application/ui_xylos.cpp +++ b/firmware/application/ui_xylos.cpp @@ -329,8 +329,6 @@ XylosView::XylosView(NavigationView& nav) { generate_message(); }; - subfamily_code.hidden(true); - text_subfamily.set_style(&style_grey); checkbox_wcsubfamily.on_select = [this](Checkbox&) { if (checkbox_wcsubfamily.value() == true) { receiver_code.set_focusable(false); @@ -342,8 +340,6 @@ XylosView::XylosView(NavigationView& nav) { generate_message(); }; - receiver_code.hidden(true); - text_receiver.set_style(&style_grey); checkbox_wcid.on_select = [this](Checkbox&) { if (checkbox_wcid.value() == true) { receiver_code.set_focusable(false); diff --git a/firmware/baseband/baseband_ads.img b/firmware/baseband/baseband_ads.img index 0152fd763..cedfc1fa2 100644 Binary files a/firmware/baseband/baseband_ads.img and b/firmware/baseband/baseband_ads.img differ diff --git a/firmware/baseband/proc_adsbtx.cpp b/firmware/baseband/proc_adsbtx.cpp index 461c9df30..4520b6671 100644 --- a/firmware/baseband/proc_adsbtx.cpp +++ b/firmware/baseband/proc_adsbtx.cpp @@ -50,9 +50,12 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) { if (!bit_part) { if (bit_pos >= 112) { // Stop + message.n = 200; + shared_memory.application_queue.push(message); + configured = false; cur_bit = 0; } else { - cur_bit = shared_memory.tx_data[bit_pos]; + cur_bit = 0; //shared_memory.tx_data[bit_pos]; bit_pos++; bit_part = 1; } @@ -66,7 +69,7 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) { // 1001010110100110 0110010110010101 if (cur_bit) { - phase = (phase + 0x1FE0000); // What ? + phase = (phase + 0x1FE00); // What ? sphase = phase + (64 << 18); re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]); @@ -78,17 +81,13 @@ void ADSBTXProcessor::execute(const buffer_c8_t& buffer) { buffer.p[i] = {(int8_t)re, (int8_t)im}; } - - // TEST - message.n = 200; - shared_memory.application_queue.push(message); - configured = false; } void ADSBTXProcessor::on_message(const Message* const p) { const auto message = *reinterpret_cast(p); if (message.id == Message::ID::ADSBConfigure) { + bit_part = 0; bit_pos = 0; cur_bit = 0; preamble = true; diff --git a/firmware/baseband/proc_dtmf_tx.cpp b/firmware/baseband/proc_dtmf_tx.cpp index 0a320e2be..9d9e57088 100644 --- a/firmware/baseband/proc_dtmf_tx.cpp +++ b/firmware/baseband/proc_dtmf_tx.cpp @@ -93,7 +93,7 @@ void DTMFTXProcessor::execute(const buffer_c8_t& buffer) { } void DTMFTXProcessor::on_message(const Message* const msg) { - char * tone_ptr; + uint8_t * tone_ptr; const auto message = *reinterpret_cast(msg); if (message.id == Message::ID::DTMFTXConfig) { diff --git a/firmware/baseband/proc_jammer.cpp b/firmware/baseband/proc_jammer.cpp index b3b01ab12..f13e601a3 100644 --- a/firmware/baseband/proc_jammer.cpp +++ b/firmware/baseband/proc_jammer.cpp @@ -40,11 +40,11 @@ void JammerProcessor::execute(const buffer_c8_t& buffer) { for (;;) { ir++; if (ir > 15) ir = 0; - if (shared_memory.jammer_ranges[ir].active == true) break; + if (jammer_ranges[ir].enabled == true) break; } - jammer_bw = shared_memory.jammer_ranges[ir].width / 2; + jammer_bw = jammer_ranges[ir].width / 2; - message.freq = shared_memory.jammer_ranges[ir].center; + message.freq = jammer_ranges[ir].center; shared_memory.application_queue.push(message); } else { s++; @@ -73,9 +73,9 @@ void JammerProcessor::execute(const buffer_c8_t& buffer) { sample = sine_table_i8[(sphase & 0x03FC0000) >> 18]; // FM - frq = sample * jammer_bw; + delta = sample * jammer_bw; - phase = (phase + frq); + phase = (phase + delta); sphase = phase + (64 << 18); re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]); @@ -86,6 +86,9 @@ void JammerProcessor::execute(const buffer_c8_t& buffer) { }; void JammerProcessor::on_message(const Message* const msg) { + + jammer_ranges = (JammerRange*)shared_memory.tx_data; + /*const auto message = *reinterpret_cast(msg); if (message.id == Message::ID::DTMFTXConfig) { diff --git a/firmware/baseband/proc_jammer.hpp b/firmware/baseband/proc_jammer.hpp index 7b6ba8c78..7257d4484 100644 --- a/firmware/baseband/proc_jammer.hpp +++ b/firmware/baseband/proc_jammer.hpp @@ -25,6 +25,7 @@ #include "baseband_processor.hpp" #include "baseband_thread.hpp" +#include "portapack_shared_memory.hpp" class JammerProcessor : public BasebandProcessor { public: @@ -37,6 +38,8 @@ private: BasebandThread baseband_thread { 1536000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; + JammerRange * jammer_ranges; + int32_t lfsr32 = 0xABCDE; uint32_t s; int8_t r, ir, re, im; @@ -45,7 +48,7 @@ private: int32_t lfsr; uint32_t sample_count; uint32_t aphase, phase, sphase; - int32_t sample, frq; + int32_t sample, delta; RetuneMessage message; }; diff --git a/firmware/baseband/proc_rds.cpp b/firmware/baseband/proc_rds.cpp index 5bf0e82ab..34d6232df 100644 --- a/firmware/baseband/proc_rds.cpp +++ b/firmware/baseband/proc_rds.cpp @@ -22,7 +22,7 @@ #include "proc_rds.hpp" #include "portapack_shared_memory.hpp" -#include "sine_table.hpp" +#include "sine_table_int8.hpp" #include "event_m4.hpp" #include @@ -31,18 +31,18 @@ void RDSProcessor::execute(const buffer_c8_t& buffer) { for (size_t i = 0; i < buffer.count; i++) { - //Sample generation 2.28M/10=228kHz - if(s >= 9) { + // Sample generation at 2.28M / 10 = 228kHz + if (s >= 9) { s = 0; - if(sample_count >= SAMPLES_PER_BIT) { + if (sample_count >= SAMPLES_PER_BIT) { cur_bit = (rdsdata[(bit_pos / 26) & 15] >> (25 - (bit_pos % 26))) & 1; prev_output = cur_output; cur_output = prev_output ^ cur_bit; - const int32_t *src = waveform_biphase; // const ok ? + const int32_t * src = waveform_biphase; int idx = in_sample_index; - for(int j = 0; j < FILTER_SIZE; j++) { + for (int j = 0; j < FILTER_SIZE; j++) { val = (*src++); if (cur_output) val = -val; sample_buffer[idx++] += val; @@ -52,10 +52,11 @@ void RDSProcessor::execute(const buffer_c8_t& buffer) { in_sample_index += SAMPLES_PER_BIT; if (in_sample_index >= SAMPLE_BUFFER_SIZE) in_sample_index -= SAMPLE_BUFFER_SIZE; - if (bit_pos < shared_memory.bit_length) + if (bit_pos < message_length) bit_pos++; else bit_pos = 0; + sample_count = 0; } @@ -64,38 +65,39 @@ void RDSProcessor::execute(const buffer_c8_t& buffer) { out_sample_index++; if (out_sample_index >= SAMPLE_BUFFER_SIZE) out_sample_index = 0; - //AM @ 228k/4 = 57kHz - switch (mphase) { + // AM @ 228k/4 = 57kHz + // 0, sample, 0, -sample... + switch (mphase & 3) { case 0: case 2: sample = 0; break; case 1: break; - case 3: sample = -sample; break; + case 3: sample = -sample; // break; } mphase++; - if (mphase >= 4) mphase = 0; sample_count++; } else { s++; } - //FM - frq = (sample>>16) * 386760; + // FM + delta = (sample >> 16) * 386760; // ? - phase = (phase + frq); - sphase = phase + (256<<16); + phase += delta; + sphase = phase + (64 << 18); - re = (sine_table_f32[(sphase & 0x03FF0000)>>18]*127); - im = (sine_table_f32[(phase & 0x03FF0000)>>18]*127); + re = (sine_table_i8[(sphase & 0x03FF0000) >> 18]); + im = (sine_table_i8[(phase & 0x03FF0000) >> 18]); - buffer.p[i] = {(int8_t)re,(int8_t)im}; + buffer.p[i] = {(int8_t)re, (int8_t)im}; } } void RDSProcessor::on_message(const Message* const msg) { if (msg->id == Message::ID::RDSConfigure) { - //const auto message = *reinterpret_cast(p); + const auto message = *reinterpret_cast(msg); rdsdata = (uint32_t*)shared_memory.tx_data; + message_length = message.length; configured = true; } } diff --git a/firmware/baseband/proc_rds.hpp b/firmware/baseband/proc_rds.hpp index c959ad460..53e84663b 100644 --- a/firmware/baseband/proc_rds.hpp +++ b/firmware/baseband/proc_rds.hpp @@ -41,6 +41,7 @@ private: BasebandThread baseband_thread { 2280000, this, NORMALPRIO + 20, baseband::Direction::Transmit }; + uint16_t message_length; int8_t re, im; uint8_t mphase, s; uint32_t bit_pos; @@ -54,8 +55,7 @@ private: int32_t sample; int out_sample_index = SAMPLE_BUFFER_SIZE - 1; uint32_t phase, sphase; - int32_t sig, frq, frq_im, rdsc; - int32_t k; + int32_t delta; bool configured { false }; diff --git a/firmware/baseband/proc_xylos.cpp b/firmware/baseband/proc_xylos.cpp index c77c32857..1dae9151f 100644 --- a/firmware/baseband/proc_xylos.cpp +++ b/firmware/baseband/proc_xylos.cpp @@ -55,7 +55,7 @@ void XylosProcessor::execute(const buffer_c8_t& buffer) { message.n = 25; // End of message code shared_memory.application_queue.push(message); } else { - message.n = byte_pos; // Inform UI about progress (just as eye candy) + message.n = byte_pos; // Inform UI about progress (just as eye candy) shared_memory.application_queue.push(message); } @@ -74,7 +74,7 @@ void XylosProcessor::execute(const buffer_c8_t& buffer) { re = 0; im = 0; } else { - tone_sample = (sine_table_i8[(tone_phase & 0x03FC0000)>>18]); + tone_sample = (sine_table_i8[(tone_phase & 0x03FC0000) >> 18]); // Audio preview sample generation: 1536000/48000 = 32 /*if (as >= 31) { @@ -87,16 +87,16 @@ void XylosProcessor::execute(const buffer_c8_t& buffer) { // FM // 1<<18 = 262144 // m = (262144 * BW) / 1536000 / 2 - frq = tone_sample * 853; // 10kHz BW + delta = tone_sample * 853; // 10kHz BW - phase = (phase + frq); - sphase = phase + (64<<18); + phase += delta; + sphase = phase + (64 << 18); - re = (sine_table_i8[(sphase & 0x03FC0000)>>18]); - im = (sine_table_i8[(phase & 0x03FC0000)>>18]); + re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]); + im = (sine_table_i8[(phase & 0x03FC0000) >> 18]); } - buffer.p[i] = {(int8_t)re,(int8_t)im}; + buffer.p[i] = {(int8_t)re, (int8_t)im}; } //audio_output.write(audio_buffer); diff --git a/firmware/baseband/proc_xylos.hpp b/firmware/baseband/proc_xylos.hpp index f6c226fce..6d107dcbd 100644 --- a/firmware/baseband/proc_xylos.hpp +++ b/firmware/baseband/proc_xylos.hpp @@ -68,7 +68,7 @@ private: uint8_t digit = 0; uint32_t sample_count = 0; uint32_t tone_phase, phase, sphase; - int32_t tone_sample, frq; + int32_t tone_sample, delta; bool silence = true; TXDoneMessage message; diff --git a/firmware/common/lcd_ili9341.cpp b/firmware/common/lcd_ili9341.cpp index 612e27802..4c32e5654 100644 --- a/firmware/common/lcd_ili9341.cpp +++ b/firmware/common/lcd_ili9341.cpp @@ -295,7 +295,7 @@ void ILI9341::drawBMP(const ui::Point p, const uint8_t * bitmap, const bool tran uint32_t data_idx; uint8_t by, c, count, transp_idx = 0; ui::Color line_buffer[240]; - ui::Coord px = 0, py; + uint16_t px = 0, py; ui::Color palette[16]; // Abort if bad depth or no RLE diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index efb9104b6..3fb3f6cd7 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -539,13 +539,13 @@ public: class RDSConfigureMessage : public Message { public: constexpr RDSConfigureMessage( - const uint32_t length + const uint16_t length ) : Message { ID::RDSConfigure }, length(length) { } - const uint32_t length = 0; + const uint16_t length = 0; }; class RetuneMessage : public Message { diff --git a/firmware/common/portapack_shared_memory.hpp b/firmware/common/portapack_shared_memory.hpp index 882c7d1bd..52283f5e9 100644 --- a/firmware/common/portapack_shared_memory.hpp +++ b/firmware/common/portapack_shared_memory.hpp @@ -28,9 +28,9 @@ #include "message_queue.hpp" struct JammerRange { - bool active; - int64_t center; - int64_t width; + bool enabled; + uint64_t center; + uint32_t width; uint32_t duration; }; @@ -46,12 +46,9 @@ struct SharedMemory { MessageQueue app_local_queue { app_local_queue_data, app_local_queue_k }; char m4_panic_msg[32] { 0 }; - - size_t bit_length; - JammerRange jammer_ranges[16]; - - char tx_data[512] { 0 }; + // struct tx_data union for 9x JammerRange ? + uint8_t tx_data[512] { 0 }; }; extern SharedMemory& shared_memory; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 769480d30..178d9f90e 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -103,7 +103,7 @@ void Widget::hidden(bool hide) { if( hide ) { // TODO: Instead of dirtying parent entirely, dirty only children // that overlap with this widget. - parent()->dirty_overlapping_children_in_rect(parent_rect); + dirty_overlapping_children_in_rect(parent_rect); /* TODO: Notify self and all non-hidden children that they're * now effectively hidden? */ @@ -1116,6 +1116,35 @@ SymField::SymField( set_focusable(true); } +SymField::SymField( + Point parent_pos, + size_t length, + bool hex +) : Widget { { parent_pos, { static_cast(8 * length), 16 } } }, + length_ { length }, + hex_ { hex } +{ + uint8_t c; + + // Hex field auto-init + for (c = 0; c < length; c++) + set_symbol_list(c, "0123456789ABCDEF"); + + set_focusable(true); +} + +uint64_t SymField::value_hex_u64() { + uint8_t c; + uint64_t v = 0; + + if (hex_) { + for (c = 0; c < length_; c++) + v += values_[c] << (4 * (length_ - 1 - c)); + return v; + } else + return 0; +} + uint32_t SymField::value(const uint32_t index) { if (index >= length_) return 0; diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index ff9001cb8..7360d88a3 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -447,6 +447,7 @@ public: std::function on_change; SymField(Point parent_pos, size_t length); + SymField(Point parent_pos, size_t length, bool hex); SymField(const SymField&) = delete; SymField(SymField&&) = delete; @@ -455,6 +456,7 @@ public: void set_value(const uint32_t index, const uint32_t new_value); void set_length(const uint32_t new_length); void set_symbol_list(const uint32_t index, const std::string symbol_list); + uint64_t value_hex_u64(); void paint(Painter& painter) override; @@ -468,6 +470,7 @@ private: uint32_t selected_ = 0; size_t length_, prev_length_; bool erase_prev_ = false; + bool hex_ = false; int32_t clip_value(const uint32_t index, const uint32_t value); }; diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index 990d84b0a..6311d0d92 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ diff --git a/sdcard/numbers/anounce.wav b/sdcard/numbers/anounce.wav new file mode 100644 index 000000000..166d590a4 Binary files /dev/null and b/sdcard/numbers/anounce.wav differ diff --git a/sdcard/yosemitesam.wav b/sdcard/yosemitesam.wav deleted file mode 100644 index 5fc5a72e4..000000000 Binary files a/sdcard/yosemitesam.wav and /dev/null differ