//Program to implement John Jenkins I/Q sq wave demod scheme
//see his sync_filter_2c.xlsx document
//Purpose (v3):
// Produce a running estimate of the magnitude of a square-wave modulated IR
// signal in the presence of ambient interferers. The estimate is computed by
// adding the absolute values of the running sums of the I & Q outputs from a
// digital bandpass filter centered at the square-wave frequency (nominally 520Hz).
// The filter is composed of two N-element circular buffers, one for the
// I 'channel' and one for the Q 'channel'. Each element in each circular buffer
// represents one full-cycle sum of the I or Q 'channels' of the sampler. Each
// full-cycle sum is composed of four 1/4 cycle groups of samples (nominally 5
// samples/group), where each group is first summed and then multiplied by the
// appropriate sign to form the I and Q 'channels'. The output is updated once
// per input cycle, i.e. approximately once every 2000 uSec. The size of the
// nominally 64 cycle circular determines the bandwidth of the filter.
//Plan:
// Step1: Collect a 1/4 cycle group of samples and sum them into a single value
// Step2: Assign the appropriate multiplier to form I & Q channel values, and
// add the result to the current cycle sum I & Q variables respectively
// Step3: After 4 such groups have been collected and processed into full-cycle
// I & Q sums, add them to their respective N-cycle running totals
// Step4: Subtract the oldest cycle sums from their respective N-cycle totals, and
// then overwrite these values with the new ones
// Step5: Compute the final demodulate sensor value by summing the absolute values
// of the I & Q running sums.
// Step6: Update buffer indicies so that they point to the new 'oldest value' for
// the next cycle
//Notes:
// step 1 is done each acquisition period
// step 2 is done each SAMPLES_PER_GROUP acquisition periods
// step 3-6 are done each SAMPLES_PER_CYCLE acquisition periods
#include <ADC.h>
ADC *adc = new ADC(); // adc object;
#pragma region ProgConsts
const int OUTPUT_PIN = 32; //lower left pin
const int IRDET1_PIN = A0; //aka pin 14
//const int SQWAVE_FREQ_HZ = 520; //approximate
//const int USEC_PER_SAMPLE = 1E6 / (SQWAVE_FREQ_HZ * SAMPLES_PER_CYCLE);
const int SAMPLES_PER_CYCLE = 20;
const int GROUPS_PER_CYCLE = 4;
const int SAMPLES_PER_GROUP = SAMPLES_PER_CYCLE / GROUPS_PER_CYCLE;
const float USEC_PER_SAMPLE = 95.7; //value that most nearly zeroes beat-note
const int RUNNING_SUM_LENGTH = 64;
const int NUMSENSORS = 4;
const int aSensorPins[NUMSENSORS] = { A0, A1, A2, A3 };
const int aMultVal_I[GROUPS_PER_CYCLE] = { 1, 1, -1, -1 };
const int aMultVal_Q[GROUPS_PER_CYCLE] = { -1, 1, 1, -1 };
#pragma endregion Program Constants
#pragma region ProgVars
int aSampleSum[NUMSENSORS];//sample sum for each channel
int aCycleGroupSum_Q[NUMSENSORS];//cycle sum for each channel, Q component
int aCycleGroupSum_I[NUMSENSORS];//cycle sum for each channel, I component
int aCycleSum_Q[NUMSENSORS][RUNNING_SUM_LENGTH];//running cycle sums for each channel, Q component
int aCycleSum_I[NUMSENSORS][RUNNING_SUM_LENGTH];//running cycle sums for each channel, I component
int RunningSumInsertionIndex = 0;
int aRunningSum_Q[NUMSENSORS];//overall running sum for each channel, Q component
int aRunningSum_I[NUMSENSORS];//overall running sum for each channel, I component
int aFinalVal[NUMSENSORS];//final value = abs(I)+abs(Q) for each channel
#pragma endregion Program Variables
elapsedMicros sinceLastOutput;
int SampleSumCount; //sample sums taken so far. range is 0-4
int CycleGroupSumCount; //cycle group sums taken so far. range is 0-3
void setup()
{
Serial.begin(115200);
pinMode(OUTPUT_PIN, OUTPUT);
pinMode(IRDET1_PIN, INPUT);
digitalWrite(OUTPUT_PIN, LOW);
//decreases conversion time from ~15 to ~5uSec
adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);
adc->setResolution(12);
adc->setAveraging(1);
}
void loop()
{
//this runs every 95.7uSec
if (sinceLastOutput > 95.7)// stopped
{
sinceLastOutput = 0;
//start of timing pulse
digitalWrite(OUTPUT_PIN, HIGH);
// Step1: Collect a 1/4 cycle group of samples for each channel and sum them into a single value
// this section executes each USEC_PER_SAMPLE period
Serial.print("SampleSumCount = "); Serial.println(SampleSumCount);
for (int i = 0; i < NUMSENSORS; i++)
{
aSampleSum[i] += adc->analogRead(aSensorPins[i]);
}
SampleSumCount++; //goes from 0 to SAMPLES_PER_GROUP-1
// Step2: Every 5th acquisition cycle, assign the appropriate multiplier to form I & Q channel values, and
// add the result to the current cycle sum I & Q variables respectively
if(SampleSumCount == SAMPLES_PER_GROUP) //an entire sample group sum is ready for further processing
{
SampleSumCount = 0; //starts a new sample group
Serial.print("CycleGroupSumCount = "); Serial.println(CycleGroupSumCount);
for (int j = 0; j < NUMSENSORS; j++)
{
aCycleGroupSum_I[j] += aSampleSum[j] * aMultVal_I[j]; //add new I comp to cycle sum
aCycleGroupSum_Q[j] += aSampleSum[j] * aMultVal_Q[j]; //add new Q comp to cycle sum
}
CycleGroupSumCount++;
}//if(SampleSumCount == SAMPLES_PER_GROUP)
// Step3: After 4 such groups have been collected and processed into full-cycle
// I & Q sums, add them to their respective N-cycle running totals
if(CycleGroupSumCount == GROUPS_PER_CYCLE) //now have a complete cycle sum for I & Q - load into running sum circ buff
{
CycleGroupSumCount = 0; //start a new cycle group next time
Serial.print("RunningSumInsertionIndex = "); Serial.println(RunningSumInsertionIndex);
for (int k = 0; k < NUMSENSORS; k++)
{
// Step4: Subtract the oldest cycle sums from their respective N-cycle totals, and
// then overwrite these values with the new ones
int oldestvalue_I = aCycleSum_I[k][RunningSumInsertionIndex];
int oldestvalue_Q = aCycleSum_Q[k][RunningSumInsertionIndex];
aRunningSum_I[k] = aRunningSum_I[k] + aCycleGroupSum_I[k] - oldestvalue_I;
aRunningSum_Q[k] = aRunningSum_Q[k] + aCycleGroupSum_Q[k] - oldestvalue_Q;
aCycleSum_I[k][RunningSumInsertionIndex] = aCycleGroupSum_I[k];
aCycleSum_Q[k][RunningSumInsertionIndex] = aCycleGroupSum_Q[k];
// Step5: Compute the final demodulate sensor value by summing the absolute values
// of the I & Q running sums
int RS_I = aRunningSum_I[k]; int RS_Q = aRunningSum_Q[k];
aFinalVal[k] = abs((int)RS_I) + abs((int)RS_Q);
}
// Step6: Update buffer indicies so that they point to the new 'oldest value' for
// the next cycle
RunningSumInsertionIndex++;
if (RunningSumInsertionIndex >= RUNNING_SUM_LENGTH) RunningSumInsertionIndex = 0;
}
//end of timing pulse
digitalWrite(OUTPUT_PIN, LOW);
}//else
}//loop