ECE 4760: Final Project

PIC32 Amatuer Radio Modem

Using Baofeng UV-3R Radios

Noah Levy (nml45)

Geoffrey Keating (gbk42)

Introduction    

"A PIC32 Radio Link to Send Data Using MSK and Amatuer Radio." -Project Soundbyte

Our project was borne of ignorance. Digital radios are something that are pervasive in modern society. Yet even as senior-level ECE undergraduates, we felt that we understood them poorly. At Cornell there are classes on embedded systems, and classes on Digital Signal Processing and Digital Communications, but no single class that actually mixes the two. Our product was designed as an effort to get some experience in this space, and as a proof of concept at a low budget radio project for future ECE 4760 students.

Neither embedded wireless communications nor digital modems are particularly unheard of in the context of ECE4760. A number of ECE4760 projects rely on monolithic ISM band digital radio Zigbee or Bluetooth ICs (essentially black-boxes) for enabling everything from an accelerometer controlled RC car to wireless pedometers. Our project was largely inspired by an acoustic modem ECE4760 project by Greg Malysa and Arseny Romanenko. Similar to them, a secondary factor attracting us to this project is it's similarity to problems in underwater acoustic communications. Gaining insight in this space would be very useful for Cornell University Autonomous Underwater Vehicle Team (CUAUV), with which Noah is associated.

As far as we know our project is the first to combine a digital modem with an off-the-shelf RF stage. We hope there will be more.

 

High Level Design    top

Overview

The wireless modem consists of two identical boards that provide input and recieve output from each Baofeng UV-3R radio. The two boards have several key modules that implement the system: the PIC32 microcontroller, a 12-bit digital to analog converter, a gain stage, and a cable adapter.

The center of the system, the PIC32, takes input via RS232 cable which uses one of the two UART systems in the PIC32 (cable shown below). This cable carries the text or other data the user wishes to send to the PIC32, which buffers the input. To comply with our communications protocol, the PIC32 packages the data with sychronization bytes, or a special byte of data indicating the start of a frame.

Figure 1 - Serial Cable

A frame is defined as the longest possible string of bits that can be transmitted without a clock synchronization. Since the clocks of the radio and PIC32s are not identical, the modulator and demodulator can drift out of phase and produce bit errors. Therefore, the PIC32 packages the transmission into a clock pulse followed by a brief silence and then a full frame of data. This pattern can be seen in Figure 2 where the first narrow pulse synchronizes the clock and the rest of the waveform is modulated data. The red and green dots are the locations of the beginning and ending bytes of the signal.

Figure 2 - Simulated Transmission

The data sent out of the PIC32 is modulated via Minimum Shift Keying (MSK), which is a frequency modulation scheme with continuous phase, which eliminates sharp phase transitions that would otherwise broaden the bandwidth. The modulation occurs at a baseband centered at 1600Hz, and is sampled at a standard audio rate of 44.1 kHz. The radios themselves have a "Push To Talk" feature designed to allow the , the PIC32 toggles an output pin to trigger the push to talk button on the radio.

Once the data reaches the DAC, the waveform is output in 12 bit precision and run through a small op amp gain stage that introduces a DC offset to avoid clipping on the output. The output of the gain stage is sent through a TRS cable, which is incompatible with the UV-3R, so a small board re-maps the audio, push to talk, and ground to a TRRS connector, which is widely known as the standard cell phone connector with a band for microphone input (Figure 3).

Figure 3 - TRRS versus TRS

For the project demonstration, the radios were set to the VHF band. The demodulator conducts sampling with the PIC32's analog to digital converter (ADC), which the radio's output at 44.1 kHz. A decoder implemented in software then detects the symbol frequencies associated with either a 1 or 0 by measuring the average power of each bit in both I and Q channels to determine which frequency is dominant. This data is then placed into a buffer and sent out via the same serial scheme as the reciever to a terminal.

Analog Front End

While consumer-targeted RF modules are cheap, individual RF components are not. In trying to select an analog front end for the transceiver we were therefore stymied. Eventually we settled on a non-ISM dual band amatuer radio hand transceiver, the Baofeng UV-3R, due to it's low cost. By using this as the front end we were able to avoid spending time and money designing the analog sections of the physical layer, and focus on the actual modem. This approach had drawbacks, the UV-3R is designed for FM voice modulation, as a result it's bandwidth is governed by Carson's Rule which states that the total bandwidth occupied by an FM modulated analog signal is $2(\delta f + f_m)$ where $\delta f$ is the peak frequency deviation of the UV-3R, 5Khz and $f_m$ is the peak modulating frequency. By FCC regulation an individual FM channel on the UV-3R in wideband mode could have a width of no more than 25Khz. This meant that ignoring guardband width our modulating frequency would have to be less than 7.5Khz. In fact we found that the radios impulse response severely atennuated anything beyond 3Khz.

Standards and FCC Guidelines

The band in which we communicate in is regulated by the part 97 of FCC regulations and allows for liscensed use by HAM radio operators. The user of the transciever must have Technician class or higher certification. By convention, band planning within the frequency ranges allocated by the FCC to amatuer radio is handled by the ARRL (American Radio Relay League). A comprehensive listing of the uses for specific frequencies (of interest are frequencies around 145.550 MHz) can be found on ARRL's website.. 145.550Mhz to 145.580Mhz are allocated for "Miscellaneous and Experimental Modes". Noah has a general class amatuer radio liscense (KD2HQW), so all operation is legal.

Block Diagram

Figure 4 - Transmitter Block Diagram

Figure 4 - Receiver Block Diagram

Simulation and Testing    top

Since the PIC32 implementation would provide plenty of debugging problems without considering the difficulty of implementing a modulation scheme, we decided to simulate the system in MATLAB first. Instead of using MATLAB's helpful modulationg and demodulation functions for MSK, we implemented our own in a style that would be friendlier to port into C code.

We chose MSK because of its greater efficiency in bandwidth and because our radios work on FM modulation, phase shift keying would be troublesome as phase is often transformed in unpredictable ways in such a scheme. There were several parameters to select for in this system. These parameters are center frequency, sampling rate, and data rate. MSK requires orthogonality in data frequencies so that the phases may be aligned on bit transitions.  The orthogonality of the different symbol frequencies makes it such that the two are easily distinguishable when multiplied by an oscillator of their respective symbol frequencies.

$$ s(t)=cos\left[2\pi(f_0 + \frac{d_k}{4T})t + x_k\right]\quad kT < t < (k+1)T $$

Figure 5 - MSK Definition

The mathematical definition of MSK is shown above, which specifices a center frequency (first term inside cos()), and has a variable frequency term that moves the waveform either a quarter above or below the center depending on the current bit. This is all continuously phase matched by the term phi, which either is zero or pi depending on whether the signal is transitioning from 1 to 0 or 0 to 1. We initially wanted to have a data rate of 9600 bits per second, but upon realizing our channel bandwidth, we had to lower this dramatically. The simulated channel that was measured consisted of a laptop running MATLAB at the center that would modulate data, play the resulting vector over a USB soundcard to one radio which would be recieved by the second radio outputting into another soundcard on the same computer. This data was recorded in Audacity and reprocessed in MATLAB separately. Measuring the impulse response of the simulated channel (PCs instead of PIC32s), which we assumed to be similar in bandwidth to the final system, we found that our usable bandwidth was roughly 3 kHz. Since this was the ideal case with very high SNR, we had to lower our bit rate to acheive a functional system.

