View previous topic :: View next topic |
Author |
Message |
jake1272
Joined: 15 Mar 2021 Posts: 37
|
Data Logger |
Posted: Wed Mar 31, 2021 9:51 am |
|
|
Hello,
I want to design a data logger that samples temperature every second, by ADCing the temperature sensor, converting the value and storing it as a centigrade degree value in internal EEPROM taking 200 samples before overwriting the start EEPROM address, continuously.
And after, uploading all 200 samples in EEPROM to RS232 the instant a push-button is pressed and then return to log temperature.
I am using pic16f1847 and MCP9700/9700A 10 mV/ C. @ 3.3V linear thermometer.
The compiler is CCS v5.015
The IDE is MPLAB X IDE v5.40
Can anybody guide me with the code structure?
As far I know
To write: write_eeprom address, value
To read: value = read_eeprom address
And to send entire contents of EEPROM to PC via predefined RS232, in hex format.
Code: | void DumpEEprom (){ //EEPROM to RS232 subroutine
for(i =0;i<255;i++) printf ( "Data at address %X is: %u", i , read_eeprom i )
} |
|
|
 |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Mar 31, 2021 3:01 pm |
|
|
Setup a timer interrupt that gives you an interval of 1 second.
I gave you a link to code for that in this thread:
http://www.ccsinfo.com/forum/viewtopic.php?t=59242
Inside the timer isr, read the ADC once per second. Put the result in a
global variable. Set a global flag variable that indicates you have new data.
In main(), create a while() loop that polls that flag. If it's set, then write
the data to eeprom and clear the flag. If the data is 16-bit, then you
should move the data into a local variable before you use the data.
Code: |
void main()
{
int16 temp;
while(TRUE)
{
if(adc_data_flag)
{
// Copy adc_result (updated with new data in the timer interrupt)
// into a local variable with interrupts disabled, to avoid reading
// a partially updated int16 adc value.
disable_interrupts(GLOBAL);
temp = adc_result;
enable_interrupts(GLOBAL);
adc_data_flag = FALSE;
// Then use the data in 'temp' to write to eeprom or to display
}
} |
Also, poll the push-button inside the while() loop. If it's pressed, then
take action. |
|
 |
bkamen
Joined: 07 Jan 2004 Posts: 1615 Location: Central Illinois, USA
|
|
Posted: Wed Mar 31, 2021 8:31 pm |
|
|
And make sure to look up "write endurance" about the PIC's internal memories.
You will be surprised there are a limited number of write cycles that can easily be burned up with data-logging style operations.
Just so you're fully informed. _________________ Dazed and confused? I don't think so. Just "plain lost" will do. :D |
|
 |
Ttelmah
Joined: 11 Mar 2010 Posts: 19702
|
|
Posted: Thu Apr 01, 2021 12:22 am |
|
|
and (of course), the functions need brackets. read_eeprom(address) etc.. |
|
 |
jake1272
Joined: 15 Mar 2021 Posts: 37
|
|
Posted: Thu Apr 01, 2021 5:02 am |
|
|
Thank you all for your advice.
Here is my attempt. Any further suggestions?
Code: | #include <16F1847.h>
#DEVICE ADC=10 //Select 10 bit mode; use 8 for 8 bit mode
#fuses HS, NOWDT, PUT, BROWNOUT, NOLVP
#use delay(clock=20M)
#use rs232(baud=9600, UART1, ERRORS)
#define MCP9700 PIN_A0
#define SW PIN_A1
#define INTS_PER_SECOND 76
int8 data_seconds_timer = 0;
int16 i;
unsigned long value ;
//--------------------------
#INT_TIMER0
void t0_isr(void)
{
static int8 int_count = INTS_PER_SECOND;
int_count-- ;
if(int_count == 0) // Has 1 second passed ?
{
int_count = INTS_PER_SECOND; // If so, reload counter
if(data_seconds_timer) // Is the data timer running ?
{
data_seconds_timer--; // If so, decrement it
if(data_seconds_timer == 0) // Is it done ?
{
value = dac_write(i);
}
}
}
}
void main()
{
int16 temp;
int1 adc_data_flag;
int1 adc_result;
while(TRUE)
{
if(adc_data_flag)
{
// Copy adc_result (updated with new data in the timer interrupt)
// into a local variable with interrupts disabled, to avoid reading
// a partially updated int16 adc value.
write_eeprom (0x00 , value);
disable_interrupts(GLOBAL);
temp = adc_result;
enable_interrupts(GLOBAL);
setup_oscillator(OSC_8MHZ | OSC_PLL_ON); // Set internal oscillator to 32MHz (8MHz and PLL)
setup_adc(ADC_CLOCK_DIV_32); // Set ADC conversion time to 32Tosc
setup_adc_ports(sAN0); // Configure AN0 pin as analog
set_adc_channel(0);
adc_data_flag = FALSE;
if(input(SW))value = read_eeprom (0x00);
// Then use the data in 'temp' to write to eeprom or to display
printf("\r\n\nAll Done.\n");
}
}} |
|
|
 |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Apr 01, 2021 7:22 am |
|
|
Quote: | value = dac_write(i); |
This function is for the DAC, not the ADC. Also the function doesn't return
any value. That's shown in the CCS manual.
Quote: | void main()
{
int16 temp;
int1 adc_data_flag;
int1 adc_result; |
As written above, adc_data_flag is not global. It's local to main(). It
won't work as a way to communicate between the timer interrupt routine
and main().
adc_result can not be a 1-bit variable. If you're doing 10-bit ADC
readings, it must be 16 bits. Also, it must be global, not local.
Also, you were supposed to read the ADC in the interrupt routine and
put the result in adc_result. Then set adc_data_flag = TRUE there.
You need to learn the C language before you do anything else.
If you don't learn it first, you can't understand instructions from us. |
|
 |
Ttelmah
Joined: 11 Mar 2010 Posts: 19702
|
|
Posted: Thu Apr 01, 2021 7:32 am |
|
|
As another comment, there is no setup code for the timer, or to enable
the timer interrupt itself. You might want to consider taking an ADC
reading on each timer tick, and averaging. Otherwise the value may well
show some significant noise. |
|
 |
temtronic
Joined: 01 Jul 2010 Posts: 9408 Location: Greensville,Ontario
|
|
Posted: Thu Apr 01, 2021 8:00 am |
|
|
from your original post...
Quote: | for(i =0;i<255;i++) printf ( "Data at address %X is: %u", i , read_eeprom i ) |
This implies that the temperature readings will be 1 byte, so 20,21,22 or 68,69,70, so you'll need to convert the actual ADC bits into a single byte.
Get that code working properly and send to PC, NOT to EEPROM ! EPROM has a limited life and you could easily use it all up BEFORE your code actually works properly.
For this datalogger, you could just use an inline delay, to make coding simple..
Code: |
main()
loop start...
for loopcount=0 to <200...
read sensor
convert to byte
send to PC
delay_ms(1000)
loop end
|
This basic, simple code is easy to code....
ONCE it works THEN add the 'save to EEPROM/send EEPROM functions. |
|
 |
jake1272
Joined: 15 Mar 2021 Posts: 37
|
|
Posted: Thu Apr 01, 2021 9:16 am |
|
|
Thanks for the help |
|
 |
jake1272
Joined: 15 Mar 2021 Posts: 37
|
|
Posted: Fri Apr 02, 2021 5:07 am |
|
|
Here is my second attempt The code does not give correct reading like 16 17 degrees.
Not sure it prints 200 samples though and now I am struggling with dump EEPROM
Code: | #include <16F1847.h>
#DEVICE ADC=10 //Select 10 bit mode; use 8 for 8 bit mode
#fuses INTRC_IO, NOWDT, NOPROTECT, MCLR, NOBROWNOUT
#use delay(clock=8000000)
#use rs232(baud=9600, UART1)
void main(){
unsigned long i; //A long (16 bit) variable type is needed to hold ADC value
float degreesC; //degreesC is floating point number
signed int8 temp_eeprom;
signed int8 address=0;
setup_adc(ADC_CLOCK_INTERNAL);
setup_adc_ports(sAN0);
set_adc_channel(0);
delay_us(10);
while(1){
i = read_adc();
degreesC = (((float)i* 0.0048828) - 0.5)*100.0;
temp_eeprom = (int8)degreesC;
write_eeprom(address,temp_eeprom);
address = address + 1;
if (address > 199)address = 0;
printf("\r\nTemperature is:%f",degreesC);
delay_ms(5);
}
} |
|
|
 |
temtronic
Joined: 01 Jul 2010 Posts: 9408 Location: Greensville,Ontario
|
|
Posted: Fri Apr 02, 2021 9:02 am |
|
|
Do NOT code for the EEPROM writes and reads !
Get the read sensor/send correct data working properly first!!
Forget about floating point numbers, the 'math' takes a very,very long time and not accurate. You're far better to use 'scaled integers'. VERY fast (10-50x) and better accuracy.
If you keep testing r/w to the EEPROM, there is the probability that you'll destroy the device (EEPROM has a limited number of 'cycles')
Concentrate on getting/sending the temperature first ! ONCE that is done THEN add the 'save to EEPROM' and 'send to PC' functions. |
|
 |
Ttelmah
Joined: 11 Mar 2010 Posts: 19702
|
|
Posted: Fri Apr 02, 2021 10:57 am |
|
|
First thing to understand is that though you can read a value from the
sensor and ADC to a tiny fraction of a degree, this is completely pointless.
You are using your supply as your 'reference', so best case perhaps 1%
error. The sensor itself has a quoted accuracy of +/-2 degrees. So
setup your code to just return integer degrees. Anything else is just adding
extra complexity and gaining nothing.
Then the bytes stored are just simple degrees. No floating point needed or
wanted. The conversion for the reading is simply:
degrees=((adc_val*12)+6)/25;
This will give you a simple integer value to the limits of the accuracy you
can actually use, much faster than the floating maths (and much smaller). |
|
 |
djsb
Joined: 29 Jan 2011 Posts: 44
|
|
Posted: Thu Feb 13, 2025 11:37 am |
|
|
I'm trying a similar exercise. How can I use scaled integers to replace the floats in this code snippet
Code: |
//unsigned long value;
float adc_value = 0;
float voltage = 0;
float Temperature = 0; // MCP9700A Temp range -40 to +125 centigrade (100mV to 1.75V output).
const float DC_OFFSET = 0.5;
const float LSB_THREE = 0.0032258;//Using 10 bit ADC of PIC16F1847
const float LSB_FIVE = 0.0048875; //Using 10 bit ADC of PIC16F1847
const float LSB_ONE_V = 0.004;
const float LSB_TWO_V = 0.008;
const float LSB_FOUR_V = 0.016;
//setup_wdt(WDT_ON|WDT_1S); // Wakeup every second.
//while(TRUE)
// {
adc_value=read_adc();
Temperature = (((adc_value * LSB_FIVE) - DC_OFFSET)) * 100; // I'm using 5v supply. If using 3.3v change to LSB_THREE. Try the others listed
//LSB_TWO_V first.
voltage = adc_value * (5.0 / 1023);
printf("\r\nMCP9700A Voltage: %f", voltage);
printf("\r\nADC count is: %f",adc_value);
printf("\r\nCalculated temperature is: %f",Temperature);
delay_ms(500);
//Not figured out how this stuff (below) works yet.
//restart_wdt();// Restart watchdog at this point.
//Log_Data(); // Call your data logging function. Got to write it first though.
//printf("\r\nHello world");
//sleep(); // Go to sleep.
// }
}
|
I don't understand how
degrees=((adc_val*12)+6)/25;
can arrive at the correct result.
I also don't understand how the fixed voltage reference setting can interact with the calculation of the ADC result.
Please correct my misunderstanding, and as always I’m grateful for any help. Thanks. |
|
 |
temtronic
Joined: 01 Jul 2010 Posts: 9408 Location: Greensville,Ontario
|
|
Posted: Thu Feb 13, 2025 5:25 pm |
|
|
simple 'cheat'...use Google to find the website that has used PIC/MCP9700 and scaled integers ! I recall it's white on black background for the code so hard for my eyes to see..... |
|
 |
djsb
Joined: 29 Jan 2011 Posts: 44
|
|
Posted: Sat Feb 15, 2025 4:47 am |
|
|
How can this be implemented in CCS C code.
Quote: | The MCP9700A can output a voltage of 500mV + 10mv/°C*125°C = 1.75V. Your PIC has got a FVR Fixed Voltage Reference of 2.048V or 4.096V. If you set the FVR to 2.048V and use it as VREF each count of the ADC equals 2.048V/2^10 = 0.002V. So left shift the right justified ADC count by one bit and you get the voltage in mV. Easy?
Knowing that 10mV is 1°C you can divide by 10 and subtract the offset of 50 and have your °C. Or skip the costly division and work with units of 0.1°C.
|
This quote is from this post I made in the following EEVBLOG post
https://www.eevblog.com/forum/microcontrollers/mcp9700a-adc-reading-to-temp-conversion-without-floats-how/
I appreciate your help. Thank you. |
|
 |
|