diff --git a/firmware/adc.c b/firmware/adc.c
new file mode 100644
index 0000000..8bdf8af
--- /dev/null
+++ b/firmware/adc.c
@@ -0,0 +1,83 @@
+#include "config.h"
+#include "adc.h"
+#include "timer.h"
+
+/* Layout of buffer: 
+   adc_dmabuf[x] contains 16 samples of channel AN0
+*/
+uint16_t adc_dmabuf[16] __attribute__((space(dma)));
+
+void (*adc_adc_callback)(void) = 0;
+void (*adc_dma_callback)(void) = 0;
+
+/* ADC1 interrupt after each sample (if enabled) */
+void __attribute__((__interrupt__,auto_psv)) _ADC1Interrupt(void)
+{       
+	if (adc_adc_callback)
+		(*adc_adc_callback)();
+	IFS0bits.AD1IF = 0;
+}
+
+/* DMA0 interrupt after 16 samples */
+void __attribute__((__interrupt__,auto_psv)) _DMA0Interrupt(void)
+{
+	if (adc_dma_callback)
+		(*adc_dma_callback)();
+	IFS0bits.DMA0IF = 0;
+}
+
+/* Initialize ADC1 to constantly DMA AN0. */
+void adc_init(void)
+{
+	/* AD1CON1 */
+    AD1CON1bits.ADDMABM = 1;        /* Scatter-gather DMA */
+	AD1CON1bits.AD12B = 1;          /* 12-bit, 1 channel */
+    AD1CON1bits.FORM = 0;           /* Integer output 0000dddddddddddd */
+    AD1CON1bits.SSRC = 2;           /* Convert on Timer3 */
+    AD1CON1bits.ASAM = 1;           /* Automatically start sampling */
+    AD1CON1bits.SIMSAM = 0;         /* Simul sampling, N/A when AD12B=1 */
+
+    /* AD1CON2 */
+    AD1CON2bits.VCFG = 0;           /* Ref: AVDD / AVSS */
+//    AD1CON2bits.VCFG = 3;           /* Ref: VREF+ / VREF- */
+    AD1CON2bits.CSCNA = 0;          /* Do not scan inputs on Mux A */
+	AD1CON2bits.CHPS = 0;           /* Convert CH0 only, N/A when AD12B=1 */
+    AD1CON2bits.SMPI = 0;           /* Increase DMA address at each sample */
+    AD1CON2bits.BUFM = 0;           /* Fill buffer from start */
+    AD1CON2bits.ALTS = 0;           /* Always use Mux A settings */
+
+    /* AD1CON3 */
+    AD1CON3bits.ADRC = 0;           /* Use system clock, not internal RC */
+    AD1CON3bits.SAMC = 0;           /* Sample time, N/A when ADRC = 0 */
+    AD1CON3bits.ADCS = 0x7;         /* Tad = 8 * Tcy */
+
+    /* AD1CON4 */
+    AD1CON4bits.DMABL = 4;          /* Each input gets 16 words in DMA buf */
+
+    /* Channel 0 setup */
+    AD1CHS0bits.CH0NA = 0;          /* CH0-: Vrefl */
+    AD1CHS0bits.CH0SA = 0;          /* CH0+: AN0 */
+
+	/* Set Timer3 to trigger conversion at 128KHz */
+	timer_setup_16bit(3, 128000, 0);
+
+    /* Turn it on */
+    AD1PCFGL = 0xfffe;           	/* Analog = AN0 */
+    AD1PCFGH = 0xffff;
+    AD2PCFGL = 0xffff;
+    AD1CON1bits.ADON = 1;
+
+    /* DMA0 setup */
+    DMA0CONbits.AMODE = 0;          /* Register indirect, post-increment */
+    DMA0CONbits.MODE = 0;           /* Continuous, no ping-pong */
+    DMA0PAD = (int)&ADC1BUF0;       /* Get addresses from ADC1 */
+    DMA0CNT = 15;                   /* 16 transfers before wraparound */
+    DMA0STA = __builtin_dmaoffset(&adc_dmabuf);
+    DMA0REQbits.IRQSEL = 13;        /* Triggered by ADC1 */
+    IFS0bits.DMA0IF = 0;            
+    IEC0bits.DMA0IE = 1;            /* DMA interrupt (every 16 samples) */
+    DMA0CONbits.CHEN = 1;           /* enable this DMA channel */
+
+    IFS0bits.AD1IF = 0;
+    IEC0bits.AD1IE = 0;				/* No ADC interrupt (every sample) */
+}
diff --git a/firmware/adc.h b/firmware/adc.h
new file mode 100644
index 0000000..bba82c5
--- /dev/null
+++ b/firmware/adc.h
@@ -0,0 +1,14 @@
+#ifndef ADC_H
+#define ADC_H
+
+/* DMA region for the ADC */
+extern uint16_t adc_dmabuf[16];
+
+/* Initialize internal ADC */
+void adc_init(void);
+
+/* Callback functions */
+extern void (*adc_adc_callback)(void);
+extern void (*adc_dma_callback)(void);
+
+#endif
diff --git a/firmware/adcext.c b/firmware/adcext.c
index 916ae6d..6da2f4d 100644
--- a/firmware/adcext.c
+++ b/firmware/adcext.c
@@ -12,7 +12,7 @@
    interrupt notification if desired */
 
 /* Since the ADC is 5v, ADC SDO must be on a digital-only pin.
-   All exposed pins on B D and E are digital, so use SPI pins:
+   All exposed pins on B D and E are analog, so use SPI pins:
    ADC SDO = PIC SDI1 = RF7
    ADC SCK = PIC SCK1 = RF6
 */
