;;; fm_radio
;;;
;;; Daud Zoss
;;; 23 Jul 2014
;;;
;;; Single-chip unlicensed <20MHz FM radio transmitter that combines a <100kS/s
;;; internal 10-bit Analog-to-Digital Converter (ADC) with a 16-bit Numerically
;;; Controlled Oscillator (NCO) to achieve a Voltage Controlled Oscillator (VCO)
;;; that mixes with either the internal 16MHz reference or a <20MHz frequency
;;; reference on CLKIN, by means of an XOR gate in the internal combinational
;;; asynchronous configurable logic cell (CLC).
;;;
;;; Through the creative use of PIC peripherals switchable to each other inside
;;; the device, no external components (besides capacitors for supply bypass and
;;; DC blocking) or even wiring is required for this demonstration.  In fact,
;;; after setting up the hardware blocks the only task of the PIC is to sit in a
;;; loop and copy ADC output registers directly over to the NCO input registers.
;;;
;;; The low-end 8-pin PIC16F1501 has just enough pins to perform the input and
;;; output functions while retaining the input and output clocks as well as a
;;; reset pin when connected as follows (PDIP package pinout):
;;;
;;;                   ______ ______
;;;       _          |      U      |
;;;       |______VDD_| 1         8 |_VSS___________    ___
;;;                  |             |               V  /   \
;;;            CLKIN_| 2 i     o 7 |_CWG1B/ICSPDAT___|     |
;;;                  |             |                       |
;;;           CLKOUT_| 3 o     i 6 |_Vin/ICSPCLK     loop  |
;;;                  |             |               antenna |
;;;            nMCLR_| 4 i     o 5 |_CWG1A______||___      |
;;;                  |_____________|            ||   |     |
;;; complete BOM:                                     \___/
;;; qty 1 Microchip PIC12F1501 microcontroller
;;; qty 1 0.1uF capacitor (pin 1 to VSS)
;;; qty 1 0.1nF capacitor (DC block for loop antenna)
;;;
;;; For purposes of study, several intermediate probe points in the signal (ADC
;;; output reflected through 5-bit DAC, NCO modulating waveform, single-ended
;;; RF) can be brought out when using the higher-pincount PIC16F1503 connected as
;;; follows (PDIP package pinout):
;;;
;;;                   ______ ______
;;;       _          |      U      |
;;;       |______VDD_| 1         8 |_VSS______________
;;;                  |             |                  |
;;;            CLKIN_| 2 i    o 13 |_DACOUT1/ICSPDAT  V
;;;                  |             |        
;;;           CLKOUT_| 3 o    o 12 |_ICSPCLK
;;;                  |             |        
;;;   ___      nMCLR_| 4 i    o 11 |_CLC1   
;;;  /   \           |             |        
;;; |loop |____CWG1A_| 5 o    o 10 |_RC0____/\/\/\_
;;; |antenna         |             |              _|_
;;; |      _||_CWG1B_| 6 o    o  9 |_NCO1         \ / ~~>
;;; |     | ||       |             |              _V_
;;;  \___/        NC_| 7 i    i  8 |_Vin           |
;;;                  |_____________|               V
;;; complete BOM:
;;; qty 1 Microchip PIC16F1503 microcontroller
;;; qty 1 0.1uF capacitor (pin 1 to VSS)
;;; qty 1 0.1nF capacitor (DC block for loop antenna)
;;; qty 1 optional mode jumper (pin 2 to pin 3)
;;; qty 1 LED with limiting resistor for indication
;;; qty 1 Microchip MCP121-240 supervisor for brown-out protection
;;; qty 1 battery/power source (must exceed the greater of 2.3V and Vfwd of LED)
;;;
 
        processor       12f1501 ; // (budgetary $0.49 to $0.77)
;       processor       16f1503 ; // (budgetary $0.55 to $0.90)
 
;// INTCLOCK: run the PIC off its low-precision reference instead of CLKIN pin
#define INTCLOCK
 
#ifdef __16F1503 ;[
 
#include p16f1503.inc
 
