diff --git a/Modem/hardware.c b/Modem/hardware.c index 1f3d3d5..e64b17f 100644 --- a/Modem/hardware.c +++ b/Modem/hardware.c @@ -15,40 +15,115 @@ void hw_afsk_adcInit(int ch, Afsk *_context) context = _context; ASSERT(ch <= 5); - // Timer/Counter Control Register 1 (Timer1 settings, for short) - // Set prescaler to clk/8 (2 MHz), CTC, top = ICR1 - TCCR1A = 0; + // We need to do some configuration on the Timer/Counter Control + // Register 1, aka Timer1 + // The following bits are set: + // CS11: ClockSource 11, sets the prescaler to 8, ie 2MHz + // WGM13 and WGM12 together enables Timer Mode 12, which + // is Clear Timer on Compare, compare set to TOP, and the + // source for the TOP value is ICR1 (Input Capture Register1). + TCCR1A = 0; TCCR1B = BV(CS11) | BV(WGM13) | BV(WGM12); - // Configure ICR1 to get 9.6KHz sampling rate - ICR1 = ((CPU_FREQ / 8) / 9600) - 1; // Input capture register + + // Then we set the ICR1 register to what count value we want to + // reset (and thus trigger the interrupt) at. + // Since the prescaler is set to 2MHz, the counter will be + // incremented two million times each second, and we want the + // interrupt to trigger 9600 time each second. The formula for + // calculating the value of ICR1 (the TOP value) is: + // (CPUClock / Prescaler) / desired frequency - 1 + // So that's what well put in this register to set up our + // 9.6KHz sampling rate. + ICR1 = ((CPU_FREQ / 8) / 9600) - 1; // Set reference to AVCC (5V), select pin + // Set the ADMUX register. The first part (BV(REFS0)) sets + // the reference voltage to VCC (5V), and the next selects + // the ADC channel (basically what pin we are capturing on) ADMUX = BV(REFS0) | ch; - DDRC &= ~BV(ch); - PORTC &= ~BV(ch); - DIDR0 |= BV(ch); + DDRC &= ~BV(ch); // Set the selected channel (pin) to input + PORTC &= ~BV(ch); // Initialize the selected pin to LOW + DIDR0 |= BV(ch); // Disable the Digital Input Buffer on selected pin - // Set autotrigger on Timer1 Input capture flag - ADCSRB = BV(ADTS2) | // Setting these three on (1-1-1) sets the ADC to - BV(ADTS1) | // "Timer1 capture event" - BV(ADTS0); // + // Now a little more configuration to get the ADC working + // the way we want + ADCSRB = BV(ADTS2) | // Setting these three on (1-1-1) sets the ADC to + BV(ADTS1) | // "Timer1 capture event". That means we can declare + BV(ADTS0); // an ISR in the ADC Vector, that will then get called + // everytime the ADC has a sample ready, which will + // happen at the 9.6Khz sampling rate we set up earlier - ADCSRA = BV(ADEN) | // ADC Enable - BV(ADSC) | // ADC Start converting - BV(ADATE) | // Enable autotriggering - BV(ADIE) | // ADC Interrupt enable + ADCSRA = BV(ADEN) | // ADC Enable - Yes, we need to turn it on :) + BV(ADSC) | // ADC Start Converting - Tell it to start doing conversions + BV(ADATE) | // Enable autotriggering - Enables the autotrigger on complete + BV(ADIE) | // ADC Interrupt enable - Enables an interrupt to be called BV(ADPS2); // Enable prescaler flag 2 (1-0-0 = division by 16 = 1MHz) + // This sets the ADC to run at 1MHz. This is out of spec, + // Since it's normal operating range is only up to 200KHz. + // But don't worry, it's not dangerous! I promise it wont + // blow up :) There is a downside to running at this speed + // though, hence the "out of spec", which is that we get + // a much lower resolution on the output. In this case, + // it's not a problem though, since we don't need the full + // 10-bit resolution, so we'll take fast and less precise! } -// Declare ADC ISR +// This declares the Interrupt Service routine that will +// get called everytime the ADC finishes taking a sample. +// What actually happens here is that we take a piece of +// code, store it somewhere in memory, and then put the +// address of that "somewhere" into the Interrupt Vector +// Table of the processor, in this case the position +// "ADC_vect". This lets the processor know what to do +// when all the timing and configuration we just set up +// finally* ends up triggering the interrupt. bool hw_afsk_dac_isr; DECLARE_ISR(ADC_vect) { TIFR1 = BV(ICF1); + + // Call the routine for analysing the captured sample + // Notice that we read the ADC sample, and then bitshift + // by two places to the right, effectively eliminating + // two bits of precision. But we didn't have those + // anyway, because the ADC is running at high speed. + // We then subtract 128 from the value, to get the + // representation to match an AC waveform. We need to + // do this because the AC waveform (from the audio input) + // is biased by +2.5V, which is nessecary, since the ADC + // can't read negative voltages. By doing this simple + // math, we bring it back to an AC representation + // we can do further calculations on. afsk_adc_isr(context, ((int16_t)((ADC) >> 2) - 128)); + + // We also need to check if we're supposed to spit + // out some modulated data to the DAC. if (hw_afsk_dac_isr) + // If there is, it's easy to actually do so. We + // calculate what the sample should be in the + // DAC ISR, and apply the bitmask 11110000. This + // simoultaneously spits out our 4-bit digital + // sample to the four pins connected to our DAC + // circuit, which then converts it to an analog + // waveform. The reason for the " | BV(3)" is that + // we also need to trigger another pin controlled + // by the PORTD register. This is the PTT pin + // which tells the radio to open it transmitter. PORTD = (afsk_dac_isr(context) & 0xF0) | BV(3); else + // If we're not supposed to transmit anything, we + // keep quiet by continously sending 128, which + // when converted to an AC waveform by the DAC, + // equates to a steady, unchanging 0 volts. PORTD = 128; -} \ No newline at end of file +} + + +// * "finally" is probably the wrong description here. +// "All the f'ing time" is probably more accurate :) +// but it felt like it was a long way down here, +// writing all the explanations. I think this is a +// nice testament to how efficient and smart these +// processors are. The actual code to set up what +// took a long time to explain, is really very short. \ No newline at end of file diff --git a/Modem/hardware.h b/Modem/hardware.h index 21aff90..7ecb03a 100644 --- a/Modem/hardware.h +++ b/Modem/hardware.h @@ -13,7 +13,7 @@ void hw_afsk_dacInit(int ch, struct Afsk *_ctx); // ADC initialization #define AFSK_ADC_INIT(ch, ctx) hw_afsk_adcInit(ch, ctx) -// LED TX/RX on/off (pin 9/10) +// Here's some macros for controlling the RX/TX LEDs #define LED_TX_INIT() do { DDRB |= BV(1); } while (0) #define LED_TX_ON() do { PORTB |= BV(1); } while (0) #define LED_TX_OFF() do { PORTB &= ~BV(1); } while (0) @@ -22,6 +22,8 @@ void hw_afsk_dacInit(int ch, struct Afsk *_ctx); #define LED_RX_ON() do { PORTB |= BV(2); } while (0) #define LED_RX_OFF() do { PORTB &= ~BV(2); } while (0) + +// FIXME: remove these, they're in the DAC writes now #define PTT_INIT() do { DDRD |= BV(3); } while (0) #define PTT_ON() do { PORTD |= BV(3); } while (0) #define PTT_OFF() do { PORTD &= ~BV(3); } while (0) diff --git a/buildrev.h b/buildrev.h index 8d68363..e2786f7 100644 --- a/buildrev.h +++ b/buildrev.h @@ -1,2 +1,2 @@ -#define VERS_BUILD 357 +#define VERS_BUILD 359 #define VERS_HOST "vixen"