Figure 6 - Impulse Response of Channel (PC, SoundCard, UV-3R)

Realizing this information, we found that the maximum bit rate we could achieve centered at 1600 Hz was 551.25 Bits per second, which seemed to be limited by intersymbol interference (ISI). A plot of a simulated transmission showing ISI is shown below.

Figure 7

Another problem mentioned earlier was clock drift of the transmitter and the reciever as shown in the overview. This problem was alleviated by adding the short third frequency pulse. The frequency of the clock synchronization pulse was set to the data rate and lasted 40 bit lengths. This pulse was compared to a numerically controlled oscillator to find the phase difference between the two signals by measuring the power output by an in-phase and quadrature channel and taking the arc tangent to find the difference in angle. In phase and quadrature power detection was used in the demodulator as well.  The general form of the I/Q calculation multiplies an in-phase component (cosine) and an out of phase component (sine) by an incoming signal:

This angle determined through the I/Q power detection was converted to a number of samples to shift the clock through a conversion factor equal to:

$$ 2 \pi \frac{-f_{sample}}{R_{data}} $$

We determined that we needed to perform this correction about every 1,920 bits to safely lower the probability of bit errors to a very low level since we did not implement any error correction codes. Below are links to published MATLAB source code and outputs of these simulations.

Moduator

Demodulator

Script

Decoder

Hardware and Program Design   top

Hardware

The hardware for the project is centered around the PIC32, which is shown in the schematic below. This schematic is both a transmitter and receiver as they are implemented on one PIC32 for each end of the modem. The following subsections will breakdown the hardware design and test. Refer to Appendix A for a full schematic.

Figure 8 - One of Two PIC32 Boards

PICkit 3 Programmer

Since we used standalone PIC32s, we needed a way to program without the Microstick II that we had used in earlier labs. The PICkit 3 is a USB to pin header programmer that requires a few passive components surrounding the PIC and 2 programming pins, labeled PGED1 and PGEC1. The PICkit 3 can also supply power and ground, but is somewhat current limited, so we elected to use the serial cable that we used for UART input and output instead.

External Oscillator

For past labs in this course, the PIC32 relied on the internal RC oscillator to generate the clock, which while the datasheet claims good accuracy, we decided to use an 8 MHz external crystal oscillator to provide a more accurate clock. Crystal oscillators have tolerances in the parts per million, so using this crystal would make our clocks more robust on the sending and receiving side. Since one of the greatest problems we experienced during simulation was missed bits towards the end of transmissions, matching our clocks in separate locations to minimize clock skew was critical to system performance. In the PIC32 project files, we scaled this crystal to match our system frequency of 64 MHz and peripheral bus frequency of 32 MHz. Thee peripheral bus clock timed our interrupt service routines that modulated and demoduated incoming signals, so the crystal oscillator allowed better ISR precision.

UART Serial

We utilized the PIC32s UART module to input and output data to the PC terminals that the operator would use to read and write messages. Since our modulation and demodulation were a much higher priority task, using threading tools like Protothreads would possibly interfere with data flow, so assigning the UART to operate via lower priority interrupts was necessary. The PIC32 UART module holds a buffer of data that will trigger and interrupt when data is incoming or outgoing, so by configuring an ISR to handle writing and reading the buffer was rather simple.

The serial cable also afforded us with a simple power supply from the USB port of a PC, supplying +5 Volts and a ground. We decided that this was a compact and reliable way to deliver power to the modem, provided the supply was appropriately decoupled as the power line noise out of a USB port is somewhat high.

Power Conversion by LDO