;// PROBEPTS: bring out NCO/RF/ADC waveforms on pins 9/11/13 (PIC16F1503 only)
#define PROBEPTS
CLC1IN1 equ     RA5
CLKOUT  equ     RA4
NOTMCLR equ     RA3
CLC1    equ     RA2
AN0     equ     RA0
PINDIRA equ     (1<<CLC1IN1)|(0<<CLKOUT)|(1<<NOTMCLR)|(0<<CLC1)|(0<<RA1)|AN0
CWG1A   equ     RC5
CWG1B   equ     RC4
AN7     equ     RC3
VIN     equ     RC2
NCO1    equ     RC1

PINDIRC equ     (0<<CWG1A)|(0<<CWG1B)|(1<<AN7)|(1<<VIN)|(0<<NCO1)|(0<<RC0)
ADC_ON  equ     (6<<CHS0)|(1<<ADON)     ; // Vin on pin 8 (AN6)
 
#else ;][
#ifdef __12F1501 ;[
 
#include p12f1501.inc
 
CLC1IN1 equ     RA5
CLKOUT  equ     RA4
NOTMCLR equ     RA3
CWG1A   equ     RA2
VIN     equ     RA1
CWG1B   equ     RA0
PINDIRA equ     (1<<CLC1IN1)|(0<<CLKOUT)|(1<<NOTMCLR)|(0<<CWG1A)|(1<<VIN)|CWG1B
ANALOGA equ     (1<<VIN)
ADC_ON  equ     (1<<CHS0)|(1<<ADON)     ; // Vin on pin 6 (AN1)
 
#endif ;]
#endif ;]
 
#ifdef INTCLOCK ;[
        __CONFIG        _CONFIG1,_FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_ON & _CP_OFF & _BOREN_OFF & _CLKOUTEN_ON
#else ;][
        __CONFIG        _CONFIG1,_FOSC_ECH & _WDTE_OFF & _PWRTE_OFF & _MCLRE_ON & _CP_OFF & _BOREN_OFF & _CLKOUTEN_ON
#endif ;]
        __CONFIG        _CONFIG2,_WRT_OFF & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_ON
 
INITDIR equ     (0x3f)
INT16M  equ     (1<<IRCF3)|(1<<IRCF2)|(1<<IRCF1)|(1<<IRCF0)|(0<<SCS1)|(0<<SCS0)
CWGCLC1 equ     (1<<G1ASDLB0)|(1<<G1ASDLA0)|(1<<G1IS2)|(1<<G1IS1)|(1<<G1IS0)
CWG_INT equ     (1<<G1EN)|(1<<G1OEB)|(1<<G1OEA)|(1<<G1CS0)
CWG_EXT equ     (1<<G1EN)|(1<<G1OEB)|(1<<G1OEA)|(0<<G1CS0)
ADCLJ   equ     (0<<ADFM)
DAC1ON  equ     (1<<DACEN)|(1<<DACOE1)|(0<<DACPSS)
NCO50DC equ     (1<<N1EN)|(1<<N1OE)|(0<<N1PFM)
NCO_INT equ     (0<<N1CKS0)
NCO_EXT equ     (1<<N1CKS0)
        
        org     0x000
        bcf     INTCON,GIE
        movlp   HIGH main
        goto    main
 
        ;; handle the conversion-done interrupt from ADC and stash as NCO freq
        org     0x004
irqvec
        BANKSEL PIR1

