diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a264215c..c521a7edd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,10 +355,26 @@ endif() ") set_tests_properties(test_fdmdv_48to8 PROPERTIES PASS_REGULAR_EXPRESSION "PASS") + # Basic sanity check of Quisk complex band pass filter. Note complex filtering cosw(wn) gives + # just the +ve freq exp(jwn) so output power is 0.5 input power + add_test(NAME test_quisk_filter + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + ${CMAKE_CURRENT_BINARY_DIR}/misc/mksine in.raw 1500 1; + cat in.raw | ${CMAKE_CURRENT_BINARY_DIR}/unittest/tquisk_filter | + sox -t .s16 -r 8000 -c 1 - -t .s16 out.raw vol 2; + cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" echo \"diff_fft_mag('in.raw','out.raw'); quit;\" | octave-cli -qf + ") + set_tests_properties(test_quisk_filter PROPERTIES PASS_REGULAR_EXPRESSION "PASS") + add_test(NAME test_CML_ldpcut - COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; SHORT_VERSION_FOR_CTEST=1 octave-cli -qf ldpcut.m") + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; CTEST_SINGLE=1 octave-cli -qf ldpcut.m") set_tests_properties(test_CML_ldpcut PROPERTIES PASS_REGULAR_EXPRESSION "Nerr: 0") + add_test(NAME test_CML_ldpcut_one_stuffing + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; CTEST_ONE_STUFFING=1 octave-cli -qf ldpcut.m") + set_tests_properties(test_CML_ldpcut_one_stuffing PROPERTIES PASS_REGULAR_EXPRESSION "Ferrs: 0") + # Golay (23,11) unit tests add_test(NAME test_golay23 COMMAND sh -c "${CMAKE_CURRENT_BINARY_DIR}/unittest/golay23") add_test(NAME test_golay23_runtime_tables COMMAND sh -c "${CMAKE_CURRENT_BINARY_DIR}/unittest/golay23_runtime_tables") @@ -407,6 +423,10 @@ endif() DISPLAY=\"\" octave-cli -qf fdmdv_ut.m") set_tests_properties(test_FDMDV_modem_octave_c PROPERTIES PASS_REGULAR_EXPRESSION "errors......: 0") + # ------------------------------------------------------------------------- + # COHPSK Modem + # ------------------------------------------------------------------------- + add_test(NAME test_COHPSK_modem_octave_port COMMAND sh -c "$ && DISPLAY=\"\" octave-cli --no-gui -qf ${CMAKE_CURRENT_SOURCE_DIR}/octave/tcohpsk.m" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/octave) @@ -470,7 +490,7 @@ endif() add_test(NAME test_OFDM_modem_octave_burst_acq COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; - echo \"ofdm_acquisition; quit\" | DISPLAY=\"\" octave-cli") + echo \"ctest=1; ofdm_acquisition; quit\" | DISPLAY=\"\" octave-cli") set_tests_properties(test_OFDM_modem_octave_burst_acq PROPERTIES PASS_REGULAR_EXPRESSION "P.acq. = 1.00") add_test(NAME test_OFDM_modem_octave_datac0_postamble @@ -584,8 +604,9 @@ endif() # ------------------------------------------------------------------------- # To integrate a new mode/waveform we prototype in Octave, get the core OFDM modem - # running in C, then the FreeDV API. Here we test Octave and the C versions of the - # OFDM modem working together, to help prevent any bit rot between them + # running in C (ofdm_mod & ofdm_demod), then the FreeDV API (frredv_tx & freedv_rx). + # Here we test Octave and the C versions of the OFDM modem working together, to help + # prevent any bit rot between them # DATAC0 burst mode Octave Tx, C Rx add_test(NAME test_OFDM_modem_datac0_octave_burst @@ -630,7 +651,36 @@ endif() ./ch - - --No -17 | ./ofdm_demod --mode datac0 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") - # ------------------------------------------------------------------------- + # DATAC4 C Tx, Octave Rx, burst mode + add_test(NAME test_OFDM_modem_datac4_octave + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}; + ./src/ofdm_mod --mode datac4 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 5 > test.raw; + cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" octave-cli -qf --eval 'ofdm_ldpc_rx(\"${CMAKE_CURRENT_BINARY_DIR}/test.raw\",\"datac4\",\"packetsperburst\",1)'") + set_tests_properties(test_OFDM_modem_datac3_octave PROPERTIES PASS_REGULAR_EXPRESSION "Coded PER: 0.0000 Pckts: 5") + + # DATAC13 Octave Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac13_octave + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" octave-cli -qf --eval 'ofdm_ldpc_tx(\"${CMAKE_CURRENT_BINARY_DIR}/src/test.raw\",\"datac13\",1,3,\"awgn\",\"bursts\",5)'; + cd ${CMAKE_CURRENT_BINARY_DIR}/src; + cat test.raw | ./ofdm_demod --mode datac13 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # DATAC4 C Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac4_ldpc_burst + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./ofdm_mod --mode datac4 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 10 | + ./ch - - --No -17 | + ./ofdm_demod --mode datac4 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # DATAC13 C Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac13_ldpc_burst + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./ofdm_mod --mode datac13 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 10 | + ./ch - - --No -17 | + ./ofdm_demod --mode datac13 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # ------------------------------------------------------------------------- # LDPC # ------------------------------------------------------------------------- @@ -1007,6 +1057,24 @@ if (NOT APPLE) ./freedv_data_raw_tx --testframes 10 DATAC1 /dev/zero /dev/null") set_tests_properties(test_memory_leak_FreeDV_DATAC1_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + add_test(NAME test_memory_leak_FreeDV_DATAC3_tx + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ + ./freedv_data_raw_tx --testframes 10 DATAC3 /dev/zero /dev/null") + set_tests_properties(test_memory_leak_FreeDV_DATAC3_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + + add_test(NAME test_memory_leak_FreeDV_DATAC4_tx + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ + ./freedv_data_raw_tx --testframes 10 DATAC4 /dev/zero /dev/null") + set_tests_properties(test_memory_leak_FreeDV_DATAC4_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + + add_test(NAME test_memory_leak_FreeDV_DATAC13_tx + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ + ./freedv_data_raw_tx --testframes 10 DATAC13 /dev/zero /dev/null") + set_tests_properties(test_memory_leak_FreeDV_DATAC13_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + add_test(NAME test_memory_leak_FreeDV_700E_tx COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ @@ -1244,6 +1312,20 @@ endif(NOT APPLE) ./freedv_data_raw_rx DATAC3 - binaryOut.bin -v; diff binaryIn.bin binaryOut.bin") + add_test(NAME test_freedv_data_raw_ofdm_datac4_burst_file + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + head -c $((54*10)) binaryIn.bin; + ./freedv_data_raw_tx DATAC4 binaryIn.bin - --bursts 10 | + ./freedv_data_raw_rx DATAC4 - binaryOut.bin -v; + diff binaryIn.bin binaryOut.bin") + + add_test(NAME test_freedv_data_raw_ofdm_datac13_burst_file + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + head -c $((14*10)) binaryIn.bin; + ./freedv_data_raw_tx DATAC13 binaryIn.bin - --bursts 10 | + ./freedv_data_raw_rx DATAC13 - binaryOut.bin -v; + diff binaryIn.bin binaryOut.bin") + # FSK LDPC default 100 bit/s 2FSK, enough noise for several % raw BER to give # FEC/acquisition a work out, bursts of 1 frame as that stresses acquisition add_test(NAME test_freedv_data_raw_fsk_ldpc_100 @@ -1320,12 +1402,15 @@ endif(NOT APPLE) # Set common properties for tests that need Octave/CML set_tests_properties( test_CML_ldpcut + test_CML_ldpcut_one_stuffing test_OFDM_modem_octave_port test_OFDM_modem_octave_port_Nc_31 test_OFDM_modem_octave_datac0_mpp_coded test_OFDM_modem_datac0_octave_burst test_OFDM_modem_datac1_octave test_OFDM_modem_datac3_octave + test_OFDM_modem_datac4_octave + test_OFDM_modem_datac13_octave test_fsk_lib_4fsk_ldpc test_OFDM_modem_datac0_compression PROPERTIES diff --git a/README_data.md b/README_data.md index cf9d10190..543398831 100644 --- a/README_data.md +++ b/README_data.md @@ -12,6 +12,16 @@ The VHF data channel was developed by Jeroen Vreeken. ## Quickstart +Raw modem frame API: + +1. Let's send a 128 byte frame containing some text over the modem: + ```sh + padding=$(head -c 115 < /dev/zero | tr '\0' '-'); echo "Hello World" $padding > in.txt + ./src/freedv_data_raw_tx --bursts 1 datac3 in.txt - | ./src/freedv_data_raw_rx --framesperburst 1 datac3 - - + Hello World -------- + ``` + Note we've padded the input frame to 126 bytes, the DATAC3 framesize (less CRC). + VHF packet data API: 1. Simple test using mode 2400A and VHF packet data @@ -41,129 +51,6 @@ VHF packet data API: $ ./src/freedv_data_tx 2400A - --callsign T3ST --ssid 15 --frames 15 | src/freedv_data_rx 2400A - ``` -Raw modem frame API: - -1. Let's send a 128 byte frame containing some text over the modem: - ```sh - padding=$(head -c 115 < /dev/zero | tr '\0' '-'); echo "Hello World" $padding > in.txt - ./src/freedv_data_raw_tx --bursts 1 datac3 in.txt - | ./src/freedv_data_raw_rx --framesperburst 1 datac3 - - - Hello World -------- - ``` - Note we've padded the input frame to 126 bytes, the DATAC3 framesize (less CRC). - -# VHF Packet Data Channel - -The FreeDV VHF data channel operates on a packet level. The FreeDV modems however typically operate on a fixed frame base. This means that data packets have to be sent in multiple frames. - -The packet format is modeled after Ethernet. As a result, any protocol that is compatible with Ethernet can potentially be used over a FreeDV data link. (There are of course practical limits. Browsing the world wide web with just a few hundred bits per second will not be a pleasant experience.) - -## Header optimization - -When there are no packets available for transmission a small 'filler' packet with just the sender's address will be sent. -When there is a packet available not all of the header needs to be sent. The sender's address can often be left out if it was already sent in a previous frame. Likewise when the packet has no specific destination but is targeted at a multicast address, this can also be transmitted in a single bit as opposed to a 6 byte broadcast address. - - -## Addressing - -Since the format is based on Ethernet, a 6 byte sender and destination address is used. It is possible to encode an ITU compatible callsign in these bytes. See http://dmlinking.net/eth_ar.html for more info. Or have a look at freedv_data_tx.c and freedv_data_rx.c for an actual implementation. - -## Packet types - -The 2 byte EtherType field is used to distinguish between various protocols. - -## Checks - -Not all channels are perfect, and especially since a packet is split up over multiple frames, bits might get lost. Each packet therefore has a CRC which is checked before it is accepted. Note there is No FEC on 2400A/2400B/800XA. - -## Available modes - -The data channel is available for modes 2400A, 2400B and 800XA. - -## API - -The data channel is part of the regular FreeDV API. - -### Initialization - -After creating a new freedv instance with freedv_open(), a few more calls need to be done before the data channel is usable. - - ``` - void freedv_set_data_header (struct freedv *freedv, unsigned char *header); - ``` - -The address that will be used for 'filler' packets must be set. The freedv_set_data_header() function must be called with a 6 byte header. - - ``` - typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size); - typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size); - void freedv_set_callback_data (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state); - ``` - -Using freedv_set_callback_data() two callback functions can be provided. The datarx callback will be used whenever a new data packet has been successfully received. The datatx callback will be used when a new data packet is required for transmission. - -### Operation - - ``` - void freedv_datatx (struct freedv *f, short mod_out[]); - ``` - -During normal operation the freedv_datatx() function can be used whenever a data frame has to be sent. If no data is available it will request new data using the datatx callback. The callback function is allowed to set 'size' to zero if no data is available or if it wishes to send an address frame. - -For reception the regular freedv_rx() functions can be used as received data will automatically be reported using the datarx callback. Be aware that these functions return the actual number of received speech samples. When a data frame is received the return value will be zero. This may lead to 'gaps' in the audio stream which will have to be filled with silence. - -### Examples - -The freedv_data_tx and freedv_data_rx test programs implement the minimum needed to send and receive data packets. - -## Mixing voice and data - -Encoding only voice data is easy with the FreeDV API. Simply use the freedv_tx() function and provide it with speech samples. -Likewise encoding only data is also easy. Make sure to provide a source of data frames using the freedv_set_callback_data() function, and use the freedv_datatx() function to generate frames. - -However there are many use cases where one would like to transmit a mix of voice and data. For example one might want to transmit their callsign in a machine readable format, or a short position report. There are a few ways to do this: - -### Data bursts at start and/or end of transmission - -This method simply transmits voice frames during the transmission, except for a few moments. For example when the user keys the radio the software uses the freedv_datatx() function for a number of frames before switching to regular voice frames. -Likewise when the user releases the key the software may hold it for a number of frames to transmit data before it releases the actual radio. - -Be careful though: depending on your setup (radio, PC, soundcard, etc) the generated frames and the keying of your radio might not be perfectly in sync and the first or last frames might be lost in the actual transmission. Make sure to take this into account when using this method. - -### Data and voice interleaved - -Another method is to generate a mixed stream of frames. Compared to a small burst at the beginning or end a lot more data can be sent. We only need a way to choose between voice or data such that the recovered speech at the other side is not impacted. - -#### Detect voice activity - -When it is possible to determine activity in the voice signal (and it almost always is) this presence can be used to insert a data frame by calling freedv_datatx() instead of freedv_tx()/freedv_codectx(). This method is used in the freedv_mixed_tx demo program. When the option --codectx is given the codec2 library is used to determine the activity. - - ``` - $ ./src/freedv_mixed_tx 2400A ../raw/hts1a.raw - --codectx | src/freedv_data_rx 2400A - - $ ./src/freedv_mixed_tx 2400A ../raw/hts1a.raw - | src/freedv_data_rx 2400A - - ``` - -The advantage of this method is that the audio is not distorted, there was nothing (or near nothing) to distort. A drawback is that constant voice activity may mean there are insufficient frames for data. - -### Receiving mixed voice and data - -Receiving and decoding a mixed voice and data stream is (almost) as easy as receiving a regular voice-only transmission. -One simply uses the regular API calls for reception of speech samples. In addition, the callback functions are used for data. -There is one caveat though: when a data frame is received the API functions (like freedv_rx) will return zero as this is the amount of codec/voice data received. -For proper playback silence (or comfort noise) should be inserted for the duration of a frame to restore the timing of the original source speech samples. -An example of how this is done is provided in freedv_mixed_rx - - ``` - $ ./src/freedv_mixed_tx 2400A ../raw/hts1a.raw - | src/freedv_mixed_rx 2400A - ./hts1a_out.raw - ``` - -### Insert a data frame periodically - -This is a very simple method, simply insert a data frame every n frames, (e.g. once every 10 seconds). Since single FreeDV frames are relatively short (tens of milliseconds) the effect on received audio will be minor. The advantage of this method is that one can create a guaranteed amount of data bandwidth. A drawback is some interruption in the audio that may be noticed. - -### Combination of the above. - -A combination of the two methods may also be used. Send data when no voice is active and insert a frame when this does not occur for a long time. - # Raw Data using the FreeDV API The raw data API can be used to send frames of bytes over radio channels. The frames are protected with FEC and have a 16-bit checksum to verify correct transmission. However the raw data API may lose frames due to channel impairments, loss of sync, or acquisition delays. The caller must handle these situations. The caller is also responsible for segmentation/re-assembly of the modem frames into larger blocks of data. @@ -243,20 +130,22 @@ Some notes on this example: 1. Although the `ch` utility is designed for 8kHz sample rate operation, it just operates on sampled signals, so it's OK to use at higher sample rates. It does have some internal filtering so best to keep your signal well away from 0 and (sample rate)/2. The SNR measurement is calibrated to a 3000 Hz noise bandwidth, so won't make much sense at other sample rates. The third argument `-12` sets the noise level of the channel. 1. The `--mask` frequency offset algorithm is used, which gives better results on noisy channels, especially for 4FSK. -### Reading Further +## Reading Further 1. Examples in the [ctests](CMakeLists.txt). 1. [FSK_LDPC blog post](http://www.rowetel.com/?p=7467) -## OFDM Raw Data modes for HF Radio +# OFDM Raw Data modes for HF Radio These modes use an OFDM modem with powerful LDPC codes and are designed for sending data over HF radio channels with multipath fading. The current modes supported are: | FreeDV Mode | RF bandwidth (Hz) | Payload data rate bits/s | Payload bytes/frame | FEC | Duration (sec) | MPP test | Use case | | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | -| DATAC0 | 500 | 291 | 14 | (256,128) | 0.44 | 70/100 at 0dB | Reverse link ACK packets (all SNRs) | +| DATAC0 | 500 | 291 | 14 | (256,128) | 0.44 | 70/100 at 0dB | Reverse link ACK packets | | DATAC1 | 1700 | 980 | 510 | (8192,4096) | 4.18 | 92/100 at 5dB | Forward link data (medium SNR) | | DATAC3 | 500 | 321 | 126 | (2048,1024) | 3.19 | 74/100 at 0dB | Forward link data (low SNR) | +| DATAC4 | 250 | 87 | 56 | (1472,448) | 5.17 | 90/100 at -4dB | Forward link data (low SNR) | +| DATAC13 | 200 | 64 | 14 | (384,128) | 2.0 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | Notes: 1. 16 bits (2 bytes) per frame are reserved for a 16 bit CRC, e.g. for `datac3` we have 128 byte frames, and 128-2=126 bytes/frame of payload data. @@ -340,7 +229,7 @@ The following curves illustrate the OFDM raw data mode performance and throughpu ![](doc/c_tx_comp.png) ![](doc/c_tx_comp_thruput.png) -`datac0` doesn't perform as well as `datac3` over the MPP channel due to it's short length compared to the fading period. The throughput curve can be used as a guide for "gear shifting" between modes. These curves were generated by [snr_curves.sh](../unittest/raw_data_curves/snr_curves.sh) +The signalling modes (`datac0` and `datac13`) tend to have a "long PER tail" at they are short in duration compared to the fading period. The throughput curve can be used as a guide for "gear shifting" between modes. These curves were generated by [snr_curves.sh](../unittest/raw_data_curves/snr_curves.sh) ## SNR estimation and clipping @@ -363,7 +252,6 @@ The following plots illustrate the SNR estimates versus actual channel SNR with ## Reading Further -Resources: 1. See the raw data example in Quickstart section above. 1. For simple examples of how use the FreeDV API, see the demo programs [freedv_datac1_tx.c](demo/freedv_datac1_tx.c) and [freedv_datac1_rx.c](demo/freedv_datac1_rx.c) 1. [freedv_data_raw_tx.c](src/freedv_data_raw_tx.c) and [freedv_data_raw_rx.c](src/freedv_data_raw_rx.c) are more full deatured example programs. @@ -371,3 +259,119 @@ Resources: 1. Examples in the [ctests](CMakeLists.txt) (look for "FreeDV API raw data") 1. [Codec 2 HF Data Modes Part 1 blog post](http://www.rowetel.com/?p=7167) 1. [HF Data Acquisition](https://github.com/drowe67/codec2/pull/171) GitHub Pull Request +1. [datac4 & datac13](https://github.com/drowe67/codec2/pull/364) GitHub Pull Request +1. [FreeDATA](https://freedata.app/) uses these modems + +# VHF Packet Data Channel + +The FreeDV VHF data channel operates on a packet level. The FreeDV modems however typically operate on a fixed frame base. This means that data packets have to be sent in multiple frames. + +The packet format is modeled after Ethernet. As a result, any protocol that is compatible with Ethernet can potentially be used over a FreeDV data link. (There are of course practical limits. Browsing the world wide web with just a few hundred bits per second will not be a pleasant experience.) + +## Header optimization + +When there are no packets available for transmission a small 'filler' packet with just the sender's address will be sent. +When there is a packet available not all of the header needs to be sent. The sender's address can often be left out if it was already sent in a previous frame. Likewise when the packet has no specific destination but is targeted at a multicast address, this can also be transmitted in a single bit as opposed to a 6 byte broadcast address. + + +## Addressing + +Since the format is based on Ethernet, a 6 byte sender and destination address is used. It is possible to encode an ITU compatible callsign in these bytes. See http://dmlinking.net/eth_ar.html for more info. Or have a look at freedv_data_tx.c and freedv_data_rx.c for an actual implementation. + +## Packet types + +The 2 byte EtherType field is used to distinguish between various protocols. + +## Checks + +Not all channels are perfect, and especially since a packet is split up over multiple frames, bits might get lost. Each packet therefore has a CRC which is checked before it is accepted. Note there is No FEC on 2400A/2400B/800XA. + +## Available modes + +The data channel is available for modes 2400A, 2400B and 800XA. + +## API + +The data channel is part of the regular FreeDV API. + +### Initialization + +After creating a new freedv instance with freedv_open(), a few more calls need to be done before the data channel is usable. + + ``` + void freedv_set_data_header (struct freedv *freedv, unsigned char *header); + ``` + +The address that will be used for 'filler' packets must be set. The freedv_set_data_header() function must be called with a 6 byte header. + + ``` + typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size); + typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size); + void freedv_set_callback_data (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state); + ``` + +Using freedv_set_callback_data() two callback functions can be provided. The datarx callback will be used whenever a new data packet has been successfully received. The datatx callback will be used when a new data packet is required for transmission. + +### Operation + + ``` + void freedv_datatx (struct freedv *f, short mod_out[]); + ``` + +During normal operation the freedv_datatx() function can be used whenever a data frame has to be sent. If no data is available it will request new data using the datatx callback. The callback function is allowed to set 'size' to zero if no data is available or if it wishes to send an address frame. + +For reception the regular freedv_rx() functions can be used as received data will automatically be reported using the datarx callback. Be aware that these functions return the actual number of received speech samples. When a data frame is received the return value will be zero. This may lead to 'gaps' in the audio stream which will have to be filled with silence. + +### Examples + +The freedv_data_tx and freedv_data_rx test programs implement the minimum needed to send and receive data packets. + +## Mixing voice and data + +Encoding only voice data is easy with the FreeDV API. Simply use the freedv_tx() function and provide it with speech samples. +Likewise encoding only data is also easy. Make sure to provide a source of data frames using the freedv_set_callback_data() function, and use the freedv_datatx() function to generate frames. + +However there are many use cases where one would like to transmit a mix of voice and data. For example one might want to transmit their callsign in a machine readable format, or a short position report. There are a few ways to do this: + +### Data bursts at start and/or end of transmission + +This method simply transmits voice frames during the transmission, except for a few moments. For example when the user keys the radio the software uses the freedv_datatx() function for a number of frames before switching to regular voice frames. +Likewise when the user releases the key the software may hold it for a number of frames to transmit data before it releases the actual radio. + +Be careful though: depending on your setup (radio, PC, soundcard, etc) the generated frames and the keying of your radio might not be perfectly in sync and the first or last frames might be lost in the actual transmission. Make sure to take this into account when using this method. + +### Data and voice interleaved + +Another method is to generate a mixed stream of frames. Compared to a small burst at the beginning or end a lot more data can be sent. We only need a way to choose between voice or data such that the recovered speech at the other side is not impacted. + +#### Detect voice activity + +When it is possible to determine activity in the voice signal (and it almost always is) this presence can be used to insert a data frame by calling freedv_datatx() instead of freedv_tx()/freedv_codectx(). This method is used in the freedv_mixed_tx demo program. When the option --codectx is given the codec2 library is used to determine the activity. + + ``` + $ ./src/freedv_mixed_tx 2400A ../raw/hts1a.raw - --codectx | src/freedv_data_rx 2400A - + $ ./src/freedv_mixed_tx 2400A ../raw/hts1a.raw - | src/freedv_data_rx 2400A - + ``` + +The advantage of this method is that the audio is not distorted, there was nothing (or near nothing) to distort. A drawback is that constant voice activity may mean there are insufficient frames for data. + +### Receiving mixed voice and data + +Receiving and decoding a mixed voice and data stream is (almost) as easy as receiving a regular voice-only transmission. +One simply uses the regular API calls for reception of speech samples. In addition, the callback functions are used for data. +There is one caveat though: when a data frame is received the API functions (like freedv_rx) will return zero as this is the amount of codec/voice data received. +For proper playback silence (or comfort noise) should be inserted for the duration of a frame to restore the timing of the original source speech samples. +An example of how this is done is provided in freedv_mixed_rx + + ``` + $ ./src/freedv_mixed_tx 2400A ../raw/hts1a.raw - | src/freedv_mixed_rx 2400A - ./hts1a_out.raw + ``` + +### Insert a data frame periodically + +This is a very simple method, simply insert a data frame every n frames, (e.g. once every 10 seconds). Since single FreeDV frames are relatively short (tens of milliseconds) the effect on received audio will be minor. The advantage of this method is that one can create a guaranteed amount of data bandwidth. A drawback is some interruption in the audio that may be noticed. + +### Combination of the above. + +A combination of the two methods may also be used. Send data when no voice is active and insert a frame when this does not occur for a long time. + diff --git a/doc/c_tx_comp.png b/doc/c_tx_comp.png index 8663ca07e..5d781c059 100644 Binary files a/doc/c_tx_comp.png and b/doc/c_tx_comp.png differ diff --git a/doc/c_tx_comp_thruput.png b/doc/c_tx_comp_thruput.png index 435a88bed..0288f7aa0 100644 Binary files a/doc/c_tx_comp_thruput.png and b/doc/c_tx_comp_thruput.png differ diff --git a/doc/modem_codec_frame_design.ods b/doc/modem_codec_frame_design.ods index 449f10cf6..e6cf1c746 100644 Binary files a/doc/modem_codec_frame_design.ods and b/doc/modem_codec_frame_design.ods differ diff --git a/doc/snrest_snr_ctxc.png b/doc/snrest_snr_ctxc.png index e3b194118..326d1473d 100644 Binary files a/doc/snrest_snr_ctxc.png and b/doc/snrest_snr_ctxc.png differ diff --git a/octave/H_256_512_4.mat b/octave/H_256_512_4.mat index 143ae7321..c17a2a7f2 100644 Binary files a/octave/H_256_512_4.mat and b/octave/H_256_512_4.mat differ diff --git a/octave/diff_fft_mag.m b/octave/diff_fft_mag.m index 9bae333da..005be70e1 100644 --- a/octave/diff_fft_mag.m +++ b/octave/diff_fft_mag.m @@ -5,6 +5,9 @@ function diff_fft_mag(filename1, filename2, threshdB = -40, ignore=1000) s1 = s1(ignore:end); s2 = load_raw(filename2)'; s2 = s2(ignore:end); + + len = min([length(s1) length(s2)]); + s1 = s1(1:len); s2 = s2(1:len); S1 = abs(fft(s1.*hanning(length(s1))')); S2 = abs(fft(s2.*hanning(length(s2))')); diff --git a/octave/ldpc.m b/octave/ldpc.m index 1f308f8a4..0938b8dc7 100644 --- a/octave/ldpc.m +++ b/octave/ldpc.m @@ -96,7 +96,7 @@ function init_cml() code_param.ldpc_parity_bits_per_frame = framesize - code_param.ldpc_data_bits_per_frame; code_param.ldpc_coded_bits_per_frame = framesize; - % these variables support underfilling frame + % these variables support 1's stuffing (not using all data bits to lower code rate) code_param.data_bits_per_frame = code_param.ldpc_data_bits_per_frame; code_param.coded_bits_per_frame = code_param.ldpc_coded_bits_per_frame; code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; @@ -104,23 +104,41 @@ function init_cml() function [codeword s] = ldpc_enc(data, code_param) + if code_param.data_bits_per_frame != code_param.ldpc_data_bits_per_frame + % optionally lower the code rate by "1's stuffing" - setting Nunused data bits to 1 + Nunused = code_param.ldpc_data_bits_per_frame - code_param.data_bits_per_frame; + codeword = LdpcEncode([data ones(1,Nunused)], code_param.H_rows, code_param.P_matrix); + % remove unused data bits from codeword, as they are known to the receiver and don't need to be transmitted + codeword = [ codeword(1:code_param.data_bits_per_frame) codeword(code_param.ldpc_data_bits_per_frame+1:end) ]; + else codeword = LdpcEncode( data, code_param.H_rows, code_param.P_matrix ); - s = Modulate( codeword, code_param.S_matrix ); + end + s = Modulate( codeword, code_param.S_matrix ); endfunction -function [detected_data paritychecks] = ldpc_dec(code_param, max_iterations, demod_type, decoder_type, r, EsNo, fading) +function [detected_data paritychecks] = ldpc_dec(code_param, max_iterations, ... + demod_type, decoder_type, r, ... + EsNo, fading) + % handle "1's stuffing" case where we don't use all data bits + Nunused = code_param.ldpc_data_bits_per_frame - code_param.data_bits_per_frame; + symbol_likelihood = Demod2D( r, code_param.S_matrix, EsNo, fading); % initialize the extrinsic decoder input - input_somap_c = zeros(1, code_param.ldpc_coded_bits_per_frame ); + input_somap_c = zeros(1, code_param.ldpc_coded_bits_per_frame - Nunused); bit_likelihood = Somap( symbol_likelihood, demod_type, input_somap_c ); - input_decoder_c = bit_likelihood(1:code_param.ldpc_coded_bits_per_frame); + input_decoder_c = bit_likelihood(1:(code_param.ldpc_coded_bits_per_frame-Nunused)); + + % insert "very likely" LLRs for unsed data bits (in 1's stuffing case) + input_decoder_c = [input_decoder_c(1:code_param.data_bits_per_frame) ... + 100*ones(1,Nunused) ... + input_decoder_c(code_param.data_bits_per_frame+1:end)]; [x_hat paritychecks] = MpDecode( -input_decoder_c, code_param.H_rows, code_param.H_cols, ... - max_iterations, decoder_type, 1, 1); + max_iterations, decoder_type, 1, 1); [mx mx_ind] = max(paritychecks); detected_data = x_hat(mx_ind,:); endfunction diff --git a/octave/ldpcut.m b/octave/ldpcut.m index e700e9070..4e8287a59 100644 --- a/octave/ldpcut.m +++ b/octave/ldpcut.m @@ -39,6 +39,7 @@ code_param = ldpc_init_builtin(sim_in.code, rate, framesize, modulation, mod_order, mapping); rate = code_param.ldpc_data_bits_per_frame/code_param.ldpc_coded_bits_per_frame; else + % deal with H stored in different file formats tempStruct = load(sim_in.code); b = fieldnames(tempStruct); ldpcArrayName = b{1,1}; @@ -47,6 +48,18 @@ [code_param framesize rate] = ldpc_init_user(HRA, modulation, mod_order, mapping); end + % optional 1's stuffing + if isfield(sim_in, "data_bits_per_frame") + code_param.data_bits_per_frame = sim_in.data_bits_per_frame; + code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; + code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; + rate = code_param.data_bits_per_frame/code_param.coded_bits_per_frame; + printf("data_bits_per_frame = %d\n", code_param.data_bits_per_frame); + printf("coded_bits_per_frame = %d\n", code_param.coded_bits_per_frame); + printf("coded_syms_per_frame = %d\n", code_param.coded_syms_per_frame); + printf("rate: %f\n",rate); + end + % ---------------------------------- % run simulation at each Eb/No point % ---------------------------------- @@ -148,7 +161,7 @@ % 1/ Simplest possible one frame simulation % --------------------------------------------------------------------------------- -function test1_single(code="wimax") +function test1_single(code="wimax", data_bits_per_frame) printf("\nTest 1:Single -----------------------------------\n"); mod_order = 4; @@ -162,16 +175,25 @@ function test1_single(code="wimax") if strcmp(code,'wimax') framesize = 576*2; rate = 0.5; end if strcmp(code,'dvbs2') framesize = 16200; rate = 0.6; end code_param = ldpc_init_builtin(code, rate, framesize, modulation, mod_order, mapping); - + + % optional 1's stuffing + if nargin == 2 + code_param.data_bits_per_frame = data_bits_per_frame; + code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; + code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; + framesize = code_param.coded_bits_per_frame; + end + % find out what rate we actually obtained ... - rate = code_param.ldpc_data_bits_per_frame/code_param.ldpc_coded_bits_per_frame; + rate = code_param.data_bits_per_frame/code_param.coded_bits_per_frame; printf("Ndata_bits: %d Nparity_bits: %d Ncodeword_bits: %d rate: %3.2f\n", - code_param.ldpc_data_bits_per_frame, code_param.ldpc_parity_bits_per_frame, code_param.ldpc_coded_bits_per_frame, rate); + code_param.data_bits_per_frame, code_param.ldpc_parity_bits_per_frame, + code_param.coded_bits_per_frame, rate); % decoder needs an estimated channel EsNo (linear ratio, not dB) EsNo = 10; - tx_bits = round(rand(1, code_param.ldpc_data_bits_per_frame)); + tx_bits = round(rand(1, code_param.data_bits_per_frame)); [tx_codeword, qpsk_symbols] = ldpc_enc(tx_bits, code_param); rx_codeword = ldpc_dec(code_param, max_iterations, demod_type, decoder_type, qpsk_symbols, EsNo, ones(1,length(qpsk_symbols))); @@ -185,7 +207,7 @@ function test1_single(code="wimax") % 2/ Run a bunch of trials at just one EsNo point % --------------------------------------------------------------------------------- -function test2_multiple(code, Ntrials=100) +function test2_multiple(code, Ntrials=100, data_bits_per_frame) printf("\nTest 2: Multiple: %s ----------------------------\n", code); % these are inputs for Wimax mode, e.g. framesize defines code used @@ -194,6 +216,9 @@ function test2_multiple(code, Ntrials=100) sim_in.verbose = 2; sim_in.Ntrials = Ntrials; sim_in.EbNodBvec = 3; + if nargin == 3 + sim_in.data_bits_per_frame = data_bits_per_frame; + end run_simulation(sim_in); end @@ -240,11 +265,17 @@ function test3_curves(code,fg=1,Ntrials=100) init_cml(); -if getenv("SHORT_VERSION_FOR_CTEST") +% Ctest kicks off these tests using env variables +if getenv("CTEST_SINGLE") test1_single return; end +if getenv("CTEST_ONE_STUFFING") + test2_multiple("wimax",10,576); + return; +end +% Uncomment and try some of these tests if you like .... %test3_curves("H_1024_2048_4f.mat",1) %test1_single("dvbs2") %test3_curves("dvbs2",1,10) diff --git a/octave/ofdm_acquisition.m b/octave/ofdm_acquisition.m index c37df3c43..fd56a99c9 100644 --- a/octave/ofdm_acquisition.m +++ b/octave/ofdm_acquisition.m @@ -5,7 +5,7 @@ % % To run headless on a server: % -% DISPLAY=\"\" octave-cli --no-gui -qf ofdm_dev.m > 210218.txt & +% DISPLAY=\"\" octave-cli --no-gui -qf ofdm_acquisition.m > 210218.txt & ofdm_lib; channel_lib; @@ -40,6 +40,12 @@ SNRdB_setpoint = sim_in.SNR3kdB + mark_space_SNR_offset; %printf("SNR3kdB: %f Burst offset: %f\n", sim_in.SNR3kdB, mark_space_SNR_offset) rx = channel_simulate(Fs, SNRdB_setpoint, sim_in.foff_Hz, sim_in.channel, tx); + + % optional BPF + if strcmp(sim_in.mode,"datac4") || strcmp(sim_in.mode,"datac13") + [rx delay_samples] = ofdm_complex_bandpass_filter(states, sim_in.mode, rx); + l = length(rx); rx = [rx(delay_samples:l) zeros(1,delay_samples)]; + end endfunction @@ -120,7 +126,7 @@ delta_foff_log = [delta_foff_log pre_eval.delta_foff]; ct_log = [ct_log w+pre_eval.ct_est]; if states.verbose - printf("Pre i: %2d n: %8d ct_est: %6d delta_ct: %6d foff_est: %5.1f timing_mx: %3.2f Acq: %d\n", + printf("Pre i: %2d n: %8d ct_est: %6d delta_ct: %6d foff_est: %5.1f timing_mx: %3.2f Acq: %2d\n", i, n, pre_eval.ct_est, pre_eval.delta_ct, pre.foff_est, pre.timing_mx, target_acq(i)); end end @@ -138,7 +144,7 @@ delta_foff_log = [delta_foff_log post_eval.delta_foff]; ct_log = [ct_log w+post_eval.ct_est]; if states.verbose - printf("Post i: %2d n: %8d ct_est: %6d delta_ct: %6d foff_est: %5.1f timing_mx: %3.2f Acq: %d\n", + printf("Post i: %2d n: %8d ct_est: %6d delta_ct: %6d foff_est: %5.1f timing_mx: %3.2f Acq: %2d\n", i, n, post_eval.ct_est, post_eval.delta_ct, post.foff_est, post.timing_mx, target_acq(i)); end end @@ -160,18 +166,24 @@ plot(timing_mx_log(1,:),'+-;preamble;'); hold on; plot(timing_mx_log(2,:),'o-;postamble;'); - plot(0.35+0.1*state_log,'-g;state;'); - title('mx log'); axis([0 length(timing_mx_log) 0 0.5]); grid; + plot(0.45+0.1*state_log,'-g;state;'); + title('mx log'); axis([0 length(timing_mx_log) 0 1.0]); grid; hold off; figure(4); clf; plot(real(rx)); axis([0 length(rx) -3E4 3E4]); hold on; plot(ct_log,zeros(1,length(ct_log)),'r+','markersize', 25, 'linewidth', 2); hold off; figure(5); clf; plot_specgram(rx, Fs, 500, 2500); + all_mx = [ timing_mx_log(1,:) timing_mx_log(2,:)]; + figure(6); clf; [nn xx] = hist(all_mx); semilogy(xx,nn+1); grid; + figure(7); clf; cdf = empirical_cdf(0:0.1:1,all_mx); plot(0:0.1:1, cdf); grid; + end - Pa = length(find(target_acq == 1))/Ntests; - printf("%s %s SNR: %3.1f foff: %3.1f P(acq) = %3.2f\n", mode, channel, SNR3kdB, foff_Hz, Pa); + Pacq = length(find(target_acq == 1))/Ntests; + Pfalse_acq = length(find(target_acq == -1))/Ntests; + printf("%s %s SNR: %3.1f foff: %3.1f P(acq) = %3.2f P(false_acq) = %3.2f\n", mode, channel, SNR3kdB, foff_Hz, + Pacq, Pfalse_acq); endfunction @@ -227,5 +239,11 @@ function acquistion_curves_frame_by_frame_modes_channels_snr(Ntests=5, quick_tes % choose simulation to run here % --------------------------------------------------------- -frame_by_frame_acquisition_test("datac0", Ntests=5, 'mpp', SNR3kdB=5, foff_hz=0, verbose=1+8); -%acquistion_curves_frame_by_frame_modes_channels_snr(Ntests=50, quick_test=0) +if exist("ctest","var") + % simple tests to run as part of ctests + frame_by_frame_acquisition_test("datac0", Ntests=5, 'mpp', SNR3kdB=5, foff_hz=0, verbose=1+8); +else + % other development work here + frame_by_frame_acquisition_test("datac13", Ntests=100, 'mpp', SNR3kdB=-4, foff_hz=0, verbose=1+8); + %acquistion_curves_frame_by_frame_modes_channels_snr(Ntests=50, quick_test=0) +end diff --git a/octave/ofdm_demod_c.m b/octave/ofdm_demod_c.m index 4a397495d..531a649e1 100644 --- a/octave/ofdm_demod_c.m +++ b/octave/ofdm_demod_c.m @@ -40,9 +40,4 @@ function ofdm_demod_c(filename, mode="700D") axis([1 max(length(foff_hz_log_c),2) -mx mx]); title('Fine Freq'); ylabel('Hz') - - figure(5); clf; - plot(snr_est_log_c); - ylabel('SNR (dB)') - title('SNR Estimates') endfunction diff --git a/octave/ofdm_helper.m b/octave/ofdm_helper.m new file mode 100644 index 000000000..b34ec2390 --- /dev/null +++ b/octave/ofdm_helper.m @@ -0,0 +1,272 @@ +% ofdm_helper.m +% +% Misc functions that are used to support OFDM modem development, that +% aren't required for modem operation + +1; + +%------------------------------------------------------------------------------ +% print_config - utility function to use ascii-art to describe the modem frame +%------------------------------------------------------------------------------ + +function print_config(states) + ofdm_load_const; + + % ASCII-art packet visualisation + s=1; u=1; Nuwsyms=length(uw_ind_sym); + cr = 1:Nc+2; + for f=1:Np + for r=1:Ns + for c=cr + if r == 1 + if (c==1) && states.edge_pilots + sym="P"; + elseif (c==Nc+1) && states.edge_pilots + sym="P"; + elseif c>1 && c <=(Nc+1) + sym="P"; + else + sym=" "; + end + elseif c>1 && c <=(Nc+1) + sym="."; + if (u <= Nuwsyms) && (s == uw_ind_sym(u)) sym="U"; u++; end + s++; + else + sym=" "; + end + printf("%s",sym); + end + printf("\n"); + end + end + + printf("Nc=%d Ts=%4.3f Tcp=%4.3f Ns: %d Np: %d\n", Nc, 1/Rs, Tcp, Ns, Np); + printf("Nsymperframe: %d Nbitsperpacket: %d Nsamperframe: %d Ntxtbits: %d Nuwbits: %d Nuwframes: %d\n", + Ns*Nc, Nbitsperpacket, Nsamperframe, Ntxtbits, Nuwbits, Nuwframes); + printf("uncoded bits/s: %4.1f\n", Nbitsperpacket*Fs/(Np*Nsamperframe)); +end + +%----------------------------------------------------------------------- +% create_ldpc_test_frame - generate a test frame of bits +%----------------------------------------------------------------------- + +function [tx_bits payload_data_bits codeword] = create_ldpc_test_frame(states, coded_frame=1) + ofdm_load_const; + ldpc; + gp_interleaver; + + if coded_frame + % Set up LDPC code + + mod_order = 4; bps = 2; modulation = 'QPSK'; mapping = 'gray'; + + init_cml(); % TODO: make this path sensible and portable + load HRA_112_112.txt + [code_param framesize rate] = ldpc_init_user(HRA_112_112, modulation, mod_order, mapping); + assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits)); + + payload_data_bits = round(ofdm_rand(code_param.data_bits_per_frame)/32767); + codeword = LdpcEncode(payload_data_bits, code_param.H_rows, code_param.P_matrix); + Nsymbolsperframe = length(codeword)/bps; + + % need all these steps to get actual raw codeword bits at demod .. + + tx_symbols = []; + for s=1:Nsymbolsperframe + tx_symbols = [tx_symbols qpsk_mod( codeword(2*(s-1)+1:2*s) )]; + end + + tx_symbols = gp_interleave(tx_symbols); + + codeword_raw = []; + for s=1:Nsymbolsperframe + codeword_raw = [codeword_raw qpsk_demod(tx_symbols(s))]; + end + else + codeword_raw = round(ofdm_rand(Nbitsperpacket-(Nuwbits+Ntxtbits))/32767); + end + + % insert UW and txt bits + + tx_bits = assemble_modem_packet(states, codeword_raw, zeros(1,Ntxtbits)); + assert(Nbitsperpacket == length(tx_bits)); + +endfunction + +% automated test + +function test_assemble_disassemble(states) + ofdm_load_const; + + Nsymsperpacket = Nbitsperpacket/bps; + Ndatabitsperpacket = Nbitsperpacket-(Nuwbits+Ntxtbits); + Ndatasymsperpacket = Ndatabitsperpacket/bps; + codeword_bits = round(ofdm_rand(Ndatabitsperpacket)/32767); + tx_bits = assemble_modem_packet(states, codeword_bits, zeros(1,Ntxtbits)); + + tx_syms = zeros(1,Nsymsperpacket); + for s=1:Nsymsperpacket + if bps == 2 + tx_syms(s) = qpsk_mod(tx_bits(bps*(s-1)+1:bps*s)); + elseif bps == 4 + tx_syms(s) = qam16_mod(states.qam16,tx_bits(bps*(s-1)+1:bps*s)); + end + end + codeword_syms = zeros(1,Ndatasymsperpacket); + for s=1:Ndatasymsperpacket + if bps == 2 + codeword_syms(s) = qpsk_mod(codeword_bits(bps*(s-1)+1:bps*s)); + elseif bps == 4 + codeword_syms(s) = qam16_mod(states.qam16,codeword_bits(bps*(s-1)+1:bps*s)); + end + end + + [rx_uw rx_codeword_syms payload_amps txt_bits] = disassemble_modem_packet(states, tx_syms, ones(1,Nsymsperpacket)); + assert(rx_uw == states.tx_uw); + Ndatasymsperframe = (Nbitsperpacket-(Nuwbits+Ntxtbits))/bps; + assert(codeword_syms == rx_codeword_syms); +endfunction + +% test function, kind of like a CRC for QPSK symbols, to compare two vectors + +function acc = test_acc(v) + sre = 0; sim = 0; + for i=1:length(v) + x = v(i); + re = round(real(x)); im = round(imag(x)); + sre += re; sim += im; + %printf("%d %10f %10f %10f %10f\n", i, re, im, sre, sim); + end + acc = sre + j*sim; +end + + +% Save test bits frame to a text file in the form of a C array +% +% usage: +% ofdm_lib; test_bits_ofdm_file +% + +function test_bits_ofdm_file + Ts = 0.018; Tcp = 0.002; Rs = 1/Ts; bps = 2; Nc = 17; Ns = 8; + states = ofdm_init(bps, Rs, Tcp, Ns, Nc); + [test_bits_ofdm payload_data_bits codeword] = create_ldpc_test_frame(states); + printf("%d test bits\n", length(test_bits_ofdm)); + + f=fopen("../src/test_bits_ofdm.h","wt"); + fprintf(f,"/* Generated by test_bits_ofdm_file() Octave function */\n\n"); + fprintf(f,"const int test_bits_ofdm[]={\n"); + for m=1:length(test_bits_ofdm)-1 + fprintf(f," %d,\n",test_bits_ofdm(m)); + endfor + fprintf(f," %d\n};\n",test_bits_ofdm(end)); + + fprintf(f,"\nconst int payload_data_bits[]={\n"); + for m=1:length(payload_data_bits)-1 + fprintf(f," %d,\n",payload_data_bits(m)); + endfor + fprintf(f," %d\n};\n",payload_data_bits(end)); + + fprintf(f,"\nconst int test_codeword[]={\n"); + for m=1:length(codeword)-1 + fprintf(f," %d,\n",codeword(m)); + endfor + fprintf(f," %d\n};\n",codeword(end)); + + fclose(f); + +endfunction + + +% Get rid of nasty unfiltered stuff either side of OFDM signal +% This may need to be tweaked, or better yet made a function of Nc, if Nc changes +% +% usage: +% ofdm_lib; make_ofdm_bpf(1); + +function bpf_coeff = make_ofdm_bpf(write_c_header_file) + filt_n = 100; + Fs = 8000; + + bpf_coeff = fir2(filt_n,[0 900 1000 2000 2100 4000]/(Fs/2),[0.001 0.001 1 1 0.001 0.001]); + + if write_c_header_file + figure(1) + clf; + h = freqz(bpf_coeff,1,Fs/2); + plot(20*log10(abs(h))) + grid minor + + % save coeffs to a C header file + + f=fopen("../src/ofdm_bpf_coeff.h","wt"); + fprintf(f,"/* 1000 - 2000 Hz FIR filter coeffs */\n"); + fprintf(f,"/* Generated by make_ofdm_bpf() in ofdm_lib.m */\n"); + + fprintf(f,"\n#define OFDM_BPF_N %d\n\n", filt_n); + + fprintf(f,"float ofdm_bpf_coeff[]={\n"); + for r=1:filt_n + if r < filt_n + fprintf(f, " %f,\n", bpf_coeff(r)); + else + fprintf(f, " %f\n};", bpf_coeff(r)); + end + end + fclose(f); + end + +endfunction + +% Helper function to help design UW error thresholds, in particular for raw +% data modes. See also https://www.rowetel.com/wordpress/?p=7467 +function ofdm_determine_bad_uw_errors(Nuw) + figure(1); clf; + + % Ideally the 10% and 50% BER curves are a long way apart + + plot(0:Nuw, binocdf(0:Nuw,Nuw,0.1),';BER=0.1;'); hold on; + plot(binocdf(0:Nuw,Nuw,0.5),';BER=0.5;'); + + % Suggested threshold for raw data modes is the 5% probability + % level for the 50% BER curve. The pre/post-amble has a low chance + % of failure. If it does make an error, then we will have random + % bits presented as the UW (50% BER in UW). This threshold means + % there is only a 5% case of random bits being accepted as a valid UW + + bad_uw_errors = max(find(binocdf(0:Nuw,Nuw,0.5) <= 0.05))+1; + plot([bad_uw_errors bad_uw_errors],[0 1],';bad uw errors;'); hold off; grid + + xlabel('bits'); + printf("for Nuw = %d, suggest bad_uw_errors = %d\n", Nuw, bad_uw_errors); +end + +% Returns level threshold such that threshold_cdf of the tx magnitudes are +% beneath that level. Helper function that can be used to design +% the clipper level. See also https://www.rowetel.com/?p=7596 +function threshold_level = ofdm_determine_clip_threshold(tx, threshold_cdf) + Nsteps = 25; + mx = max(abs(tx)); + cdf = empirical_cdf(mx*(1:Nsteps)/Nsteps,abs(tx)); + threshold_level = find(cdf >= threshold_cdf)(1)*mx/25; + printf("threshold_cdf: %f threshold_level: %f\n", threshold_cdf, threshold_level); + figure(1); clf; [hh nn] = hist(abs(tx),Nsteps,1); + plotyy(nn,hh,mx*(1:Nsteps)/Nsteps,cdf); title('PDF and CDF Estimates'); grid; +end + + +% helper function that adds channel simulation and ensures we don't saturate int16 output samples +function [rx_real rx] = ofdm_channel(states, tx, SNR3kdB, channel, freq_offset_Hz) + [rx_real rx sigma] = channel_simulate(states.Fs, SNR3kdB, freq_offset_Hz, channel, tx, states.verbose); + + % multipath models can lead to clipping of int16 samples + num_clipped = length(find(abs(rx_real>32767))); + while num_clipped/length(rx_real) > 0.001 + rx_real /= 2; + num_clipped = length(find(abs(rx_real>32767))); + printf("WARNING: output samples clipped, reducing level\n") + end +endfunction + + diff --git a/octave/ofdm_ldpc_rx.m b/octave/ofdm_ldpc_rx.m index 83d115f63..6ef15aa03 100644 --- a/octave/ofdm_ldpc_rx.m +++ b/octave/ofdm_ldpc_rx.m @@ -84,6 +84,8 @@ function ofdm_ldpc_rx(filename, mode="700D", varargin) % main loop ---------------------------------------------------------------- + rx = ofdm_rx_filter(states, mode, rx); + f = 1; while(prx < Nsam) @@ -151,9 +153,8 @@ function ofdm_ldpc_rx(filename, mode="700D", varargin) % TODO 2020 support for padding with known data bits - [rx_codeword paritychecks] = ldpc_dec(code_param, mx_iter=100, demod=0, dec=0, ... - payload_syms_de/mean_amp, EsNo, payload_amps_de/mean_amp); - rx_bits = rx_codeword(1:code_param.data_bits_per_frame); + [rx_bits paritychecks] = fec_decode(states, code_param, payload_syms_de,... + payload_amps_de, mean_amp, EsNo); errors = xor(payload_bits, rx_bits); Nerrs_coded = sum(errors); @@ -270,7 +271,7 @@ function ofdm_ldpc_rx(filename, mode="700D", varargin) end end - figure(9); clf; plot_specgram(rx); + figure(9); clf; plot_specgram(rx, Fs=8000, 0, 3000); if pass_packet_count > 0 if packet_count >= pass_packet_count printf("Pass!\n"); else printf("Fail!\n"); end; diff --git a/octave/ofdm_lib.m b/octave/ofdm_lib.m index ec92b0ebd..2d3a16e7a 100644 --- a/octave/ofdm_lib.m +++ b/octave/ofdm_lib.m @@ -8,6 +8,9 @@ 1; qam16; esno_est; +ofdm_mode; +ofdm_state; +ofdm_helper; %------------------------------------------------------------- % ofdm_init @@ -99,18 +102,23 @@ % encoded bits. states.uw_ind = states.uw_ind_sym = []; - uw_step = Nc+1; % default step for UW sym placement % lets see if all UW syms will fit in frame Nuwsyms = states.Nuwbits/bps; Ndatasymsperframe = (Ns-1)*Nc; + states.spread_uw = 0; + if states.spread_uw + uw_step = 1.8*floor(states.Nbitsperpacket/states.Nuwbits); + else + uw_step = Nc+1; % default step for UW sym placement + end last_sym = floor(Nuwsyms*uw_step/bps+1); if last_sym > states.Np*Ndatasymsperframe uw_step = Nc-1; % try a different step end last_sym = floor(Nuwsyms*uw_step/bps+1); assert(last_sym <= states.Np*Ndatasymsperframe); % we still can't fit them all - + % Place UW symbols in frame for i=1:Nuwsyms ind_sym = floor(i*uw_step/bps+1); @@ -133,7 +141,10 @@ if bps == 4 tx_uw_syms = [tx_uw_syms qam16_mod(states.qam16, states.tx_uw(b:b+bps-1))]; end end states.tx_uw_syms = tx_uw_syms; - % if the UW has this many errors it is "bad", the binomal cdf can be used to set this: + + % if the UW has this many errors it is "bad", the binomal cdf can be used to + % set this with the ofdm_determine_bad_uw_errors() function below + % % Nuw=12; plot(0:Nuw, binocdf(0:Nuw,Nuw,0.05)); hold on; plot(binocdf(0:Nuw,Nuw,0.5)); hold off; states.bad_uw_errors = bad_uw_errors; @@ -279,153 +290,6 @@ test_assemble_disassemble(states); endfunction - -%------------------------------------------------------------------------------ -% ofdm_init_mode - Helper function to set up modems for various FreeDV modes, -% and parse mode string. -%------------------------------------------------------------------------------ - -function config = ofdm_init_mode(mode="700D") - % defaults for 700D - - Tcp = 0.002; - Ns = 8; - Ts = 0.018; - Nc = 17; - config.bps = 2; - config.Np = 1; - config.Ntxtbits = 4; - config.Nuwbits = 5*config.bps; - config.ftwindow_width = 32; - config.timing_mx_thresh = 0.35; - config.bad_uw_errors = 3; - config.amp_scale = 245E3; - config.amp_est_mode = 0; - config.EsNo_est_all_symbols = 1; - config.EsNodB = 3; - config.state_machine = "voice1"; - config.edge_pilots = 1; - config.clip_gain1 = 2.5; - config.clip_gain2 = 0.8; - config.foff_limiter = 0; - config.txbpf_width_Hz = 2000; - config.data_mode = ""; - - if strcmp(mode,"700D") || strcmp(mode,"700d") - % defaults above - elseif strcmp(mode,"700E") || strcmp(mode,"700e") - Ts = 0.014; Tcp=0.006; Nc = 21; Ns=4; - config.edge_pilots = 0; config.state_machine = "voice2"; - config.Nuwbits = 12; config.bad_uw_errors = 3; config.Ntxtbits = 2; - config.amp_est_mode = 1; config.ftwindow_width = 80; - config.amp_scale = 155E3; config.clip_gain1 = 3; config.clip_gain2 = 0.8; - config.foff_limiter = 1; - elseif strcmp(mode,"2020") - Ts = 0.0205; Nc = 31; - config.amp_scale = 167E3; config.clip_gain1 = 2.5; config.clip_gain2 = 0.8; - elseif strcmp(mode,"2020B") - Ts = 0.014; Tcp = 0.004; Nc = 29; Ns=5; - config.Ntxtbits = 4; config.Nuwbits = 8*2; config.bad_uw_errors = 5; - config.amp_scale = 130E3; config.clip_gain1 = 2.5; config.clip_gain2 = 0.8; - config.edge_pilots = 0; config.state_machine = "voice2"; - config.foff_limiter = 1; config.ftwindow_width = 64; - config.txbpf_width_Hz = 2200; - elseif strcmp(mode,"qam16c1") - Ns=5; config.Np=5; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; - config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 15*4; config.bad_uw_errors = 5; - config.state_machine = "data"; - config.ftwindow_width = 32; config.amp_scale = 132E3; - config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; - elseif strcmp(mode,"qam16c2") - Ns=5; config.Np=31; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; - config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 15; - config.ftwindow_width = 80; config.amp_scale = 135E3; config.state_machine = "data"; - config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; - config.tx_uw = zeros(1,config.Nuwbits = 42*4); - config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; - config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; - elseif strcmp(mode,"datac0") - Ns=5; config.Np=4; Tcp = 0.006; Ts = 0.016; Nc = 9; config.data_mode = "streaming"; - config.Ntxtbits = 0; config.Nuwbits = 32; config.bad_uw_errors = 9; - config.state_machine = "data"; - config.ftwindow_width = 80; config.amp_est_mode = 1; config.EsNodB = 3; - config.edge_pilots = 0; config.timing_mx_thresh = 0.08; - config.tx_uw = zeros(1,config.Nuwbits); - config.tx_uw(1:16) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0]; - config.amp_scale = 300E3; config.clip_gain1 = 2.2; config.clip_gain2 = 0.85; - elseif strcmp(mode,"datac1") - Ns=5; config.Np=38; Tcp = 0.006; Ts = 0.016; Nc = 27; config.data_mode = "streaming"; - config.Ntxtbits = 0; config.Nuwbits = 16; config.bad_uw_errors = 6; - config.state_machine = "data"; - config.ftwindow_width = 80; config.amp_est_mode = 1; config.EsNodB = 3; - % clipper/compression adjustment: - % 1. With clipper off increase amp_scale until peak just hit 16384 - % 2. With clipper on increase clip_gain1 until about 30% clipped - % 3. BPF will drop level beneath 16384, adjust clip_gain2 to just hit 16384 peak again - % 4. Clipped/unclipped operating point for same PER should be about 1dB apart - config.amp_scale = 145E3; config.clip_gain1 = 2.7; config.clip_gain2 = 0.8; - config.edge_pilots = 0; config.timing_mx_thresh = 0.10; - config.tx_uw = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0]; - elseif strcmp(mode,"datac3") - Ns=5; config.Np=29; Tcp = 0.006; Ts = 0.016; Nc = 9; config.data_mode = "streaming"; - config.edge_pilots = 0; - config.Ntxtbits = 0; config.Nuwbits = 40; config.bad_uw_errors = 10; - config.ftwindow_width = 80; config.timing_mx_thresh = 0.10; - config.tx_uw = zeros(1,config.Nuwbits); - config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; - config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; - config.amp_est_mode = 1; config.EsNodB = 3; - config.state_machine = "data"; - config.amp_scale = 300E3; config.clip_gain1 = 2.2; config.clip_gain2 = 0.8; - elseif strcmp(mode,"1") - Ns=5; config.Np=10; Tcp=0; Tframe = 0.1; Ts = Tframe/Ns; Nc = 1; - else - % try to parse mode string for user defined mode - vec = sscanf(mode, "Ts=%f Nc=%d Ncp=%f"); - Ts=vec(1); Nc=vec(2); Ncp=vec(3); - end - Rs=1/Ts; - config.Rs = Rs; config.Tcp = Tcp; config.Ns = Ns; config.Nc = Nc; - if !isfield(config,"tx_uw") - config.tx_uw = zeros(1,config.Nuwbits); - end -end - - -%------------------------------------------------------------------------------ -% print_config - utility function to use ascsii-art to describe the modem frame -%------------------------------------------------------------------------------ - -function print_config(states) - ofdm_load_const; - - % ASCII-art packet visualisation - s=1; u=1; Nuwsyms=length(uw_ind_sym); - cr = 1:Nc+2; - for f=1:Np - for r=1:Ns - for c=cr - if r == 1 - sym="P"; - elseif c>1 && c <=(Nc+1) - sym="."; - if (u <= Nuwsyms) && (s == uw_ind_sym(u)) sym="U"; u++; end - s++; - else - sym=" "; - end - printf("%s",sym); - end - printf("\n"); - end - end - - printf("Nc=%d Ts=%4.3f Tcp=%4.3f Ns: %d Np: %d\n", Nc, 1/Rs, Tcp, Ns, Np); - printf("Nsymperframe: %d Nbitsperpacket: %d Nsamperframe: %d Ntxtbits: %d Nuwbits: %d Nuwframes: %d\n", - Ns*Nc, Nbitsperpacket, Nsamperframe, Ntxtbits, Nuwbits, Nuwframes); - printf("uncoded bits/s: %4.1f\n", Nbitsperpacket*Fs/(Np*Nsamperframe)); -end - % Gray coded QPSK modulation function function symbol = qpsk_mod(two_bits) two_bits_decimal = sum(two_bits .* [2 1]); @@ -648,7 +512,7 @@ Can be used for acquisition (coarse timing), and fine timing. Tends end % At each timing position, correlate with known samples at all possible freq offsets. Result - % is a column vector for each timing offset. Each matrix cell is s freq,timing coordinate + % is a column vector for each timing offset. Each matrix cell is a freq,timing coordinate corr = []; for t=1:tstep:Ncorr @@ -1298,443 +1162,17 @@ Can be used for acquisition (coarse timing), and fine timing. Tends endfunction -%----------------------------------------------------------------------- -% create_ldpc_test_frame - generate a test frame of bits -%----------------------------------------------------------------------- - -function [tx_bits payload_data_bits codeword] = create_ldpc_test_frame(states, coded_frame=1) - ofdm_load_const; - ldpc; - gp_interleaver; - - if coded_frame - % Set up LDPC code - - mod_order = 4; bps = 2; modulation = 'QPSK'; mapping = 'gray'; - - init_cml(); % TODO: make this path sensible and portable - load HRA_112_112.txt - [code_param framesize rate] = ldpc_init_user(HRA_112_112, modulation, mod_order, mapping); - assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits)); - - payload_data_bits = round(ofdm_rand(code_param.data_bits_per_frame)/32767); - codeword = LdpcEncode(payload_data_bits, code_param.H_rows, code_param.P_matrix); - Nsymbolsperframe = length(codeword)/bps; - - % need all these steps to get actual raw codeword bits at demod .. - - tx_symbols = []; - for s=1:Nsymbolsperframe - tx_symbols = [tx_symbols qpsk_mod( codeword(2*(s-1)+1:2*s) )]; - end - - tx_symbols = gp_interleave(tx_symbols); - - codeword_raw = []; - for s=1:Nsymbolsperframe - codeword_raw = [codeword_raw qpsk_demod(tx_symbols(s))]; - end - else - codeword_raw = round(ofdm_rand(Nbitsperpacket-(Nuwbits+Ntxtbits))/32767); - end - - % insert UW and txt bits - - tx_bits = assemble_modem_packet(states, codeword_raw, zeros(1,Ntxtbits)); - assert(Nbitsperpacket == length(tx_bits)); - -endfunction - -% automated test - -function test_assemble_disassemble(states) - ofdm_load_const; - - Nsymsperpacket = Nbitsperpacket/bps; - Ndatabitsperpacket = Nbitsperpacket-(Nuwbits+Ntxtbits); - Ndatasymsperpacket = Ndatabitsperpacket/bps; - codeword_bits = round(ofdm_rand(Ndatabitsperpacket)/32767); - tx_bits = assemble_modem_packet(states, codeword_bits, zeros(1,Ntxtbits)); - - tx_syms = zeros(1,Nsymsperpacket); - for s=1:Nsymsperpacket - if bps == 2 - tx_syms(s) = qpsk_mod(tx_bits(bps*(s-1)+1:bps*s)); - elseif bps == 4 - tx_syms(s) = qam16_mod(states.qam16,tx_bits(bps*(s-1)+1:bps*s)); - end - end - codeword_syms = zeros(1,Ndatasymsperpacket); - for s=1:Ndatasymsperpacket - if bps == 2 - codeword_syms(s) = qpsk_mod(codeword_bits(bps*(s-1)+1:bps*s)); - elseif bps == 4 - codeword_syms(s) = qam16_mod(states.qam16,codeword_bits(bps*(s-1)+1:bps*s)); - end - end - - [rx_uw rx_codeword_syms payload_amps txt_bits] = disassemble_modem_packet(states, tx_syms, ones(1,Nsymsperpacket)); - assert(rx_uw == states.tx_uw); - Ndatasymsperframe = (Nbitsperpacket-(Nuwbits+Ntxtbits))/bps; - assert(codeword_syms == rx_codeword_syms); -endfunction - -%------------------------------------------------------------------- -% sync_state_machine - calls mode-specific sync state state_machine -%------------------------------------------------------------------- - -function states = sync_state_machine(states, rx_uw) - if strcmp(states.state_machine, "voice1") - states = sync_state_machine_voice1(states, rx_uw); - elseif strcmp(states.state_machine, "data") - if strcmp(states.data_mode, "streaming") - states = sync_state_machine_data_streaming(states, rx_uw); - else - states = sync_state_machine_data_burst(states, rx_uw); - end - elseif strcmp(states.state_machine, "voice2") - states = sync_state_machine_voice2(states, rx_uw); - else - assert(0); - endif -endfunction - -%-------------------------------------------------------------------- -% Due to the low pilot symbol insertion rate and acquisition issues -% the earlier OFDM modem waveforms (700D and 2020) need a complex -% state machine to help them avoid false sync. -%-------------------------------------------------------------------- - -function states = sync_state_machine_voice1(states, rx_uw) - ofdm_load_const; - next_state = states.sync_state; - states.sync_start = states.sync_end = 0; - - if strcmp(states.sync_state,'search') - - if states.timing_valid - states.frame_count = 0; - states.sync_counter = 0; - states.modem_frame = 0; - states.sync_start = 1; - next_state = 'trial'; - end - end - - if strcmp(states.sync_state,'synced') || strcmp(states.sync_state,'trial') - - states.frame_count++; - - % UW occurs at the start of a packet - if states.modem_frame == 0 - states.uw_errors = sum(xor(tx_uw,rx_uw)); - - if strcmp(states.sync_state,'trial') - if states.uw_errors >= states.bad_uw_errors - states.sync_counter++; - states.frame_count = 0; - end - if states.sync_counter == 2 - next_state = "search"; - states.phase_est_bandwidth = "high"; - end - if states.frame_count == 4 - next_state = "synced"; - % change to low bandwidth, but more accurate phase estimation - states.phase_est_bandwidth = "low"; - end - if states.uw_errors < 2 - next_state = "synced"; - % change to low bandwidth, but more accurate phase estimation - states.phase_est_bandwidth = "low"; - else - next_state = "search"; - end - end - - if strcmp(states.sync_state,'synced') - if states.uw_errors > 2 - states.sync_counter++; - else - states.sync_counter = 0; - end - - if states.sync_counter == 6 - next_state = "search"; - states.phase_est_bandwidth = "high"; - end - end - end % if modem_frame == 0 .... - - % keep track of where we are up to in packet - states.modem_frame++; - if (states.modem_frame >= states.Np) states.modem_frame = 0; end - end - - states.last_sync_state = states.sync_state; - states.sync_state = next_state; -endfunction - - -%------------------------------------------------------- -% data (streaming mode) state machine -%------------------------------------------------------- - -function states = sync_state_machine_data_streaming(states, rx_uw) - ofdm_load_const; - next_state = states.sync_state; - states.sync_start = states.sync_end = 0; - - if strcmp(states.sync_state,'search') - if states.timing_valid - states.sync_start = 1; - states.sync_counter = 0; - next_state = 'trial'; - end - end - - states.uw_errors = sum(xor(tx_uw,rx_uw)); - - if strcmp(states.sync_state,'trial') - if states.uw_errors < states.bad_uw_errors; - next_state = "synced"; - states.packet_count = 0; - states.modem_frame = Nuwframes; - else - states.sync_counter++; - if states.sync_counter > Np - next_state = "search"; - end - end - end - - % Note packetsperburst==0 we don't ever lose sync, which is useful for - % stream based testing or external control of state machine - - if strcmp(states.sync_state,'synced') - states.modem_frame++; - if (states.modem_frame >= states.Np) - states.modem_frame = 0; - states.packet_count++; - if (states.packetsperburst) - if (states.packet_count >= states.packetsperburst) - next_state = "search"; - end - end - end - end - - states.last_sync_state = states.sync_state; - states.sync_state = next_state; -endfunction - -%------------------------------------------------------- -% data (burst mode) state machine -%------------------------------------------------------- - -function states = sync_state_machine_data_burst(states, rx_uw) - ofdm_load_const; - next_state = states.sync_state; - states.sync_start = states.sync_end = 0; - - if strcmp(states.sync_state,'search') - if states.timing_valid - states.sync_start = 1; - states.sync_counter = 0; - next_state = 'trial'; - end - end - - states.uw_errors = sum(xor(tx_uw,rx_uw)); - - % pre or post-amble has told us this is the start of the packet. Confirm we - % have a valid frame by checking the UW after the modem frames containing - % the UW have been received - if strcmp(states.sync_state,'trial') - states.sync_counter++; - if states.sync_counter == Nuwframes - if states.uw_errors < states.bad_uw_errors; - next_state = "synced"; - states.packet_count = 0; % number of packets in this burst - states.modem_frame = Nuwframes; % which modem frame we are up to in packet - else - next_state = "search"; - % reset rxbuf to make sure we only ever do a postamble loop once through same samples - states.rxbufst = states.Nrxbufhistory; - states.rxbuf = zeros(1, states.Nrxbuf); - end - end - end - - if strcmp(states.sync_state,'synced') - states.modem_frame++; - if (states.modem_frame >= states.Np) - states.modem_frame = 0; % start of new packet - states.packet_count++; - if (states.packetsperburst) - if (states.packet_count >= states.packetsperburst) - next_state = "search"; % we've finished this burst - % reset rxbuf to make sure we only ever do a postamble loop once through same samples - states.rxbufst = states.Nrxbufhistory; - states.rxbuf = zeros(1, states.Nrxbuf); - end - end - end - end - - states.last_sync_state = states.sync_state; - states.sync_state = next_state; -endfunction - -%------------------------------------------------------- -% fast sync voice state state_machine -%------------------------------------------------------- - -function states = sync_state_machine_voice2(states, rx_uw) - ofdm_load_const; - next_state = states.sync_state; - states.sync_start = states.sync_end = 0; - - if strcmp(states.sync_state,'search') - - if states.timing_valid - states.frame_count = 0; - states.sync_counter = 0; - states.modem_frame = 0; - states.sync_start = 1; - next_state = 'trial'; - end - end - - if strcmp(states.sync_state,'synced') || strcmp(states.sync_state,'trial') - - states.frame_count++; - - % UW occurs at the start of a packet - if states.modem_frame == 0 - states.uw_errors = sum(xor(tx_uw,rx_uw)); - - if strcmp(states.sync_state,'trial') - if states.uw_errors <= states.bad_uw_errors - next_state = "synced"; - else - next_state = "search"; - end - end - - if strcmp(states.sync_state,'synced') - if states.uw_errors > states.bad_uw_errors - states.sync_counter++; - else - states.sync_counter = 0; - end - - if states.sync_counter == 6 - next_state = "search"; - end - end - end - - % keep track of where we are up to in packet - states.modem_frame++; - if (states.modem_frame >= states.Np) states.modem_frame = 0; end - end - - states.last_sync_state = states.sync_state; - states.sync_state = next_state; -endfunction - - % ------------------------------------------------------------------------------ -% codec_to_frame_packing - Set up a bunch of constants to support modem frame -% construction from LDPC codewords and codec source bits +% Handle FEC encoding/decoding % ------------------------------------------------------------------------------ -function [code_param Nbitspercodecframe Ncodecframespermodemframe] = codec_to_frame_packing(states, mode) +function [frame_bits bits_per_frame] = fec_encode(states, code_param, mode, payload_bits) ofdm_load_const; - mod_order = 4; bps = 2; modulation = 'QPSK'; mapping = 'gray'; - - init_cml(); - if strcmp(mode, "700D") - load HRA_112_112.txt - code_param = ldpc_init_user(HRA_112_112, modulation, mod_order, mapping); - assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits)); - % unused for this mode - Nbitspercodecframe = Ncodecframespermodemframe = 0; - end - if strcmp(mode, "700E") - load HRA_56_56.txt - code_param = ldpc_init_user(HRA_56_56, modulation, mod_order, mapping); - assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits)); - % unused for this mode - Nbitspercodecframe = Ncodecframespermodemframe = 0; - end - if strcmp(mode, "2020") - load HRA_504_396.txt - code_param = ldpc_init_user(HRA_504_396, modulation, mod_order, mapping); - code_param.data_bits_per_frame = 312; - code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; - code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; - printf("2020 mode\n"); - printf("ldpc_data_bits_per_frame = %d\n", code_param.ldpc_data_bits_per_frame); - printf("ldpc_coded_bits_per_frame = %d\n", code_param.ldpc_coded_bits_per_frame); - printf("ldpc_parity_bits_per_frame = %d\n", code_param.ldpc_parity_bits_per_frame); - printf("data_bits_per_frame = %d\n", code_param.data_bits_per_frame); - printf("coded_bits_per_frame = %d\n", code_param.coded_bits_per_frame); - printf("coded_syms_per_frame = %d\n", code_param.coded_syms_per_frame); - printf("ofdm_bits_per_frame = %d\n", Nbitsperframe); - Nbitspercodecframe = 52; Ncodecframespermodemframe = 6; - printf(" Nuwbits: %d Ntxtbits: %d\n", Nuwbits, Ntxtbits); - Nparity = code_param.ldpc_parity_bits_per_frame; - totalbitsperframe = code_param.data_bits_per_frame + Nparity + Nuwbits + Ntxtbits; - printf("Total bits per frame: %d\n", totalbitsperframe); - assert(totalbitsperframe == Nbitsperframe); - end - if strcmp(mode, "qam16c1") - load H2064_516_sparse.mat - code_param = ldpc_init_user(HRA, modulation='QAM', mod_order=16, mapping="", reshape(states.qam16,1,16)); - end - if strcmp(mode, "qam16c2") - framesize = 16200; rate = 0.6; - code_param = ldpc_init_builtin("dvbs2", rate, framesize, modulation='QAM', mod_order=16, mapping="", reshape(states.qam16,1,16)); - end - if strcmp(mode, "datac0") - load H_128_256_5.mat - code_param = ldpc_init_user(H, modulation, mod_order, mapping); - end - if strcmp(mode, "datac1") - load H_4096_8192_3d.mat - code_param = ldpc_init_user(HRA, modulation, mod_order, mapping); - end - if strcmp(mode, "datac3") - load H_1024_2048_4f.mat - code_param = ldpc_init_user(H, modulation, mod_order, mapping); - end - if strcmp(mode, "datac0") || strcmp(mode, "datac1") || strcmp(mode, "datac3") || strcmp(mode, "qam16c1") || strcmp(mode, "qam16c2") - printf("ldpc_data_bits_per_frame = %d\n", code_param.ldpc_data_bits_per_frame); - printf("ldpc_coded_bits_per_frame = %d\n", code_param.ldpc_coded_bits_per_frame); - printf("ldpc_parity_bits_per_frame = %d\n", code_param.ldpc_parity_bits_per_frame); - printf("Nbitsperpacket = %d\n", Nbitsperpacket); - Nparity = code_param.ldpc_parity_bits_per_frame; - totalbitsperframe = code_param.data_bits_per_frame + Nparity + Nuwbits + Ntxtbits; - printf("totalbitsperframe = %d\n", totalbitsperframe); - assert(totalbitsperframe == Nbitsperpacket); - Nbitspercodecframe = Ncodecframespermodemframe = -1; - end -endfunction - - -% ------------------------------------------------------------------------------ -% fec_encode - Handle FEC encoding -% ------------------------------------------------------------------------------ - -function [frame_bits bits_per_frame] = fec_encode(states, code_param, mode, payload_bits, ... - Ncodecframespermodemframe, Nbitspercodecframe) - ofdm_load_const; - if strcmp(mode, "2020") + if code_param.data_bits_per_frame != code_param.ldpc_data_bits_per_frame + % optionally lower the code rate by "one stuffing" - setting Nunused data bits to 1 Nunused = code_param.ldpc_data_bits_per_frame - code_param.data_bits_per_frame; - frame_bits = LdpcEncode([payload_bits zeros(1,Nunused)], code_param.H_rows, code_param.P_matrix); - % remove unused data bits + frame_bits = LdpcEncode([payload_bits ones(1,Nunused)], code_param.H_rows, code_param.P_matrix); + % remove unused data bits from codeword, as they are known to the receiver and don't need to be transmitted frame_bits = [ frame_bits(1:code_param.data_bits_per_frame) frame_bits(code_param.ldpc_data_bits_per_frame+1:end) ]; else frame_bits = LdpcEncode(payload_bits, code_param.H_rows, code_param.P_matrix); @@ -1743,111 +1181,18 @@ function test_assemble_disassemble(states) endfunction - -% test function, kind of like a CRC for QPSK symbols, to compare two vectors - -function acc = test_acc(v) - sre = 0; sim = 0; - for i=1:length(v) - x = v(i); - re = round(real(x)); im = round(imag(x)); - sre += re; sim += im; - %printf("%d %10f %10f %10f %10f\n", i, re, im, sre, sim); - end - acc = sre + j*sim; -end - - -% Save test bits frame to a text file in the form of a C array -% -% usage: -% ofdm_lib; test_bits_ofdm_file -% - -function test_bits_ofdm_file - Ts = 0.018; Tcp = 0.002; Rs = 1/Ts; bps = 2; Nc = 17; Ns = 8; - states = ofdm_init(bps, Rs, Tcp, Ns, Nc); - [test_bits_ofdm payload_data_bits codeword] = create_ldpc_test_frame(states); - printf("%d test bits\n", length(test_bits_ofdm)); - - f=fopen("../src/test_bits_ofdm.h","wt"); - fprintf(f,"/* Generated by test_bits_ofdm_file() Octave function */\n\n"); - fprintf(f,"const int test_bits_ofdm[]={\n"); - for m=1:length(test_bits_ofdm)-1 - fprintf(f," %d,\n",test_bits_ofdm(m)); - endfor - fprintf(f," %d\n};\n",test_bits_ofdm(end)); - - fprintf(f,"\nconst int payload_data_bits[]={\n"); - for m=1:length(payload_data_bits)-1 - fprintf(f," %d,\n",payload_data_bits(m)); - endfor - fprintf(f," %d\n};\n",payload_data_bits(end)); - - fprintf(f,"\nconst int test_codeword[]={\n"); - for m=1:length(codeword)-1 - fprintf(f," %d,\n",codeword(m)); - endfor - fprintf(f," %d\n};\n",codeword(end)); - - fclose(f); - -endfunction - - -% Get rid of nasty unfiltered stuff either side of OFDM signal -% This may need to be tweaked, or better yet made a function of Nc, if Nc changes -% -% usage: -% ofdm_lib; make_ofdm_bpf(1); - -function bpf_coeff = make_ofdm_bpf(write_c_header_file) - filt_n = 100; - Fs = 8000; - - bpf_coeff = fir2(filt_n,[0 900 1000 2000 2100 4000]/(Fs/2),[0.001 0.001 1 1 0.001 0.001]); - - if write_c_header_file - figure(1) - clf; - h = freqz(bpf_coeff,1,Fs/2); - plot(20*log10(abs(h))) - grid minor - - % save coeffs to a C header file - - f=fopen("../src/ofdm_bpf_coeff.h","wt"); - fprintf(f,"/* 1000 - 2000 Hz FIR filter coeffs */\n"); - fprintf(f,"/* Generated by make_ofdm_bpf() in ofdm_lib.m */\n"); - - fprintf(f,"\n#define OFDM_BPF_N %d\n\n", filt_n); - - fprintf(f,"float ofdm_bpf_coeff[]={\n"); - for r=1:filt_n - if r < filt_n - fprintf(f, " %f,\n", bpf_coeff(r)); - else - fprintf(f, " %f\n};", bpf_coeff(r)); - end - end - fclose(f); - end - +function [rx_bits paritychecks] = fec_decode(states, code_param, ... + payload_syms_de, payload_amps_de, ... + mean_amp, EsNo) + ofdm_load_const; + % note ldpc_dec() handles optional lower code rate zero-stuffing + [rx_codeword paritychecks] = ldpc_dec(code_param, mx_iter=100, demod=0, dec=0, ... + payload_syms_de/mean_amp, EsNo, + payload_amps_de/mean_amp); + rx_bits = rx_codeword(1:code_param.data_bits_per_frame); endfunction -% returns level threshold such that threshold_cdf of the tx magnitudes are beneath that level -function threshold_level = ofdm_determine_clip_threshold(tx, threshold_cdf) - Nsteps = 25; - mx = max(abs(tx)); - cdf = empirical_cdf(mx*(1:Nsteps)/Nsteps,abs(tx)); - threshold_level = find(cdf >= threshold_cdf)(1)*mx/25; - printf("threshold_cdf: %f threshold_level: %f\n", threshold_cdf, threshold_level); - figure(1); clf; [hh nn] = hist(abs(tx),Nsteps,1); - plotyy(nn,hh,mx*(1:Nsteps)/Nsteps,cdf); title('PDF and CDF Estimates'); grid; -end - - function [tx nclipped] = ofdm_clip(states, tx, threshold_level, plot_en=0) ofdm_load_const; tx_ = tx; @@ -1872,13 +1217,11 @@ function test_assemble_disassemble(states) end [tx nclipped] = ofdm_clip(states, tx*states.clip_gain1, states.ofdm_peak); - % BPF, we actually shift the signal back down to baseband to filter - ssbfilt_n = 100; - ssbfilt_coeff = fir1(ssbfilt_n, states.txbpf_width_Hz/states.Fs); - lo = exp(j*2*pi*states.fcentre*(1:length(tx))/(states.Fs)); - tx = lo.*filter(ssbfilt_coeff,1,tx.*conj(lo)); - - % filter messs up peak levels use this to get us back to approx 16384 + cutoff_norm = states.txbpf_width_Hz/states.Fs; + w_centre = mean(states.w); centre_norm = w_centre/(2*pi); + tx = ofdm_complex_bandpass_filter(cutoff_norm, centre_norm,100,tx); + + % filter messes up peak levels use this to get us back to approx 16384 tx *= states.clip_gain2; end @@ -1897,19 +1240,30 @@ function test_assemble_disassemble(states) endfunction -% helper function that adds channel simulation and ensures we don't saturate int16 output samples -function [rx_real rx] = ofdm_channel(states, tx, SNR3kdB, channel, freq_offset_Hz) - [rx_real rx sigma] = channel_simulate(states.Fs, SNR3kdB, freq_offset_Hz, channel, tx, states.verbose); - - % multipath models can lead to clipping of int16 samples - num_clipped = length(find(abs(rx_real>32767))); - while num_clipped/length(rx_real) > 0.001 - rx_real /= 2; - num_clipped = length(find(abs(rx_real>32767))); - printf("WARNING: output samples clipped, reducing level\n") +% Complex bandpass filter built from low pass prototype as per src/filter.c, +% cutoff_freq and center_freq are normalised such that cutoff_freq = 0.5 is Fs/2 +function out = ofdm_complex_bandpass_filter(cutoff_freq,center_freq,n_coeffs,in) + lowpass_coeff = fir1(n_coeffs-1, cutoff_freq); + k = (0:n_coeffs-1); + bandpass_coeff = lowpass_coeff .* exp(j*2*pi*center_freq*k); + out = filter(bandpass_coeff,1,in); +endfunction + + +% Complex bandpass filter for Rx - just used on the very low SNR modes to help +% with acquisition +function [rx delay_samples] = ofdm_rx_filter(states, mode, rx) + delay_samples = 0; + if strcmp(mode,"datac4") || strcmp(mode,"datac13") + w_centre = mean(states.w); centre_norm = w_centre/(2*pi); + n_coeffs = 100; + cutoff_Hz = 400; cutoff_norm = cutoff_Hz/states.Fs; + rx = ofdm_complex_bandpass_filter(cutoff_norm,centre_norm,n_coeffs,rx); + delay_samples = n_coeffs/2; end endfunction + % returns an unpacked CRC16 (array of 16 bits) calculated from an array of unpacked bits function unpacked_crc16 = crc16_unpacked(unpacked_bits) % pack into bytes diff --git a/octave/ofdm_mode.m b/octave/ofdm_mode.m new file mode 100644 index 000000000..074fd84f4 --- /dev/null +++ b/octave/ofdm_mode.m @@ -0,0 +1,249 @@ +% ofdm_mode.m +% +% Library of functions to help setting up OFDM modes + +%------------------------------------------------------------------------------ +% ofdm_init_mode - Helper function to set up modems for various FreeDV modes, +% and parse mode string. +%------------------------------------------------------------------------------ + +1; + +function config = ofdm_init_mode(mode="700D") + % defaults for 700D + + Tcp = 0.002; + Ns = 8; + Ts = 0.018; + Nc = 17; + config.bps = 2; + config.Np = 1; + config.Ntxtbits = 4; + config.Nuwbits = 5*config.bps; + config.ftwindow_width = 32; + config.timing_mx_thresh = 0.35; + config.bad_uw_errors = 3; + config.amp_scale = 245E3; + config.amp_est_mode = 0; + config.EsNo_est_all_symbols = 1; + config.EsNodB = 3; + config.state_machine = "voice1"; + config.edge_pilots = 1; + config.clip_gain1 = 2.5; + config.clip_gain2 = 0.8; + config.foff_limiter = 0; + config.txbpf_width_Hz = 2000; + config.data_mode = ""; + + if strcmp(mode,"700D") || strcmp(mode,"700d") + % defaults above + elseif strcmp(mode,"700E") || strcmp(mode,"700e") + Ts = 0.014; Tcp=0.006; Nc = 21; Ns=4; + config.edge_pilots = 0; config.state_machine = "voice2"; + config.Nuwbits = 12; config.bad_uw_errors = 3; config.Ntxtbits = 2; + config.amp_est_mode = 1; config.ftwindow_width = 80; + config.amp_scale = 155E3; config.clip_gain1 = 3; config.clip_gain2 = 0.8; + config.foff_limiter = 1; + elseif strcmp(mode,"2020") + Ts = 0.0205; Nc = 31; + config.amp_scale = 167E3; config.clip_gain1 = 2.5; config.clip_gain2 = 0.8; + elseif strcmp(mode,"2020B") + Ts = 0.014; Tcp = 0.004; Nc = 29; Ns=5; + config.Ntxtbits = 4; config.Nuwbits = 8*2; config.bad_uw_errors = 5; + config.amp_scale = 130E3; config.clip_gain1 = 2.5; config.clip_gain2 = 0.8; + config.edge_pilots = 0; config.state_machine = "voice2"; + config.foff_limiter = 1; config.ftwindow_width = 64; + config.txbpf_width_Hz = 2200; + elseif strcmp(mode,"qam16c1") + Ns=5; config.Np=5; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; + config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 15*4; config.bad_uw_errors = 5; + config.state_machine = "data"; + config.ftwindow_width = 32; config.amp_scale = 132E3; + config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; + elseif strcmp(mode,"qam16c2") + Ns=5; config.Np=31; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; + config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 15; + config.ftwindow_width = 80; config.amp_scale = 135E3; config.state_machine = "data"; + config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; + config.tx_uw = zeros(1,config.Nuwbits = 42*4); + config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + elseif strcmp(mode,"datac0") + Ns=5; config.Np=4; Tcp = 0.006; Ts = 0.016; Nc = 9; config.data_mode = "streaming"; + config.Ntxtbits = 0; config.Nuwbits = 32; config.bad_uw_errors = 9; + config.state_machine = "data"; + config.ftwindow_width = 80; config.amp_est_mode = 1; config.EsNodB = 3; + config.edge_pilots = 0; config.timing_mx_thresh = 0.08; + config.tx_uw = zeros(1,config.Nuwbits); + config.tx_uw(1:16) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0]; + config.amp_scale = 300E3; config.clip_gain1 = 2.2; config.clip_gain2 = 0.85; + elseif strcmp(mode,"datac5") + Ns=5; config.Np=58; Tcp = 0.004; Ts = 0.016; Nc = 35; config.data_mode = "streaming"; + config.Ntxtbits = 0; config.Nuwbits = 40; config.bad_uw_errors = 14; + config.state_machine = "data"; + config.ftwindow_width = 80; config.amp_est_mode = 1; config.EsNodB = 3; + config.amp_scale = 145E3; config.clip_gain1 = 2.7; config.clip_gain2 = 0.8; + config.edge_pilots = 0; config.timing_mx_thresh = 0.10; + config.tx_uw = zeros(1,config.Nuwbits); + config.tx_uw(1:16) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0]; + elseif strcmp(mode,"datac1") + Ns=5; config.Np=38; Tcp = 0.006; Ts = 0.016; Nc = 27; config.data_mode = "streaming"; + config.Ntxtbits = 0; config.Nuwbits = 16; config.bad_uw_errors = 6; + config.state_machine = "data"; + config.ftwindow_width = 80; config.amp_est_mode = 1; config.EsNodB = 3; + % clipper/compression adjustment: + % 1. With clipper off increase amp_scale until peak just hit 16384 + % 2. With clipper on increase clip_gain1 until about 30% clipped + % 3. BPF will drop level beneath 16384, adjust clip_gain2 to just hit 16384 peak again + % 4. Clipped/unclipped operating point for same PER should be about 1dB apart + config.amp_scale = 145E3; config.clip_gain1 = 2.7; config.clip_gain2 = 0.8; + config.edge_pilots = 0; config.timing_mx_thresh = 0.10; + config.tx_uw = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0]; + elseif strcmp(mode,"datac3") + Ns=5; config.Np=29; Tcp = 0.006; Ts = 0.016; Nc = 9; config.data_mode = "streaming"; + config.edge_pilots = 0; + config.Ntxtbits = 0; config.Nuwbits = 40; config.bad_uw_errors = 10; + config.ftwindow_width = 80; config.timing_mx_thresh = 0.10; + config.tx_uw = zeros(1,config.Nuwbits); + config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.amp_est_mode = 1; config.EsNodB = 3; + config.state_machine = "data"; + config.amp_scale = 300E3; config.clip_gain1 = 2.2; config.clip_gain2 = 0.8; + elseif strcmp(mode,"datac4") + Ns=5; config.Np=47; Tcp = 0.006; Ts = 0.016; Nc = 4; config.data_mode = "streaming"; + config.edge_pilots = 0; + config.Ntxtbits = 0; config.Nuwbits = 32; config.bad_uw_errors = 12; + config.ftwindow_width = 80; config.timing_mx_thresh = 0.5; + config.tx_uw = zeros(1,config.Nuwbits); + config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.amp_est_mode = 1; config.EsNodB = 3; + config.state_machine = "data"; + config.amp_scale = 2*300E3; config.clip_gain1 = 1.2; config.clip_gain2 = 1.0; + config.txbpf_width_Hz = 400; + elseif strcmp(mode,"datac13") + Ns=5; config.Np=18; Tcp = 0.006; Ts = 0.016; Nc = 3; config.data_mode = "streaming"; + config.edge_pilots = 0; + config.Ntxtbits = 0; config.Nuwbits = 48; config.bad_uw_errors = 18; + config.ftwindow_width = 80; config.timing_mx_thresh = 0.45; + config.tx_uw = zeros(1,config.Nuwbits); + config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; + config.amp_est_mode = 1; config.EsNodB = 3; + config.state_machine = "data"; + config.amp_scale = 2.5*300E3; config.clip_gain1 = 1.2; config.clip_gain2 = 1.0; + config.txbpf_width_Hz = 400; + elseif strcmp(mode,"1") + Ns=5; config.Np=10; Tcp=0; Tframe = 0.1; Ts = Tframe/Ns; Nc = 1; + else + % try to parse mode string for user defined mode + vec = sscanf(mode, "Ts=%f Nc=%d Ncp=%f"); + Ts=vec(1); Nc=vec(2); Ncp=vec(3); + end + Rs=1/Ts; + config.Rs = Rs; config.Tcp = Tcp; config.Ns = Ns; config.Nc = Nc; + if !isfield(config,"tx_uw") + config.tx_uw = zeros(1,config.Nuwbits); + end +end + +% ------------------------------------------------------------------------------ +% codec_to_frame_packing - Set up a bunch of constants to support modem frame +% construction from LDPC codewords and codec source bits +% ------------------------------------------------------------------------------ + +function [code_param Nbitspercodecframe Ncodecframespermodemframe] = codec_to_frame_packing(states, mode) + ofdm_load_const; + mod_order = 4; bps = 2; modulation = 'QPSK'; mapping = 'gray'; + + init_cml(); + if strcmp(mode, "700D") + load HRA_112_112.txt + code_param = ldpc_init_user(HRA_112_112, modulation, mod_order, mapping); + assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits)); + % unused for this mode + Nbitspercodecframe = Ncodecframespermodemframe = 0; + end + if strcmp(mode, "700E") + load HRA_56_56.txt + code_param = ldpc_init_user(HRA_56_56, modulation, mod_order, mapping); + assert(Nbitsperframe == (code_param.coded_bits_per_frame + Nuwbits + Ntxtbits)); + % unused for this mode + Nbitspercodecframe = Ncodecframespermodemframe = 0; + end + if strcmp(mode, "2020") + load HRA_504_396.txt + code_param = ldpc_init_user(HRA_504_396, modulation, mod_order, mapping); + code_param.data_bits_per_frame = 312; + code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; + code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; + printf("2020 mode\n"); + printf("ldpc_data_bits_per_frame = %d\n", code_param.ldpc_data_bits_per_frame); + printf("ldpc_coded_bits_per_frame = %d\n", code_param.ldpc_coded_bits_per_frame); + printf("ldpc_parity_bits_per_frame = %d\n", code_param.ldpc_parity_bits_per_frame); + printf("data_bits_per_frame = %d\n", code_param.data_bits_per_frame); + printf("coded_bits_per_frame = %d\n", code_param.coded_bits_per_frame); + printf("coded_syms_per_frame = %d\n", code_param.coded_syms_per_frame); + printf("ofdm_bits_per_frame = %d\n", Nbitsperframe); + Nbitspercodecframe = 52; Ncodecframespermodemframe = 6; + printf(" Nuwbits: %d Ntxtbits: %d\n", Nuwbits, Ntxtbits); + Nparity = code_param.ldpc_parity_bits_per_frame; + totalbitsperframe = code_param.data_bits_per_frame + Nparity + Nuwbits + Ntxtbits; + printf("Total bits per frame: %d\n", totalbitsperframe); + assert(totalbitsperframe == Nbitsperframe); + end + if strcmp(mode, "qam16c1") + load H2064_516_sparse.mat + code_param = ldpc_init_user(HRA, modulation='QAM', mod_order=16, mapping="", reshape(states.qam16,1,16)); + end + if strcmp(mode, "qam16c2") + framesize = 16200; rate = 0.6; + code_param = ldpc_init_builtin("dvbs2", rate, framesize, modulation='QAM', mod_order=16, mapping="", reshape(states.qam16,1,16)); + end + if strcmp(mode, "datac5") + framesize = 16200; rate = 0.6; + code_param = ldpc_init_builtin("dvbs2", rate, framesize, modulation='QPSK', mod_order=4, mapping=""); + end + if strcmp(mode, "datac0") || strcmp(mode, "datac13") + load H_128_256_5.mat + code_param = ldpc_init_user(H, modulation, mod_order, mapping); + end + if strcmp(mode, "datac1") + load H_4096_8192_3d.mat + code_param = ldpc_init_user(HRA, modulation, mod_order, mapping); + end + if strcmp(mode, "datac3") + load H_1024_2048_4f.mat + code_param = ldpc_init_user(H, modulation, mod_order, mapping); + end + if strcmp(mode, "datac4") + load H_1024_2048_4f + code_param = ldpc_init_user(H, modulation, mod_order, mapping); + code_param.data_bits_per_frame = 448; + code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; + code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; + end + if strcmp(mode, "datac13") + load H_256_512_4.mat + code_param = ldpc_init_user(H, modulation, mod_order, mapping); + code_param.data_bits_per_frame = 128; + code_param.coded_bits_per_frame = code_param.data_bits_per_frame + code_param.ldpc_parity_bits_per_frame; + code_param.coded_syms_per_frame = code_param.coded_bits_per_frame/code_param.bits_per_symbol; + end + if strcmp(mode, "datac0") || strcmp(mode, "datac1") || strcmp(mode, "datac3") ... + || strcmp(mode, "datac4") || strcmp(mode, "qam16c1") ... + || strcmp(mode, "qam16c2") || strcmp(mode, "datac5") || strcmp(mode, "datac13") + printf("ldpc_data_bits_per_frame = %d\n", code_param.ldpc_data_bits_per_frame); + printf("ldpc_coded_bits_per_frame = %d\n", code_param.ldpc_coded_bits_per_frame); + printf("ldpc_parity_bits_per_frame = %d\n", code_param.ldpc_parity_bits_per_frame); + printf("Nbitsperpacket = %d\n", Nbitsperpacket); + Nparity = code_param.ldpc_parity_bits_per_frame; + totalbitsperframe = code_param.data_bits_per_frame + Nparity + Nuwbits + Ntxtbits; + printf("totalbitsperframe = %d\n", totalbitsperframe); + assert(totalbitsperframe == Nbitsperpacket); + Nbitspercodecframe = Ncodecframespermodemframe = -1; + end +endfunction + + diff --git a/octave/ofdm_state.m b/octave/ofdm_state.m new file mode 100644 index 000000000..a2eca260b --- /dev/null +++ b/octave/ofdm_state.m @@ -0,0 +1,271 @@ +% ofdm_state.m +% +% Library of state machine functions for the OFDM modem + +1; + +%------------------------------------------------------------------- +% sync_state_machine - calls mode-specific sync state state_machine +%------------------------------------------------------------------- + +function states = sync_state_machine(states, rx_uw) + if strcmp(states.state_machine, "voice1") + states = sync_state_machine_voice1(states, rx_uw); + elseif strcmp(states.state_machine, "data") + if strcmp(states.data_mode, "streaming") + states = sync_state_machine_data_streaming(states, rx_uw); + else + states = sync_state_machine_data_burst(states, rx_uw); + end + elseif strcmp(states.state_machine, "voice2") + states = sync_state_machine_voice2(states, rx_uw); + else + assert(0); + endif +endfunction + +%-------------------------------------------------------------------- +% Due to the low pilot symbol insertion rate and acquisition issues +% the earlier OFDM modem waveforms (700D and 2020) need a complex +% state machine to help them avoid false sync. +%-------------------------------------------------------------------- + +function states = sync_state_machine_voice1(states, rx_uw) + ofdm_load_const; + next_state = states.sync_state; + states.sync_start = states.sync_end = 0; + + if strcmp(states.sync_state,'search') + + if states.timing_valid + states.frame_count = 0; + states.sync_counter = 0; + states.modem_frame = 0; + states.sync_start = 1; + next_state = 'trial'; + end + end + + if strcmp(states.sync_state,'synced') || strcmp(states.sync_state,'trial') + + states.frame_count++; + + % UW occurs at the start of a packet + if states.modem_frame == 0 + states.uw_errors = sum(xor(tx_uw,rx_uw)); + + if strcmp(states.sync_state,'trial') + if states.uw_errors >= states.bad_uw_errors + states.sync_counter++; + states.frame_count = 0; + end + if states.sync_counter == 2 + next_state = "search"; + states.phase_est_bandwidth = "high"; + end + if states.frame_count == 4 + next_state = "synced"; + % change to low bandwidth, but more accurate phase estimation + states.phase_est_bandwidth = "low"; + end + if states.uw_errors < 2 + next_state = "synced"; + % change to low bandwidth, but more accurate phase estimation + states.phase_est_bandwidth = "low"; + else + next_state = "search"; + end + end + + if strcmp(states.sync_state,'synced') + if states.uw_errors > 2 + states.sync_counter++; + else + states.sync_counter = 0; + end + + if states.sync_counter == 6 + next_state = "search"; + states.phase_est_bandwidth = "high"; + end + end + end % if modem_frame == 0 .... + + % keep track of where we are up to in packet + states.modem_frame++; + if (states.modem_frame >= states.Np) states.modem_frame = 0; end + end + + states.last_sync_state = states.sync_state; + states.sync_state = next_state; +endfunction + + +%------------------------------------------------------- +% data (streaming mode) state machine +%------------------------------------------------------- + +function states = sync_state_machine_data_streaming(states, rx_uw) + ofdm_load_const; + next_state = states.sync_state; + states.sync_start = states.sync_end = 0; + + if strcmp(states.sync_state,'search') + if states.timing_valid + states.sync_start = 1; + states.sync_counter = 0; + next_state = 'trial'; + end + end + + states.uw_errors = sum(xor(tx_uw,rx_uw)); + + if strcmp(states.sync_state,'trial') + if states.uw_errors < states.bad_uw_errors; + next_state = "synced"; + states.packet_count = 0; + states.modem_frame = Nuwframes; + else + states.sync_counter++; + if states.sync_counter > Np + next_state = "search"; + end + end + end + + % Note packetsperburst==0 we don't ever lose sync, which is useful for + % stream based testing or external control of state machine + + if strcmp(states.sync_state,'synced') + states.modem_frame++; + if (states.modem_frame >= states.Np) + states.modem_frame = 0; + states.packet_count++; + if (states.packetsperburst) + if (states.packet_count >= states.packetsperburst) + next_state = "search"; + end + end + end + end + + states.last_sync_state = states.sync_state; + states.sync_state = next_state; +endfunction + +%------------------------------------------------------- +% data (burst mode) state machine +%------------------------------------------------------- + +function states = sync_state_machine_data_burst(states, rx_uw) + ofdm_load_const; + next_state = states.sync_state; + states.sync_start = states.sync_end = 0; + + if strcmp(states.sync_state,'search') + if states.timing_valid + states.sync_start = 1; + states.sync_counter = 0; + next_state = 'trial'; + end + end + + states.uw_errors = sum(xor(tx_uw,rx_uw)); + + % pre or post-amble has told us this is the start of the packet. Confirm we + % have a valid frame by checking the UW after the modem frames containing + % the UW have been received + if strcmp(states.sync_state,'trial') + states.sync_counter++; + if states.sync_counter == Nuwframes + if states.uw_errors < states.bad_uw_errors; + next_state = "synced"; + states.packet_count = 0; % number of packets in this burst + states.modem_frame = Nuwframes; % which modem frame we are up to in packet + else + next_state = "search"; + % reset rxbuf to make sure we only ever do a postamble loop once through same samples + states.rxbufst = states.Nrxbufhistory; + states.rxbuf = zeros(1, states.Nrxbuf); + end + end + end + + if strcmp(states.sync_state,'synced') + states.modem_frame++; + if (states.modem_frame >= states.Np) + states.modem_frame = 0; % start of new packet + states.packet_count++; + if (states.packetsperburst) + if (states.packet_count >= states.packetsperburst) + next_state = "search"; % we've finished this burst + % reset rxbuf to make sure we only ever do a postamble loop once through same samples + states.rxbufst = states.Nrxbufhistory; + states.rxbuf = zeros(1, states.Nrxbuf); + end + end + end + end + + states.last_sync_state = states.sync_state; + states.sync_state = next_state; +endfunction + +%------------------------------------------------------- +% fast sync voice state state_machine +%------------------------------------------------------- + +function states = sync_state_machine_voice2(states, rx_uw) + ofdm_load_const; + next_state = states.sync_state; + states.sync_start = states.sync_end = 0; + + if strcmp(states.sync_state,'search') + + if states.timing_valid + states.frame_count = 0; + states.sync_counter = 0; + states.modem_frame = 0; + states.sync_start = 1; + next_state = 'trial'; + end + end + + if strcmp(states.sync_state,'synced') || strcmp(states.sync_state,'trial') + + states.frame_count++; + + % UW occurs at the start of a packet + if states.modem_frame == 0 + states.uw_errors = sum(xor(tx_uw,rx_uw)); + + if strcmp(states.sync_state,'trial') + if states.uw_errors <= states.bad_uw_errors + next_state = "synced"; + else + next_state = "search"; + end + end + + if strcmp(states.sync_state,'synced') + if states.uw_errors > states.bad_uw_errors + states.sync_counter++; + else + states.sync_counter = 0; + end + + if states.sync_counter == 6 + next_state = "search"; + end + end + end + + % keep track of where we are up to in packet + states.modem_frame++; + if (states.modem_frame >= states.Np) states.modem_frame = 0; end + end + + states.last_sync_state = states.sync_state; + states.sync_state = next_state; +endfunction + diff --git a/octave/save_array_c_header.m b/octave/save_array_c_header.m index d51efeef8..1fd87bb10 100644 --- a/octave/save_array_c_header.m +++ b/octave/save_array_c_header.m @@ -5,7 +5,7 @@ function save_array_c_header(array, array_name, filename) f=fopen(filename,"wt"); fprintf(f,"/* Generated by save_array_c_header.m Octave function */\n\n"); - fprintf(f,"const int %s[]={\n", array_name); + fprintf(f,"const float %s[]={\n", array_name); for m=1:length(array)-1 fprintf(f," % .16f,\n",array(m)); endfor diff --git a/octave/snr_curves_plot.m b/octave/snr_curves_plot.m index 0c277d510..8a2e23436 100644 --- a/octave/snr_curves_plot.m +++ b/octave/snr_curves_plot.m @@ -58,8 +58,10 @@ function snrest_snr_screen(source, channel) snr_scatter(source, 'datac0', channel,'b+-') snr_scatter(source, 'datac1', channel,'g+-') snr_scatter(source, 'datac3', channel,'r+-') + snr_scatter(source, 'datac4', channel,'c+-') + snr_scatter(source, 'datac13', channel,'m+-') xlabel('SNR (dB)'); ylabel('SNRest (dB)'); grid('minor'); - axis([-5 12 -5 12]); + axis([-12 12 -12 12]); a = axis; plot([a(1) a(2)],[a(1) a(2)],'bk-'); hold off; grid; @@ -111,6 +113,8 @@ function thruput_v_snr(source, mode, channel, colour) if strcmp(mode,"datac0") Rb=291; end; if strcmp(mode,"datac1") Rb=980; end; if strcmp(mode,"datac3") Rb=321; end; + if strcmp(mode,"datac4") Rb=87; end; + if strcmp(mode,"datac13") Rb=65; end; if strcmp(channel,"awgn") plot(snr, Rb*(1-per), sprintf('%s;%s %s;', colour, mode, channel)); else @@ -199,12 +203,16 @@ function octave_c_tx_comp_print(channel) per_v_snr('ctxc','datac0','awgn','bo-') per_v_snr('ctxc','datac1','awgn','go-') per_v_snr('ctxc','datac3','awgn','ro-') + per_v_snr('ctxc','datac4','awgn','co-') + per_v_snr('ctxc','datac13','awgn','mo-') per_v_snr('ctxc','datac0','mpp','bx-') per_v_snr('ctxc','datac1','mpp','gx-') per_v_snr('ctxc','datac3','mpp','rx-') + per_v_snr('ctxc','datac4','mpp','cx-') + per_v_snr('ctxc','datac13','mpp','mx-') xlabel('SNR (dB)'); ylabel('PER'); grid; hold off; - axis([-5 10 1E-3 1]); + axis([-10 10 1E-3 1]); title('PER of C Raw Data Modes (with compression)'); endfunction @@ -220,14 +228,18 @@ function octave_c_tx_comp_print(channel) thruput_v_snr('ctxc','datac0','awgn','bo-') thruput_v_snr('ctxc','datac1','awgn','go-') thruput_v_snr('ctxc','datac3','awgn','ro-') + thruput_v_snr('ctxc','datac4','awgn','co-') + thruput_v_snr('ctxc','datac13','awgn','mo-') thruput_v_snr('ctxc','datac0','mpp','bx-') thruput_v_snr('ctxc','datac1','mpp','gx-') thruput_v_snr('ctxc','datac3','mpp','rx-') + thruput_v_snr('ctxc','datac4','mpp','cx-') + thruput_v_snr('ctxc','datac13','mpp','mx-') xlabel('SNR (dB)'); ylabel('bits/s'); grid; hold off; - axis([-6 12 0 1000]); + axis([-10 10 0 1000]); title(' Throughput for C Tx (with compression)'); - legend('location','east'); + legend('location','west'); endfunction function c_tx_comp_thruput_print; diff --git a/src/filter.h b/src/filter.h index 0a23162f2..ba484241e 100644 --- a/src/filter.h +++ b/src/filter.h @@ -42,7 +42,7 @@ extern float filtP550S750[160]; extern float filtP650S900[100]; extern float filtP900S1100[100]; extern float filtP1100S1300[100]; - +extern float filtP200S400[100]; extern float quiskFilt120t480[480]; #endif diff --git a/src/filter_coef.h b/src/filter_coef.h index b20b398e0..d5ad8b4cc 100644 --- a/src/filter_coef.h +++ b/src/filter_coef.h @@ -513,6 +513,120 @@ float filtP1100S1300[]={ 0.0002976192596492 }; +/* + Low pass prototype, sample rate 8000 Hz, 60dB dB atten. + + Used as an input BPF for datac4 and datac13 + + Generated using Octave: + octave:77> h = fir1(99, 400/8000); f=0:500; w=f*pi/4000; H=freqz(h,1,w); + octave:78> clf; plot(f,20*log10(abs(H))); grid; axis([0 500 -60 10]) + octave:79> save_array_c_header(h,"filtP200S400","t.h") +*/ + +float filtP200S400[]={ + 0.0004961403001099, + 0.0004878954300076, + 0.0004773254753068, + 0.0004613755012320, + 0.0004356367390736, + 0.0003945564225800, + 0.0003317489139155, + 0.0002403980632049, + 0.0001137356780462, + -0.0000544235817172, + -0.0002691144005802, + -0.0005336465184803, + -0.0008490522002088, + -0.0012135843244779, + -0.0016222933008125, + -0.0020667090843560, + -0.0025346513768318, + -0.0030101867721908, + -0.0034737462756490, + -0.0039024104893224, + -0.0042703630503403, + -0.0045495058923843, + -0.0047102228630850, + -0.0047222714599590, + -0.0045557762351127, + -0.0041822920363898, + -0.0035759009450005, + -0.0027143037436366, + -0.0015798651637566, + -0.0001605721201941, + 0.0015491343107337, + 0.0035476892962647, + 0.0058259285585604, + 0.0083668321698320, + 0.0111455063209044, + 0.0141294135724972, + 0.0172788535145399, + 0.0205476868890766, + 0.0238842874108655, + 0.0272326970839423, + 0.0305339530892875, + 0.0337275476120884, + 0.0367529765550393, + 0.0395513291682391, + 0.0420668683822783, + 0.0442485511620683, + 0.0460514395405405, + 0.0474379561100668, + 0.0483789425435863, + 0.0488544860205982, + 0.0488544860205982, + 0.0483789425435863, + 0.0474379561100668, + 0.0460514395405405, + 0.0442485511620683, + 0.0420668683822783, + 0.0395513291682391, + 0.0367529765550394, + 0.0337275476120884, + 0.0305339530892875, + 0.0272326970839423, + 0.0238842874108655, + 0.0205476868890766, + 0.0172788535145399, + 0.0141294135724972, + 0.0111455063209044, + 0.0083668321698320, + 0.0058259285585604, + 0.0035476892962647, + 0.0015491343107337, + -0.0001605721201941, + -0.0015798651637566, + -0.0027143037436366, + -0.0035759009450005, + -0.0041822920363898, + -0.0045557762351127, + -0.0047222714599590, + -0.0047102228630850, + -0.0045495058923843, + -0.0042703630503403, + -0.0039024104893224, + -0.0034737462756490, + -0.0030101867721908, + -0.0025346513768318, + -0.0020667090843560, + -0.0016222933008125, + -0.0012135843244779, + -0.0008490522002088, + -0.0005336465184803, + -0.0002691144005802, + -0.0000544235817172, + 0.0001137356780462, + 0.0002403980632049, + 0.0003317489139155, + 0.0003945564225800, + 0.0004356367390736, + 0.0004613755012320, + 0.0004773254753068, + 0.0004878954300076, + 0.0004961403001099 +}; + // FIR filter suitable for changing rates 7500 to/from 8000 // Sample 120000 Hz, pass 2700, stop 3730, ripple 0.1dB, atten 100 dB. Stop 0.03108. float quiskFilt120t480[480] = { diff --git a/src/freedv_2020.c b/src/freedv_2020.c index 8625d9a1c..135bcd22f 100644 --- a/src/freedv_2020.c +++ b/src/freedv_2020.c @@ -4,7 +4,7 @@ AUTHOR......: David Rowe DATE CREATED: May 2020 - Functions that implement the FreeDV 2020 mode. + Functions that implement the FreeDV 2020 modes. \*---------------------------------------------------------------------------*/ @@ -62,29 +62,23 @@ void freedv_2020x_open(struct freedv *f) { assert(f->ldpc != NULL); ldpc_codes_setup(f->ldpc, f->ofdm->codename); - int data_bits_per_frame; + ldpc_mode_specific_setup(f->ofdm, f->ldpc); int vq_type; switch (f->mode) { case FREEDV_MODE_2020: - data_bits_per_frame = 312; vq_type = 1; /* vanilla VQ */ break; case FREEDV_MODE_2020B: - f->ldpc->protection_mode = LDPC_PROT_2020B; - data_bits_per_frame = 156; vq_type = 2; /* index optimised VQ for increased robustness to single bit errors */ break; case FREEDV_MODE_2020C: - data_bits_per_frame = 156; vq_type = 2; /* index optimised VQ for increased robustness to single bit errors */ break; default: assert(0); } - set_data_bits_per_frame(f->ldpc, data_bits_per_frame); int coded_syms_per_frame = f->ldpc->coded_bits_per_frame/f->ofdm->bps; - f->ofdm_bitsperframe = ofdm_get_bits_per_frame(f->ofdm); f->ofdm_nuwbits = f->ofdm->config.nuwbits; f->ofdm_ntxtbits = f->ofdm->config.txtbits; @@ -95,7 +89,7 @@ void freedv_2020x_open(struct freedv *f) { fprintf(stderr, "vq_type = %d\n", vq_type); fprintf(stderr, "ldpc_data_bits_per_frame = %d\n", f->ldpc->ldpc_data_bits_per_frame); fprintf(stderr, "ldpc_coded_bits_per_frame = %d\n", f->ldpc->ldpc_coded_bits_per_frame); - fprintf(stderr, "data_bits_per_frame = %d\n", data_bits_per_frame); + fprintf(stderr, "data_bits_per_frame = %d\n", f->ldpc->data_bits_per_frame); fprintf(stderr, "coded_bits_per_frame = %d\n", f->ldpc->coded_bits_per_frame); fprintf(stderr, "coded_syms_per_frame = %d\n", f->ldpc->coded_bits_per_frame/f->ofdm->bps); fprintf(stderr, "ofdm_bits_per_frame = %d\n", f->ofdm_bitsperframe); diff --git a/src/freedv_700.c b/src/freedv_700.c index 3ea5bce09..235284ea1 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -117,6 +117,7 @@ void freedv_ofdm_voice_open(struct freedv *f, char *mode) { assert(f->ldpc != NULL); ldpc_codes_setup(f->ldpc, f->ofdm->codename); + ldpc_mode_specific_setup(f->ofdm, f->ldpc); #ifdef __EMBEDDED__ f->ldpc->max_iter = 10; /* limit LDPC decoder iterations to limit CPU load */ #endif @@ -171,6 +172,8 @@ void freedv_ofdm_data_open(struct freedv *f) { if (f->mode == FREEDV_MODE_DATAC0) strcpy(mode, "datac0"); if (f->mode == FREEDV_MODE_DATAC1) strcpy(mode, "datac1"); if (f->mode == FREEDV_MODE_DATAC3) strcpy(mode, "datac3"); + if (f->mode == FREEDV_MODE_DATAC4) strcpy(mode, "datac4"); + if (f->mode == FREEDV_MODE_DATAC13) strcpy(mode, "datac13"); ofdm_init_mode(mode, &ofdm_config); f->ofdm = ofdm_create(&ofdm_config); @@ -180,6 +183,7 @@ void freedv_ofdm_data_open(struct freedv *f) { f->ldpc = (struct LDPC*)MALLOC(sizeof(struct LDPC)); assert(f->ldpc != NULL); ldpc_codes_setup(f->ldpc, f->ofdm->codename); + ldpc_mode_specific_setup(f->ofdm, f->ldpc); #ifdef __EMBEDDED__ f->ldpc->max_iter = 10; /* limit LDPC decoder iterations to limit CPU load */ #endif @@ -453,7 +457,8 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, int demod_i uint8_t decoded_codeword[Npayloadbitsperpacket]; symbols_to_llrs(llr, payload_syms_de, payload_amps_de, EsNo, ofdm->mean_amp, Npayloadsymsperpacket); - iter = run_ldpc_decoder(ldpc, decoded_codeword, llr, &parityCheckCount); + ldpc_decode_frame(ldpc, &parityCheckCount, &iter, decoded_codeword, llr); + //iter = run_ldpc_decoder(ldpc, decoded_codeword, llr, &parityCheckCount); memcpy(f->rx_payload_bits, decoded_codeword, Ndatabitsperpacket); if (strlen(ofdm->data_mode)) { @@ -534,7 +539,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, int demod_i print_truncated = 1; if (print_full) { fprintf(stderr, "%3d nin: %4d st: %-6s euw: %2d %2d mf: %2d f: %5.1f pbw: %d snr: %4.1f eraw: %4d ecdd: %4d iter: %3d " - "pcc: %3d rxst: %s\n", + "pcc: %4d rxst: %s\n", f->frames++, ofdm->nin, ofdm_statemode[ofdm->last_sync_state], ofdm->uw_errors, @@ -545,7 +550,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, int demod_i } if (print_truncated) { fprintf(stderr, "%3d nin: %4d st: %-6s euw: %2d %2d mf: %2d f: %5.1f pbw: %d " - " rxst: %s\n", + " rxst: %s\n", f->frames++, ofdm->nin, ofdm_statemode[ofdm->last_sync_state], ofdm->uw_errors, diff --git a/src/freedv_api.c b/src/freedv_api.c index 3448009a5..516d60480 100644 --- a/src/freedv_api.c +++ b/src/freedv_api.c @@ -135,7 +135,9 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { FDV_MODE_ACTIVE( FREEDV_MODE_FSK_LDPC, mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, mode) || - FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, mode)) == false) return NULL; + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, mode)) == false) return NULL; /* set everything to zero just in case */ f = (struct freedv*)CALLOC(1, sizeof(struct freedv)); @@ -160,6 +162,8 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { if (FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, mode)) freedv_ofdm_data_open(f); if (FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, mode)) freedv_ofdm_data_open(f); if (FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f); + if (FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f); + if (FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, mode)) freedv_ofdm_data_open(f); varicode_decode_init(&f->varicode_dec_states, 1); @@ -239,13 +243,15 @@ void freedv_close(struct freedv *freedv) { if (FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, freedv->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, freedv->mode) || - FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, freedv->mode)) + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, freedv->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, freedv->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, freedv->mode)) { FREE(freedv->rx_syms); FREE(freedv->rx_amps); FREE(freedv->ldpc); ofdm_destroy(freedv->ofdm); - } + } FREE(freedv); } @@ -269,13 +275,17 @@ static int is_ofdm_mode(struct freedv *f) { FDV_MODE_ACTIVE( FREEDV_MODE_700E, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, f->mode) || - FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode); + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, f->mode); } static int is_ofdm_data_mode(struct freedv *f) { return FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, f->mode) || - FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode); + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, f->mode); } /*---------------------------------------------------------------------------*\ @@ -454,7 +464,9 @@ void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[], unsigned char *packe if (FDV_MODE_ACTIVE( FREEDV_MODE_700D, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, f->mode) || - FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode)) freedv_comptx_ofdm(f, mod_out); + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, f->mode)) freedv_comptx_ofdm(f, mod_out); if (FDV_MODE_ACTIVE( FREEDV_MODE_FSK_LDPC, f->mode)) { freedv_tx_fsk_ldpc_data(f, mod_out); @@ -1016,7 +1028,9 @@ int freedv_rawdatacomprx(struct freedv *f, unsigned char *packed_payload_bits, C if (FDV_MODE_ACTIVE( FREEDV_MODE_700D, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC0, f->mode) || FDV_MODE_ACTIVE( FREEDV_MODE_DATAC1, f->mode) || - FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode)) rx_status = freedv_comp_short_rx_ofdm(f, (void*)demod_in, 0, 1.0f); + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC3, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC4, f->mode) || + FDV_MODE_ACTIVE( FREEDV_MODE_DATAC13, f->mode)) rx_status = freedv_comp_short_rx_ofdm(f, (void*)demod_in, 0, 1.0f); if (FDV_MODE_ACTIVE( FREEDV_MODE_FSK_LDPC, f->mode)) { rx_status = freedv_rx_fsk_ldpc_data(f, demod_in); } diff --git a/src/freedv_api.h b/src/freedv_api.h index 317ceaaf7..add15d474 100644 --- a/src/freedv_api.h +++ b/src/freedv_api.h @@ -60,6 +60,8 @@ #define FREEDV_MODE_DATAC1 10 #define FREEDV_MODE_DATAC3 12 #define FREEDV_MODE_DATAC0 14 +#define FREEDV_MODE_DATAC4 18 +#define FREEDV_MODE_DATAC13 19 // Sample rates used #define FREEDV_FS_8000 8000 @@ -134,6 +136,12 @@ #if !defined(FREEDV_MODE_DATAC3_EN) #define FREEDV_MODE_DATAC3_EN FREEDV_MODE_EN_DEFAULT #endif +#if !defined(FREEDV_MODE_DATAC4_EN) + #define FREEDV_MODE_DATAC4_EN FREEDV_MODE_EN_DEFAULT +#endif +#if !defined(FREEDV_MODE_DATAC13_EN) + #define FREEDV_MODE_DATAC13_EN FREEDV_MODE_EN_DEFAULT +#endif #define FDV_MODE_ACTIVE(mode_name, var) ((mode_name##_EN) == 0 ? 0: (var) == mode_name) diff --git a/src/freedv_data_raw_rx.c b/src/freedv_data_raw_rx.c index ac1f22b4f..64ce1f128 100644 --- a/src/freedv_data_raw_rx.c +++ b/src/freedv_data_raw_rx.c @@ -38,6 +38,7 @@ #include "modem_stats.h" #include "octave.h" #include "fsk.h" +#include "ldpc_codes.h" /* other processes can end this program using signals */ @@ -49,7 +50,8 @@ void INThandler(int sig) { int main(int argc, char *argv[]) { FILE *fin, *fout; - struct freedv_advanced adv = {0,2,100,8000,1000,200, "H_256_512_4"}; + char codename[80] = "H_256_512_4"; + struct freedv_advanced adv = {0,2,100,8000,1000,200, codename}; struct freedv *freedv; int nin, nbytes, nbytes_out = 0, nframes_out = 0, buf = 0; int mode; @@ -60,27 +62,9 @@ int main(int argc, char *argv[]) { int quiet = 0; int single_line_summary = 0; float snr_sum = 0.0; + int fsk_lower = 0, fsk_upper = 0; + int user_fsk_lower = 0, user_fsk_upper = 0; - if (argc < 3) { - helpmsg: - fprintf(stderr, "\nusage: %s [options] FSK_LDPC|DATAC0|DATAC1|DATAC3 InputModemSpeechFile BinaryDataFile\n" - " -v or --vv verbose options\n" - " --testframes count raw and coded errors in testframes sent by tx\n" - " --framesperburst N N frames per burst (default 1, must match Tx)\n" - " --scatter file write scatter diagram symbols to file (Octave text file format)\n" - " --singleline single line summary at end of test, used for logging\n" - " --quiet\n" - "\n" - "For FSK_LDPC only:\n\n" - " -m 2|4 number of FSK tones\n" - " --Fs FreqHz sample rate (default 8000)\n" - " --Rs FreqHz symbol rate (default 100)\n" - " --mask shiftHz Use \"mask\" freq estimator (default is \"peak\" estimator)\n\n", argv[0]); - - fprintf(stderr, "example: %s --framesperburst 1 --testframes datac0 samples.s16 /dev/null\n\n", argv[0]); - exit(1); - } - int o = 0; int opt_idx = 0; while( o != -1 ){ @@ -89,13 +73,18 @@ int main(int argc, char *argv[]) { {"help", no_argument, 0, 'h'}, {"Fs", required_argument, 0, 'f'}, {"Rs", required_argument, 0, 'r'}, + {"shift", required_argument, 0, 's'}, {"vv", no_argument, 0, 'x'}, {"vvv", no_argument, 0, 'y'}, {"mask", required_argument, 0, 'k'}, - {"framesperburst", required_argument, 0, 's'}, + {"framesperburst", required_argument, 0, 'g'}, {"scatter", required_argument, 0, 'c'}, {"quiet", required_argument, 0, 'q'}, {"singleline", no_argument, 0, 'b'}, + {"fsk_lower", required_argument, 0, 'l'}, + {"fsk_upper", required_argument, 0, 'u'}, + {"code", required_argument, 0, 'o'}, + {"listcodes", no_argument, 0, 'i'}, {0, 0, 0, 0} }; @@ -119,6 +108,14 @@ int main(int argc, char *argv[]) { case 'm': adv.M = atoi(optarg); break; + case 'l': + fsk_lower = atoi(optarg); + user_fsk_lower = 1; + break; + case 'u': + fsk_upper = atoi(optarg); + user_fsk_upper = 1; + break; case 'q': quiet = 1; break; @@ -126,6 +123,9 @@ int main(int argc, char *argv[]) { adv.Rs = atoi(optarg); break; case 's': + adv.tone_spacing = atoi(optarg); + break; + case 'g': framesperburst = atoi(optarg); break; case 't': @@ -140,6 +140,17 @@ int main(int argc, char *argv[]) { case 'y': verbose = 3; break; + case 'o': + if (ldpc_codes_find(optarg) == -1) { + fprintf(stderr, "%s not found, try --listcodes\n", optarg); + exit(1); + } + strcpy(codename, optarg); + break; + case 'i': + ldpc_codes_list(); + exit(0); + break; case 'h': case '?': goto helpmsg; @@ -148,6 +159,32 @@ int main(int argc, char *argv[]) { } int dx = optind; + if (argc < 3) { + helpmsg: + fprintf(stderr, "\nusage: %s [options] FSK_LDPC|DATAC0|... InputModemSpeechFile BinaryDataFile\n" + " -v or --vv verbose options\n" + " --testframes count raw and coded errors in testframes sent by tx\n" + " --framesperburst N N frames per burst (default 1, must match Tx)\n" + " --scatter file write scatter diagram symbols to file (Octave text file format)\n" + " --singleline single line summary at end of test, used for logging\n" + " --quiet\n" + "\n" + "For FSK_LDPC only:\n\n" + " -m 2|4 number of FSK tones\n" + " --Fs FreqHz sample rate (default 8000)\n" + " --Rs FreqHz symbol rate (default 100)\n" + " --mask shiftHz Use \"mask\" freq estimator (default is \"peak\" estimator)\n\n" + " --shift FreqHz shift between tones (default 200)\n" + " --fsk_lower freq lower limit of freq estimator (default 0)\n" + " --fsk_upper freq upper limit of freq estimator (default Fs/2)\n" + " --code CodeName LDPC code (defaults (512,256)\n" + " --listcodes list available LDPC codes\n" + "\n", argv[0]); + + fprintf(stderr, "example: %s --framesperburst 1 --testframes datac0 samples.s16 /dev/null\n\n", argv[0]); + exit(1); + } + if( (argc - dx) < 3) { fprintf(stderr, "too few arguments.\n"); goto helpmsg; @@ -158,6 +195,8 @@ int main(int argc, char *argv[]) { if (!strcmp(argv[dx],"DATAC0") || !strcmp(argv[dx],"datac0")) mode = FREEDV_MODE_DATAC0; if (!strcmp(argv[dx],"DATAC1") || !strcmp(argv[dx],"datac1")) mode = FREEDV_MODE_DATAC1; if (!strcmp(argv[dx],"DATAC3") || !strcmp(argv[dx],"datac3")) mode = FREEDV_MODE_DATAC3; + if (!strcmp(argv[dx],"DATAC4") || !strcmp(argv[dx],"datac4")) mode = FREEDV_MODE_DATAC4; + if (!strcmp(argv[dx],"DATAC13") || !strcmp(argv[dx],"datac13")) mode = FREEDV_MODE_DATAC13; if (mode == -1) { fprintf(stderr, "Error in mode: %s\n", argv[dx]); exit(1); @@ -177,12 +216,19 @@ int main(int argc, char *argv[]) { exit(1); } - if (mode != FREEDV_MODE_FSK_LDPC) - freedv = freedv_open(mode); - else { + if (mode == FREEDV_MODE_FSK_LDPC) { freedv = freedv_open_advanced(mode, &adv); struct FSK *fsk = freedv_get_fsk(freedv); fsk_set_freq_est_alg(fsk, mask); + + /* optionally set freq estimator limits */ + if (!user_fsk_lower) fsk_lower = 0; + if (!user_fsk_upper) fsk_upper = adv.Fs/2; + fprintf(stderr,"Setting estimator limits to %d to %d Hz.\n", fsk_lower, fsk_upper); + fsk_set_freq_est_limits(fsk,fsk_lower,fsk_upper); + } + else { + freedv = freedv_open(mode); } assert(freedv != NULL); diff --git a/src/freedv_data_raw_tx.c b/src/freedv_data_raw_tx.c index a4b3ddd46..b2e47f879 100644 --- a/src/freedv_data_raw_tx.c +++ b/src/freedv_data_raw_tx.c @@ -37,6 +37,7 @@ #include "freedv_api.h" #include "fsk.h" #include "ofdm_internal.h" +#include "ldpc_codes.h" size_t send_preamble(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out); size_t send_modulated_data(struct freedv *freedv, FILE *fout, int use_complex, size_t n_mod_out, uint8_t bytes_in[]); @@ -46,7 +47,8 @@ void comp_to_short(short mod_out_short[], COMP mod_out_comp[], int n_mod_out); int main(int argc, char *argv[]) { FILE *fin, *fout; - struct freedv_advanced adv = {0,2,100,8000,1000,200, "H_256_512_4"}; + char codename[80] = "H_256_512_4"; + struct freedv_advanced adv = {0,2,100,8000,1000,200, codename}; struct freedv *freedv; int mode; int use_clip, use_txbpf, testframes, Ntestframes = 0; @@ -58,36 +60,6 @@ int main(int argc, char *argv[]) { int postdelay_ms = 0; uint8_t source_byte = 0; - if (argc < 4) { - helpmsg: - fprintf(stderr, "\nusage: %s [options] FSK_LDPC|DATAC0|DATAC1|DATAC3 InputBinaryDataFile OutputModemRawFile\n" - "\n" - " --testframes T send a total of T test frames (T should equal B*N)\n" - " --bursts B send B bursts of N testframes (default 1)\n" - " --framesperburst N burst mode, N frames per burst (default 1)\n" - " --delay ms testframe inter-burst delay in ms\n" - " --postdelay ms additional delay at end of run in ms\n" - " -c complex signed 16 bit output format (default real)\n" - " --clip 0|1 clipping for reduced PAPR\n" - " --txbpf 0|1 bandpass filter\n" - " --seq send packet sequence numbers (breaks testframe BER counting)\n" - " --source Byte insert a (non-zero) source address att byte[0]\n" - " --complexout complex sample output (default real)\n" - " --quiet\n" - "\n" - "For FSK_LDPC only:\n\n" - " -a amp maximum amplitude of FSK signal\n" - " -m 2|4 number of FSK tones\n" - " --Fs FreqHz sample rate (default 8000)\n" - " --Rs FreqHz symbol rate (default 100)\n" - " --tone1 FreqHz freq of first tone (default 1000)\n" - " --shift FreqHz shift between tones (default 200)\n\n" - , argv[0]); - fprintf(stderr, "example: $ %s --testframes 6 --bursts 3 --framesperburst 2 datac0 /dev/zero samples.s16\n", argv[0]); - fprintf(stderr, "example: $ %s -c --testframes 10 FSK_LDPC /dev/zero samples.iq16\n\n", argv[0]); - exit(1); - } - use_clip = -1; use_txbpf = -1; testframes = 0; int framesperburst = 1; int quiet = 0; @@ -113,11 +85,13 @@ int main(int argc, char *argv[]) { {"amp", required_argument, 0, 'a'}, {"quiet", no_argument, 0, 'q'}, {"complexout", no_argument, 0, 'c'}, + {"code", required_argument, 0, 'o'}, + {"listcodes", no_argument, 0, 'x'}, {0, 0, 0, 0} }; - o = getopt_long(argc,argv,"a:cdt:hb:l:e:f:g:r:1:s:m:qi:",long_opts,&opt_idx); - + o = getopt_long(argc,argv,"a:cdt:hb:l:e:f:g:r:1:s:m:qi:o:x",long_opts,&opt_idx); + switch(o) { case 'a': amp = atof(optarg)/2.0; @@ -173,6 +147,17 @@ int main(int argc, char *argv[]) { case 's': adv.tone_spacing = atoi(optarg); break; + case 'o': + if (ldpc_codes_find(optarg) == -1) { + fprintf(stderr, "%s not found, try --listcodes\n", optarg); + exit(1); + } + strcpy(codename, optarg); + break; + case 'x': + ldpc_codes_list(); + exit(0); + break; case 'h': case '?': goto helpmsg; @@ -181,6 +166,38 @@ int main(int argc, char *argv[]) { } int dx = optind; + if (argc < 4) { + helpmsg: + fprintf(stderr, "\nusage: %s [options] FSK_LDPC|DATAC0|... InputBinaryDataFile OutputModemRawFile\n" + "\n" + " --testframes T send a total of T test frames (T should equal B*N)\n" + " --bursts B send B bursts of N testframes (default 1)\n" + " --framesperburst N burst mode, N frames per burst (default 1)\n" + " --delay ms testframe inter-burst delay in ms\n" + " --postdelay ms additional delay at end of run in ms\n" + " -c complex signed 16 bit output format (default real)\n" + " --clip 0|1 clipping for reduced PAPR\n" + " --txbpf 0|1 bandpass filter\n" + " --seq send packet sequence numbers (breaks testframe BER counting)\n" + " --source Byte insert a (non-zero) source address att byte[0]\n" + " --complexout complex sample output (default real)\n" + " --quiet\n" + "\n" + "For FSK_LDPC only:\n\n" + " -a amp maximum amplitude of FSK signal\n" + " -m 2|4 number of FSK tones\n" + " --Fs FreqHz sample rate (default 8000)\n" + " --Rs FreqHz symbol rate (default 100)\n" + " --tone1 FreqHz freq of first tone (default 1000)\n" + " --shift FreqHz shift between tones (default 200)\n\n" + " --code CodeName LDPC code (defaults (512,256)\n" + " --listcodes list available LDPC codes\n\n" + , argv[0]); + fprintf(stderr, "example: $ %s --testframes 6 --bursts 3 --framesperburst 2 datac0 /dev/zero samples.s16\n", argv[0]); + fprintf(stderr, "example: $ %s -c --testframes 10 FSK_LDPC /dev/zero samples.iq16\n\n", argv[0]); + exit(1); + } + if( (argc - dx) < 3) { fprintf(stderr, "too few arguments.\n"); goto helpmsg; @@ -191,6 +208,8 @@ int main(int argc, char *argv[]) { if (!strcmp(argv[dx],"DATAC0") || !strcmp(argv[dx],"datac0")) mode = FREEDV_MODE_DATAC0; if (!strcmp(argv[dx],"DATAC1") || !strcmp(argv[dx],"datac1")) mode = FREEDV_MODE_DATAC1; if (!strcmp(argv[dx],"DATAC3") || !strcmp(argv[dx],"datac3")) mode = FREEDV_MODE_DATAC3; + if (!strcmp(argv[dx],"DATAC4") || !strcmp(argv[dx],"datac4")) mode = FREEDV_MODE_DATAC4; + if (!strcmp(argv[dx],"DATAC13") || !strcmp(argv[dx],"datac13")) mode = FREEDV_MODE_DATAC13; if (mode == -1) { fprintf(stderr, "Error: in mode: %s", argv[dx]); exit(1); @@ -230,8 +249,8 @@ int main(int argc, char *argv[]) { uint8_t bytes_in[bytes_per_modem_frame]; if (mode == FREEDV_MODE_FSK_LDPC) { - if (!quiet) fprintf(stderr, "Frequency: Fs: %4.1f kHz Rs: %4.1f kHz Tone1: %4.1f kHz Shift: %4.1f kHz M: %d \n", - (float)adv.Fs/1E3, (float)adv.Rs/1E3, (float)adv.first_tone/1E3, (float)adv.tone_spacing/1E3, adv.M); + if (!quiet) fprintf(stderr, "Frequency: Fs: %4.1f Hz Rs: %5.0f Hz Tone1: %5.0f Hz Shift: %5.0f Hz M: %d \n", + (float)adv.Fs, (float)adv.Rs, (float)adv.first_tone, (float)adv.tone_spacing, adv.M); if (adv.tone_spacing < adv.Rs) { fprintf(stderr, "Need shift: %d > Rs: %d\n", adv.tone_spacing, adv.Rs); diff --git a/src/gp_interleaver.c b/src/gp_interleaver.c index 52a157a50..26e74bf6e 100644 --- a/src/gp_interleaver.c +++ b/src/gp_interleaver.c @@ -47,7 +47,9 @@ static const int b_table[] = { 106, 67, /* 2020B: (112,56) partial protection */ 112, 71, /* 700D: HRA_112_112 */ 128, 83, /* datac0: H_128_256_5 */ + 192, 127, /* datac13: H_256_512_4, 128 data bits used */ 210, 131, /* 2020: HRAb_396_504 with 312 data bits used */ + 736, 457, /* datac4: H_1024_2048_4f, 448 data bits used */ 1024, 641, /* datac3: H_1024_2048_4f */ 1290, 797, /* datac2: H2064_516_sparse */ 4096, 2531 /* datac1: H_4096_8192_3d */ diff --git a/src/interldpc.c b/src/interldpc.c index 5ca300b5c..b17e9b7eb 100644 --- a/src/interldpc.c +++ b/src/interldpc.c @@ -64,6 +64,19 @@ void set_data_bits_per_frame(struct LDPC *ldpc, int new_data_bits_per_frame) { ldpc->coded_bits_per_frame = ldpc->data_bits_per_frame + ldpc->NumberParityBits; } +/* 1' stuffing (code rate reduction) - tweak LDPC code setup for selected modes */ +void ldpc_mode_specific_setup(struct OFDM *ofdm, struct LDPC *ldpc) { + /* mode specific set up */ + if (!strcmp(ofdm->mode,"2020")) set_data_bits_per_frame(ldpc, 312); + if (!strcmp(ofdm->mode,"2020B")) { + set_data_bits_per_frame(ldpc, 156); + ldpc->protection_mode = LDPC_PROT_2020B; + } + if (!strcmp(ofdm->mode,"2020C")) set_data_bits_per_frame(ldpc, 156); + if (!strcmp(ofdm->mode,"datac4")) set_data_bits_per_frame(ldpc, 448); + if (!strcmp(ofdm->mode,"datac13")) set_data_bits_per_frame(ldpc, 128); +} + /* LDPC encode frame - generate parity bits and a codeword, applying the selected FEC protection scheme */ void ldpc_encode_frame(struct LDPC *ldpc, int codeword[], unsigned char tx_bits_char[]) { diff --git a/src/interldpc.h b/src/interldpc.h index 096f5b408..2c18f6283 100644 --- a/src/interldpc.h +++ b/src/interldpc.h @@ -41,6 +41,7 @@ void set_up_ldpc_constants(struct LDPC *ldpc, int code_length, int parity_bits); void set_data_bits_per_frame(struct LDPC *ldpc, int new_data_bits_per_frame); +void ldpc_mode_specific_setup(struct OFDM *ofdm, struct LDPC *ldpc); void ldpc_encode_frame(struct LDPC *ldpc, int codeword[], unsigned char tx_bits_char[]); void qpsk_modulate_frame(COMP tx_symbols[], int codeword[], int n); void ldpc_decode_frame(struct LDPC *ldpc, int *parityCheckCount, int *iter, uint8_t out_char[], float llr[]); diff --git a/src/modem_stats.h b/src/modem_stats.h index 604caee36..fbc80f744 100644 --- a/src/modem_stats.h +++ b/src/modem_stats.h @@ -35,7 +35,7 @@ #endif #define MODEM_STATS_NC_MAX 50 -#define MODEM_STATS_NR_MAX 160 +#define MODEM_STATS_NR_MAX 320 #define MODEM_STATS_ET_MAX 8 #define MODEM_STATS_EYE_IND_MAX 160 #define MODEM_STATS_NSPEC 512 diff --git a/src/ofdm.c b/src/ofdm.c index 023d65f97..552196c65 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -52,6 +52,9 @@ static float cnormf(complex float); static void allocate_tx_bpf(struct OFDM *); static void deallocate_tx_bpf(struct OFDM *); +static float find_carrier_centre(struct OFDM *ofdm); +static void allocate_rx_bpf(struct OFDM *); +static void deallocate_rx_bpf(struct OFDM *); static void dft(struct OFDM *, complex float *, complex float *); static void idft(struct OFDM *, complex float *, complex float *); static complex float vector_sum(complex float *, int); @@ -213,6 +216,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->codename = "HRA_112_112"; ofdm->amp_est_mode = 0; ofdm->tx_bpf_en = true; + ofdm->rx_bpf_en = false; ofdm->amp_scale = 245E3; ofdm->clip_gain1 = 2.0; ofdm->clip_gain2 = 0.9; @@ -247,6 +251,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->codename = config->codename; ofdm->amp_est_mode = config->amp_est_mode; ofdm->tx_bpf_en = config->tx_bpf_en; + ofdm->rx_bpf_en = config->rx_bpf_en; ofdm->foff_limiter = config->foff_limiter; ofdm->amp_scale = config->amp_scale; ofdm->clip_gain1 = config->clip_gain1; @@ -294,6 +299,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->config.codename = ofdm->codename; ofdm->config.amp_est_mode = ofdm->amp_est_mode; ofdm->config.tx_bpf_en = ofdm->tx_bpf_en; + ofdm->config.rx_bpf_en = ofdm->rx_bpf_en; ofdm->config.foff_limiter = ofdm->foff_limiter; ofdm->config.amp_scale = ofdm->amp_scale; ofdm->config.clip_gain1 = ofdm->clip_gain1; @@ -363,11 +369,6 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->aphase_est_pilot_log = MALLOC(sizeof (float) * (ofdm->rowsperframe * ofdm->nc)); assert(ofdm->aphase_est_pilot_log != NULL); - /* Null pointers to unallocated buffers */ - ofdm->tx_bpf = NULL; - if (ofdm->tx_bpf_en) - allocate_tx_bpf(ofdm); - /* store complex BPSK pilot symbols */ assert(sizeof (pilotvalues) >= (ofdm->nc + 2) * sizeof (int8_t)); @@ -387,6 +388,14 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->tx_nlower = roundf((ofdm->tx_centre / ofdm->rs) - tval) - 1.0f; ofdm->rx_nlower = roundf((ofdm->rx_centre / ofdm->rs) - tval) - 1.0f; + /* Tx and Rx band pass filters */ + ofdm->tx_bpf = NULL; + if (ofdm->tx_bpf_en) + allocate_tx_bpf(ofdm); + ofdm->rx_bpf = NULL; + if (ofdm->rx_bpf_en) + allocate_rx_bpf(ofdm); + for (i = 0; i < ofdm->nrxbuf; i++) { ofdm->rxbuf[i] = 0.0f; } @@ -468,7 +477,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { // work out how many frames UW is spread over int symsperframe = ofdm->bitsperframe / ofdm->bps; - ofdm->nuwframes = (int) ceilf((float)ofdm->uw_ind_sym[nuwsyms-1]/symsperframe); + ofdm->nuwframes = (int) ceilf((float)(ofdm->uw_ind_sym[nuwsyms-1]+1)/symsperframe); ofdm->tx_uw_syms = MALLOC(sizeof (complex float) * (ofdm->nuwbits / ofdm->bps)); assert(ofdm->tx_uw_syms != NULL); @@ -566,6 +575,12 @@ static void allocate_tx_bpf(struct OFDM *ofdm) { quisk_filt_cfInit(ofdm->tx_bpf, filtP400S600, sizeof (filtP400S600) / sizeof (float)); quisk_cfTune(ofdm->tx_bpf, ofdm->tx_centre / ofdm->fs); } + else if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13")) { + quisk_filt_cfInit(ofdm->tx_bpf, filtP200S400, sizeof (filtP200S400) / sizeof (float)); + // centre the filter on the mean carrier freq, allows a narrower filter to be used + float tx_centre = find_carrier_centre(ofdm); + quisk_cfTune(ofdm->tx_bpf, tx_centre / ofdm->fs); + } else assert(0); } @@ -576,6 +591,36 @@ static void deallocate_tx_bpf(struct OFDM *ofdm) { ofdm->tx_bpf = NULL; } +static float find_carrier_centre(struct OFDM *ofdm) { + float rx_centre = 0.0; + for(int c=0; cnc+2; c++) + rx_centre += (ofdm->rx_nlower + c) * ofdm->doc; + return (ofdm->fs/TAU)*rx_centre/(ofdm->nc+2); +} + +static void allocate_rx_bpf(struct OFDM *ofdm) { + ofdm->rx_bpf = MALLOC(sizeof(struct quisk_cfFilter)); + assert(ofdm->rx_bpf != NULL); + + /* Receive bandpass filter; complex coefficients, center frequency */ + + if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13")) { + quisk_filt_cfInit(ofdm->rx_bpf, filtP200S400, sizeof (filtP200S400) / sizeof (float)); + // centre the filter on the mean carrier freq, allows a narrower filter to be used + float rx_centre = find_carrier_centre(ofdm); + //fprintf(stderr, " rx_centre: %f\n", rx_centre); + quisk_cfTune(ofdm->rx_bpf, rx_centre / ofdm->fs); + } + else assert(0); +} + +static void deallocate_rx_bpf(struct OFDM *ofdm) { + assert(ofdm->rx_bpf != NULL); + quisk_filt_destroy(ofdm->rx_bpf); + FREE(ofdm->rx_bpf); + ofdm->rx_bpf = NULL; +} + void ofdm_destroy(struct OFDM *ofdm) { int i; @@ -586,6 +631,9 @@ void ofdm_destroy(struct OFDM *ofdm) { if (ofdm->tx_bpf) { deallocate_tx_bpf(ofdm); } + if (ofdm->rx_bpf) { + deallocate_rx_bpf(ofdm); + } FREE(ofdm->pilot_samples); FREE(ofdm->rxbuf); @@ -1025,7 +1073,7 @@ void ofdm_hilbert_clipper(struct OFDM *ofdm, complex float *tx, size_t n) { for(int i=0; iamp_scale; if (ofdm->clip_en) { - // this gain set the drive into the Hilbert Clipper and sets PAPR + // this gain sets the drive into the Hilbert Clipper and sets PAPR for(int i=0; iclip_gain1; ofdm_clip(tx, OFDM_PEAK, n); } @@ -1391,6 +1439,11 @@ static int ofdm_sync_search_stream(struct OFDM *ofdm) { } static int ofdm_sync_search_core(struct OFDM *ofdm) { + if (ofdm->rx_bpf_en) { + assert(ofdm->rx_bpf != NULL); + complex float *rxbuf_in = &ofdm->rxbuf[(ofdm->nrxbuf - ofdm->nin)]; + quisk_ccfFilter(rxbuf_in, rxbuf_in, ofdm->nin, ofdm->rx_bpf); + } if (!strcmp(ofdm->data_mode, "burst")) return ofdm_sync_search_burst(ofdm); else @@ -1404,14 +1457,13 @@ static int ofdm_sync_search_core(struct OFDM *ofdm) { */ /* - * This is a wrapper to maintain the older functionality with an - * array of COMPs as input + * This wrapper accepts an array of COMPs as input */ void ofdm_demod(struct OFDM *ofdm, int *rx_bits, COMP *rxbuf_in) { complex float *rx = (complex float *) &rxbuf_in[0]; // complex has same memory layout int i, j; - /* shift the buffer left based on nin */ + /* shift the buffer left based on nin */ for (i = 0, j = ofdm->nin; i < (ofdm->nrxbuf - ofdm->nin); i++, j++) { ofdm->rxbuf[i] = ofdm->rxbuf[j]; } @@ -1425,7 +1477,7 @@ void ofdm_demod(struct OFDM *ofdm, int *rx_bits, COMP *rxbuf_in) { } /* - * This is a wrapper with a new interface to reduce memory allocated. + * This is a wrapper with a real short interface to minimise allocated memory. * This works with ofdm_demod and freedv_api. Gain is not used here. */ void ofdm_demod_shorts(struct OFDM *ofdm, int *rx_bits, short *rxbuf_in, float gain) { @@ -1438,11 +1490,10 @@ void ofdm_demod_shorts(struct OFDM *ofdm, int *rx_bits, short *rxbuf_in, float g } /* insert latest input samples onto tail of rxbuf */ - for (j = 0, i = (ofdm->nrxbuf - ofdm->nin); i < ofdm->nrxbuf; j++, i++) { ofdm->rxbuf[i] = ((float)rxbuf_in[j] / 32767.0f); } - + ofdm_demod_core(ofdm, rx_bits); } @@ -1454,6 +1505,12 @@ static void ofdm_demod_core(struct OFDM *ofdm, int *rx_bits) { int prev_timing_est = ofdm->timing_est; int i, j, k, rr, st, en; + if (ofdm->rx_bpf_en) { + assert(ofdm->rx_bpf != NULL); + complex float *rxbuf_in = &ofdm->rxbuf[(ofdm->nrxbuf - ofdm->nin)]; + quisk_ccfFilter(rxbuf_in, rxbuf_in, ofdm->nin, ofdm->rx_bpf); + } + /* * get user and calculated freq offset */ @@ -2549,6 +2606,7 @@ void ofdm_print_info(struct OFDM *ofdm) { fprintf(stderr, "ofdm->foff_est_en = %s\n", ofdm->foff_est_en ? "true" : "false"); fprintf(stderr, "ofdm->phase_est_en = %s\n", ofdm->phase_est_en ? "true" : "false"); fprintf(stderr, "ofdm->tx_bpf_en = %s\n", ofdm->tx_bpf_en ? "true" : "false"); + fprintf(stderr, "ofdm->rx_bpf_en = %s\n", ofdm->rx_bpf_en ? "true" : "false"); fprintf(stderr, "ofdm->dpsk_en = %s\n", ofdm->dpsk_en ? "true" : "false"); fprintf(stderr, "ofdm->phase_est_bandwidth_mode = %s\n", phase_est_bandwidth_mode[ofdm->phase_est_bandwidth_mode]); } diff --git a/src/ofdm_demod.c b/src/ofdm_demod.c index 04eb5150f..babe91ee0 100644 --- a/src/ofdm_demod.c +++ b/src/ofdm_demod.c @@ -67,7 +67,7 @@ void opt_help() { fprintf(stderr, " --in filename Name of InputModemRawFile\n"); fprintf(stderr, " --out filename Name of OutputOneCharPerBitFile\n"); fprintf(stderr, " --log filename Octave log file for testing\n"); - fprintf(stderr, " --mode modeName Predefined mode e.g. 700D|2020|datac1\n"); + fprintf(stderr, " --mode modeName Predefined mode e.g. 700D|2020|datac1 etc\n"); fprintf(stderr, " --nc [17..62] Number of Carriers (17 default, 62 max)\n"); fprintf(stderr, " --np Number of packets\n"); fprintf(stderr, " --ns Nframes One pilot every ns symbols (8 default)\n"); @@ -322,7 +322,7 @@ int main(int argc, char *argv[]) { int Nsymsperframe = Nbitsperframe / ofdm_config->bps; int Nsymsperpacket = Nbitsperpacket / ofdm_config->bps; int Nmaxsamperframe = ofdm_get_max_samples_per_frame(ofdm); - int Npayloadbitsperframe = ofdm_bitsperframe - ofdm_nuwbits - ofdm_ntxtbits; + int Npayloadbitsperframe = ofdm_bitsperframe; int Npayloadbitsperpacket = Nbitsperpacket - ofdm_nuwbits - ofdm_ntxtbits; int Npayloadsymsperframe = Npayloadbitsperframe/ofdm_config->bps; int Npayloadsymsperpacket = Npayloadbitsperpacket/ofdm_config->bps; @@ -335,18 +335,11 @@ int main(int argc, char *argv[]) { if (ldpc_en) { ldpc_codes_setup(&ldpc, ofdm->codename); - if (verbose > 1) { fprintf(stderr, "using: %s\n", ofdm->codename); } - - /* mode specific set up */ - if (!strcmp(mode,"2020")) set_data_bits_per_frame(&ldpc, 312); - if (!strcmp(mode,"2020B")) { - set_data_bits_per_frame(&ldpc, 156); - ldpc.protection_mode = LDPC_PROT_2020B; - } - if (!strcmp(mode,"2020C")) set_data_bits_per_frame(&ldpc, 156); + ldpc_mode_specific_setup(ofdm, &ldpc); Ndatabitsperpacket = ldpc.data_bits_per_frame; if (verbose > 1) { + fprintf(stderr, "using: %s\n", ofdm->codename); fprintf(stderr, "LDPC codeword data bits = %d\n", ldpc.ldpc_data_bits_per_frame); fprintf(stderr, "LDPC codeword total bits = %d\n", ldpc.ldpc_coded_bits_per_frame); fprintf(stderr, "LDPC codeword data bits used = %d\n", Ndatabitsperpacket); @@ -393,8 +386,8 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Warning EsNo: %f hard coded\n", EsNo); /* More logging */ - COMP payload_syms_log[NFRAMES][Npayloadsymsperframe]; - float payload_amps_log[NFRAMES][Npayloadsymsperframe]; + COMP payload_syms_log[NFRAMES][Npayloadsymsperpacket]; + float payload_amps_log[NFRAMES][Npayloadsymsperpacket]; for (i = 0; i < NFRAMES; i++) { for (j = 0; j < Npayloadsymsperframe; j++) { diff --git a/src/ofdm_internal.h b/src/ofdm_internal.h index dc3839d2d..caa7876ac 100644 --- a/src/ofdm_internal.h +++ b/src/ofdm_internal.h @@ -98,7 +98,8 @@ struct OFDM_CONFIG { char *codename; /* name of LDPC code used with this mode */ uint8_t tx_uw[MAX_UW_BITS]; /* user defined unique word */ int amp_est_mode; - bool tx_bpf_en; /* default clippedtx BPF state */ + bool tx_bpf_en; /* default tx (mod) hilbert clipper BPF enable */ + bool rx_bpf_en; /* default rx (demod) input BPF enable */ bool foff_limiter; /* tames freq offset updates in low SNR */ float amp_scale; /* used to scale Tx waveform to approx FREEDV_PEAK with clipper off */ float clip_gain1; /* gain we apply to Tx signal before clipping to control PAPR*/ @@ -174,6 +175,7 @@ struct OFDM { // Pointers struct quisk_cfFilter *tx_bpf; + struct quisk_cfFilter *rx_bpf; complex float *pilot_samples; complex float *rxbuf; @@ -236,6 +238,7 @@ struct OFDM { bool foff_est_en; bool phase_est_en; bool tx_bpf_en; + bool rx_bpf_en; bool dpsk_en; bool postambledetectoren; /* allows us to optionally disable the postamble detector */ diff --git a/src/ofdm_mod.c b/src/ofdm_mod.c index c2bbee3ca..800b410e3 100644 --- a/src/ofdm_mod.c +++ b/src/ofdm_mod.c @@ -52,7 +52,7 @@ void opt_help() { fprintf(stderr, "\nusage: %s [options]\n\n", progname); fprintf(stderr, " --in filename Name of InputOneCharPerBitFile\n"); fprintf(stderr, " --out filename Name of OutputModemRawFile\n"); - fprintf(stderr, " --mode modeName Predefined mode 700D|700E|2020|2020B|datac0|datac1|datac3\n"); + fprintf(stderr, " --mode modeName Predefined mode 700D|700E|2020|2020B|datac0 ... etc\n"); fprintf(stderr, " --nc [17..62] Number of Carriers (17 default, 62 max)\n"); fprintf(stderr, " --ns symbols One pilot every ns symbols (8 default)\n"); fprintf(stderr, " --tcp Nsecs Cyclic Prefix Duration (.002 default)\n"); @@ -250,18 +250,11 @@ int main(int argc, char *argv[]) { struct LDPC ldpc; if (ldpc_en) { ldpc_codes_setup(&ldpc, ofdm->codename); - if (verbose > 1) { fprintf(stderr, "using: %s\n", ofdm->codename); } - - /* mode specific set up */ - if (!strcmp(mode,"2020")) set_data_bits_per_frame(&ldpc, 312); - if (!strcmp(mode,"2020B")) { - set_data_bits_per_frame(&ldpc, 156); - ldpc.protection_mode = LDPC_PROT_2020B; - } - if (!strcmp(mode,"2020C")) set_data_bits_per_frame(&ldpc, 156); + ldpc_mode_specific_setup(ofdm, &ldpc); Ndatabitsperpacket = ldpc.data_bits_per_frame; if (verbose > 1) { + fprintf(stderr, "using: %s\n", ofdm->codename); fprintf(stderr, "LDPC codeword data bits = %d\n", ldpc.ldpc_data_bits_per_frame); fprintf(stderr, "LDPC codeword total bits = %d\n", ldpc.ldpc_coded_bits_per_frame); fprintf(stderr, "LDPC codeword data bits used = %d\n", Ndatabitsperpacket); diff --git a/src/ofdm_mode.c b/src/ofdm_mode.c index 43803ce49..13f6e8c5f 100644 --- a/src/ofdm_mode.c +++ b/src/ofdm_mode.c @@ -44,6 +44,7 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->clip_gain2 = 0.8; config->clip_en = true; config->tx_bpf_en = true; + config->rx_bpf_en = false; config->amp_scale = 245E3; config->foff_limiter = false; memset(config->tx_uw, 0, MAX_UW_BITS); @@ -109,7 +110,6 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->txtbits = 0; config->state_machine = "data"; config->ftwindowwidth = 80; config->timing_mx_thresh = 0.10; config->codename = "H_1024_2048_4f"; config->amp_est_mode = 1; - /* custom UW - we use a longer UW with higher bad_uw_errors threshold due to high raw BER */ config->nuwbits = 40; config->bad_uw_errors = 10; uint8_t uw[] = {1,1,0,0, 1,0,1,0, 1,1,1,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; assert(sizeof(uw) <= MAX_UW_BITS); @@ -117,7 +117,35 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { memcpy(&config->tx_uw[config->nuwbits-sizeof(uw)], uw, sizeof(uw)); config->data_mode = "streaming"; config->amp_scale = 300E3; config->clip_gain1 = 2.2; config->clip_gain2 = 0.8; - } + } else if (strcmp(mode,"datac4") == 0) { + config->ns=5; config->np=47; config->tcp = 0.006; config->ts = 0.016; config->nc = 4; + config->edge_pilots = 0; + config->txtbits = 0; config->state_machine = "data"; + config->ftwindowwidth = 80; config->timing_mx_thresh = 0.5; + config->codename = "H_1024_2048_4f"; config->amp_est_mode = 1; + config->nuwbits = 32; config->bad_uw_errors = 12; + uint8_t uw[] = {1,1,0,0, 1,0,1,0, 1,1,1,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; + assert(sizeof(uw) <= MAX_UW_BITS); + memcpy(config->tx_uw, uw, sizeof(uw)); + memcpy(&config->tx_uw[config->nuwbits-sizeof(uw)], uw, sizeof(uw)); + config->data_mode = "streaming"; + config->amp_scale = 2*300E3; config->clip_gain1 = 1.2; config->clip_gain2 = 1.0; + config->rx_bpf_en = true; + } else if (strcmp(mode,"datac13") == 0) { + config->ns=5; config->np=18; config->tcp = 0.006; config->ts = 0.016; config->nc = 3; + config->edge_pilots = 0; + config->txtbits = 0; config->state_machine = "data"; + config->ftwindowwidth = 80; config->timing_mx_thresh = 0.45; + config->codename = "H_256_512_4"; config->amp_est_mode = 1; + config->nuwbits = 48; config->bad_uw_errors = 18; + uint8_t uw[] = {1,1,0,0, 1,0,1,0, 1,1,1,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; + assert(sizeof(uw) <= MAX_UW_BITS); + memcpy(config->tx_uw, uw, sizeof(uw)); + memcpy(&config->tx_uw[config->nuwbits-sizeof(uw)], uw, sizeof(uw)); + config->data_mode = "streaming"; + config->amp_scale = 2.5*300E3; config->clip_gain1 = 1.2; config->clip_gain2 = 1.0; + config->rx_bpf_en = true; + } else { assert(0); } diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 986ee3f62..080ebc5b2 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -90,13 +90,15 @@ target_link_libraries(t16_8 codec2) add_executable(t16_8_short t16_8_short.c ../src/fdmdv.c ../src/kiss_fft.c) target_link_libraries(t16_8_short codec2) - add_executable(t48_8 t48_8.c ../src/fdmdv.c ../src/kiss_fft.c) target_link_libraries(t48_8 codec2) add_executable(t48_8_short t48_8_short.c ../src/fdmdv.c ../src/kiss_fft.c) target_link_libraries(t48_8_short codec2) +add_executable(tquisk_filter tquisk_filter.c) +target_link_libraries(tquisk_filter codec2) + # Build CML as part of unit test setup find_program(OCTAVE_CMD octave-cli REQUIRED) message("Octave command: ${OCTAVE_CMD}") diff --git a/unittest/check_peak.sh b/unittest/check_peak.sh index 38a2c4fef..6462d04ad 100755 --- a/unittest/check_peak.sh +++ b/unittest/check_peak.sh @@ -50,6 +50,8 @@ if [ "$1" == "LPCNet" ]; then data_test "datac0" data_test "datac1" data_test "datac3" + data_test "datac4" + data_test "datac13" fi exit 0 diff --git a/unittest/raw_data_curves/Makefile b/unittest/raw_data_curves/Makefile index 81b845576..9f13d1f63 100644 --- a/unittest/raw_data_curves/Makefile +++ b/unittest/raw_data_curves/Makefile @@ -20,7 +20,7 @@ all: test \ clean: rm -f *.txt *.png *.raw -# trap common setup error +# run this first, traps common CML setup error test: source snr_curves.sh; test_ldpc @@ -55,25 +55,37 @@ $(snr_ch_mpp): source snr_curves.sh; generate_ch_data datac1 mpp source snr_curves.sh; generate_ch_data datac3 mpp +# C without compression + $(snr_ctx): source snr_curves.sh; generate_snrest_v_snr_data datac0 awgn source snr_curves.sh; generate_snrest_v_snr_data datac1 awgn source snr_curves.sh; generate_snrest_v_snr_data datac3 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac4 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac13 awgn $(snr_ctx_mpp): source snr_curves.sh; generate_snrest_v_snr_data datac0 mpp source snr_curves.sh; generate_snrest_v_snr_data datac1 mpp source snr_curves.sh; generate_snrest_v_snr_data datac3 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac4 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac13 mpp + +# C with compression $(snr_ctxc): source snr_curves.sh; generate_snrest_v_snr_data datac0 awgn 1 source snr_curves.sh; generate_snrest_v_snr_data datac1 awgn 1 source snr_curves.sh; generate_snrest_v_snr_data datac3 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac4 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac13 awgn 1 $(snr_ctxc_mpp): source snr_curves.sh; generate_snrest_v_snr_data datac0 mpp 1 source snr_curves.sh; generate_snrest_v_snr_data datac1 mpp 1 source snr_curves.sh; generate_snrest_v_snr_data datac3 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac4 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac13 mpp 1 # Octave and C curves should be on top of each other, indicating Octave # and ch noise injection/SNR measurement are equivalent @@ -101,7 +113,7 @@ octave_c_tx_comp_mpp.png: $(snr_oct_mpp) $(snr_ctxc_mpp) echo "snr_curves_plot; octave_c_tx_comp_print('mpp'); quit" | \ octave-cli -p $(CODEC2)/octave -# composite AWGN and MPP from C Tx (compressed) - what end users would run +# combined AWGN and MPP from C Tx (compressed) - what end users would run c_tx_comp.png: $(snr_ctxc) $(snr_ctxc_mpp) echo "snr_curves_plot; c_tx_comp_print; quit" | \ octave-cli -p $(CODEC2)/octave diff --git a/unittest/raw_data_curves/snr_curves.sh b/unittest/raw_data_curves/snr_curves.sh index 6f8d1174f..6da671c58 100755 --- a/unittest/raw_data_curves/snr_curves.sh +++ b/unittest/raw_data_curves/snr_curves.sh @@ -106,12 +106,13 @@ function generate_snrest_v_snr_data { snr_nudge=0 aNo_list=$No_list + + # nudge SNR test range to get meaningful results for these tests if [ "$mode" == "datac1" ]; then - # nudge SNR test range to get meaningful results for this test snr_nudge=4 - else - # few extra points to test SNRest at high SNRs on low rate waveforms - aNo_list=$No_list" -28 -30" + fi + if [[ "$mode" == "datac4" || "$mode" == "datac13" ]]; then + snr_nudge=-6 fi ch_multipath='' @@ -158,6 +159,13 @@ function generate_snrest_v_snr_data { done echo ${SNRoffset} > offset_${id}_${mode}_${channel}.txt + + # trap not enough fading file samples (with mpp) + grep "Fading file finished" ${ch_log} + if [ $? -eq 0 ]; then + cat ${ch_log} + exit 1 + fi SNRch=$(cat ${ch_log} | grep SNR3k | tr -s ' ' | cut -d' ' -f3) echo ${SNRch} > snr_${id}_${mode}_${channel}.txt } @@ -166,7 +174,7 @@ function generate_snrest_v_snr_data { function test_ldpc { echo "ldpcut; quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave if [ "$?" -ne 0 ]; then - echo "basic octave test failed, you may need to" + echo "basic octave test failed, you may need to" echo "(a) run ctests to create build_xxx/cml" echo "(b) set up ~/.octaverc as per octave/ldpc.m" exit 1 diff --git a/unittest/tquisk_filter.c b/unittest/tquisk_filter.c new file mode 100644 index 000000000..d6c752988 --- /dev/null +++ b/unittest/tquisk_filter.c @@ -0,0 +1,48 @@ +/* + tquisk_filter.c + + Unit test for complex band pass filters in src/filter.c + + cd codec2/build_linux + ./misc/mksine - 1500 2 | unittest/tquisk_filter | aplay + + By adjusting the frequency you can audibly test filter response. +*/ + +#include +#include +#include +#include +#include "filter.h" +#include "filter_coef.h" + +#define N 159 /* processing buffer size (odd number deliberate) */ +#define CENTRE 1500.0 +#define FS 8000.0 + +int main() { + short buf_short[N]; + complex float buf[N]; + struct quisk_cfFilter *bpf; + int i; + int n = 0; + + bpf = malloc(sizeof(struct quisk_cfFilter)); + assert(bpf != NULL); + quisk_filt_cfInit(bpf, filtP200S400, sizeof (filtP200S400) / sizeof (float)); + quisk_cfTune(bpf, CENTRE/FS); + + while(fread(buf_short, sizeof(short), N, stdin) == N) { + for(i=0; i