Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

MPLABs C18 Compile Error

Status
Not open for further replies.

yngndrw

New Member
Hi,

I'm used to writing applications for normal PC's in C++, not PICs.

I'm using MPLABs C18 compiler, with no optimisations. (Trial ran out.)

The code I'm trying to compile is for a K-Type thermometer. It is ment to just output the temp to an LCD. The code works fine in a C++ application for the PC.

The error I get is:
Executing: "C:\MCC18\bin\mplink.exe" /l"C:\MCC18\lib" "C:\MCC18\lkr\18f452.lkr" "C:\PIC Projects\HZ Thermometer\Main.o" "C:\PIC Projects\HZ Thermometer\Util.o" "C:\PIC Projects\HZ Thermometer\LCD.o" /m"HZ Thermometer.map" /w /o"HZ Thermometer.cof"
MPLINK 4.12, Linker
Copyright (c) 2007 Microchip Technology Inc.
Error - section 'MATH_DATA' can not fit the section. Section 'MATH_DATA' length=0x00000014
Errors : 1

I think this means that it's run out of memory in a bank, but I thought C18 was ment to handle banks for me. To be honest, I can't remember much about banks. (Not touched PICs in a while.) The PIC I'm using is a PIC18f452.

Here is Main.c:
Code:
// Includes
#include <P18F452.h>
#include <math.h>
#include "Defines.h"
#include "Util.h"
#include "LCD.h"
// Setup
#pragma config OSCS=OFF, OSC=HS
#pragma config BOR=OFF, PWRT=ON, WDT=OFF
//#pragma config CCP2MX=OFF
#pragma config STVR=ON, LVP=OFF, DEBUG=OFF
#pragma config CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF, CPB=OFF, CPD=OFF
#pragma config WRT0=OFF, WRT1=OFF, WRT2=OFF, WRT3=OFF, WRTC=OFF, WRTB=OFF, WRTD=OFF
#pragma config EBTR0=OFF, EBTR1=OFF, EBTR2=OFF, EBTR3=OFF, EBTRB=OFF
// Defines
#ifdef CLOCK_20MHZ
 #define TMR0_START 0xFE // 2 steps
#else
 #define TMR0_START 0x9C // 100 steps