Since the PIC32 and DAC ran on 3.3 Volts and our serial cable supplied 5 Volts, we had to downconvert our voltage to supply a 3.3 Volt rail. We did this by connecting two low dropout voltage regulators in parallel (see data sheet seciton for specifications. We used two MCP 1702s to supply a maximum of 500 mA since we believed at the time that with our op amp gain stage, we would have the small chance of exceeding 250 mA. Once we converted the gain stage to a 5V rail for clipping reasons, this parallel configuration simply became a huge safegaurd against any power resets, which at that point were unlikely to occur. In any case, the LDO supplied 3.3 Volts the the outermoost rail on the solder board and was appropriately decoupled with 1 uF capacitors as recommended in the MCP 1702 data sheet.

DAC

To synthesize the output of the PIC32, we used a MCP4822 DAC (see Data Sheets) to output a signal with 12 bits of precision. The DAC requires 3.3 Volts to operate and is dual channel, but since we only needed one, we use channel A, which was outputted through pin 8. The DAC recieved values from the PIC32 through the PIC32's SPI channel, which requires a chip select, clock, and data output. These three inputs were provided by pins 12, 25, and 24 on the PIC32 respectively. The outgoing signal of the DAC was fed into an op amp gain stage to isolate it from the Baofeng audio input jack. Data was sent to the DAC at 44.1 kHz, the sample rate of the outgoing waveform from the PIC32. INSERT FIGURE

Op Amp Gain Stage

When initially testing the modulator, we realized that the radio was clipping the output of the DAC by introducing a low load impedance. To isolate these two components, we implemented the shchematic below to provide isolation between the radio audio jack and the DAC output as well as gain the outgoing signal by a factor of two to ensure the radio would be transmitting at its maximum potential. Since we were using only a single voltage rail, the configuration of the op-amp required a DC bias since the input was AC coupled (BW1), which we divided equally with 100 kilo ohm resistors to place the center point at 1.7 Volts as the division was off the 3.3 Volt rail. The op amp was running on the 5 Volt rail to avoid clipping. The feedback resistor R2 was chosen to provide a gain of 2, or twice the value of R1. Bandwidth 2 was selected as to pass our outgoing frequency so it was placed well above 1600 Hz. The third bandwidth was not necessary as we did not need to filter the output of the op amp to treat our signal.

Figure 9 - OP Amp Configuration, Analog Devices

PTT Circuit and Radio Breakout Board

Since the Baofeng UV-3R is a handheld voice radio, there is a push to talk (PTT) button that triggers a transmission. Just like most smartphones, the TRRS input audiojack breaks out to stereo audio, a microphone, and a ground connection (see the figure in the introduction regarding TRRS and TRS). The microphone jack band on the audio input is the PTT trigger and is active low, so the transistor Q2 in the schematic is meant to short the connection to ground when the board is prepared to output a signal. A pin on the PIC32 is set to high, which drives the MOSFET into saturation, shorting the PTT connection to groung, triggering the radio.

The PTT output is connected to an audio jack adapter board which converts from TRS from the PIC32 to TRRS for the Baofeng. The output to the Baofeng connects the audio out from the PIC32 over a voltage divider which divides the signal to ensure the radio doesn't clip the output waveform. The incoming signal has no division because the Baofeng has an adjustable volume knob, which we set to 5 as it was a good level to avoid clipping on the incoming audio.

ADC

To sample the incoming audio, the PIC32's analog to digital converter was used to sample the input at a rate of 44.1 kHz. The identical sampling rate was implemented by sampling every interrupt that audio was being output to the DAC. Timer 3 counted out the sampling period and the samples were collected in 16 bit integer format from pin 24.

Software

All commented code that was used for programming the PIC32 can be found in Appendix B.

config.h

To configure the PIC32, a header file was made to specify PLL divisors, multipliers and macros for the system clock and peripheral clock frequencies. JTAG programming was also turned off.

fixed.h

Since fixed point math was essential in allowing enough time between interrupts when calculating the ouput waveform sent to the DAC, we included a header file that defined helpful functions that converted to and from fix16 (Q16.16) from integers and floating point numbers. This file was identical to the one used in Lab 3, the TFT video game lab. This file was distributed to ECE 4760 students courtesy of Bruce Land.

main.h

Main.h is the main header file for main.c, which is the modem source code. This file contains parameters for practically all functions in the project. Note that many of the definitions are in both fixed and floating point for flexibility since some times floating point was necessary.

sin_lookup.c

This file contains a 1024 step sin lookup table that is normalized to 4096. This data is formatted for the 12 bit DAC that is on the PIC32 board. The length of this vector is defined in the file and accesible as a macro in main.h.

main.c

Main.c is the parent file for the whole project, containing all the modem implementation. We will start by discussing the serial interface that is implemented through UART.

The UART interface is configured for 8 data bits, 1 stop bit and 9600 baud as well as interrupts on every new incoming character. The function initUART() contains all configuration information. When a user types into their terminal that is connected to the PIC32, the UART interrupts, checks flags for RX interrupts and echoes the character if it is a data character (and letter, punctuation), deletes if it detects a backspace, or kicks off a transmission if it sees an enter. Hitting the enter key sets the flag doTriggerSend, which tells the function triggerSend to begin packing the UART input into a buffer ready for transmission. This condition is checked constantly in a while(1) at the end of main() and not in the ISR.

The method triggerSend is called when there is a string ready to be sent. triggerSend must be implented atomically or run the risk of parsing a message incompletely. The method also does not preempt if the state is not in WAIT to let any higher priority processing finish. Before processing the user's input word, the system is set to state PTT_WAIT_HIGH, which sets the PTT on the Baofeng low, indicating the start of a transmission. Since each message starts with a sync word, the method first appends the sync word on the front of the array to be transmitted, called tx_buffer. Once the sync word has been appended, the method begins packing the tx_buffer with each bit of each character byte. This continues until the end of the message that the user typed, at which point triggerSend appends two additional sync words to indicate the end of a transmission to the decoder. The doTriggerSend flag is reset in main after the completion of triggerSend

Once the tx_buffer has been packed, the system must wait for the Baofeng to end the PTT transient. There is a spike in voltage when PTT is triggered, which would interfere with any transmission. In the Timer 3 ISR, a counter tracks this on every interrupt and when the wait is over, indicates that it is time to send the clock synchronization pulse. The clock synchronization allows for remote clock recovery and will be discussed in detail further below. Once the clock pulse state is entered, the system modulates 40 symbols worth of a clock frequency (equal to the data rate) waveform. Tones are sent out by stepping phase accumulators through the sin lookup table and performing a first order approximation to better represent the outgoing waveform, as implemented by sin_interp(). After the clock has been sent, we must enter another waiting period in which the channel is allowed to relax to prepare for the data. All waiting periods output the value 2048 to the DAC which is the mid point of its output range.

Once it is time to send the data out, the ISR steps through the tx_buffer, modulating each bit with MSKmod(). MSKmod takes the current bit from the tx_buffer, sets the frequency of the waveform, and if it is a different frequency than the last bit, it stitches the phase together according to the MSK algorithm (phase changes are either pi or 2*pi). This transition is tracked by the value of nrz_bit and nrz_bit_prev. To maintain continuous phase when the modulated bit, phase_clip maintains continuity by performing the modulo operation on the current index of the phase to keep it within 0 and 1024. The output of MSKmod is stored in the variable value, where it is then sent out via SPI to the DAC. This waveform is then transmitted out through the Baofeng. Once the tx_buffer is exhausted, PTT is lowered and the state is set to WAIT.

To recieve data, a state machine in MSKdecode was implemented to detect the presence of the clock pulse, which was analyzed via in phase and quadrature counters, and the angle of the clock pulse to the NCO clock pulse corrected the phase error to ensure the demodulator was decoding the proper symbol instead of half of one and half of another. A ring buffer was implemented (decode_ring), which scanned 8 bit sequences for the sync word to begin the reception of data. Once this buffer saw a sync word as detected by checkDecodeRingEquality, MSKdecode was called to begin receiving data by measuring the amount of power in each sample compared the the NCOs for either a 1, zero, or clock. Once the defined number of samples in a symbol was reached, the in-phase and quadrature components of each symbol type are compiled and the one with the most power was declared to be the bit received. The selected bit is then added to a buffer, recvd_buffer, which collects symbols into bytes until 2 sync words are detected that then end the transmission. This buffer is then sent over UART to the received terminal where it is displayed. The done() method is invoked, which sends the data and resets all phase counters and indexes used so that the next reception can be accurate.

Results    top

When testing the radio setup, we encountered reasonable success transmitting and receiving both from PIC32 to PIC32 and from MATLAB to PIC32. The latter was important as before our demo, a still unsolved problem destroyed one of our boards, but we were still able to prove functionality using one transciever board.

On average, transmissions were decoded correctly about 50-60% of the time if short in length. The input tested was 'ECE 4760'. Longer inputs fared poorly, being decoded only 10-20% of the time. These inputs were anywhere from 35-150 characters long, or 280-1,200 bits. This can almost certainly be attributed to clock synchronization, which plagued our simulations since the beginning of the project. Below is the final block diagram of and demodulator.

Figure 9 - Block Diagram Implemented

To verify and debug UART operation, we captured a waveform on the oscilloscope of an input character and its echo, shown below.

Figure 10 - UART Input and Echo

The reason why we implemented the PTT_WAIT states is shown below as the impulse response of the triggering of the radio.

Figure 11 - Push to Talk Impulse that is Allowed to Settle

Shown below is an example of a transmission with a missed sync word at the beginning, likely caused by a bad clock synchronization. Compare this to the successful transmission in figure 13

Figure 12 - A Failed Transmission

Figure 13 - A Failed Transmission

Conclusions    top

After finishing our implementation, we were amazed at how many different error modes were possible for a simple modem like ours. While we achieved success without error correcting codes or coding algorithms, a lot of work was put in to simply transmit at a low data rate over short distances. If we were to redo this project, thinking about coding our data with parity or other error detection would be critical. In addition, exploring other radios that did not have such a narrow channel would be worth looking into since the Baofeng was intended for voice, not digital data transmission.

Our choice of MSK was the right one as we made the best use of our limited channel bandwidth and succeed in a fairly clean implementation.  The hardware used in this project could have been slightly better researched as our audio cable incompatability made our project slightly bulkier than we would have liked.  We would have also tried to get software written to have the PIC be programmable, serial interfaceable, and powered by the USB cable soldered onto the board.  However, this was exceedingly complex and not feasible in the time given.  On the software side, we did our best to compartmentalize operation well so that the code was fairly modular.  We would have liked to fiddle with the parameters we left openly defined and accesible, but in order to maintain the best performance at the settings we had, we concentrated on debugging the final configuration as listed in Appendix B.

The design however adhered to standards set by the FCC as we did not modify the radios in any way other than setting them to lower power mode. With respect to the IEEE code of ethics, we adhered to the code of ethics as shown below in our references section. Resources found on the internet were invaluable to our success as MSK is an old technology. In particular, Dr. Aoife's lecture was helpful on non-coherent demodulation of MSK. To ensure we were operating within the law, Noah upgraded his HAM radio liscence and Geoffrey read FCC regulations and took practice tests on relevant VHF regulation. As the code mandates, all claims in this report are accurate to the extent of our collected data, especially our received data success rate.

Appendix A    top

Appendix B    top

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
 * File:   config.h
 * Author: Noah Levy and Geoffrey Keating
 */

#ifndef CONFIG_H
#define	CONFIG_H

// serial stuff
#include <stdio.h>

//Get clock from Peripheral, HS external clock
#pragma config FNOSC = PRIPLL, POSCMOD = HS
//Divide and multiply to get 32 MHz
#pragma config FPLLIDIV = DIV_2, FPLLMUL = MUL_16
#pragma config FPBDIV = DIV_2, FPLLODIV = DIV_2
// Turn JTAG off
#pragma config FWDTEN = OFF, JTAGEN = OFF, FSOSCEN = OFF
#pragma config DEBUG = OFF

//System clock frequency
#define	SYS_FREQ 64000000L
//Peripheral Frequency
#define	PBCLK 32000000L

#endif	/* CONFIG_H */

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* 
 * File:   fixed.h *
 */

#ifndef FIXED_H
#define	FIXED_H

#ifdef	__cplusplus
extern "C" {
#endif
#include <stdint.h>
typedef signed int fix16 ;


// MACROS FOR FIX16 CONVERSION
#define multfix16(a,b) ((fix16)(((( int64_t)(a))*(( int64_t)(b)))>>16)) // ex: multiply two fixed 16:16
#define float2fix16(a) ((fix16)((a)*65536.0))
#define fix2float16(a) ((float)(a)/65536.0)
#define fix2int16(a)    ((int32_t)((a)>>16))
#define int2fix16(a)    ((fix16)((a)<<16))
#define divfix16(a,b) ((fix16)((((int64_t)(a)<<16)/(b)))) 
#define sqrtfix16(a) (float2fix16(sqrt(fix2float16(a)))) 
#define absfix16(a) abs(a)


#ifdef	__cplusplus
}
#endif

#endif	/* FIXED_H */

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#ifndef _MAIN_H    /* Guard against multiple inclusion */
#define _MAIN_H
	#include "fixed.h"
    #include <math.h>

#ifdef __cplusplus
extern "C" {
#endif

#define INPUT_VEC_SIZE 245037

//Reference to the sin function table, 1024 steps
extern fix16 sin_lookup[1024];
//input vector
extern uint16_t input_vec[INPUT_VEC_SIZE];

//Get rid of warnings in MPLab
#define _SUPPRESS_PLIB_WARNING
//Sync word length for beginning of transmission
#define LEN_SYNC 8
//Bit stuffing parameter
#define CONSECUTIVE_ONES_MAX 5
#define BUFFER_LENGTH 2560 //MUST be more than 6/5 of FRAME_PAYLOAD_MAXIMUM_BITLENGTH
//Define one frame worth of bits
#define FRAME_PAYLOAD_MAXIMUM_BITLENGTH 1920
#define FRAME_PAYLOAD_MAXIMUM_BYTELENGTH (FRAME_PAYLOAD_MAXIMUM_BITLENGTH >> 3)
//Amplitude of outgoing sine_wave
#define AMPLITUDE                  int2fix16(1)
//The sampling frequency of the DAC
#define _int_SAMPLING_FREQUENCY    44100
#define _fix16_SAMPLING_FREQUENCY  int2fix16(_int_SAMPLING_FREQUENCY)
//The data, or symbol rate identical in this 2 frequency MSK case
#define _fix16_SYMBOL_RATE         int2fix16(441) 
//Data period
#define _fix16_MODULATION_T        divfix16(int2fix16(1),_fix16_SYMBOL_RATE)
//Samples in one symbol
#define _int_SAMPLES_PER_SYMBOL         fix2int16(divfix16((unsigned)_fix16_SAMPLING_FREQUENCY,_fix16_SYMBOL_RATE))
//The center frequency around which MSK pivots
#define _int_CENTER_FREQ           1600
#define _fix16_CENTER_FREQ         int2fix16(_int_CENTER_FREQ)
#define BITS_PER_BYTE              8
//The clock sync pulse length
#define _int_CLOCK_TONE_SYMBOL_LENGTH   40
#define _int_CLOCK_TONE_SAMPLE_LENGTH   (_int_CLOCK_TONE_SYMBOL_LENGTH*_int_SAMPLES_PER_SYMBOL)
//Silence period to allow channel to relax after clock pulse
#define _int_CLOCK_GUARD_SAMPLE_LENGTH (_int_SAMPLING_FREQUENCY/5) 
#define SIN_LENGTH                 1024
//2^12
#define _int_DAC_RANGE                  4096
//Half voltage output of DAC
#define _int_DAC_MIDRAIL                (4096 >> 1)
#define _fix16_DAC_MIDRAIL         fix2float16(.5f)

//MSK FREQUENCIES
#define _fix16_FREQ_0              (_fix16_CENTER_FREQ - (_fix16_SYMBOL_RATE >> 2)) 
#define _fix16_FREQ_1              (_fix16_CENTER_FREQ + (_fix16_SYMBOL_RATE >> 2))
    
//The steps to count by in each frequency for the sin_table    
#define _fix16_PHASE_STEP_CLOCK    ( ( (((uint64_t)(SIN_LENGTH<<16)*(uint64_t)_fix16_SYMBOL_RATE)) /((uint64_t)_int_SAMPLING_FREQUENCY) ) >> 16) //TODO: Round correctly
#define _fix16_PHASE_STEP_0        ( ( (((uint64_t)(SIN_LENGTH<<16)*(uint64_t)_fix16_FREQ_0)) /((uint64_t)_int_SAMPLING_FREQUENCY) ) >> 16)
#define _fix16_PHASE_STEP_1        ( ( (((uint64_t)(SIN_LENGTH<<16)*(uint64_t)_fix16_FREQ_1)) /((uint64_t)_int_SAMPLING_FREQUENCY) ) >> 16)

//Power threshold for reception 
#define _fix16_RECVD_POWER_THRESHOLD int2fix16(3)
#define _int_BITS_CLOCKLIKE_DETECT 20

//Phase adjust for synchronization
#define _float_CLOCK_CONVERSION_FACTOR  (-1.0f*((float)_int_SAMPLES_PER_SYMBOL/(2.0f*M_PI)))

//UART SPECIAL CHARS
#define DEL_CHAR 127
#define ESC      27
//DAC Parameters
#define DAC_config_chan_A 0b0011000000000000
#define DAC_FREQUENCY 44100
#define SS      LATAbits.LATA4
#define dirSS   TRISAbits.TRISA4

//Push to talk output pin
#define PTT LATBbits.LATB7
//Wait for transient from radio's PTT to relax
#define _int_PTT_WAIT_LENGTH _int_SAMPLING_FREQUENCY/5


    typedef enum
    transceiver_state{WAIT,TRANSMITTING,PTT_WAIT_UP,PTT_WAIT_DOWN,CLOCK_TONE_OUT,RECV_START_PAYLOAD,RECV_PAYLOAD_PROC}tvr_state;
#ifdef __cplusplus
}
#endif

#endif 

/* *****************************************************************************
 End of File
 */

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
/* Author: Noah Levy (nml45) & Geoffrey Keating (gbk42)
    ECE 4760 Final Project Fall 2015

*/

//Includes for IO, PLIB, config, main
#include <stdlib.h>
#include "config.h"
#include "main.h"
#include <plib.h>
#include <xc.h>

// Include fixed point math macros
#include "fixed.h"

//UART Defines (from pt_cornell1.2 by Bruce Land)
#define PB_DIVISOR (1 << OSCCONbits.PBDIV) // read the peripheral bus divider, FPBDIV
#define clrscr() putsUART2( "\x1b[2J")
#define home()   putsUART2( "\x1b[H")
#define pcr()    putcUART2( '\r')
#define crlf     putchar(0x0a); putchar(0x0d);
#define backspace 0x7f // make sure your backspace matches this!
#define BAUDRATE 9600
//Number of characters to send
int num_send_chars = 0;


//Buffer Parameters
#define BUFFER_LENGTH 256 //Unit is bytes
unsigned int uart_byte_idx = 0;
unsigned char uart_buffer[BUFFER_LENGTH];   //the buffer that holds input from UART
unsigned char tx_buffer[BUFFER_LENGTH];     //outgoing data buffer
unsigned char recvd_buffer[BUFFER_LENGTH];  //incoming data buffer


//Sync word for encoding
char SYNC[8] = {0, 1, 1, 1, 1, 1, 1, 0};


//function prototypes
fix16 MSKmod();
fix16 sin_interp();
fix16 phase_clip(fix16);
uint16_t scale_fix16_for_dac(fix16 val);
void resetPhaseAccumulators();
void MSKdecode(fix16 sample);
void done();
char checkDecodeRingEquality();


// The current bit being modulated
volatile int bit_idx = 0;
// The current index within the bit for sampling reasons
volatile int sample_idx = 0;

volatile fix16 phase_k = 0;     //phase corrector
volatile char m_bit;            //the current bit to modulate
volatile char nrz_bit;         
volatile char phase_idx = 0;    //
volatile char nrz_bit_prev;
volatile fix16 phase_acc_0;     //phase counter for zeros
volatile fix16 phase_acc_1;     //phase counter for ones
volatile fix16 phase_acc_clock; //phase counter for the clock
volatile int ptt_counter;       //counter for PTT wait period
volatile int clock_sample_idx;  //
volatile int demod_count;       //how many samples have been recieved
volatile char consec_ones;      //bit stuffing tracker
volatile int recvd_symbols;     
volatile int recvd_bit_idx;     //index of the current recieved bit
volatile int bits_clocklike;    //number of bits for clock symbol threshold
volatile int tx_bit_len;        //length of transmission outgoing

//POWER ESTIMATORS FOR SYMBOLS/CLOCK for decision making
volatile fix16 r0_Isum, r0_Qsum, r1_Isum, r1_Qsum, rclock_Isum, rclock_Qsum;
//initialize the state the modem is in, wait is default
volatile tvr_state state = WAIT;
//ring buffer for decoding
volatile char decode_ring[LEN_SYNC];
//head of ring buffer
volatile char decode_ring_head; 
//indicate when to begin a transmission
volatile char doTriggerSend;

//This function prepares the data in a buffer for a transmission out to the UV-3R
void triggerSend(char * buf, int len)
{
    //Busy Wait for Sending to be done
    while (state != WAIT)
    {
        ;//DO NOTHING!
    }
    INTDisableInterrupts(); //Begin Critical Section
    //reset wait counter
    ptt_counter = 0;
    consec_ones = 0;
    //change state, throw PTT high
    state = PTT_WAIT_UP;
    PORTSetBits(IOPORT_B,BIT_7);

    int d = 0;
    //reset bit counters for transmission
    int tx_bit_idx = 0;
    char consec_ones = 0;
    tx_bit_len = 0;
    //send out the sync pulse first
    while(tx_bit_idx < LEN_SYNC)
    {
        char bytewise_bit_idx = (tx_bit_idx % BITS_PER_BYTE);
        tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] &= ~(1<< bytewise_bit_idx); //unconditionally reset target bit
        if(SYNC[tx_bit_idx]) //If that BIT turns out to be a 1.
        {
        tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] |=  (1<< bytewise_bit_idx); //If sync bit is one set target bit
        }
        tx_bit_idx++;
    }
    //the buffer bit index that traverses the length of the transmission
    int buf_bit_idx = 0;
    while(buf_bit_idx < len*BITS_PER_BYTE)
    {

        char bytewise_bit_idx = (tx_bit_idx % BITS_PER_BYTE);
        //get the current bit
        char buf_bit = (buf[(buf_bit_idx/BITS_PER_BYTE)] >> (buf_bit_idx % BITS_PER_BYTE)) & 0x01;
            //set tx_buffer to that bit
            tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] &= ~(1<<bytewise_bit_idx);
            //if its a 1, increment bit stuffer
            if(buf_bit)
            {
                tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] |= (1<<bytewise_bit_idx);
                consec_ones++;
            }
            else    //otherwise reset bit stuffer
            {
                consec_ones = 0;
            }
            tx_bit_idx++;
            buf_bit_idx++;
            //when we hit 5 consecutive ones, stuff a bit, but dont increment buf_bit_idx
            if (consec_ones == 5)
            {
                consec_ones = 0;
                bytewise_bit_idx = (tx_bit_idx % BITS_PER_BYTE); 
                tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] &= ~(1<<bytewise_bit_idx);
                tx_bit_idx++;
            }
    }
    //end the transmission with 2 sync words
    int tx_bit_idx_start = tx_bit_idx;  //where we ended the data
    int sync_idx = 0;//sync word length counter
    while(tx_bit_idx < (tx_bit_idx_start + LEN_SYNC*2))
    {
        char bytewise_bit_idx = (tx_bit_idx % BITS_PER_BYTE);
        tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] &= ~(1<< bytewise_bit_idx); //unconditionally reset target bit
        if(SYNC[sync_idx % LEN_SYNC]) //If that BIT turns out to be a 1.
        {
        tx_buffer[(tx_bit_idx/BITS_PER_BYTE)] |=  (1<< bytewise_bit_idx); //If sync bit is one set target bit
        }
        tx_bit_idx++;
        sync_idx++;
    }
    tx_bit_len = tx_bit_idx;
    sample_idx = 0;
    nrz_bit_prev = -1;
    bit_idx = 0;
    //Reset all the parameters so as not to offset other functionality
    resetPhaseAccumulators();
    INTEnableInterrupts(); //End critical Section
}


