1
\$\begingroup\$

Can anybody recommend a good resource/website for EXAMPLES of pic memory usage in C? I'm having difficulty following all the TBLPTR/TBLRD stuff without seeing it used in lots of different, simple applications. I'm surprised at how hard it is to find clear, BASIC examples of how to handle memory in PIC's.

Something like what a program would do with ADC results, storing in a table, choosing memory locations, etc.

\$\endgroup\$
  • 1
    \$\begingroup\$ Any C compiler worth its salt will take care of the intricacies of bank selection, TBLPTR, choosing memory locations etc. for you. \$\endgroup\$ – mlp Oct 2 '12 at 11:43
  • 1
    \$\begingroup\$ I agree. It's amazingly difficult to find good clear examples of this kind of thing for PICs. \$\endgroup\$ – Rocketmagnet Oct 3 '12 at 21:30
2
\$\begingroup\$

You're asking a very broad question; there are a wide variety of PICs, and I would probably not use "formal" memory management for any of them. There is no need.

A/D conversion results, filtering, UART buffers, etc. would normally be stored in plain old arrays in memory. You may augment them with head and tail pointers for ring buffers or index variables or other means, but your typical data manipulation techniques are no different for a PIC than you would have on a PC once you've got the address for the buffer. With small embedded microcontrollers you simply declare the array and use it.

Alternatively, you can work with the linker to artificially lower the amount of available memory that the system will see and then use the table read/write instructions and/or standard pointer accesses to hit this "hidden" memory. You reduce the memory the compiler can use so that the stack (which typically grows from the top of memory down) does not collide with your storage area, or BSS and the heap (which are usually located at the start of data memory) don't interfere with your memory area. The exact means to achieve this linker magic depends on the compiler suite of course.

If you could provide some more details, perhaps more specific application as well I could provide more specific answers. I think you are perhaps over-analyzing the issue. C is C; you can declare arbitrary memory and use it on a PC as well, so long as you don't run afoul of the MMU.

edit to add example ADC reading and averaging code:

I tend to write my code simply and clearly; I use small single-purpose functions and tend to write code as state machines. I also tend to leave optimization to the compiler unless I have a specific need to hand-optimize. This has come from years (almost two decades) of embedded design and learning things the hard way.

The code sets up a 1ms timer interrupt, two ADC channels and then samples them continuously; the readings are filtered using a simple sliding average filter. In this example, the data samples from each ADC channel are not stored individually. Each channel has its own running sum; the last 8 values (the FILTER_POINTS constant) are averaged and this filtered count value is stored. Care must be taken to make sure that the sum variable is big enough to store the number that can be created if 8 ADC samples are full-scale.

Finally, a temperature in 1/10 of a degree F, using a conversion function. The conversion function of course is specific to the sensors that I'm sampling. Your own conversion function(s) would have to be written to suit your sensors.

This is written for use with the CCS compiler; I do NOT like this compiler for a variety of reasons, but it's what most of my clients use because it's price is the lowest among its competitors.

#device PIC24F32KA304 ADC=12
#device ANSI
#include <24f32ka304.h>

#fuses OSCIO, NOPROTECT, NOWDT

#use delay(OSC=8MHz, CLOCK=32MHz)
#use standard_io(all)

#include <stdlib.h>
#include <math.h>
#include <stdio.h>


/* I hate mixed case types */
typedef BOOLEAN bool;


/* pin definitions */
#define PIN_FOO     (PIN_C8)
#define PIN_BAR     (PIN_A4)
#define PIN_BAZ     (PIN_A9)

#define VREFP       (0)
#define VREFN       (1)
#define CHAN1       (4)
#define CHAN2       (5)

#define FILTER_POINTS       (8)


/*
 * 10mV per degree F.
 * 2.048V reference, 4096 counts, for 0.5mV per count
 * therefore 10mV/0.5mV or 20 counts per degree F
 */
#define COUNTS_PER_DEGREE       (20)
#define COUNTS_PER_DEG_TENTHS       (COUNTS_PER_DEGREE / 10)


/* globals */
volatile bool one_ms_flag;
volatile bool onehundred_ms_flag;
volatile bool one_second_flag;

unsigned int32 chan1_sum, chan2_sum;        /* summing values for the averaging filter */
unsigned int16 chan1_counts, chan2_counts;  /* raw ADC values for temp sensors */
unsigned int16 chan1_temp, chan2_temp;      /* temperatures, in 0.1 degrees F steps */


/*
 * Timer ISR
 */