@@ -30,16 +30,22 @@
 /* Initialize ADC */
 void adcext_init(void)
 {
-	int i;
+	int32_t i;
 
 	TRIS_SDO = 1;
 	LAT_SCK = 0;
 	TRIS_SCK = 0;
-
+	
 	/* Startup delay CS down to SCK high t4 = 5000ns */
 	for (i = 0; i < 5000 / 25; i++)
 		wait_25ns();
 
+	/* We need to monitor BUSY, I think.  With the 2-wire
+	   interface, we never know if we start in the middle of 
+       a data output phase. For now, consider reading broken..
+	   just return early.  XXXX */
+	return;
+
 	/* Trigger a dummy read so we're prepared for the
 	   next conversion */
 	(void) adcext_read();
@@ -118,7 +124,7 @@ void adcext_start_conversion(void)
 	LAT_SCK = 0;
 
 	/* Wait tKQMAX for SCK down to SDO valid in case we
-	   call adc_is_conversion_finished right away. */
+	   call adcext_is_conversion_ready right away. */
 	wait_200ns();
 }
 
diff --git a/firmware/adcext.h b/firmware/adcext.h
index 8b113fc..eb308aa 100644
--- a/firmware/adcext.h
+++ b/firmware/adcext.h
@@ -1,5 +1,5 @@
-#ifndef ADC_H
-#define ADC_H
+#ifndef ADCEXT_H
+#define ADCEXT_H
 
 /* Initialize ADC */
 void adcext_init(void);
diff --git a/firmware/config.h b/firmware/config.h
index fb63bfa..38a7652 100644
--- a/firmware/config.h
+++ b/firmware/config.h
@@ -13,7 +13,6 @@ typedef signed long long int64_t;
 typedef unsigned long long uint64_t;
 
 #define FCY 40000000
-#define TMR1_RATE 8000
 
 void config_init(void);
 
diff --git a/firmware/uart.c b/firmware/uart.c
index 288a215..aa5aa87 100644
--- a/firmware/uart.c
+++ b/firmware/uart.c
@@ -12,7 +12,7 @@ void uart_init(int uart, int32_t rate)
 
 	if (uart == 1) {
 		U1MODE = 0;
-		U1MODEbits.BRGH = 0;
+		U1MODEbits.BRGH = 0; // errata: BRGH=1 is broken
 		U1BRG = brg;
 		U1STA = 0;
 		U1MODEbits.UARTEN = 1;
diff --git a/firmware/zoom.c b/firmware/zoom.c
index 7466494..a73007b 100644
--- a/firmware/zoom.c
+++ b/firmware/zoom.c
@@ -1,39 +1,37 @@
 #include "config.h"
 #include "adc.h"
+#include "adcext.h"
 #include "dac.h"
 #include "uart.h"
 #include "timer.h"
 #include <stdio.h>
 #include <math.h>
 
-int32_t tmr1_ms = 0;
-void TISR_HANDLER(1)
+int new_data = 0;
+uint16_t data;
+
+void callback(void)
 {
+	/* Send most recent sample to PC */
+	data = adc_dmabuf[15];
+	new_data = 1;
 }
 
 int main(void)
 {
 	config_init();
-	uart1_init(1000000);	
+	uart1_init(500000);
 	adcext_init();
 	dac_init();
-	timer_setup_16bit(1, 1000, 1);
-
-	TRISDbits.TRISD0 = 0;
-	TRISDbits.TRISD1 = 1;
-	TRISDbits.TRISD2 = 1;
+	adc_init();
+	adc_dma_callback = callback;
 
 	while(1) {
-		/* these are ints, so it should be atomic */
-/*
-		tmp = output_count;
-		tmp2 = dacval;
-		
-		uart1_put_hex((tmp & 0x7F) | (LATDbits.LATD0 ? 0x80 : 0x00));
-		uart1_put_hex((tmp2 & 0xFF00) >> 8);
-		uart1_put_hex((tmp2 & 0x00FF));
-		uart1_put('\r');
-*/
+		if (new_data) {
+			uart1_put_hex16(data);
+			uart1_crlf();
+			new_data = 0;
+		}
 	}
 
 	for(;;) continue;
diff --git a/firmware/zoom.mcp b/firmware/zoom.mcp
index 4d3822c..b6b477b 100644
--- a/firmware/zoom.mcp
+++ b/firmware/zoom.mcp
@@ -31,6 +31,8 @@ file_010=no
 file_011=no
 file_012=no
 file_013=no
+file_014=no
+file_015=no
 [FILE_INFO]
 file_000=zoom.c
 file_001=config.c
@@ -39,13 +41,15 @@ file_003=util.c
 file_004=dac.c
 file_005=timer.c
 file_006=adcext.c
-file_007=config.h
-file_008=uart.h
-file_009=util.h
-file_010=dac.h
-file_011=timer.h
-file_012=adcext.h
-file_013=p33fj256gp710.gld
+file_007=adc.c
+file_008=config.h
+file_009=uart.h
+file_010=util.h
+file_011=dac.h
+file_012=timer.h
+file_013=adcext.h
+file_014=adc.h
+file_015=p33fj256gp710.gld
 [SUITE_INFO]
 suite_guid={479DDE59-4D56-455E-855E-FFF59A3DB57E}
 suite_state=