//Reset all the phase counters that step the different frequencies, called
//at the beginning of every transmission
void resetPhaseAccumulators()
{

     phase_k = 0; //Start as pi/2 (begin as cos)
     phase_idx = 0;
     phase_acc_0 = (int2fix16(SIN_LENGTH) >> 2);
     phase_acc_1 = (int2fix16(SIN_LENGTH) >> 2);
     phase_acc_clock = (int2fix16(SIN_LENGTH) >> 2);
}

//Reset the Power sums for estimating symbols
void resetSums()
{
     r0_Isum = 0;   //0 bit in phase sum
     r0_Qsum = 0;   //1 bit quadrature sum
     r1_Isum = 0;   //0 bit in phase sum
     r1_Qsum = 0;   //1 bit quadrature sum
     rclock_Isum=0; //c bit in phase sum
     rclock_Qsum=0; //clock bit quadrature sum
}

//This ISR controlls the sampling time of data  that is modulated and demodulated
void __ISR(_TIMER_3_VECTOR, ipl2) Timer3Handler(void)   {
    
    //RESET INTERRUPT AND TIMER
    mT3ClearIntFlag();
    fix16 value = 0;
    
    //Capture incoming data constantly
    AcquireADC10();
    fix16 sample = ReadADC10(0);

    //if the modem is waiting or starting a reception, or processing, hold DAC at 2048
    if (state == WAIT || state == RECV_START_PAYLOAD || state == RECV_PAYLOAD_PROC)
    {
       MSKdecode(sample);
       value = _fix16_DAC_MIDRAIL; 
    }
    //Otherwise if the PTT is high, wait until the transient from the radio relaxes
    else if ( state == PTT_WAIT_UP)
    {
        if(ptt_counter++  == _int_PTT_WAIT_LENGTH)
        {
            //if relaxation complete, then send clock tone
            state = CLOCK_TONE_OUT;
            //reset to begin TX
            resetPhaseAccumulators();
            //start at cosine
            phase_acc_clock = (int2fix16(SIN_LENGTH) >> 2);
            //zero clock samples made
            clock_sample_idx=0;
        }
        //hold at 2048
        value = _fix16_DAC_MIDRAIL; 

    }
    
    //Same as PTT_WAIT_UP except we are waiting after a transmission
    else if (state == PTT_WAIT_DOWN)
    {
        if(ptt_counter++  == _int_PTT_WAIT_LENGTH)
            state = WAIT;
            resetPhaseAccumulators();
            resetSums();
        value = _fix16_DAC_MIDRAIL;
    }
    //If sending clock tone out, stepping the phase accumulator accordingly
    else if (state == CLOCK_TONE_OUT)
    {
        //step forward
        phase_acc_clock += _fix16_PHASE_STEP_0;
        //check for overflow on sine table
        phase_acc_clock = phase_acc_clock >= int2fix16(SIN_LENGTH) ? phase_acc_clock - int2fix16(SIN_LENGTH) : phase_acc_clock;
        //increment sample counter
        clock_sample_idx++;
        //if not done yet...
        if(clock_sample_idx < _int_CLOCK_TONE_SAMPLE_LENGTH)
        {
            //Continue normally, interpolate from 1st order approx
            value = sin_interp(phase_acc_clock);
        }
        //implement waiting period between clock pulse and data, hold at 2048
        else if( clock_sample_idx < (_int_CLOCK_TONE_SAMPLE_LENGTH + _int_CLOCK_GUARD_SAMPLE_LENGTH) )
        {
            value = _fix16_DAC_MIDRAIL; 
        }
        //If we are done waiting after the clock, lets transmit!
        else if( clock_sample_idx == (_int_CLOCK_TONE_SAMPLE_LENGTH + _int_CLOCK_GUARD_SAMPLE_LENGTH) )
        {
            state = TRANSMITTING;
            value = _fix16_DAC_MIDRAIL; 
        }
    }
    //THIS IS THE TRANSMITTING STATE, WE ARE NOW MODULATING DATA OUT TO THE DAC
    else if (state == TRANSMITTING)
    {
        //get the modulated value of the current sample from MSKmod
        value = MSKmod();
        //if at the end of transmission, take off PTT
        if(bit_idx == tx_bit_len)
        {
            INTDisableInterrupts(); //Begin Critical Section
            state = PTT_WAIT_DOWN;
            ptt_counter = 0;
            PORTClearBits(IOPORT_B,BIT_7);
            INTEnableInterrupts(); //End critical Section
        }
   }
    
    //INCREMENT PHASE accumulators
    phase_acc_0 += _fix16_PHASE_STEP_0;

    phase_acc_1 += _fix16_PHASE_STEP_1;
    
    phase_acc_clock += _fix16_PHASE_STEP_CLOCK;

    //check for phase accumulator overflows
    phase_acc_0 = phase_acc_0 >= int2fix16(SIN_LENGTH) ? phase_acc_0 - int2fix16(SIN_LENGTH) : phase_acc_0;
    phase_acc_1 = phase_acc_1 >= int2fix16(SIN_LENGTH) ? phase_acc_1 - int2fix16(SIN_LENGTH) : phase_acc_1;
    phase_acc_clock = phase_acc_clock >= int2fix16(SIN_LENGTH) ? phase_acc_clock - int2fix16(SIN_LENGTH) : phase_acc_clock; 
    
    //Scale the output so the DAC does not clip
      uint16_t out = scale_fix16_for_dac(value);

    //Set CS (Port A, Bit 4, pin 12 on PDIP) high to begin the SPI transmission
    SS = 0;
    while (TxBufFullSPI1()); //Busy waits in ISR is bad. Let's fix this
    WriteSPI1(DAC_config_chan_A | out );

    // wait for end of transaction
    while (SPI1STATbits.SPIBUSY); 

    //Set CS (Port A, Bit 4, pin 12 on PDIP) low to end transaction
    SS=1;
}