#INT_TIMER1 level=7
void system_isr(void)
{
    static int ticks = 0;

    one_ms_flag = TRUE;

    if ((ticks % 100) == 0) {
        onehundred_ms_flag = TRUE;
    }

    if (++ticks > 999) {

        one_second_flag = TRUE;
        ticks = 0;
    }
}


/*
 * stupid simple sliding average (FILTER_POINTS data points)
 * runsum is the running average (which is basically the sum of the last FILTER_POINTS values)
 * function returns the new average
 */
static unsigned int16 filter_analog(unsigned int32 *runsum, unsigned int16 new)
{
    *runsum -= *runsum / FILTER_POINTS;
    *runsum += new;

    return *runsum / FILTER_POINTS;
}



/*
 * counts are 0.5mV/count, and sensor is 10mV/oF
 * returns in 1/10 degrees (60F = 600)
 * rounds to half a degree
 */
int to_degrees_f(int counts)
{
    int deg_f;
    int tenths;

    deg_f = counts / COUNTS_PER_DEG_TENTHS;
    tenths = deg_f % 10;
    deg_f /= 10;
    deg_f *= 10;

    if (tenths >= 5) {
        deg_f += 5;
    }

    return deg_f;
}


/*
 * ADC loop
 * meant to be called every 100ms or so.
 * if reset is true, will reset the ADC state machine and reconfigure the ADC.
 */
void adc_loop(bool reset)
{
    static enum { ADC_INIT=0, SMPL_CHAN1, SMPL_CHAN2 } adc_state = ADC_INIT;
    unsigned int16 adc_value;

    if (reset) {
        adc_state = ADC_INIT;
    }

    switch(adc_state) {
    case ADC_INIT:
        setup_adc(ADC_CLOCK_DIV_128);
        setup_adc_ports(sAN0|sAN1, VREF_VREF);
        set_adc_channel(CHAN1);
        read_adc(ADC_START_ONLY);
        chan1_sum = chan2_sum = FILTER_SEED;
        chan1_counts = chan2_counts = 0;
        chan1_temp = chan2_temp = 0;
        adc_state = SMPL_CHAN1;
        break;

    case SMPL_CHAN1:
        adc_value = read_adc(ADC_READ_ONLY);
        chan1_counts = filter_analog(&chan1_sum, adc_value);
        chan1_temp = to_degrees_f(chan1_counts);
        set_adc_channel(CHAN2);
        read_adc(ADC_START_ONLY);
        adc_state = SMPL_CHAN2;
        break;

    case SMPL_CHAN2:
        adc_value = read_adc(ADC_READ_ONLY);
        chan2_counts = filter_analog(&chan2_sum, adc_value);
        chan2_temp = to_degrees_f(chan2_counts);
        set_adc_channel(CHAN1);
        read_adc(ADC_START_ONLY);
        adc_state = SMPL_CHAN1;
        break;

    default:
        adc_state = ADC_INIT;
    };
}


void main(void)
{

/* Set up timer1 for 1ms interrupts. Tcy = Fosc/2 = 16MHz, /64 is 4us. 250 4us ticks is 1ms. */
    setup_timer1(TMR_INTERNAL | TMR_DIV_BY_64, 250);

/* set up I/O */
    output_low(PIN_FOO);
    output_low(PIN_BAR);
    output_float(PIN_BAZ);

/* reset the ADC subsystem */
    adc_loop(TRUE);

/* variable setup */
    one_second_flag = onehundred_ms_flag = one_ms_flag = FALSE;

    enable_interrupts(INT_TIMER1);

/* main loop */
    while (1) {
        if (one_ms_flag) {
            /* do stuff every 1ms */

            one_ms_flag = FALSE;
        }

        if (onehundred_ms_flag) {
            adc_loop(FALSE);

            onehundred_ms_flag = FALSE;
        }

        if (one_second_flag) {
            /* do stuff every 1s */

            one_second_flag = FALSE;
        }
    }
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I posted some code over on another post: electronics.stackexchange.com/questions/42554/… I only needed to "handle" a small amount of sensor data but I'm so confused with the arrays and table pointers, banks, etc. I felt okay about it when I had an assembly class about a year ago but I figured C would be easier. If I wanted to store the data from the linked code into a table, how would you recommend I go about it since it's a growing dataset? \$\endgroup\$ – Mark Oct 2 '12 at 5:33
  • \$\begingroup\$ edited my answer to add an example program. I haven't compiled it but it should be pretty close to functional. It shows how to set up a main loop, perform time-driven sampling and averaging, and conversion from an ADC count value to something more meaningful to humans. Let me know if you have questions. \$\endgroup\$ – akohlsmith Oct 2 '12 at 7:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.