#endif
#define PWM_MAX 0x64 // 100 PWM steps, 10KHz clock, 10ms per cycle (100Hz)
#define ADC_MAX 0x3E8 // 1000 clocks per step, 10KHz clock, 100ms per cycle (10Hz)
#define ADC_DIVIDER 204.60 // ( 1023.00 / 5.00 )
#define ADC_KTYPE_REF 0x00
#define ADC_THERMISTOR 0x01
#define ADC_KTYPE_0 0x02
#define ADC_KTYPE_1 0x03
#define ADC_KTYPE_2 0x04
#define ADC_KTYPE_3 0x05
// Math Constants
const float kMathE = 2.71828182845904523536;
// Thermistor Conversion Constants
const float kThermistorA1 = 3.354016e-3;
const float kThermistorB1 = 2.569850e-4;
const float kThermistorC1 = 2.620131e-6;
const float kThermistorD1 = 6.383091e-8;
const float kThermistorRefResistance = 4700.00;
// K-Type Conversion Constants
const float kKTypeA1 = 0.00;
const float kKTypeB1 = 2.5173462e+1;
const float kKTypeC1 = -1.1662878;
const float kKTypeD1 = -1.0833638;
const float kKTypeE1 = -8.9773540e-1;
const float kKTypeF1 = -3.7342377e-1;
const float kKTypeG1 = -8.6632643e-2;
const float kKTypeH1 = -1.0450598e-2;
const float kKTypeI1 = -5.1920577e-4;
const float kKTypeA2 = 0.00;
const float kKTypeB2 = 2.508355e+1;
const float kKTypeC2 = 7.860106e-2;
const float kKTypeD2 = -2.503131e-1;
const float kKTypeE2 = 8.315270e-2;
const float kKTypeF2 = -1.228034e-2;
const float kKTypeG2 = 9.804036e-4;
const float kKTypeH2 = -4.413030e-5;
const float kKTypeI2 = 1.057734e-6;
const float kKTypeJ2 = -1.052755e-8;
// Globals
unsigned int g_iTaskPWMCount = 0x00;
unsigned int g_iTaskADCCount = 0x00;
volatile unsigned int g_iPWMChannel[] = { 0x00 };
volatile unsigned int g_iADCCurrentChannel = 0x00;
volatile float g_fADCChannel[] = { 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 };
volatile unsigned char g_iDisplayDirty = 0x01;
// Prototypes
void main( void );
void InterruptHandlerHigh( void );
void InterruptHandlerLow( void );
void Init( void );
void EnableInterrupts( void );
float ThermistorResistanceToTemperature( float fResistance );
float KTypeVoltageToTemperature( float fVoltage );
// High Priority Interrupt Entry Point
#pragma code InterruptVectorHigh = 0x08
void InterruptVectorHigh( void )
{
 _asm
  goto InterruptHandlerHigh
 _endasm
}
#pragma code
#pragma interrupt InterruptHandlerHigh
void InterruptHandlerHigh( void )
{
 // TMR0 - Every 0.1ms (10KHz)
 if( IS_SET( INTCON, 2 ) )
 {
  CLEAR( INTCON, 2 );
  TMR0L = TMR0_START;
  // PWM Task
  g_iTaskPWMCount++;
  if( g_iTaskPWMCount > PWM_MAX )
  {
   g_iTaskPWMCount = 0x00;
  }
  // Channel 0 (LCD, Pin B1)
  if( g_iTaskPWMCount < g_iPWMChannel[0] )
  {
   PIN_ON( PORTB, PIN( 1 ) );
  }
  else
  {
   PIN_OFF( PORTB, PIN( 1 ) );
  }
  // ADC Task
  g_iTaskADCCount++;
  if( g_iTaskADCCount > ADC_MAX )
  {
   // No ADC Conversion In Progress
   if( IS_NOT_SET( ADCON0, 2 ) )
   {
    g_iTaskADCCount = 0x00;
    g_iADCCurrentChannel++;
    if( g_iADCCurrentChannel > ADC_KTYPE_3 )
    {
     g_iADCCurrentChannel = 0x00;
    }
    // Channel 0 (K-Type Ref, Pin AN0)
    if( g_iADCCurrentChannel == ADC_KTYPE_REF )
    {
     ADC_CHANNEL_0;
    }
    // Channel 1 (Thermistor, Pin AN1)
    else if( g_iADCCurrentChannel == ADC_THERMISTOR )
    {
     ADC_CHANNEL_1;
    }
    // Channel 2 (K-Type Channel 0, Pin AN4)
    else if( g_iADCCurrentChannel == ADC_KTYPE_0 )
    {
     ADC_CHANNEL_4;
    }
    // Channel 3 (K-Type Channel 1, Pin AN5)
    else if( g_iADCCurrentChannel == ADC_KTYPE_1 )
    {
     ADC_CHANNEL_5;
    }
    // Channel 4 (K-Type Channel 2, Pin AN6)
    else if( g_iADCCurrentChannel == ADC_KTYPE_2 )
    {
     ADC_CHANNEL_6;
    }
    // Channel 5 (K-Type Channel 3, Pin AN7)
    else if( g_iADCCurrentChannel == ADC_KTYPE_3 )
    {
     ADC_CHANNEL_7;
    }
    SET( ADCON0, 2 ); // Start ADC Conversion
   }
  }
 }
}
// Low Priority Interrupt Entry Point
#pragma code InterruptVectorLow = 0x18
void InterruptVectorLow( void )
{
 _asm
  goto InterruptHandlerLow
 _endasm
}
#pragma code
#pragma interruptlow InterruptHandlerLow
void InterruptHandlerLow( void )
{
 // ADC Conversion Complete
 if( IS_SET( PIR1, 6 ) )
 {
  CLEAR( PIR1, 6 );
  g_fADCChannel[g_iADCCurrentChannel] = (float)ADRES / ADC_DIVIDER;
  g_iDisplayDirty = 1;
 }
}
// Main Entry Point
void main( void )
{
 float fThermistorResistance = 0.00;
 float fKTypeVoltage0 = 0.00;
 Init();
 // LCD Backlight
 while( g_iPWMChannel[0] <= PWM_MAX )
 {
  g_iPWMChannel[0]++;
  Delay( 0x08 );
 }
 g_iPWMChannel[0] = PWM_MAX;
 while( 1 )
 {
  if( g_iDisplayDirty == 1 )
  {
   fThermistorResistance = ( 1000.00 * 5.00 / g_fADCChannel[ADC_THERMISTOR] ) - 1000.00;
   LCDLine( 0 );
   LCDPrint( "Thermistor: " );
   LCDFloatNumber2DP( ThermistorResistanceToTemperature( fThermistorResistance ) );
   LCDLine( 1 );
   LCDPrint( "K-Type Ref: " );
   LCDFloatNumber2DP( g_fADCChannel[ADC_KTYPE_REF] );
   fKTypeVoltage0 = g_fADCChannel[ADC_KTYPE_0] / 250.00 - 6.458; //todo - constants
   LCDLine( 2 );
   LCDPrint( "K-Type 0: " );
   LCDFloatNumber2DP( KTypeVoltageToTemperature( fKTypeVoltage0 ) );
   LCDLine( 3 );
   LCDPrint( "K-Type 4: " );
   LCDFloatNumber2DP( g_fADCChannel[ADC_KTYPE_3] );
  }
  Delay( 0x10 );
 }
}
void Init( void )
{
 // Set Port A as all inputs
 TRISA = 0x7F;
 // Disable all interrupts
 RCON = 0x00;
 INTCON = 0x00;
 INTCON2 = 0x00;
 INTCON3 = 0x00;
 PIR1 = 0x00;
 PIR2 = 0x00;
 PIE1 = 0x00;
 PIE2 = 0x00;
 IPR1 = 0x00;
 IPR2 = 0x00;
 
 // Setup the ADC
 ADRESL = 0x00;
 ADRESH = 0x00;
 ADCON0 = 0x81;
 ADCON1 = 0x88;
 //todo - remove:
// CLEAR( ADCON1, 3 );//temp - removes the ADC ref's
 // Initialise the LCD
 LCDInit();
 // Enable interrupts for multi-tasking
 EnableInterrupts();
}
void EnableInterrupts( void )
{
 // Setup TMR0
 TMR0L = TMR0_START;
#ifdef CLOCK_4MHZ
 T0CON = 0xC8; // 1:1 prescaler
#else
 #ifdef CLOCK_8MHZ
  T0CON = 0xC0; // 1:2 prescaler
 #else
  #ifdef CLOCK_20MHZ
   T0CON = 0xC7; // 1:256 prescaler
  #endif
 #endif
#endif
/*
 4 / ms @ 1:256
 8 / ms @ 1:128
 16 / ms @ 1:64
 32 / ms @ 1:32
 64 / ms @ 1:16
 128 / ms @ 1:8
 256 / ms @ 1:4
 512 / ms @ 1:2
 1024 / ms @ 1 <-- selected
 256 / 4 = 64ms = 15.625hz
 1 / 4 = 0.25ms = 4khz
 1 / 8 = 0.125ms = 8khz
 1 / 16 = 0.0625ms = 16khz
 1 / 32 = 0.03125ms = 32khz
 1 / 64 = 0.015625ms = 64khz
 1 / 128 = 0.0078125ms = 128khz
 1 / 256 = 0.00390625ms = 256khz
 1 / 512 = 0.001953125ms = 512khz
 1 / 1024 = 0.0009765625ms = 1024khz
 8 / 1024 = 0.0078125ms = 128khz
 9 / 1024 = 0.0087890625ms = 114khz
 10 / 1024 = 0.009765625ms = 102khz
 2 / 4 = 0.5ms = 2khz
 100 / 512 = 0.1953125ms = 5khz
 100 / 1024 = 0.09765625ms = 10khz <-- selected
 255 / 256 = 0.99609375ms = 1khz
 255 / 1024 = 0.2490234375ms = 4khz
 1khz = 1ms
 4khz = 0.25ms
 10khz = 0.1ms <-- target
 100khz = 0.01ms
*/
 RCON |= 0x80; // Enable Priorities
 // Enable interrupts for TMR0
 INTCON |= 0xA0; // We need high priority interrupts and the TMR0 overflow interrupt
 INTCON2 |= 0x04; // Set TMR0 as high priority
 // Enable interrupt for ADC
 INTCON |= 0x40; // We need low priority interrupts
 PIE1 |= 0x40; // Enable the ADC interrupt
}
float ThermistorResistanceToTemperature( float fResistance )
{
 float fLnR = log( fResistance / kThermistorRefResistance );
 float fTemperatureKelvin = 1.00 / ( ( kThermistorA1 + kThermistorB1 * fLnR + kThermistorC1 * pow( fLnR, 2.00 ) + kThermistorD1 * pow( fLnR, 3.00 ) ) );
 return fTemperatureKelvin - 273.15;
}
float KTypeVoltageToTemperature( float fVoltage )
{
 float fVoltageMilivolts = fVoltage / 1000.00;
 if( fVoltageMilivolts < 0.00 )
 {
  // Range: -200C -> 0C
  return kKTypeA1
    + kKTypeB1 * fVoltageMilivolts
    + kKTypeC1 * pow( fVoltageMilivolts, 2.00 )
    + kKTypeD1 * pow( fVoltageMilivolts, 3.00 )
    + kKTypeE1 * pow( fVoltageMilivolts, 4.00 )
    + kKTypeF1 * pow( fVoltageMilivolts, 5.00 )
    + kKTypeG1 * pow( fVoltageMilivolts, 6.00 )
    + kKTypeH1 * pow( fVoltageMilivolts, 7.00 )
    + kKTypeI1 * pow( fVoltageMilivolts, 8.00 );
 }
 else
 {
  // Range: 0C -> 500C
  return kKTypeA2
    + kKTypeB2 * fVoltageMilivolts
    + kKTypeC2 * pow( fVoltageMilivolts, 2.00 )
    + kKTypeD2 * pow( fVoltageMilivolts, 3.00 )
    + kKTypeE2 * pow( fVoltageMilivolts, 4.00 )
    + kKTypeF2 * pow( fVoltageMilivolts, 5.00 )
    + kKTypeG2 * pow( fVoltageMilivolts, 6.00 )
    + kKTypeH2 * pow( fVoltageMilivolts, 7.00 )
    + kKTypeI2 * pow( fVoltageMilivolts, 8.00 )
    + kKTypeJ2 * pow( fVoltageMilivolts, 9.00 );
 }
}

