/* ==================================================== ECE320 Final Project: Digital Modem (Receiver Part) by Matthew J Perry, Andi Susanto ---------------------------------------------------- Bit rate : 1378 bps Actual data transfer rate: 43 bytes/s The actual data transfer rate is much slower than bit rate because the transmitter needs to send an 8 bit header and the actual data byte repeated three times (for error detection and recovery purpose) for each byte of data. */ #include "dsp.h" #undef BlockLen #define BlockLen 64 // Block length #define ByteLen 8 // Byte length #define SymLen 32 // Symbol length // Uncomment the following lines to : //#define FAKE_INPUT // Use a predefined input #define TEST_PLL // Display the output of PLL block //#define TEST_DLL // Display the output of DLL block //#define TEST_BITS // Display the received data in binary format #define MOD2(x, y) ((x) & ((y)-1)) // x modulus y, wher y is a power of 2 typedef unsigned short ushort; typedef unsigned long ulong; // ================= // Phase Locked Loop // ================= #define LPF_LEN 10 // The PLL's Low-pass filter coefficients #define FRACBITS 7 // Total bits devoted to the fraction #define PHASE_INDEX(p) (((p) >> FRACBITS) & 255) #define COSINE(p) cosine[((p) >> FRACBITS) & 255] #define SINE(p) sine[((p) >> FRACBITS) & 255] // Increment the phase by frac/128. #define INCR_PHASE(p, frac) p = (p + frac) & ((256 << FRACBITS) - 1) // The carrier frequency is 2pi/5, or 44100/5, or 8820 Hz // In cosine table speak, this is 1/5 of 256, or 51.2, or 51 and 26/128, // or (51 << 7) + 26 = 6554 #define W_CARRIER (51 << FRACBITS) + 26 #define M_1_OVER_2PI 0.15915494309190f #define RADIAN_TO_FRAC(rad) (short)((float)rad * M_1_OVER_2PI) #define WRAP(i) MOD2(i, BlockLen) // Access the cosine lookup table using fake fractions. // Basically, we use a 16 bit phase, with the top 9 bits as the integer // part, the bottom 7 as fractional. // This only works for 256 cosine values! Have to change if we want to // change the # of values. Always need a power of 2. short cosine[256] = { #include "../cosine.int" }; short sine[256] = { #include "../sine.int" }; short lpf_coef[LPF_LEN] = { #include "../lpf.int" }; short *Rcvptr, *Xmitptr; // Apply our Low-pass filter to x. Compute y[i] only. short lowpass(short *x, short i) { long sum = 0; short f; for (f = 0; f < LPF_LEN; f++, i--) { sum = _smac(sum, lpf_coef[f], x[WRAP(i)]); } return _rnd(sum); } // =================================== // Costas Loop PLL (main PLL function) // ----------------------------------- // - used for carier phase recovery // =================================== void demodulate(short *input, short *output) { #define K 4915 // .15 #define alpha 9830 // .35.215189175235227 #define beta 22938 // .7 = 1-alpha // These statics are for the PLL. These must retain their information // between calls to demodulate. static short zi[BlockLen]; static short zq[BlockLen]; static ushort phase = 0; static short pdiff_prev = 0; short yi[BlockLen]; short yq[BlockLen]; short pdiff, amount; short i; for (i = 0; i < BlockLen; i++) { // Demodulation // demodulate by multiplying by sin and cos short phase_idx = PHASE_INDEX(phase); zi[i] = _smpy(input[i], cosine[phase_idx]); zq[i] = _smpy(input[i], sine[phase_idx]); // Low Pass // low pass to chop off high freq part of the demodulation yi[i] = lowpass(zi, i); yq[i] = lowpass(zq, i); // Phase Detector // Find out how wrong our phase is. pdiff = _smpy(yi[i], yq[i]); // Seems we have a pipeline conflict. This caused things to break, // for a long, long time. MAKE SURE we have enough cycles between // the previous smpy and the next. //if (i < 10) { } output[i] = yq[i]; // Loop Filter // How much should we change it? pdiff = _smpy(K, _sadd(_smpy(beta, pdiff), pdiff_prev)); // pdiff = K * (beta*pdiff + alpha*pdiff_prev); pdiff_prev = _smpy(alpha, pdiff); // NCO // Adjust the phase. amount = RADIAN_TO_FRAC(pdiff); INCR_PHASE(phase, W_CARRIER + amount); #ifdef TEST_PLL Xmitptr[6*i] = output[i]; Xmitptr[6*i + 1] = input[i]; #endif } } // ================= // Delay Locked Loop // ================= #define HEADER_NORM 1 #define HEADER_FLIP 2 #define HWRAP(i) ((i) & 7) #define HINCR(i) i = HWRAP(i + 1) // For testing purposes short testData[] = { 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 1,1,0,0,0,0,1,1, 0,0,0,1,1,1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1 }; short sampIdx = 0; short nextWrite = 0; short midAvg = 0; short testIdx = 0; short testLen = sizeof(testData)/sizeof(short); // ================================================== // Averaging Filter (Part of DLL) // ------------------------------ // - compute the average of the previous 32 samples. // - adjust 1/0 threhold value // ================================================== void average(short *input, short *output) { #define NSAMP SymLen static short prevInput[NSAMP]; static short currAvg = 0; static short minAvg = 32767; static short maxAvg = -32767; short i; for (i=0; i < BlockLen; i++) { short tmp = input[i] / NSAMP; // Find running average value currAvg = _sadd(tmp, _ssub(currAvg, prevInput[MOD2(i, NSAMP)])); prevInput[MOD2(i, NSAMP)] = tmp; output[i] = currAvg; // Use a flexible threhold point to minimize // effect of change in background noise if (currAvg > maxAvg) maxAvg = currAvg; if (currAvg < minAvg) minAvg = currAvg; #ifdef TEST_DLL Xmitptr[6*i] = output[i]; Xmitptr[6*i + 1] = 0; #endif } // Adjust 0/1 threshold midAvg = (maxAvg/2 + minAvg/2); } // ======================================================= // Symbol Recognition (Part of DLL) // -------------------------------- // - use treshold value from averaging filter to determine // whether the sampled point is 1 or 0 // ======================================================= void symbolRec(short *input, short *output) { // For Offset Decision testing purpose #ifdef TEST_DLL Xmitptr[6*sampIdx + 1] = 32767; #endif if (input[sampIdx] >= (midAvg)) output[nextWrite] = 1; else output[nextWrite] = 0; HINCR(nextWrite); } // =================================================================== // Offset Decision (Part of DLL) // ----------------------------- // - check for early, late and on-time sample and adjust decision stat // - adjust sample point when decision stat exceeds threshold // =================================================================== void offsetDec(short *input) { #define DECISION_THRESH 5000 static short decisionSum = 0; short curSamp, earlySamp, lateSamp; short tmpIdx = sampIdx; if (tmpIdx <= 0) tmpIdx = 1; else if (tmpIdx >= BlockLen-1) tmpIdx = BlockLen-2; // ontime * (late - early): if pos slope then pos, if neg slope then // neg, if 0 slope then 0. // Adjust these to be centered around 0 first. earlySamp = _ssub(input[tmpIdx-1], midAvg); curSamp = _ssub(input[tmpIdx], midAvg); lateSamp = _ssub(input[tmpIdx+1], midAvg); // Update decision statistic decisionSum = _sadd(decisionSum, _smpy(curSamp, _ssub(lateSamp, earlySamp))); sampIdx = tmpIdx; if (decisionSum > DECISION_THRESH) { // too many pos slopes, sample later sampIdx = MOD2(sampIdx + SymLen + 1, BlockLen); decisionSum = 0; } else if (decisionSum < -DECISION_THRESH) { // too many neg slopes, sample earlier sampIdx = MOD2(sampIdx + SymLen - 1, BlockLen); decisionSum = 0; } else { // still below threhold points, sample normally sampIdx = MOD2(sampIdx + SymLen, BlockLen); } } // ================================================= // Header Recognition // ------------------ // - scan for a header or flipped header bit pattern // - return 1 if we have a headerBit, // 2 if headerFlip, 0 otherwise. // ================================================= short check_header(short *bits) { static short hdrStart = 0; static short headerBits[] = {1,1,0,0,0,0,1,1}; short i, j; // check normal first for (i = 0, j = hdrStart; i < 8; i++, j++) { if (bits[HWRAP(j)] != headerBits[i]) { break; } } // if matched all of headerBits if (i == 8) { hdrStart = 0; return HEADER_NORM; } // now check flipped for (i = 0, j = hdrStart; i < 8; i++, j++) { if (bits[HWRAP(j)] != !headerBits[i]) { break; } } // if matched all of !headerBits if (i == 8) { hdrStart = 0; return HEADER_FLIP; } // no match, invalid header HINCR(hdrStart); return 0; } // ======================================================================= // Part of random number Generator // - Based on MP5 random number function // - For testing purpose, used to create a noise affected signal // - Returns as an integer a random bit, based on the 15 // low-significance bits in iseed (which is modified for the next call). // ======================================================================= short randbit(unsigned short *iseed) { unsigned short newbit; // The accumulated XORs. // XOR bit 15 and bit 14 newbit = (*iseed >> 14) & 1 ^ (*iseed >> 13) & 1; // Leftshift the seed and put the result of the XORs in its bit 1. *iseed=(*iseed << 1) | newbit; return (short)(newbit ? 1 : 0); } // ============================================================= // Part of random number Generator // - Based on MP5 random number function // - For testing purpose, used to create a noise affected signal // ============================================================= short randnum(void) { static unsigned short iseed = 0; short i, num = 0; for (i = 0; i <= 6; i++) num = _sadd(num, randbit(&iseed) << i); if (randbit(&iseed)) num = -num; return num; } // ============= // Main function // ============= main() { used_recv_chans = 1; used_xmit_chans = 2; xmit_append = 0; short i; short hasHeader = 0; short flipBits = 0; short numBits = 0; short gotBits = 0; short ret = 0; short currBit = 0; short currChar; short input[BlockLen]; short demodulated[BlockLen]; short averaged[BlockLen]; short bits[ByteLen]; short tripleBits[ByteLen]; // Start-up message char *greeting = "\n\r" "ECE320 Receiver\n\r" "---------------\n\r"; ushort tphase = 0; for (i = 0; greeting[i]; i++) SerialTX(greeting[i]); while ( 1 ) { // Process one bit at a time to make use of // sliding window scheme in header recongition if (gotBits == 0) { WaitAudio(&Rcvptr, &Xmitptr); for (i=0; i= testLen) testIdx = 0; if (!testData[testIdx]) input[i] = -input[i]; #else // or obtain input normally from DSP input channel 1 input[i] = Rcvptr[4*i]; #endif } // Phase through PLL block demodulate(input, demodulated); // Phase thorough Averaging Filter average(demodulated, averaged); gotBits = BlockLen/SymLen; } gotBits--; if (numBits < 8) numBits++; symbolRec(averaged, bits); offsetDec(averaged); // For testing purpose (display all received data in binary format) #ifdef TEST_BITS SerialTX(bits[MOD2(nextWrite-1, 8)] + '0'); #endif // Header recognition if (numBits == 8 && !hasHeader) { ret = check_header(bits); if (ret == HEADER_NORM) flipBits = 0; else if (ret == HEADER_FLIP) flipBits = 1; else continue; // No match, keep reading more bits // For testing purpose, make output data more readable #ifdef TEST_BITS SerialTX('\n'); SerialTX('\r'); #endif // Obtain a valid header, now read the next byte for actual data hasHeader = 1; nextWrite = 0; numBits = 0; continue; } // Doesn't have header byte, jump back the the beginning of the loop // to read the next byte if (!hasHeader) continue; // Part of error recovery process if (numBits != 3) continue; // OK, we have 3 redundant bits from the transmitter. // Next time, start back at the beginning. numBits = 0; nextWrite = 0; // Part of error recovery process // Assume majority is the correct value if (bits[0] + bits[1] + bits[2] < 2) tripleBits[currBit] = 0; else tripleBits[currBit] = 1; currBit++; // Still need to read more bits to construct a valid char if (currBit != 8) continue; // We have a full byte for (i=0,currChar=0; i < ByteLen; i++) { if (flipBits) // Correct flipped bits currChar += (!tripleBits[i] << (7 - i)); else currChar += (tripleBits[i] << (7 - i)); // Clear out the old bits array so we don't accidentally // recognize a header. bits[i] = 0; } // Interpret special char if (currChar == '\n' || currChar == '\r') { // Enter SerialTX('\n'); SerialTX('\r'); } else if (currChar == 0x08) { // BackSpace SerialTX(0x08); SerialTX(0x20); SerialTX(0x08); } else SerialTX(currChar); // Reinitialize hasHeader = 0; nextWrite = 0; numBits = 0; currBit = 0; } }