ADCC - adc module for K and Q families

General discussion relating to the library modules supplied with the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Jerry Messina
Swordfish Developer
Posts: 1488
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

ADCC - adc module for K and Q families

Post by Jerry Messina » Thu Feb 16, 2023 2:32 pm

Here's a new module for the ADC in the K and Q devices with an ADCC peripheral,
which is used on the K40, K42, K83, and the entire Q family.

Currently the module just supports basic operation of the ADC (no filtering, math, etc)

Code: Select all

//
//------------------------------------------------------------------------------
// Name    : ADCC.bas
// Author  : Jerry Messina
// Date    : 13 FEB 2023
// Version : 1.00
//
// ADCC/ADC2 analog-to-digital converter with computation peripheral
// used by K40, K42, K83, Q10, Q40, Q41, Q43, Q71, Q83, and Q84
//
// ver 1.00 13 FEB 2023
//  - initial version with basic legacy mode support
//------------------------------------------------------------------------------
//
module ADCC

// check for _adc2 peripheral
#if not defined(_adc2) or (_adc2 = 0)
  #error _device + " does not support the ADCC/ADC2 peripheral"
#endif

// determine adcc peripheral version
#if _device in (18F24K40, 18F25K40, 18F26K40, 18F27K40) or _
    _device in (18F45K40, 18F46K40, 18F47K40) or _
    _device in (18F65K40, 18F66K40, 18F67K40) or _
    _device in (18LF24K40, 18LF25K40, 18LF26K40, 18LF27K40) or _
    _device in (18LF45K40, 18LF46K40, 18LF47K40) or _
    _device in (18LF65K40, 18LF66K40, 18LF67K40) or _
    _device in (18F24Q10, 18F25Q10, 18F26Q10, 18F27Q10) or _
    _device in (18F44Q10, 18F45Q10, 18F46Q10, 18F47Q10)
  #option _ADCC_VERSION = 1
#elseif _device in (18F25K83, 18F26K83, 18LF25K83, 18LF26K83)
  #option _ADCC_VERSION = 2
#else
  #option _ADCC_VERSION = 0
#endif
public const ADCC_VERSION = _ADCC_VERSION

// justification 0=left, 1=right (default)
#option _ADCC_JUSTIFY = 1
#if not(_ADCC_JUSTIFY in (0, 1))
  #error _ADCC_JUSTIFY, "_ADCC_JUSTIFY invalid"
#endif
public const ADCC_JUSTIFY = _ADCC_JUSTIFY

// software acq time, in usec
// set to 0 to use hardware ADACQ register and call SetAcq() 
#option _ADCC_ACQ_TIME = 10
public const ADCC_ACQ_TIME = _ADCC_ACQ_TIME

// hdw ADACQ register size
#if (_ADCC_VERSION = 1)            // K40 or Q10
  #define _ADCC_ADACQ_SIZE = byte
  public type ADACQ_T = byte
#else
  #define _ADCC_ADACQ_SIZE = word
  public type ADACQ_T = word
#endif

// automatically discharge the sample cap in SetChan
//  = 0: do not discharge
//  > 0: discharge to VSS and wait n usecs
// see 'ADC channel selection' discussion below
#option _ADCC_DISCHARGE_CAP = 0     // in usecs (0=disable)
#if not(_ADCC_DISCHARGE_CAP in (0 to 255))
  #error _ADCC_DISCHARGE_CAP, "_ADCC_DISCHARGE time invalid"
#endif
public const ADCC_DISCHARGE_CAP = _ADCC_DISCHARGE_CAP  

// ADCC VSS input channel (analog GND)
#if (_ADCC_VERSION = 1) or (_ADCC_VERSION = 2)    // K40, Q10, or K83
  #option _ADCC_VSS_CHANNEL = %111100
#else
  #option _ADCC_VSS_CHANNEL = %111011
#endif
public const ADCC_VSS_CHANNEL = _ADCC_VSS_CHANNEL

// ADCON0 bits
public const
    ADON = 7,        // ADC enable, 0=ADC disabled, 1=ADC enabled
    ADCONT = 6,      // continuous operation, 0=single, 1=cont/retrigger
    ADCS = 4,        // clock select, 0=Fosc(uses ADCLK), 1=FRC
    ADFM = 2,        // justification, 0=left justified, 1=right justified
    ADGO = 0         // conversion status, 0=done, 1=start/in process
#if (_ADCC_JUSTIFY = 1)
  const ADCC_DEFAULT_ADCON0 = (1<<ADON) + (1<<ADCS) + (1<<ADFM)
#else
  const ADCC_DEFAULT_ADCON0 = (1<<ADON) + (1<<ADCS)
#endif

// ADREF constants
public const
    ADPREF_VDD = %00,       // bits 1 and 0
    ADPREF_EXT = %10,
    ADPREF_FVR = %11,
    ADNREF_VSS = 0<<4,      // bit 4
    ADNREF_EXT = 1<<4
