Float to string using PIC16F1824 - c

I have some trouble using a PIC16F1824.
I want to convert an value retrieve from the ADC converter and transmit it to an UART.
I've used MCC to generate my code.
When I debug my code step by step, the voltage value is similar that the one I've obtained with my scope.
But when I want to print this value on the serial interface, I have an error with the argument %f (or %.2f) in printf.
C:\Program
Files\Microchip\xc8\v2.20\pic\sources\c99\common\nf_fputc.c:16::
warning: (1498) pointer (unknown) in expression may have no targets
C:\Program
Files\Microchip\xc8\v2.20\pic\sources\c99\common\doprnt.c:66:: error:
(1250) could not find space (80 bytes) for variable _dbuf (908) exit
status = 1
Here is my code
#include "mcc_generated_files/mcc.h"
void main(void) {
SYSTEM_Initialize();
uint16_t convertedValue, convertedValue2;
float voltage, voltage2;
float temp = 0;
volatile float sensorRH = 0;
char buffer[5];
while (1) {
// ADC_SelectChannel(channel_AN0);
// ADC_StartConversion();
// while (!ADC_IsConversionDone());
// convertedValue = ADC_GetConversionResult();
// voltage = convertedValue * 0.0048875;
// temp = (voltage2 - 0.424) / 0.00625; // LM60
ADC_SelectChannel(channel_AN2);
ADC_StartConversion();
while (!ADC_IsConversionDone());
convertedValue2 = ADC_GetConversionResult();
voltage2 = convertedValue2 * 0.0048875; // Tension 5V - RĂ©solution 10 bits (5/1023)
sensorRH = (voltage2 - 0.852) / 0.031483; // HIH-4000
printf("ADC converted value = %.2f\n", sensorRH);
sprintf(buffer, "%f",sensorRH);
}
}
I've also tried to :
include stdio lib : #include <stdio.h>
change the float value to a volatile float sensorRH;
declare my float outside the main function. This gave me an other error :
C:\Program
Files\Microchip\xc8\v2.20\pic\sources\c99\common\nf_fputc.c:16::
warning: (1498) pointer (unknown) in expression may have no targets
mcc_generated_files/eusart.c:64:: error: (1250) could not find space
(8 bytes) for variable _eusartTxBuffer (908) exit status = 1
try the sprintf function with different size of buffer (same error than the first one).
I'm using a PIC16F1824 with a PIC4kit debugger and XC8 compiler.
Thanks in advance

The error you get means you have either not enough RAM or Flash, or in other words you use too much space. A very simple way to reduce space by a small amount is by enabling optimizer. A way to reduce more is by writing code that does not require so much space. The printf() family takes a significant amount of space and floating point also. If you can do not use printf() or sprintf() and replace the floating point operations with integer operations, this should be possible but it may give you a bit less precise calculations which should not matter since your ADC is not very precise to begin with.

To address the diagnostic message from the compiler:
C:\Program Files\Microchip\xc8\v2.20\pic\sources\c99\common\nf_fputc.c:16:: warning: (1498) pointer (unknown) in expression may have no targets
C:\Program Files\Microchip\xc8\v2.20\pic\sources\c99\common\doprnt.c:66:: error: (1250) could not find space (80 bytes) for variable _dbuf (908) exit status = 1
The first one "warning: (1498)" is a generated because the XC8 compiler is stupid when handling what's sometimes called opaque pointers. But since this is in reference to Microchip library code that you should not change and the code is in fact correct just ignore this warning.
The second one "error: (1250)" is an error in your code. You have declared more object than there is available RAM for.
This is code I just created for you that uses only integer math:
#include "mcc_generated_files/mcc.h"
/*
Main application
*/
void main(void)
{
#define VREF_VALUE (500) // ADC VREF voltage in 1/100 of a volt
uint32_t convertedValue2;
uint16_t voltage2;
// initialize the device
SYSTEM_Initialize();
// When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
// Use the following macros to:
// Enable the Global Interrupts
//INTERRUPT_GlobalInterruptEnable();
// Enable the Peripheral Interrupts
//INTERRUPT_PeripheralInterruptEnable();
// Disable the Global Interrupts
//INTERRUPT_GlobalInterruptDisable();
// Disable the Peripheral Interrupts
//INTERRUPT_PeripheralInterruptDisable();
while (1)
{
// Add your application code
ADC_SelectChannel(channel_AN2);
__delay_ms(1000);
ADC_StartConversion();
while (!ADC_IsConversionDone());
convertedValue2 = ADC_GetConversionResult();
voltage2 = (uint16_t)( (convertedValue2 * VREF_VALUE + 0x8000ul) >> 16 );
printf("ADC voltage = %1u.%02u\r\n", voltage2/100, voltage2%100);
}
}
As you have not provided any details on the sensor that you are using I cannot show you the integer math to do scaling and offset specific to your sensor.
The complete MPLABX v5.40 project an be found on my git hub repository here.

