View previous topic :: View next topic |
Author |
Message |
danPic
Joined: 19 Dec 2017 Posts: 3
|
PIC24F ADC Register Access Problem |
Posted: Tue Dec 19, 2017 1:06 am |
|
|
Hi,
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?
main.c:
Code: |
#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>
#endif
#ifdef CCS
#include <24F32KA302.h>
#FUSES NOWRTB NOBSS NOWRT NOPROTECT FRC_PLL SOSC_DIGITAL LPRCHIGH NOIESO SOSC_HIGH CKSNOFSM ICSP3 MCLR
#endif
#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++)
{
}
adcRead(&measuredValue);
}
}
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;
}
|
registers.h:
Code: |
#ifndef REGISTERS_H
#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;
#endif
|
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Dec 19, 2017 1:24 am |
|
|
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. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9243 Location: Greensville,Ontario
|
|
Posted: Tue Dec 19, 2017 5:55 am |
|
|
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 (x.xxx ?) 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.
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Tue Dec 19, 2017 8:55 am |
|
|
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:
Code: |
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:
Code: |
#BIT DONE=GETENV("BIT:DONE")
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.
Code: |
.................... // 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... |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9243 Location: Greensville,Ontario
|
|
Posted: Tue Dec 19, 2017 9:33 am |
|
|
As pointed out, not only is CCS code smaller it will be FASTER ! Something you say you need in your original post.
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Tue Dec 19, 2017 12:53 pm |
|
|
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. |
|
|
danPic
Joined: 19 Dec 2017 Posts: 3
|
|
Posted: Tue Dec 19, 2017 11:47 pm |
|
|
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. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Wed Dec 20, 2017 4:48 am |
|
|
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!... |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19538
|
|
Posted: Fri Dec 22, 2017 4:21 am |
|
|
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:
Code: |
#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.
So:
Code: |
#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. |
|
|
danPic
Joined: 19 Dec 2017 Posts: 3
|
|
Posted: Tue Jan 02, 2018 5:49 pm |
|
|
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 ). 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. |
|
|
|