//Return 1 if the current 8 bits  in the ring buffer are a sync word
char checkDecodeRingEquality()
{
    char equals = 1;
    int j;
    for(j = 0; j < LEN_SYNC; j++)
    {
        equals &= (SYNC[j] == decode_ring[(j+decode_ring_head) % LEN_SYNC]);
    }
    return equals;
}


//Eestimate the power of the current sample
void MSKdecode(fix16 sample)
{
    //These are numerically controlled oscillators, we must step them and interpolate to get
    //the next value for demodulation
    
    //For 0
fix16 phi_d0_inphase = sin_interp(phase_acc_0 + int2fix16(SIN_LENGTH/4));
fix16 phi_d0_quad = sin_interp(phase_acc_0);
    //For 1
fix16 phi_d1_inphase = sin_interp(phase_acc_1 + int2fix16(SIN_LENGTH/4));
fix16 phi_d1_quad = sin_interp(phase_acc_1);
    //For clock
fix16 phi_clock_inphase = sin_interp(phase_acc_clock + int2fix16(SIN_LENGTH/4));
fix16 phi_clock_quad = sin_interp(phase_acc_clock);

    //POWER ESTIMATORS, take the power of the current sample with respect to oscillators
r0_Isum = r0_Isum + multfix16(phi_d0_inphase,sample);
r0_Qsum = r0_Qsum + multfix16(phi_d0_quad,sample);

r1_Isum = r1_Isum + multfix16(phi_d1_inphase,sample);
r1_Qsum = r1_Qsum + multfix16(phi_d1_quad,sample);

rclock_Isum = rclock_Isum + multfix16(phi_clock_inphase,sample);
rclock_Qsum = rclock_Qsum + multfix16(phi_clock_quad,sample);

//If we have recieved a full byte, decide what symbol it is
if (demod_count == _int_SAMPLES_PER_SYMBOL)
{
    //Total power in each bit bin (incl. clock)
    fix16 r0 = multfix16(r0_Isum,r0_Isum) + multfix16(r0_Qsum,r0_Qsum);
    fix16 r1 = multfix16(r1_Isum,r1_Isum) + multfix16(r1_Qsum,r1_Qsum);
    fix16 rclock = multfix16(rclock_Isum,rclock_Isum) + multfix16(rclock_Qsum,rclock_Qsum);
    //decide based on total power
    char in_bit = r1 > r0;
    
    //commit the newly decoded bit to the ring buffer
    char commit_bit = decode_ring[decode_ring_head];
    decode_ring[decode_ring_head] = in_bit;
    decode_ring_head = (decode_ring_head + 1) % LEN_SYNC;

    //If we are waiting, zero out counters for reception
    if (state == WAIT)
    {
        consec_ones = 0;

        recvd_symbols = 0;
        recvd_bit_idx = 0;
        
        //check for a sync word
        if(checkDecodeRingEquality())
        {
            //This means we start a payload
            state = RECV_START_PAYLOAD;
            //We have received a symbol
            recvd_symbols = 1;
        }
        //If we are receiving a clock pulse
        if (rclock >= _fix16_RECVD_POWER_THRESHOLD)
        {   
            //...and we have seen enough to know its a clock
            if(bits_clocklike > _int_BITS_CLOCKLIKE_DETECT)
            {
                //Adjust the phase of the RX oscillators to match the TX
                bits_clocklike = 0;
                float phase_correction = atan2(fix2float16(rclock_Qsum),fix2float16(rclock_Isum));

                int count_adjustment =(int)(phase_correction*_float_CLOCK_CONVERSION_FACTOR);
                //count_adjustment < 0 ? count_adjustment + _int_SAMPLES_PER_SYMBOL : count_adjustment; 
                demod_count += count_adjustment;
            }
            else
            {
                bits_clocklike++;
            }
        }
        else
        {
            bits_clocklike = 0;
        }
    }
    //If we are kicking off a payload
    else if (state == RECV_START_PAYLOAD)
    {
        //If we are done with the transmission b/c a sync pulse
        if(checkDecodeRingEquality())
        {
           
            state = WAIT;
            resetPhaseAccumulators();
            done();
            bits_clocklike=0;
        }
        //Done with the sync, process the payload, reset bit stuff
        if(recvd_symbols == LEN_SYNC)
        {
            
            state = RECV_PAYLOAD_PROC;
            consec_ones = 0;
        }
        recvd_symbols++;
    }
    
    //Unstuff the incoming data, place it in the RX_buffer
    else if (state == RECV_PAYLOAD_PROC)
    {
        if( consec_ones < CONSECUTIVE_ONES_MAX)
        {
            recvd_buffer[recvd_bit_idx/BITS_PER_BYTE] &= ~(1<<(recvd_bit_idx % BITS_PER_BYTE));
            if(commit_bit == 1 || commit_bit == 3)
            {
                recvd_buffer[recvd_bit_idx/BITS_PER_BYTE] |= (1<<(recvd_bit_idx % BITS_PER_BYTE));    
                consec_ones = consec_ones + 1;
            }
            else
            {
                consec_ones = 0;
            }
            recvd_bit_idx++;
        }
        else
        {
            consec_ones = 0;
        }
        if(checkDecodeRingEquality() || recvd_symbols >=(BUFFER_LENGTH*8))
        {
            //state = RECV_START_PAYLOAD;
            //recvd_symbols = 0;
            done();
            bits_clocklike=0;
        }
    recvd_symbols++;
    }
    //reset the power sums for each new symbol
    resetSums();
    demod_count = demod_count - _int_SAMPLES_PER_SYMBOL;
    }
//increment the demodulator counter
demod_count = demod_count+1;
}