Any ideas on how to fix this ?

Thanks,
-Andrew.
 
Instead of:
const float xxxxxx = ??????;
try:
const rom float xxxxxx = ??????;
Or this:
#define xxxxxx ??????
Since you are just using them as constants anyway.
Or better yet, get rid of all floating point! You are no longer on a PC with 1GB of ram. Write your code to fit the hardware.
 
kchriste: Well I had already tried using #define and that didn't fix it. Just tried adding rom and the error changed slightly:
Error - section '.tmpdata' can not fit the section. Section '.tmpdata' length=0x00000098

I hear you about not using floating point numbers, but in all honesty I wouldn't know where to start with converting the formulas to integers. I guess I could multiply everything by 1000 then divide at the end ? Actually that wouldn't work because some of the constants are very small, e.g. -1.052755e-8.
Regardless, C18 should support floating point so I don't see why it shouldn't be used. A thermometer shouldn't be a demanding application. :p

aussiepoof: I presume you want the linker to compile it ? If so you'll want the other files too. I've attached the whole project. The linker file isn't included, but it's one of the standard ones supplied with C18. (C:\MCC18\lkr\18f452.lkr)

Thanks for your replies.
 

Attachments

  • HZ Thermometer - Package.zip
    6.1 KB · Views: 215
yngndrw said:
I hear you about not using floating point numbers, but in all honesty I wouldn't know where to start with converting the formulas to integers. I guess I could multiply everything by 1000 then divide at the end ? Actually that wouldn't work because some of the constants are very small, e.g. -1.052755e-8.
Regardless, C18 should support floating point so I don't see why it shouldn't be used. A thermometer shouldn't be a demanding application.