// Vref = Vdd & Vss
const ADCC_DEFAULT_ADREF = ADPREF_VDD + ADNREF_VSS
    
// adc result register alias   
public dim
    wADRES as ADRESL.AsWord,            // 16 bit ADC result
    wADPREV as ADPREVL.AsWord           // previous result (not currently used)
    
// ADC channel selection (from the datasheet)
// The ADPCH register determines which channel is connected to the Sample-and-Hold circuit for conversion. 
// When switching channels, it is recommended to have some acquisition time (ADACQ register) before starting the next
// conversion.
// To reduce the chance of measurement error, it is recommended to discharge the Sample-and-Hold capacitor
// when switching between ADC channels by starting a conversion on a channel connected to VSS
// and terminating the conversion after the acquisition time has elapsed.
//
public inline sub dischargeSampleCap()
    ADPCH = ADCC_VSS_CHANNEL        // ADC input = VSS
    delayus(ADCC_DISCHARGE_CAP)     // wait, in usecs
end sub

public sub SetChan(chan as byte)
  #if (_ADCC_DISCHARGE_CAP > 0)
    dischargeSampleCap()
  #endif
    ADPCH = chan                // select channel
  #if (_ADCC_ACQ_TIME > 0)      // use software acq time
    delayus(ADCC_ACQ_TIME)      // wait acq time
  #endif
end sub

// convert the currently selected channel
// result in ADRESH:ADRESL
public function Read() as wADRES
    ADCON0.bits(ADGO) = 1

    while (ADCON0.bits(ADGO) = 1)
    end while
    
    // adc result (ADRESH:ADRESL)
    result = wADRES
end function

// set chan and convert
// result in ADRESH:ADRESL
public function Read(chan as byte) as wADRES 
    SetChan(chan)
    result = Read()
end function

// set ADACQ acq time and ADCLK (requires ADCON0.ADCS=0)
public sub SetAcq(pADACQ as ADACQ_T, pADCLK as byte = 0)
  #if (_ADCC_ADACQ_SIZE = byte)
    ADACQ = pADACQ
  #elseif (_ADCC_ADACQ_SIZE = word)
    ADACQL = pADACQ.bytes(0)
    ADACQH = pADACQ.bytes(1)
  #endif

    ADCLK = pADCLK
end sub
    
// init the ADCC settings
public sub Init(pADCON0 as byte=ADCC_DEFAULT_ADCON0, pADREF as byte=ADCC_DEFAULT_ADREF)
    ADCON1 = $00        // precharge=0, single sample
    ADCON2 = $00        // legacy mode, no filtering, ADRES->ADPREV
    ADCON3 = $00        // no math functions
    ADREF = pADREF      // ADC Reference Select
    ADCAP = 0           // default S&H capacitance
    ADRPT = 0           // no repeat measurements
    ADCNT = 0           // repeat count = 0
    ADACT = 0           // auto-conversion disabled
    SetAcq(0, 0)        // default to software acq time, ADRC clock

    ADCON0 = pADCON0    // ADC Control Register 0 (enable, cont mode, clock select, justification, go)
    
    dischargeSampleCap()
end sub

end module
example:

Code: Select all

device = 18F26Q43
clock = 64

include "intosc.bas"

include "adcc.bas"

dim w as word
dim i as byte

// initialize the ADCC 
ADCC.Init()

// read chan 1
w = ADCC.Read(1)

// avg four readings from chan 2
ADCC.SetChan(2)
w = 0
for i = 1 to 4
    w = w + ADCC.Read()
next 
w = w/4
This module will be included in the next update, which should be available soon.

yolk
Posts: 16
Joined: Thu May 31, 2007 2:03 pm
Location: italy

Re: ADCC - adc module for K and Q families

Post by yolk » Thu Sep 26, 2024 7:01 am

Hi Jerry

Just for info

I suppose that ANA0 is channel 0

what about ANB0 and ANC0 ? (and following)

Jerry Messina
Swordfish Developer
Posts: 1488
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: ADCC - adc module for K and Q families

Post by Jerry Messina » Thu Sep 26, 2024 1:11 pm

The SetChan() sub uses the channel number setting for the ADC Positive Channel Selection Register, so it'll likely change depending on the device. Consult the datasheet for the ADPCH register settings.

For the 26Q43 in the demo program the datasheet section 40.7.8 ADPCH shows:
000000 RA0/ANA0
001000 RB0/ANB0
010000 RC0/ANC0

So ANB0 would use 'SetChan(8)', ANC0 would use 'SetChan(16)', etc (or you could use the binary constant shown in the table by prefixing the number with '%')

fyi - there's a newer version of ADCC.bas available in the 2.2.4.1 update just made available within the past few days.

Post Reply