CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug Reports on this forum. Send them to

PIC24F ADC Register Access Problem

Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message

Joined: 19 Dec 2017
Posts: 3

View user's profile Send private message

PIC24F ADC Register Access Problem
PostPosted: Tue Dec 19, 2017 1:06 am     Reply with quote


I'm writing some software to read adc values with a PIC24F32KA302. I'm writing/reading the registers directly because I need precise control over the peripheral. The software compiles correctly with CCS but is getting stuck when waiting for the adc DONE bit (I'm using a polling approach initially). I wanted to check that it wasn't a problem with setting up the peripheral so I compiled the same code with the xc16 compiler and it works as expected. Has anyone experienced this before?


#define XC

#ifdef XC
    // FBS
    #pragma config BWRP = OFF    // Boot Segment Write Protect->Disabled
    #pragma config BSS = OFF    // Boot segment Protect->No boot program flash segment

    // FGS
    #pragma config GWRP = OFF    // General Segment Write Protect->General segment may be written
    #pragma config GSS0 = OFF    // General Segment Code Protect->No Protection

    // FOSCSEL
    #pragma config FNOSC = FRCPLL    // Oscillator Select->Fast RC Oscillator with Postscaler and PLL Module (FRCDIV+PLL)
    #pragma config SOSCSRC = DIG    // SOSC Source Type->Digital Mode for use with external source
    #pragma config LPRCSEL = HP    // LPRC Oscillator Power and Accuracy->High Power, High Accuracy Mode
    #pragma config IESO = OFF    // Internal External Switch Over bit->Internal External Switchover mode disabled (Two-speed Start-up disabled)

    // FOSC
    #pragma config POSCMOD = NONE    // Primary Oscillator Configuration bits->Primary oscillator disabled
    #pragma config OSCIOFNC = OFF    // CLKO Enable Configuration bit->CLKO output disabled
    #pragma config POSCFREQ = HS    // Primary Oscillator Frequency Range Configuration bits->Primary oscillator/external clock input frequency greater than 8MHz
    #pragma config SOSCSEL = SOSCHP    // SOSC Power Selection Configuration bits->Secondary Oscillator configured for high-power operation
    #pragma config FCKSM = CSDCMD    // Clock Switching and Monitor Selection->Both Clock Switching and Fail-safe Clock Monitor are disabled

    // FWDT
    #pragma config WDTPS = PS32768    // Watchdog Timer Postscale Select bits->1:32768
    #pragma config FWPSA = PR128    // WDT Prescaler bit->WDT prescaler ratio of 1:128
    #pragma config FWDTEN = OFF    // Watchdog Timer Enable bits->WDT disabled in hardware; SWDTEN bit disabled
    #pragma config WINDIS = OFF    // Windowed Watchdog Timer Disable bit->Standard WDT selected(windowed WDT disabled)

    // FPOR
    #pragma config BOREN = BOR0    // Brown-out Reset Enable bits->Brown-out Reset disabled in hardware, SBOREN bit disabled
    #pragma config LVRCFG = OFF    // Low Voltage Regulator Configuration bit->Low Voltage regulator is not available
    #pragma config PWRTEN = ON    // Power-up Timer Enable bit->PWRT enabled
    #pragma config I2C1SEL = PRI    // Alternate I2C1 Pin Mapping bit->Use Default SCL1/SDA1 Pins For I2C1
    #pragma config BORV = V18    // Brown-out Reset Voltage bits->Brown-out Reset set to lowest voltage (1.8V)
    #pragma config MCLRE = ON    // MCLR Pin Enable bit->RA5 input pin disabled,MCLR pin enabled

    // FICD
    #pragma config ICS = PGx3    // ICD Pin Placement Select bits->EMUC/EMUD share PGC3/PGD3

    // FDS
    #pragma config DSWDTPS = DSWDTPSF    // Deep Sleep Watchdog Timer Postscale Select bits->1:2,147,483,648 (25.7 Days)
    #pragma config DSWDTOSC = LPRC    // DSWDT Reference Clock Select bit->DSWDT uses Low Power RC Oscillator (LPRC)
    #pragma config DSBOREN = ON    // Deep Sleep Zero-Power BOR Enable bit->Deep Sleep BOR enabled in Deep Sleep
    #pragma config DSWDTEN = ON    // Deep Sleep Watchdog Timer Enable bit->DSWDT enabled

    #include <xc.h>

#ifdef CCS
    #include <24F32KA302.h>



#include <stdint.h>

#include "registers.h"

void adcRead(uint16_t* measuredValue);

int main() {

    // Set TRISB => RB14 input
    portBRegisters->tris = (0x1 << 14);
    // Set ANSB => RB14 analog input
    ansRegisters->ansb = (0x1 << 14);
    adcRegisters->ad1con1 = 0x8400;
    adcRegisters->ad1con2 = 0;
    adcRegisters->ad1con3 = 0;
    adcRegisters->ad1con5 = 0;
    adcRegisters->ad1chs = 0;
    adcRegisters->ad1cssl = 0;
    adcRegisters->ad1cssh = 0;
    adcRegisters->ad1chith = 0;
    adcCtmuRegisters->ad1ctmuenh = 0;
    uint16_t measuredValue;
    while (1)
        uint16_t i;
        for (i = 0; i < 1000; i++)

void adcRead(uint16_t* measuredValue)
    // Configure CH0SA => select the channel from mux
    adcRegisters->ad1chs = 0xA;
    // Set SAMP => start sampling
    adcRegisters->ad1con1 |= (0x1 << 1);
    //Provide Delay
    uint16_t i;
    for( i=0; i < 25; i++ )
    // Reset SAMP => stop sampling
    adcRegisters->ad1con1 &= ~(0x1 << 1);
    // Check DONE => A/D conversion cycle completed
    while (!(adcRegisters->ad1con1 & 0x1))
    *measuredValue = adcBufRegisters->adc1buf0;


#define   REGISTERS_H

#include <stdint.h>

// Port registers

typedef struct {
    volatile uint16_t tris;
    volatile uint16_t port;
    volatile uint16_t lat;
    volatile uint16_t odc;
} PortRegisters_t;

PortRegisters_t * portARegisters = (PortRegisters_t *)0x2C0;
PortRegisters_t * portBRegisters = (PortRegisters_t *)0x2C8;

// Analog select registers
typedef struct {
   volatile uint16_t ansa;
   volatile uint16_t ansb;
} AnsRegisters_t;

typedef struct {
    volatile uint16_t adc1buf0;
    volatile uint16_t adc1buf1;
    volatile uint16_t adc1buf2;
    volatile uint16_t adc1buf3;
    volatile uint16_t adc1buf4;
    volatile uint16_t adc1buf5;
    volatile uint16_t adc1buf6;
    volatile uint16_t adc1buf7;
    volatile uint16_t adc1buf8;
    volatile uint16_t adc1buf9;
    volatile uint16_t adc1buf10;
    volatile uint16_t adc1buf11;
    volatile uint16_t adc1buf12;
    volatile uint16_t adc1buf13;
    volatile uint16_t adc1buf14;
    volatile uint16_t adc1buf15;
    volatile uint16_t adc1buf16;
    volatile uint16_t adc1buf17;
} AdcBufRegisters_t;

typedef struct {
    volatile uint16_t ad1con1;
    volatile uint16_t ad1con2;
    volatile uint16_t ad1con3;
    uint16_t SPACE1;
    volatile uint16_t ad1chs;
    uint16_t SPACE2[2];
    volatile uint16_t ad1cssh;
    volatile uint16_t ad1cssl;
    uint16_t SPACE3;
    volatile uint16_t ad1con5;
    volatile uint16_t ad1chith;
    volatile uint16_t ad1chitl;
} AdcRegisters_t;

typedef struct {
    volatile uint16_t ad1ctmuenh;
    volatile uint16_t ad1ctmuenl;
} AdcCtmuRegisters_t;

AnsRegisters_t * ansRegisters = (AnsRegisters_t *)0x4E0;
AdcBufRegisters_t * adcBufRegisters = (AdcBufRegisters_t * )0x300;
AdcRegisters_t * adcRegisters = (AdcRegisters_t *)0x340;
AdcCtmuRegisters_t * adcCtmuRegisters = (AdcCtmuRegisters_t *)0x360;

PCM programmer

Joined: 06 Sep 2003
Posts: 20137

View user's profile Send private message

PostPosted: Tue Dec 19, 2017 1:24 am     Reply with quote

One method would be to write it using CCS routines, make it work, and
then look at the .LST file. Then using the .LST file as a guide, translate it
into your non-compiler specific code.

Joined: 01 Jul 2010
Posts: 5830
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Tue Dec 19, 2017 5:55 am     Reply with quote

Before you do PCM P suggestion, I'd compile the code as is of XC and dump the listing, then compile for CCS and dump that listing. Now compare the listings. In theory they should be identical.....
If not, it will be obvious what is different. A long shot is that the compiler version ( ?) has a bug like a wrong bit in the device header or adc registers ?

BTW I envy all those that can type ! Especially long words and lots of them. Sure is a LOT of code just to read the adc compared to the easy CCS way.


Joined: 11 Mar 2010
Posts: 12653

View user's profile Send private message

PostPosted: Tue Dec 19, 2017 8:55 am     Reply with quote

This is a terrifyingly inefficient way to access registers in CCS.....

Honestly modify the CCS part of the setup to give direct access.

For instance:
adcRegisters->ad1con1 &= ~(0x1 << 1);

You have declared a pointer 'adcRegisters', which is meant to point to a structure of type 'AdcRegisters_t', which contains the element ad1con1.

To access this, you have to load the value in 'adcRegisters', and then use this as a pointer to access the memory location. Codes as:

022C:  MOV     808,W0
022E:  MOV     [W0],W5
0230:  AND     W5,#1,W0
0232:  CP0     W0
0234:  BRA     NZ,238
....................     {
0236:  BRA     22C

Six instructions.

If you instead use the standard CCS approach:


    while (!DONE)
//codes as
022C:  BTSC.B  340.0
022E:  BRA     232
....................     {
0230:  BRA     22C

Just 3 instructions.

This is actually one of the least expanded by doing it this way. It is far more efficient just to use the #BIT and #BYTE directives.


....................     // Reset SAMP => stop sampling
....................     adcRegisters->ad1con1 &= ~(0x1 << 1);
0224:  MOV     808,W5
0226:  MOV     #FFFD,W0
0228:  AND     W0,[W5],W0
022A:  MOV     W0,[W5]

//While with SAMP declared:

....................     // Reset SAMP => stop sampling
....................     SAMP=FALSE;
0224:  BCLR.B  340.1

If you want to run XC code, then use XC. If you want to use CCS, rewrite to do things the CCS way.

I'd not be surprised if the core problem is actually something involving one of the rotations on an indexed value done this way...

Joined: 01 Jul 2010
Posts: 5830
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Tue Dec 19, 2017 9:33 am     Reply with quote

As pointed out, not only is CCS code smaller it will be FASTER ! Something you say you need in your original post.

Joined: 11 Mar 2010
Posts: 12653

View user's profile Send private message

PostPosted: Tue Dec 19, 2017 12:53 pm     Reply with quote

Also as a couple of other comments:
Doing the sample time by looping is silly. The chip can do programmable sampling. Ideally if time is important, program this. Even better if this is programmed you can trigger the ADC to acquire and then sample automatically, and then do something else while the ADC is working.
Even better, the entire ADC operation can be done automatically using DMA.

Joined: 19 Dec 2017
Posts: 3

View user's profile Send private message

PostPosted: Tue Dec 19, 2017 11:47 pm     Reply with quote

Thanks for the feedback, I was trying to set it up with simple loops/polling to get a good understanding of the peripheral before implementing interrupts/DMA. I'm fairly new to programming pics and appreciate the explanation of instruction differences. I wasn't able to find a significant difference in the disassembly output from the xc and ccs but will continue investigating.

Joined: 11 Mar 2010
Posts: 12653

View user's profile Send private message

PostPosted: Wed Dec 20, 2017 4:48 am     Reply with quote

If you are looking to go DMA, then you need to be using the hardware acquisition ability. The DMA doesn't do delays like this!...

Joined: 11 Mar 2010
Posts: 12653

View user's profile Send private message

PostPosted: Fri Dec 22, 2017 4:21 am     Reply with quote

Never having tried to manually sample, I thought I'd look at this to see if I could spot 'why' you were having problems. Answer is in the data sheet. You can only start conversion by dropping the sample bit, if the SSRC bits are some pattern not equal to 0. You have these set to 0.

Working instead with ASAM set on, so the chip will handle the sampling, a comment:

Unfortunately the CCS setup for the TAD multiplier, is rather poorly done, and doesn't help to optimise this setting, if you want to get the 'ideal' time. For instance on a 24FJ128GA702 at 16Mhz. You have a Tad min of 278nSec. Now this implies a maximum clock rate of 3.6Mhz. 16/3.6 = 4.44, so the lowest divisor available over this would be 5. Giving a Tad of 312nSec, and then perhaps your calculations say you need a minimum sample time of 2uSec for your source. 7TAD sample time would give 2.187uSec. Yet the CCS settings only give:


#define ADC_TAD_MUL_0            0x1F00
#define ADC_TAD_MUL_2            0x1D00
#define ADC_TAD_MUL_4            0x1B00
#define ADC_TAD_MUL_8            0x1700
#define ADC_TAD_MUL_16           0x0F00
#define ADC_TAD_MUL_31           0x0000

//and clock rates of
#define ADC_CLOCK                0x0000
#define ADC_CLOCK_DIV_2          0x0001
#define ADC_CLOCK_DIV_4          0x0003
#define ADC_CLOCK_DIV_8          0x0007
#define ADC_CLOCK_DIV_16         0x000F
#define ADC_CLOCK_DIV_32         0x001F
#define ADC_CLOCK_DIV_64         0x003F

Now since the TAD multipliers are 'inverted', you can't OR them together to get another combination. So I wrote my own #define to generate other values. I also did the same for the ADC CLOCK values.


#define ADC_TAD_MUL(x) (((x&0x1F)*256)^0x01F00)
#define ADC_CLOCK_DIV(x) ((x-1)&0xFF)

//Which can then be used as:
   setup_adc(ADC_CLOCK_DIV(5) | ADC_TAD_MUL(7)); //312.5nSec and 2.187uSec sample

This was being used here on a 24F128GA702 chip at 16MHz.

Allowing in this case the ADC clock to be running of Fosc/5 (16MHz here) to give 3.2MHz for the ADC clock, and the Tsamp to be set to 7 cycles of this. The code needs only trigger the start, and the ADC will two clocks later start to sample (2.5 worst case), sample for 7 cycles, then convert automatically. It takes 14 clock cycles to convert (12bit mode), 0.5 between sampling and conversion, (as shown). So a total of 7.5uSec.

Far the most accurate way to set the timings. Smile

Joined: 19 Dec 2017
Posts: 3

View user's profile Send private message

PostPosted: Tue Jan 02, 2018 5:49 pm     Reply with quote

I thought setup_adc() wouldn't set the ASAM bit because it has functions for starting the adc or is that just for the conversion? Is there a listing of the source they use in the CCS functions or it can only be found in the disassembly?

I've found what the problem was. I was unaware that the in-circuit debugger requires a space of memory on the chip (PIC newbie Embarassed ). With XC16 and MPLAB X it automatically detected I was using a debugger and did not use the memory space that the debugger used. However with CCS and MPLAB X it was storing my register struct pointers in that memory space which got changed as the debugger updated that memory.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2001, 2005 phpBB Group