Finally, I send my ADC values in millivolt over the UART and make the conversion on the PC software.
It's not really clean, but it do the job...
Thanks
int i = 0;
int j = 0;
while (1) {
ADC_SelectChannel(channel_AN2);
ADC_StartConversion();
while (!ADC_IsConversionDone());
convertedValue = ADC_GetConversionResult();
voltage = convertedValue * 0.0048875;
i = voltage * 100;
__delay_ms(500);
ADC_SelectChannel(channel_AN3);
ADC_StartConversion();
while (!ADC_IsConversionDone());
convertedValue2 = ADC_GetConversionResult();
voltage2 = convertedValue2 * 0.0048875;
j = voltage2 * 100;
printf("ADC voltage = %1u - %1u\r\n", i, j);
}

Thanks a lot for the answers.
I think I understand the advice, convert the value in integer and then divide it to get a float value in the serial interface.
I just have a problem with this line
voltage2 = (uint16_t)( (convertedValue2 * VREF_VALUE + 0x8000ul) >> 16 );
It give me something wrong
I'm using the sensors
LM60
HIH-4000
I've made the conversion with this code
voltage = convertedValue * 0.0048875; // VREF : 5V / 10 bits ADC
temp = (voltage - 0.424) / 0.00625; // LM60
sensorRH = (voltage - 0.852) / 0.031483; // HIH-4000
Thanks for your help

Related

Why does my audio output increase of 100Hz on each cycle?