// Implementation of Minimum Shift Keying to send out data. Key parameters are listed in main.h under 'Mod parameters'
fix16 MSKmod()
{
    //The bit to be modulated, take from TX_BUFFER
    m_bit = (tx_buffer[bit_idx/BITS_PER_BYTE] >> (bit_idx % BITS_PER_BYTE)) & 0x01;

    nrz_bit = -1 + 2*(m_bit);

    fix16 value;

    //Choose the correct frequency for the outgoing bit
    if(m_bit)
    {
    value = sin_interp(phase_acc_1+phase_k);
    }
    else
    {
    value = sin_interp(phase_acc_0+phase_k);
    }
    sample_idx++;
    //if we have sent out a whole bit, then reset for next symbol
    if(sample_idx == _int_SAMPLES_PER_SYMBOL)
    {
        sample_idx = 0;
        bit_idx = bit_idx + 1;
        nrz_bit_prev = nrz_bit;


        m_bit = (tx_buffer[bit_idx/BITS_PER_BYTE] >> (bit_idx % BITS_PER_BYTE)) & 0x01;
        nrz_bit = -1 + 2*(m_bit);
        phase_idx++;
        //stitch the phases together according to MSK algorithm , only correct by pi or 2*pi
        if((nrz_bit_prev != nrz_bit) && (phase_idx & 0x01 == 1))
        {
            
            phase_k = phase_k + int2fix16(SIN_LENGTH/2);
        }
        phase_k = phase_clip(phase_k);
    
    }
    return value;
}