;       btfss   PIR1,ADIF       ; void irqvec(void) {
;       goto    irqend          ;  if (PIR1 & (1<<ADIF)) {
irqadc
        bcf     PIR1,ADIF       ;   PIR1 &= ~(1<<ADIF);
        BANKSEL ADCON0
        movf    ADRESH,w        ;
        movwf   INDF1           ;   *FSR1 = ADRESH; // NCOINCH...
        movf    ADRESL,w        ;                   // must be written before...
        movwf   INDF0           ;   *FSR0 = ADRESL; // NCOINCL
        bsf     ADCON0,1        ;   ADCON0 |= 1<<GO_NOT_DONE;// start next conv
irqend
        retfie                  ; } } void main(void) {
 
main
        ;; configure pin modes, directions, etc.
        BANKSEL ANSELA
#ifdef __16F1503 ;[
        movlw   PINDIRC         ;
        movwf   ANSELC          ;  ANSELC = PINDIRC;    // RC inputs are analog
#ifdef PROBEPTS ;[
        movlw   1<<AN0          ;
        movwf   ANSELA          ;  ANSELA = 1<<AN0;     // RA digital except AN0
#else ;][
        clrf    ANSELA          ;  ANSELA = 0;          // RA inputs are digital
#endif ;]
#else ;][
#ifdef __12F1501 ;[
        movlw   ANALOGA         ;
        movwf   ANSELA          ;  ANSELA = ANALOGA;    // only Vin is analog
#endif ;]
#endif ;]
        BANKSEL TRISA
        movlw   INITDIR         ;  w = INITDIR;
#ifdef __16F1503 ;[
        movwf   TRISC           ;  TRISC = w;   // all 6 inputs for now
        movlw   PINDIRA         ;  w = PINDIRA;
#endif ;]
        movwf   TRISA           ;  TRISA = w;
#ifdef __12F1503 ;[
        movlw   PINDIRA         ;  w = PINDIRA;
#endif ;]
        BANKSEL WPUA
        movwf   WPUA            ;  WPUA = w;    // RA inputs are pulled
        
        ;; configure CWG and finalize pin directions for that bank
        BANKSEL CWG1CON0
        clrf    CWG1DBR         ;  CWG1DBR = 0; // no rising dead band
        clrf    CWG1DBF         ;  CWG1DBF = 0; // no falling dead band
        clrf    CWG1CON2        ;  CWG1CON2 = 0;// no special shutdown behavior
        movlw   CWGCLC1         ;
        movwf   CWG1CON1        ;  CWG1CON1 = CWGCLC1; // tri-state when CLC off
#ifdef INTCLOCK ;[
        movlw   CWG_INT         ;
#else ;][
        movlw   CWG_EXT         ;
#endif ;]
        movwf   CWG1CON0        ;  CWG1CON0 = INTCLOCK ? CWG_INT : CWG_EXT;
        BANKSEL TRISA
#ifdef __16F1503 ;[
        movlw   PINDIRC         ;
        movwf   TRISC           ;  TRISC = PINDIRC;
        BANKSEL LATC
#ifdef PROBEPTS ;[
        movlw   1<<RC0          ;
        movwf   LATC            ;  LATC = 1<<RC0; // light the LED indicator...
#else ;][
        clrf    LATC            ;  LATC = 0;
#endif ;]
#else ;][
 
        movlw   PINDIRA         ;
        movwf   TRISA           ;  TRISA = PINDIRA;
#endif ;]
 
        ;; select and stabilize the oscillator (if using the internal 16MHz one)
#ifdef INTCLOCK ;[
        BANKSEL OSCCON
        movlw   INT16M          ;
        movwf   OSCCON          ;  OSCCON = INT16M;
#ifndef __DEBUG ;[
wait16m
        btfsc   OSCSTAT,HFIOFR  ;  while ((OSCSTAT & (1<<HFIOFR) == 0) ||
        btfss   OSCSTAT,HFIOFS  ;         (OSCSTAT & (1<<HFIOFS) == 0))
        goto    wait16m         ;   ;
#endif ;]
#ifdef PROBEPTS ;[
        BANKSEL LATC            ;
        bcf     LATC,RC0        ;  LATC = 0; // ...until oscillator stability
        BANKSEL ADCON0
#endif ;]
#else ;][
        BANKSEL ADCON0
#endif ;]
 
        ;; configure ADC and enable interrupt
        clrf    ADCON1          ;  ADCON1 = ADCLJ; // left-justified, max freq
        movlw   ADC_ON          ;
        movwf   ADCON0          ;  ADCON0 = AD_ON; // mux Vin channel, stand by
        bsf     PIE1,ADIE       ;  PIE1 |= 1<<ADIE;// enable ADC interrupt
        bsf     ADCON0,1        ;  ADCON0 |= 1<<GO_NOT_DONE; // start 1st conv
        BANKSEL PIR1
        bcf     PIR1,ADIF       ;  PIR1 &= ~(1<<ADIE); // prevent false 1st conv
        bsf     INTCON,PEIE     ;  INTCON |= 1<<PEIE; // periph. interrupts on
 
#ifdef PROBEPTS ;[
        ;; configure ADC-mirroring DAC
        BANKSEL DACCON0
        clrf    DACCON1         ;  DACCON1 = 0; // zero the initial output
        movlw   DAC1ON          ;
        movwf   DACCON0         ;  DACCON0 = DAC1ON; // ICSPDAT pin, VDD ref.
#endif ;]
 
        ;; configure NCO
        BANKSEL NCO1CON
        movlw   NCO50DC         ;
        movwf   NCO1CON         ;  NCO1CON = NCO50DC;
        clrf    NCO1INCH        ;  NCO1INCH = 0;
        clrf    NCO1INCL        ;  NCO1INCL = 0;
#ifndef INTCLOCK ;[
        movlw   0<<N1CKS0       ;
#else ;][
        movlw   1<<N1CKS0       ;
#endif ;]
        movwf   NCO1CLK         ;  NCO1CLK |= INTCLOCK ? 0 : 1;
 
        ;; configure CLC1 = Fosc ^ NCO (output from Microchip CLC Designer)
        BANKSEL CLC1GLS0
        movlw   H'08'
        movwf   CLC1GLS0
        movlw   H'08'
        movwf   CLC1GLS1
        movlw   H'20'
        movwf   CLC1GLS2
        movlw   H'20'
        movwf   CLC1GLS3
        movlw   H'01'
        movwf   CLC1SEL0
        movlw   H'44'
        movwf   CLC1SEL1
        movlw   H'00'
        movwf   CLC1POL
        movlw   H'C1'
        movwf   CLC1CON
 
#ifndef PROBEPTS ;[
        bcf     CLC1CON,LC1OE   ; CLC1CON &= ~(1<<LC1OE); // CLC output back off
#endif ;]
        
        ;; go into loop, just constantly resetting FSRx pointers to the value
        ;; expected by the ISR for ADC (any future subroutines are free to use
        ;; FSRx after disabling all interrupts; upon exit this loop will restore
        ;; the values and re-enable interrupts)
loop
        movlw   high NCO1INCH   ; while (1) {
        movwf   FSR1H           ;
        movwf   FSR0H           ;
        movlw   low NCO1INCH    ;
        movwf   FSR1L           ;  FSR1 = &NCOINCH;
        movlw   low NCO1INCL    ;
        movwf   FSR0L           ;  FSR0 = &NCOINCL;
        bsf     INTCON,GIE      ;  INTCON |= 1<<GIE; // enable all interrupts
        
#ifdef PROBEPTS ;[
        BANKSEL ADRESH
        asrf    ADRESH,w        ;
        asrf    WREG,w          ;
        asrf    WREG,w          ;
        BANKSEL DACCON1
        movwf   DACCON1         ;  DACCON1 = ADRESH>>3; // 0x00 to 0x1f
;       BANKSEL LATC    
        andlw   0x18            ;  // high 2 bits give 0x00, 0x08, 0x10 or 0x18
        btfss   STATUS,Z        ;
        goto    overmin         ;  if ((DACCON1 < 0x08) ||
        bcf     LATC,RC0        ;
        goto    loop            ;
overmin xorlw   0x18            ;
        btfss   STATUS,Z        ;
        goto    undrmax         ;      (DACCON1 >= 0x18))
        bcf     LATC,RC0        ;   LATC &= ~(1<<RC0); // LED indicator off
        goto    loop            ;  else
undrmax bsf     LATC,RC0        ;   LATC |= 1<<RC0; // LED indicator on
#endif  ;]
        goto    loop            ; }
        end                     ;}
        
Advertisements