I have a bug in my audio code.
Expected behavior: sinewave output, sweeping from 100Hz to 200Hz, resetting to 100Hz every second
Actual behavior: sinewave output, sweeping from 100Hz to 200Hz, but then rising 100Hz on each cycle, so on the second cycle it will sweep from 200Hz to 300Hz, then from 300Hz to 400Hz, and so on...
I'm generating a 1Hz rising sawtooth wave, and scaling and offsetting it so it rises from 100 to 200 every second. I'm also printing its value, which shows that it's behaving as expected.
But for some reason, if I use that value as frequency for my sinewave, the resulting sound rises 100Hz on each cycle.
Plugging a fixed frequency into my sinewave function works as expected.
It's only when I use the two together that I'm getting the bug. The thing I really can't explain is that the bug is only in the output audio -- the printed values are still all fine.
I'm using miniaudio as audio backend, and it's the only dependency. It should compile without errors nor warnings on Win, Linux and Mac.
It's a single header library, you only need to include miniaudio.h, so it should be easy to replicate.
Here is my code:
/*
compiling on Win10 with GCC:
gcc -g0 test_nodep.c -o test_nodep.exe -Wall -Wextra -Wshadow -Wvla -pedantic-errors -ansi
*/
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <stdint.h>
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h" /* https://github.com/mackron/miniaudio - single header file audio os backend library */
/* global variables */
int32_t DEVICE_FORMAT = ma_format_f32; /* 32-bit float */
int32_t DEVICE_CHANNELS = 1; /* mono */
int32_t DEVICE_SAMPLE_RATE = 48000;
float clock = 0;
float time = 0;
static __inline__ float tik(float interval, float len, float offset){
return (len<=0)*(fmod(time-offset, interval)==0) +
(len>0)*((fmod(time-offset, interval)>=0)&&(fmod(time-offset, interval)<=(len)));
}
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount){
float* Samples = pOutput;
ma_uint32 SampleIndex;
/* audio-callback variable definitions */
float test_saw;
float test_saw_freq = 1.f;
float i;
for(SampleIndex = 0; SampleIndex < frameCount; SampleIndex++){
test_saw = fmod(clock, (DEVICE_SAMPLE_RATE/test_saw_freq))/(DEVICE_SAMPLE_RATE/test_saw_freq); /* 1Hz rising saw, output range [0..1] */
test_saw = test_saw * 100.f + 100.f; /* shift range into [100..200] */
if(tik(.125f,0.f,0.f)){ /* this is to print the test_saw value every 1/8 of a second */
printf("== test_saw: %.2f", test_saw);
for(i=0.f;i<test_saw/10.f;i++){
printf(" ");
}
printf("%c\n", 254);
}
/* this is the output function, a sinewave, with frequency sweeping continuously from 100Hz to 200Hz */
/* f(t) = sin(2*PI * frequency + time) */
/* instead of a fixed frequency, I'm using test_saw, sweeping from 100Hz to 200Hz every second */
*Samples = (float)sin((double)(time * MA_TAU * test_saw));
/* using the same function with a fixed frequency works as expected, no problems */
/* *Samples = (float)sin((double)(time * MA_TAU * 100.f)); */
clock++;
clock*=(clock<FLT_MAX); /* continuously rising value, +1 on each sample, zeroes out when float is at its max value, to prevent float overflow */
time = clock/DEVICE_SAMPLE_RATE; /* same value, in seconds */
Samples++;
}
(void)pDevice;
(void)pInput;
}
int main(){
ma_device_config deviceConfig;
ma_device device;
/* audio output device configuration */
deviceConfig = ma_device_config_init(ma_device_type_playback); /* initialize for playback */
deviceConfig.playback.format = DEVICE_FORMAT;
deviceConfig.playback.channels = DEVICE_CHANNELS;
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
deviceConfig.dataCallback = data_callback;
/* audio output device initialization */
if(ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS){
printf("Failed to open playback device.\n");
return -4;
}
printf("== Device Name: %s\n", device.playback.name);
printf("== Sample Rate: %u Hz\n", DEVICE_SAMPLE_RATE);
if (ma_device_start(&device) != MA_SUCCESS) {
printf("== Failed to start playback device.\n");
ma_device_uninit(&device);
return -5;
}
printf("~~~ You should hear sound now ~~~\n");
printf("== Press Enter to quit...");
getchar();
ma_device_uninit(&device); /* turn off sound */
return 0;
}

"No source code lines were found at current PC 0x74e." with double in MPLAB X