//This function prevents clipping on the DAC
uint16_t scale_fix16_for_dac(fix16 val)
{
    //set the output reference above or below the midrail (add 2^16 +1 divide by 32)
    int32_t out = ((val + (uint32_t)0x10001) >> 5); 
    //If higher than DAC allows, ensure 4095
    out >= 4096 ? 4095 : out;
    //If negative, flooor at zero
    out < 0 ? 0 : out;
    //scale down (this was tuned)
    out = (( out*2)/3);
    return (uint16_t)out;
}

//Add a string terminator to the decoded buffer, send to terminal of receiver user
void done()
{
    
    INTDisableInterrupts(); //Begin Critical Section
    //string term.
    recvd_buffer[(recvd_bit_idx/8)] = '\0';
    //UART macros
    clrscr();
    home();
    //send each bit over UART
    int j;
    for(j = 0; j < (recvd_bit_idx/8)+1;j++)
    {
        char out = recvd_buffer[j] < 32 ? recvd_buffer[j] = ' ' : recvd_buffer[j];
            putcUART2(out);
    }
    
    //reset all demodulation tools
    state = WAIT;
    recvd_bit_idx = 0;
    recvd_symbols = 0;
    bits_clocklike=0;
    resetPhaseAccumulators();
    resetSums();
    INTEnableInterrupts(); //End Critical Section
}

//Ensure that phase does not run outside of the sin lookup table
fix16 phase_clip(fix16 phase)
{
    while(phase >= int2fix16(SIN_LENGTH))
    {
        phase -= int2fix16(SIN_LENGTH);
    }
    while(phase < 0)
    {
        phase += int2fix16(SIN_LENGTH);
    }
    return phase;
}

//Implement the first order hold for increased NCO accuracy
fix16 inline sin_interp(fix16 index)
{
    index = phase_clip(index);
    uint16_t low_idx = fix2int16(index);
    uint16_t high_idx = low_idx == (SIN_LENGTH-1) ? 0 : low_idx+1;
    fix16 y1 = sin_lookup[low_idx];
    fix16 y2 = sin_lookup[high_idx];
    fix16 mu = y1-y2;
    return multfix16(y1,(int2fix16(1)-mu))+multfix16(y2,mu);
}