It's possible to use float, but incredibly inefficient and clumsy. You may be shocked to find out just how long it takes to perform a float calc!

Yes, multiply by a constant so you have whole numbers. Preferrably multiply by bit shifting. This is somewhat faster on the multiply stage and FAR faster on the divide-back-out stage.

You mention this is for a Type K thermocouple. These are so close to linear you could just do a constant slope (deg K per ADC code) and be fairly accurate. Or you could do a lookup table. Table could have the deg K value for every 32 ADC codes, your program can do a linear interpolation between the points higher and lower than the ADC value.
 
yngndrw: basically there is limited space for temporary results and floats are very big so your coding style (and MCC18's relative stupidity in such things) meant you were over-using this limited space by calculating the return value in kTypeVoltageToTemperature in a single long statement.

I have re-written your routine such that it now compiles and links happily:

Code:
float KTypeVoltageToTemperature( float fVoltage )
{
  float fTemperature;
  float fVoltageMilivolts = fVoltage / 1000.00;

  if( fVoltageMilivolts < 0.00 )
  {
    // Range: -200C -> 0C
    fTemperature = kKTypeA1;
    fTemperature += kKTypeB1 * fVoltageMilivolts;
    fTemperature += kKTypeC1 * pow( fVoltageMilivolts, 2.00 );
    fTemperature += kKTypeD1 * pow( fVoltageMilivolts, 3.00 );
    fTemperature += kKTypeE1 * pow( fVoltageMilivolts, 4.00 );
    fTemperature += kKTypeF1 * pow( fVoltageMilivolts, 5.00 );
    fTemperature += kKTypeG1 * pow( fVoltageMilivolts, 6.00 );
    fTemperature += kKTypeH1 * pow( fVoltageMilivolts, 7.00 );
    fTemperature += kKTypeI1 * pow( fVoltageMilivolts, 8.00 );
  }
  else
  {
    // Range: 0C -> 500C
    fTemperature = kKTypeA2;
    fTemperature +=  kKTypeB2 * fVoltageMilivolts;
    fTemperature +=  kKTypeC2 * pow( fVoltageMilivolts, 2.00 );
    fTemperature +=  kKTypeD2 * pow( fVoltageMilivolts, 3.00 );
    fTemperature +=  kKTypeE2 * pow( fVoltageMilivolts, 4.00 );
    fTemperature +=  kKTypeF2 * pow( fVoltageMilivolts, 5.00 );
    fTemperature +=  kKTypeG2 * pow( fVoltageMilivolts, 6.00 );
    fTemperature +=  kKTypeH2 * pow( fVoltageMilivolts, 7.00 );
    fTemperature +=  kKTypeI2 * pow( fVoltageMilivolts, 8.00 );
    fTemperature +=  kKTypeJ2 * pow( fVoltageMilivolts, 9.00 );
  }
  return fTemperature;
}
You'll see that I've not forced the compiler to use lots of temporary result storage by immediately adding each constant multiplication result to the return value. The result is also better programming practice as there's a single return point from the routine.

Hope this helps,
Paul
 
Oznog: I'll have a look at converting it to fixed-point soon, what kind of accuracy would I be looking at by having two constant slopes. (One for negative and one for positive, as I have now with the constants.)

aussiepoof: Thank you very much for this, I wouldn't have worked that out in a million years. I guesss working with x86 / x64 processors, we don't realise just how much memory we actually use. It's an interesting point for anyone looking to optimise an application running on a larger processor.

Thanks again to everyone who's helped me out. :)
 
You're quite welcome yngndrw! This is why I really think all computing graduates need to understand how compilers work... that and it's always handy to be able to confirm that your tools (e.g. the compiler) don't have bugs :)

P.
 
Thanks aussiepoof, I had the same problem. Just broke the long expression into smaller sentences and everything worked fine.
 
yngndrw: basically there is limited space for temporary results and floats are very big so your coding style (and MCC18's relative stupidity in such things) meant you were over-using this limited space by calculating the return value in kTypeVoltageToTemperature in a single long statement.

I have re-written your routine such that it now compiles and links happily:

Code:
float KTypeVoltageToTemperature( float fVoltage )
{
  float fTemperature;
  float fVoltageMilivolts = fVoltage / 1000.00;

  if( fVoltageMilivolts < 0.00 )
  {
    // Range: -200C -> 0C
    fTemperature = kKTypeA1;
    fTemperature += kKTypeB1 * fVoltageMilivolts;
    fTemperature += kKTypeC1 * pow( fVoltageMilivolts, 2.00 );
    fTemperature += kKTypeD1 * pow( fVoltageMilivolts, 3.00 );
    fTemperature += kKTypeE1 * pow( fVoltageMilivolts, 4.00 );
    fTemperature += kKTypeF1 * pow( fVoltageMilivolts, 5.00 );
    fTemperature += kKTypeG1 * pow( fVoltageMilivolts, 6.00 );
    fTemperature += kKTypeH1 * pow( fVoltageMilivolts, 7.00 );
    fTemperature += kKTypeI1 * pow( fVoltageMilivolts, 8.00 );
  }
  else
  {
    // Range: 0C -> 500C
    fTemperature = kKTypeA2;
    fTemperature +=  kKTypeB2 * fVoltageMilivolts;
    fTemperature +=  kKTypeC2 * pow( fVoltageMilivolts, 2.00 );
    fTemperature +=  kKTypeD2 * pow( fVoltageMilivolts, 3.00 );
    fTemperature +=  kKTypeE2 * pow( fVoltageMilivolts, 4.00 );
    fTemperature +=  kKTypeF2 * pow( fVoltageMilivolts, 5.00 );
    fTemperature +=  kKTypeG2 * pow( fVoltageMilivolts, 6.00 );
    fTemperature +=  kKTypeH2 * pow( fVoltageMilivolts, 7.00 );
    fTemperature +=  kKTypeI2 * pow( fVoltageMilivolts, 8.00 );
    fTemperature +=  kKTypeJ2 * pow( fVoltageMilivolts, 9.00 );
  }
  return fTemperature;
}

I know the poster that modified the coding was only trying to make it compile, but this is really the wrong way to code a polynomial. The power function is expensive.

Try coding it like:

Code:
fTemperature =                    kKTypeA1 + 
               fVoltageMilivolts*(kKTypeB1 + 
               fVoltageMilivolts*(kKTypeC1 + 
               fVoltageMilivolts*(kKTypeD1 + 
               fVoltageMilivolts*(kKTypeE1 + 
               fVoltageMilivolts*(kKTypeF1 + 
               fVoltageMilivolts*(kKTypeG1 + 
               fVoltageMilivolts*(kKTypeH1 + 
               fVoltageMilivolts*(kKTypeI1))))))));

Coding this way, you can evaluate an Nth order polynomial with N multiplies and N adds (if I counted right and didn't miss any parentheses). Note that it has as many multiples and adds as the first case above, but no power function calls. Also, I would code the divide by 1000 as 0.001*fVoltage. No need to do a divide if you don't have to.

I find the PICs (the PIC24 anyway) very fast on floating point. I am currently integrating a set of six coupled first order differential equations on a PIC24 in approximately 300 steps (the algorithm uses step doubling to get the desired accuracy) in about 250 ms. I know 250 ms sounds like a long time, but for the user to wait and with my current interface design, it's a non-issue. It's much, much faster than I thought it would be considering it's doing many, many thousands of floating point operations.

Brad
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top