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.
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.
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).
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
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.
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.
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.
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:
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.
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.
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.
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.
To verify and debug UART operation, we captured a waveform on the oscilloscope of an input character and its echo, shown below.
The reason why we implemented the PTT_WAIT states is shown below as the impulse response of the triggering of the radio.
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
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
References top
This section provides links to external reference documents, code, and websites used throughout the project.
Datasheets
- PIC32 Datasheet
- Peripheral Libraries for Compiler
- MCP4822 Datasheet (DAC)
- MOSFET for PTT (K4017)
- LM234N Datasheet (Op Amp)
- Baofeng UV-3R Manual
- LDO
References
-
Moloney, Aoife. "Non–Coherent Detection." School of Electronics and Communications Dublin Institute of Technology. Apr. 2005. Lecture.