//Set up timer 3 to run at 44.1KHz
void Timer3Setup()  {
    OpenTimer3(T3_ON | T3_PS_1_1 | T3_SOURCE_INT, 725); //internal, 32Mhz, count to 725
    ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_2); //high priority
    mT3ClearIntFlag();
}

//================================================================
// SS = PORTA4


//WRITTEN BY TAHMID, FROM HIS BLOG
void initDAC(void){
/* Steps:
 *    1. Setup SS as digital output.
 *    2. Map SDO to physical pin.
 *    3. Configure SPI control and clock with either of a or b:
 *        a. OpenSPIx(config1, config2) and SpiChnSetBrg(spi_channel, spi_brg)
 *        b. SpiChnOpen(spi_channel, config, spi_divider)
 */

    dirSS = 0;                    // make SS an output
    SS = 1;                        // set SS = 1 to deselect slave
    PPSOutput(2, RPB5, SDO1);    // map SDO1 to RB5

#define spi_channel    1
    // Use channel 2 since channel 1 is used by TFT display

#define spi_brg    0
    // Divider = 2 * (spi_brg + 1)
    // Divide by 2 to get SPI clock of FPBDIV/2 -> max SPI clock

//    SpiChnSetBrg(spi_channel, spi_brg);
    // see pg 203 in plib reference

#define spi_divider 2
/* Unlike OpenSPIx(), config for SpiChnOpen describes the non-default
 * settings. eg for OpenSPI2(), use SPI_SMP_OFF (default) to sample
 * at the middle of the data output, use SPI_SMP_ON to sample at end. For
 * SpiChnOpen, using SPICON_SMP as a parameter will use the non-default
 * SPI_SMP_ON setting.
 */
#define config SPI_OPEN_MSTEN | SPI_OPEN_MODE16 | SPI_OPEN_DISSDI | SPI_OPEN_CKE_REV
    /*    SPI_OPEN_MSTEN        -> Master mode enable
     *    SPI_OPEN_MODE16        -> 16-bit SPI mode
     *    SPI_OPEN_DISSDI        -> Disable SDI pin since PIC32 to DAC is a
     *                            master-to-slave    only communication
     *    SPI_OPEN_CKE_REV    -> Output data changes on transition from active
     *                            clock to idle clock state
     */
    SpiChnOpen(spi_channel, config, spi_divider);
}

//Intialize the ADC to sample through pin 24
void initADC(void)
{
    CloseADC10();
    //int16 format, don't automatically sample
    #define PARAM1  ADC_FORMAT_INTG16 | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_OFF
    //Reference voltages, interrupt sampling
    #define PARAM2  ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_OFF | ADC_SAMPLES_PER_INT_1 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF
    //clock divider info
    #define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_5 | ADC_CONV_CLK_Tcy2 
    //Port selection in
    #define PARAM4  ENABLE_AN11_ANA // pin 24
    #define PARAM5  SKIP_SCAN_ALL

    SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF | ADC_CH0_POS_SAMPLEA_AN11 ); // configure to sample AN11 
    OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using the parameters defined above
    EnableADC10(); // Enable the ADC
}

void UART_Setup()   {
    PPSInput (2, U2RX, RPB11); //Assign U2RX to pin RPB11 -- Physical pin 22 on 28 PDIP
    PPSOutput(4, RPB10, U2TX); //Assign U2TX to pin RPB10 -- Physical pin 21 on 28 PDIP
    //FROM PT_CORNELL_1.2
    UARTConfigure(UART2, UART_ENABLE_PINS_TX_RX_ONLY);
    //interrupt when each character recieved
    UARTSetFifoMode(UART2, UART_INTERRUPT_ON_RX_NOT_EMPTY);
    //Configuration of UART protocol
    UARTSetLineControl(UART2, UART_DATA_SIZE_8_BITS | UART_PARITY_NONE | UART_STOP_BITS_1);
    UARTSetDataRate(UART2, PBCLK, BAUDRATE);
    UARTEnable(UART2, UART_ENABLE_FLAGS(UART_PERIPHERAL | UART_RX | UART_TX));

    INTEnable(INT_SOURCE_UART_RX(UART2), INT_ENABLED);
    INTSetVectorPriority(INT_VECTOR_UART(UART2), INT_PRIORITY_LEVEL_4);
}

//ISR for sending and receiving UART data to a terminal
void __ISR(_UART2_VECTOR, ipl4) IntUart2Handler(void)
{
	// Is this an RX interrupt?
	if(INTGetFlag(INT_U2RX))
	{
        PORTToggleBits(IOPORT_B,BIT_15);

		// Clear the RX interrupt Flag
		// Echo what we just received
        unsigned char c = UARTGetDataByte(UART2);
        
        //if enter pressed, begin the TX
        if(c == '\r')
        {
            clrscr();
            home();
            //doUartEcho=1;
            doTriggerSend=1;
        }
        else if(c == DEL_CHAR)
        {
          uart_byte_idx= uart_byte_idx > 0 ? uart_byte_idx - 1: uart_byte_idx;
        }//delete char
       
        else if(c>= ' ' && c <='~')
        {
          putcUART2(c);
          uart_buffer[uart_byte_idx]=c;
          uart_byte_idx++;
        }//delete char
        INTClearFlag(INT_U2RX); //CLR FLAG


	}
	// We don't care about TX interrupt
	if (INTGetFlag(INT_U2TX))
	{
        INTClearFlag(INT_U2TX);
	}
}


// === Main  ======================================================
void main(void) {
 SYSTEMConfigPerformance(SYS_FREQ);
  
  ANSELA = 0; ANSELB = 0; CM1CON = 0; CM2CON = 0;
  
  // === config threads ==========
  // turns OFF UART support and debugger pin
  // === setup system wide interrupts  ========
  INTEnableSystemMultiVectoredInt();
  
  //SET OUTPUTS, see schematic
  mPORTBSetPinsDigitalOut(BIT_15 | BIT_7);  
  mPORTASetPinsDigitalOut(BIT_0 | BIT_4);
  mPORTASetPinsDigitalOut(BIT_4);
  PORTClearBits(IOPORT_A,BIT_4);
  PORTClearBits(IOPORT_B,BIT_7);
  //initialize trigger send to waiting
  doTriggerSend=0;
  //hardware setups
  Timer3Setup();
  initDAC();
  initADC();
  UART_Setup();
  
  PORTClearBits(IOPORT_A,BIT_4);

      long j = 0; 

    while (1){

        if(doTriggerSend)
        {
            
            triggerSend(uart_buffer,uart_byte_idx);
            uart_byte_idx=0;
            doTriggerSend = 0;
        }
        for(;j < 100000L; j++)
        {
            ;
        }
        //toggle LED
          PORTToggleBits(IOPORT_B,BIT_15);

    //triggerSend(msg,252);
    }//while
  } // main

// === end  ======================================================

 

Appendix C   top

Tasks by Lab Partner:

Noah - Simulation code of demodulator, simulated modulator, data visualizations, C implementation of MSK (de)modulator

Geoffrey - Soldering of hardware, Testing of simulations, simulated modulator, website, figure collection, hardware testing, UART serial interface, DAC SPI

Appendix D   top

Bill of Materials:

BOM