Need some help with a simple calculation done on a PIC24FJ128GB204.
Using MPLAB X 5.2, XC16 compiler, ICD4.
I have a sensor that returns data in 6 bytes: [temp MSB][temp LSB][CRC][humidityMSB][humidityLSB][crc]
Formula for the temp (RC = raw counts): T(deg C) = -45 +175*(RC/(2^16-1))
As too often is the case, I had it working correctly, but somehow changed something and cannot find the issue.
My function:
int8_t SHT31_ConvertData( uint8_t MeasurementData[],
int16_t * TempResult,
uint8_t * RHResult){
// converts data from raw counts to temp (deg C) and RH (%)
// __attribute__((aligned(2))) double TempCelcius = 0;
double TempCelcius = 0;
uint16_t TempRaw,RHRaw = {0};
double RH = 0;
// combine both bytes to get raw temperature value
TempRaw = (MeasurementData[0] << 8);
TempRaw += MeasurementData[1];
TempCelcius = TempRaw;
TempCelcius /= 0xFFFF;
TempCelcius *= 175;
TempCelcius -= 45;
TempCelcius *= 100;
*TempResult = (int)TempCelcius;
// combine both bytes to get raw relative humidity value
RHRaw = (MeasurementData[3] << 8);
RHRaw += MeasurementData[4];
RH = RHRaw;
RH /= 0xFFFF;
RH *= 100;
*RHResult = RH;
return 1;
}
While debugging I sometimes (say 50% of the time) have an issue with the calculations, most often at the
TempCelcius = TempRaw; or TempCelcius /= 0xFFFF; lines.
The debugger console will have lines such as "No source code lines were found at current PC 0x74e. Open program memory view to see instruction code disassembly."
The instruction for that address in the program memory would be (for example) ADD W8, W8, W8. There would always be a command at that memory location.
I don't have enough background yet to solve this myself, so far I found the following possible causes:
Vcap with too high an ESR (checked)
WDT on (checked, HW also disabled in pragmas)
Unhandled interrupt (added _DefaultInterrupt with breakpoint to check)
Accessing word or long at an odd address (thus address error exception) (don't really understand this completely. Tried __attribute__((aligned(2))) double TempCelcius = 0; hoping to align.)
Division by 0 (not the case, I think)
Bad initialized pointers (given my proficiency in C, that could be the case, but I don't use pointers in that part of code)
SFR window open while debugging (is closed.)
What could be the issue? If necessary, will post pragmas, memory map, ...

sprintf re-entry into 64bit operation in 32 bit MCU with interrupts

In reference to SO question: 52164135
The setup:
I have a function which converts many double values into a predefined string. The input is a array of struct from which we concatenate two double values into a string. A double is of size 8 bytes or 64 bits and my MCU of operation is STM32, a 32 bit ARM micro-controller.
An interrupt is also running parallelly.
The data should look like:
[[12.11111111,12.11111111],[12.22222222,12.22222222],...]
But I get (very rarely):
[[12.11111111,12.11111111],[55.01[12.33333333,12.33333333],...]
Note: I missed out [12.22222222,12.22222222]
sprintf is not re-entrant:
According to this discussion, on AVRFreaks, sprintf is not re-entrant. (The discussion is on using sprintf in a interrupt enabled hardware environment.) Which means if an interrupt occurs in-between a sprintf operation the stack fails to continue the operation it was doing.
Since my MCU is a 32 bit one, to perform a 64 bit operation it will take two clock cycles. And if we assume an interrupt occurred in between the sprintf operation according the the above discussion sprintf should fail.
Question
1. Will sprintf fail in case it is interrupted?
Here is the string function, an interrupt routine also runs in the background which deals with other sensor data (local and global)
/* #brief From the array of GPS structs we create a string of the format
* [[lat,long],[lat,long],..]
* #param input The input array of GPS structs
* #param output The output string which will contain lat, long
* #param sz Size left in the output buffer
* #return 0 Successfully completed operation
* 1 Failed / Error
*/
int get_gps60secString(GPS_periodic_t input[GPS_PERIODIC_ARRAY_SIZE],
char *output, size_t sz)
{
int cnt = snprintf(output, sz, "[");
if (cnt < 0 || cnt >= sz)
return 1;
output += cnt;
sz -= cnt;
int i = 0;
for (i = 0; i < GPS_PERIODIC_ARRAY_SIZE; i++) {
cnt = snprintf(output, sz, "[%0.8f,%0.8f]%s",
input[i].point.latitude, input[i].point.longitude,
i + 1 == GPS_PERIODIC_ARRAY_SIZE ? "" : ",");
if (cnt < 0 || cnt >= sz)
return 1;
output += cnt;
sz -= cnt;
}
cnt = snprintf(output, sz, "]");
if (cnt < 0 || cnt >= sz)
return 1;
return 0; // no error
}
What's happening inside the interrupt routine
void GPS_InterruptHandler(UART_HandleTypeDef *UartHandle)
{
gps_UART_RxInterrupt_Disable();
GPS_t l_sGpsInfo;
memset(&l_sGpsInfo,0,sizeof(GPS_t));
status=Validate_get_gpsInfo((char*)g_gps_readBuff,&l_sGpsInfo,100);
MEMS_interruptHandler(); //Inertial sensor ISR
gps_UART_RxInterrupt_Enable();
}
sprintf will ony fail during an interrupt if it is called again during that interrupt (assuming it uses global variables that are re-used; would it use only stack variables, then it is re-entrant).
So if your interrupt handler is calling sprintf and during that call a new, same or higher priority interrupt occurs then it can fail. However, during the processing of an interrupt, interrupts are normally disabled so there can't (shouldn't!) be another interupt of the same type occurring.
But why convert this raw data during interrupt handling? Why not store/pass this data to the user-level routine via a buffer and have that functionality convert the raw data? That would be consistent with the idea that an interrupt handler should be as short (fast) as possible.

C changing variable is being stepped over

My issue arises with the variable total. The debugger steps over like it's not even there, or if you get rid of the *, it runs and runs and provides a weird number. The idea of the program is to add dimes (or 10) to the total until it is greater than the goal which is 10000.
I'm writing this in C with IAR Embedded Workbench and am using a MSP430G2553.
#include <msp430g2553.h>
#include <stdio.h>
volatile unsigned int i;
int dime=0;
int goalz =10000;
int main( void )
{
// Stop watchdog timer to prevent time out reset
WDTCTL = WDTPW + WDTHOLD;
P1DIR |= 0x00;
for(;;){
P1OUT =0x01;
while(1)
{
if((BIT4 & P1IN))
{
P1OUT |= 0x01;
dime++;
int *total = 0;
*total = &dime;
}
else
{
P1OUT |= 0x00;
}
}
}
}
int *total = 0;
*total = &dime;
that is wrong, because you're trying to store the address of dime (not its value) in the location pointed by total, i.e. zero => not what you want (someone suggests this is the location of a register, so even if it doesn't crash, it's not valid. Writing the INTENA register is not good!).
The debugger probably optimizes out this statement, writing directly in zero.
not completely sure of what you want to achieve, but you have to declare total as a global variable and add dime to it. No need to use pointers or variable addresses here.
Probably, the debugger is steps over since the compiler is not even generating the code. You are creating the variable total (pointer or integer) in the stack (i.e., you add an item), you assign a value, and then you are out of scope so the variable is not there anymore.
I think that what you are trying to achieve is:
if (...) {
static int total = 0;
total = dime
}
but, then again, the variable total is completely useless, since it will always be the same value as dime. How should be total and dime different?

calculation in C for C2000 device

I'm having some trouble with my C code. I have an ADC which will be used to determine whether to shut down (trip zone) the PWM which I'm using. But my calculation seem to not work as intended, because the ADC shuts down the PWM at the wrong voltage levels. I initiate my variables as:
float32 current = 5;
Uint16 shutdown = 0;
and then I calculate as:
// Save the ADC input to variable
adc_info->adc_result0 = AdcRegs.ADCRESULT0>>4; //bit shift 4 steps because adcresult0 is effectively a 12-bit register not 16-bit, ADCRESULT0 defined as Uint16
current = -3.462*((adc_info->adc_result0/1365) - 2.8);
// Evaluate if too high or too low
if(current > 9 || current < 1)
{
shutdown = 1;
}
else
{
shutdown = 0;
}
after which I use this if statement:
if(shutdown == 1)
{
EALLOW; // EALLOW protected register
EPwm1Regs.TZFRC.bit.OST = 1; // Force a one-shot trip-zone interrupt
EDIS; // end write to EALLOW protected register
}
So I want to trip the PWM if current is above 9 or below 1 which should coincide with an adc result of <273 (0x111) and >3428 (0xD64) respectively. The ADC values correspond to the voltages 0.2V and 2.51V respectively. The ADC measure with a 12-bit accuracy between the voltages 0 and 3V.
However, this is not the case. Instead, the trip zone goes off at approximately 1V and 2.97V. So what am I doing wrong?
adc_info->adc_result0/1365
Did you do integer division here while assuming float?
Try this fix:
adc_info->adc_result0/1365.0
Also, the #pmg's suggestion is good. Why spending cycles on calculating the voltage, when you can compare the ADC value immediately against the known bounds?
if (adc_info->adc_result0 < 273 || adc_info->adc_result0 > 3428)
{
shutdown = 1;
}
else
{
shutdown = 0;
}
If you don't want to hardcode the calculated bounds (which is totally understandable), then define them as calculated from values which you'd want to hardcode literally:
#define VOLTAGE_MIN 0.2
#define VOLTAGE_MAX 2.51
#define AREF 3.0
#define ADC_PER_VOLT (4096 / AREF)
#define ADC_MIN (VOLTAGE_MIN * ADC_PER_VOLT) /* 273 */
#define ADC_MAX (VOLTAGE_MAX * ADC_PER_VOLT) /* 3427 */
/* ... */
shutdown = (adcresult < ADC_MIN || adcresult > ADC_MAX) ? 1 : 0;
/* ... */
When you've made sure to grasp the integer division rules in C, consider a little addition to your code style: to always write constant coefficients and divisors with a decimal (to make sure they get a floating point type), e.g. 10.0 instead of 10 — unless you specifically mean integer division with truncation. Sometimes it may also be a good idea to specify float literal precision with appropriate suffix.

Resources