This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM3358: Processors forum

Part Number: AM3358

I'm developing AD routines for PRU0 on a BeagleBone Black.

Using TI's sample code I have been able to read channel 0, aka AIN0 on the Black and send the data back to an ARM-side host program using RPMSG where it is converted to a voltage value and displayed on screen.  (I don't have a debug probe so I am doing this the hard way!).

I actually need to read three A/D inputs so first I modified the code to change the channel and read each one independently.  This just confirmed that I had the right configuration values for each channel I need to read. The data is going into FIFO0 based on the sample code.

Now, I want to set up two new steps to read channels 4 (AIN4) and 5 (AIN5) when channel 0 is read.   So I configured step2 to read channel 4 and step3 to read channel5.   I send the data for channel 4 to FIFO0 and for kicks decided to send the data for channel 5 to FIFO1 (really, just to see if I could, I'll leave channel 5 of this going forward).

So, two questions, the Section 12.5.1.10 of the TRM says that bit 1 controls whether the tag is written in the FIFO with the data.  However, it's confusing to me.  The field is called Step_ID_tag.  The description says "Writing 1 to this bit will store the Step ID number with the captured ADC data in the field.  0 - Write zeros. 1 = Store the channel ID tag."  So which is it?  Step ID or Channel ID? 

Also, how is the data stored in the FIFO?  Is it LSB first?  I sent the raw string back to my host program and output it as hex and couldn't really convert it.   I got 6 bytes (for example, 0x476b0) back.  No matter what the input voltage being measured, the data always ends with b0.   So it appears to be LSB first.  However, I'm not doing anything to 'invert' it however it must be getting inverted because my conversion to volts is accurate.  

I'm an old dog learning and relearning new tricks.  I may have forgotten something simple here so I'd appreciate your help very much!

  • Hi Walter,

    Thanks for posting-- I will route it to appropriate team to address this issue.

    Thanks

  • Walter

    I agree that the different wording on ADC sample tagging is confusing and I have submitted feedback for this to be corrected in the next revision of the TRM.

    The sample tag stores the Step ID. 

    0x0 = Step 1
    0x1 = Step 2
    ....
    0xE = Step 15
    0xF = Step 16

    The format of the FIFO data is defined in the FIFO0DATA and FIFO1DATA registers.  The tag is stored in ADCCHNLID which is FIFOxDATA[19:16]. 

    I can't see how the 0x476b0 is a valid value from the ADC.  Do you get any errors in the IRWSTATUS register?

    --Paul 

  • I'm not checking the IRWSTATUS register but I can.   The weird thing is that the data returned from the PRU to the host through RPMSG converts to the correct analog input reading but the characters coming through the RPMSG never change when printed out by the host.    I thought that maybe I was accidentally printing the address of the readbuf variable (which would never change) but I don't think so.

    Let me share my code for the PRU and the host side.  I would greatly appreciate a look at this for the problem.     

    For the PRU,  it's mostly based on TI's example code but I need to read three channels of A/D so you'll see some mess in there that should be commented out.

    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pru_cfg.h>
    #include <pru_intc.h>
    #include <rsc_types.h>
    #include <pru_rpmsg.h>
    #include "resource_table_0.h"
    #include "prugpio.h"
    #include "hw_types.h"
    #include "tsc_adc.h"
    #include "soc_AM335x.h"
    #include "hw_control_AM335x.h"
    #include "hw_cm_per.h"
    #include "hw_cm_wkup.h"


    static void ADCConfigure(void);
    static void GPIOConfigure(void);
    static void GetElapsedTime(void);

    volatile register uint32_t __R30;
    volatile register uint32_t __R31;

    /* Host-0 Interrupt sets bit 30 in register R31 */
    #define HOST_INT ((uint32_t) 1 << 30)

    /* The PRU-ICSS system events used for RPMsg are defined in the Linux device tree
    * PRU0 uses system event 16 (To ARM) and 17 (From ARM)
    * PRU1 uses system event 18 (To ARM) and 19 (From ARM)
    * Be sure to change the values in resource_table_0.h too.
    */
    #define TO_ARM_HOST 16
    #define FROM_ARM_HOST 17

    /*
    * Using the name 'rpmsg-pru' will probe the rpmsg_pru driver found
    * at linux-x.y.z/drivers/rpmsg/rpmsg_pru.c
    */
    #define CHAN_NAME "rpmsg-pru"
    #define CHAN_DESC "Channel 30"
    #define CHAN_PORT 30

    /*
    * Used to make sure the Linux drivers are ready for RPMsg communication
    * Found at linux-x.y.z/include/uapi/linux/virtio_config.h
    */
    #define VIRTIO_CONFIG_S_DRIVER_OK 4

    // EDEN Definitions, prefixed with E_

    #define E_NUM_SAMPLES 10

    // Global variables


    // char payload[RPMSG_BUF_SIZE];
    char payload[24];


    /*
    * main.c
    */
    void main(void)
    {
    struct pru_rpmsg_transport transport;
    uint16_t src, dst, len, CAValve;
    volatile uint8_t *status;
    volatile uint32_t led; // line to control LED

    // use pru0_pru_r30_15 (P8_11) for compressed air valve , 15th bit of __R30, 100000000000 or 0x8000
    CAValve = 0x8000;

    unsigned int i, count;
    unsigned int Data;
    //unsigned int DetTSample[E_NUM_SAMPLES]; // top detector data
    unsigned int DetBSample[E_NUM_SAMPLES]; //bottom detector data
    //unsigned int Pressure[E_NUM_SAMPLES]; // read pressure sensor
    //long SampleTime[E_NUM_SAMPLES]; // time sample was read

    // char temp[24];

    unsigned int sampleno;


    //uint32_t *gpio1 = (uint32_t *)GPIO1;

    ADCConfigure();
    GPIOConfigure();
    GetElapsedTime();

    /* Allow OCP master port access by the PRU so the PRU can read external memories */
    CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;

    /* Clear the status of the PRU-ICSS system event that the ARM will use to 'kick' us */
    CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;

    /* Make sure the Linux drivers are ready for RPMsg communication */
    status = &resourceTable.rpmsg_vdev.status;
    while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK));

    /* Initialize the RPMsg transport structure */
    pru_rpmsg_init(&transport, &resourceTable.rpmsg_vring0, &resourceTable.rpmsg_vring1, TO_ARM_HOST, FROM_ARM_HOST);

    /* Create the RPMsg channel between the PRU and ARM user space using the transport structure. */
    while (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS);
    while (1)
    {
    /* Check bit 30 of register R31 to see if the ARM has kicked us */
    if (__R31 & HOST_INT)
    {
    /* Clear the event status */
    CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;
    /* Receive all available messages, multiple messages can be sent per kick */
    while (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS)
    {

    if (!strcmp(payload,"MODE1")) // so if the payload is the instruction to read the analog sensors, do this
    {
    for (sampleno=0; sampleno<E_NUM_SAMPLES; sampleno++)
    {
    DetBSample[sampleno]=0;

    // Start step
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPENABLE) = 0xfe;

    // read channel AIN0, BBB & BBBw

    // Wait for interrupt
    while (!(HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQSTATUS)&0x02));

    // Clear interrupt
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQSTATUS) = 0x02;

    Data = 0xFFFFFFFF;

    count = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFOCOUNT(0));
    //sample = count;
    // reading ADC

    __R30 |= CAValve; // turn compressed air on, start fluid flow

    // __delay_cycles(100000000); // built in delay just to give time to see the LED



    for (i = 0; i < count; i++)
    {
    Data = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFODATA(0));

    // ltoa((long)Data, payload); // put the value in (long)DetBSample[sampleno] in the character string payload
    //strncat(payload,",0",4); // append the channel number to the payload with a comma to separate it from data

    if ((Data & 0x000F0000) == 0)
    { // checking the channel tag for channel 0
    // if channel == 0
    DetBSample[sampleno] = Data & 0xFFF;
    // DetBSample[sampleno] = Data;

    // strncat(payload,",0",4); // append the channel number to the payload with a comma to separate it from data
    }
    /*
    if ((Data & 0x000F0000) == 4) { // checking the channel tag for channel 4
    // if channel == 4
    DetTSample[sampleno] = Data & 0xFFF;
    ltoa((long)DetTSample[sampleno], payload); // put the value in (long)DetTSample[sampleno] in the character string payload
    strncat(payload,",4",4); // append the channel number to the payload with a comma to separate it from data
    }
    if ((Data & 0x000F0000) == 5) { // checking the channel tag for channel 5
    // if channel == 5
    Pressure[sampleno] = Data & 0xFFF;
    ltoa((long)Pressure[sampleno], payload); // put the value in (long)Pressure[sampleno] in the character string payload
    strncat(payload,",5",4); // append the channel number to the payload with a comma to separate it from data
    }*/

    __R30 &= ~CAValve; // using this for debugging as indicator that this loop is running
    }


    // Turn CA Valve off now, stops fluid flow

    // __R30 &= ~CAValve;

    // __delay_cycles(100000000); // built in delay just to give time to see the LED

    memcpy(payload, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 24);
    ltoa((long)DetBSample[sampleno], payload); // put the value in (long)DetBSample[sampleno] in the character string payload
    //len = strlen(payload) + 1;
    pru_rpmsg_send(&transport, dst, src, payload, 24);

    // __delay_cycles(5000000);
    }
    }
    else if (!strcmp(payload,"HI")) // For communications setup verification
    {
    strcpy(payload,"HI BACK\0"); // put the acknowledgement response in payload
    pru_rpmsg_send(&transport, dst, src, payload, 24); // send the response back
    }

    }
    }
    }
    }


    static void ADCConfigure(void)
    {
    unsigned int i, count, data;

    /* Enable ADC module clock */
    HWREG(SOC_CM_WKUP_REGS + CM_WKUP_ADC_TSC_CLKCTRL) = 0x02;

    /* Disable ADC module for configuration */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) &= ~0x01;

    /* fs = 24MHz / ((CLKDIV+1)*2*Channels*(OpenDly+Average*(14+SampleDly)))
    * = 53.57kHz
    * CLKDIV = 0
    * Channels = 1
    * Average = 16
    * OpenDly = 0
    * SampleDly = 0
    */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_ADC_CLKDIV) = 0;

    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_ADCRANGE) = 0xFFF << 16;

    /* Disable all steps for now */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPENABLE) &= 0xFF;

    /* Unlock step configuration */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) |= 0x04;

    // Step 1 config: SW mode, one shot mode,
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(0)) = 0x00000000; // AIN0, Bottom sensor, FIFO0
    // HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(0)) = 0x04000000; // AIN0, Bottom sensor, FIFO1
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPDELAY(0)) = 0xFF000000;

    // Step 2 config: SW mode, one shot mode
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(1)) = 0x00200000; // AIN4, Top sensor, FIFO0
    // HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(1)) = 0x04200000; // AIN4, Top sensor, FIFO1
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPDELAY(1)) = 0xFF000000;

    // Step 3 config: SW mode, one shot mode
    //HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPCONFIG(2)) = 0x00280000; // AIN5, Pressure sensor, FIFO0
    //HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_STEPDELAY(2)) = 0xFF000000;

    /* Enable channel ID tag */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) |= 0x02;

    /* Clear end-of-sequence interrupt */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQSTATUS) = 0x02;

    /* Enable end-of-sequence interrupt */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_IRQENABLE_SET) = 0x02;

    /* Lock step configuration */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) &= ~0x04;

    /* Empty FIFO 0 */
    count = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFOCOUNT(0));
    for (i = 0; i < count; i++) {
    data = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFODATA(0));
    }

    /* Enable ADC module */
    HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_CTRL) |= 0x01;
    }

    Host side: (just the section that reads and displays the values from the RPMSG for the mode that we define to read the analog inputs.)

    /* Open the rpmsg_pru character device file */
    pollfds[0].fd = open(DEVICE_NAME, O_RDWR);
    /*
    * If the RPMsg channel doesn't exist yet the character device
    * won't either.
    * Make sure the PRU firmware is loaded and that the rpmsg_pru
    * module is inserted.
    */
    if (pollfds[0].fd < 0) {
    printf("Failed to open %s\n", DEVICE_NAME);
    return -1;
    }

    /* The RPMsg channel exists and the character device is opened */
    printf("Opened %s, sending %d messages\n\n", DEVICE_NAME, NUM_MESSAGES);

    for (i = 0; i < NUM_MESSAGES; i++) {
    /* Send 'hello world!' to the PRU through the RPMsg channel */
    result = write(pollfds[0].fd, "MODE1", 13);
    if (result > 0)
    printf("Message %d: Sent to PRU\n", i);

    /* Poll until we receive a message from the PRU and then print it */
    result = read(pollfds[0].fd, readBuf, MAX_BUFFER_SIZE);
    if (result > 0)
    {
    Volts = atof(readBuf)*ADC_Res;
    printf("Message %d received from PRU:%s or %x\n", i, readBuf, readBuf);
    printf("Volts read: %.3f\n",Volts);

    }
    }

    /* Received all the messages the example is complete */
    printf("Received %d messages, closing %s\n", NUM_MESSAGES, DEVICE_NAME);

    /* Close the rpmsg_pru character device file */
    close(pollfds[0].fd);

    }

    Output example:

    1 - kick PRU and read value returned.
    1
    Opened /dev/rpmsg_pru30, sending 100 messages

    Message 0: Sent to PRU
    Message 0 received from PRU:3735 or 4b50b0
    Volts read: 1.641
    Message 1: Sent to PRU
    Message 1 received from PRU:3744 or 4b50b0
    Volts read: 1.645
    Message 2: Sent to PRU
    Message 2 received from PRU:3752 or 4b50b0
    Volts read: 1.649
    Message 3: Sent to PRU
    Message 3 received from PRU:3756 or 4b50b0
    Volts read: 1.651
    Message 4: Sent to PRU
    Message 4 received from PRU:3752 or 4b50b0
    Volts read: 1.649
    Message 5: Sent to PRU
    Message 5 received from PRU:3755 or 4b50b0
    Volts read: 1.650
    Message 6: Sent to PRU
    Message 6 received from PRU:3745 or 4b50b0
    Volts read: 1.646
    Message 7: Sent to PRU
    Message 7 received from PRU:3744 or 4b50b0
    Volts read: 1.645
    Message 8: Sent to PRU
    Message 8 received from PRU:3727 or 4b50b0
    Volts read: 1.638
    Message 9: Sent to PRU
    Message 9 received from PRU:3727 or 4b50b0
    Volts read: 1.638
    Message 10: Sent to PRU
    Message 10 received from PRU:3746 or 4b50b0

  • Hello Walter,

    We are not going to spend time going through your code at this point in time. Are you doing some sanity checks with your data? e.g., connect the ADC input to GND. Do you see the value change to almost 0? Connect the input to 1.8V. Do you see the value go to 4095?

    Based on sample pru_adc_userspace.c function convertVoltage, you should expect the raw ADC value passed to userspace to be 0-4095 for 0V - 1.8V.

    Regards,

    Nick

  • Fair enough.  I have done a lot of checking that the inputs are wired and configured correctly.  

    For example, the input is wired to a bench power supply that displays the output voltage to two decimal places.  The output of my host program matches that value as I increase and decrease the power supply setting, never going above 1.75V I might add because I know the AD can't handle more than 1.8, at least on the BeagleBone Black.

    The decimal value that is returned to the host program via the RPMSG transport changes as the input voltage is changed.  I can take that decimal value and perform the math to get the correct voltage ->  Decimal value returned/4096*1.8=correct voltage as input from the power supply.

    I have connected the input to GND and I get an output of almost if not always zero.   I did plug the input into the Beaglebone's VDD_ADC, which is 1.8VDC and I get 1.8V output (+/- a bit)

    I believe I am reading one channel of the AD correctly.   I need to read three and I can read each one independently.  Now, I need to read them in sequence (the code I posted does not do this yet).  I am trying to read the data in the FIFO, check the tag to know which step it came from and be able to apply logic based on that.   Passing this data to the host is important because we can then output it to some analysis software.  But right now, I'm not getting the tag.

    The problem I am having is with RPMSG and/or understanding FIFO & the step tag.   

    TI's example code is the basis for this snippet I have put in place...I have modified it to put the Data into an array.  The index to this array is managed prior to this snippet.  

    For (i = 0; i < count; i++)
    {
    Data = HWREG(SOC_ADC_TSC_0_REGS + TSC_ADC_SS_FIFODATA(0));

    if ((Data & 0x000F0000) == 0)
    { // checking the channel tag for channel 0
    // if channel == 0
    DetBSample[sampleno] = Data & 0xFFF;

    So I would expect that if I did not AND Data with 0xFFF, then the step id tag would not be stripped off.  And that I could pass that and the data through RPMSG to the host as one string of data.

  • Hello Walter,

    Looking at the Touchscreen Controller TRM chapter > Registers > FIFO0DATA register, you are correct that channel tag would be in bits 19-16, and ADC data would be stored in bits 11-0.

    Did you make sure to enable the tag option? Otherwise, ADCCHNLID will always be 0.

    Regards,

    Nick

  • I believe the tag option is enabled.  I'm debugging without the benefit of a debug probe so I have to send values back to the host to read them.  I'm working on it and think my issue with not seeing it on the host side is my incorrect use of the printf statement.  I think it's printing the address of the buffer instead of the values in it.   

    Quick, related question.  If I set up the steps so that step 1 reads analog channel 0 and step 2 reads analog channel 4 and step 3 reads analog channel 5.  With all the other parameters the same (one-shot, FIFO0, etc.) and the data going into FIFO0, is it safe to assume that if only steps 1,2 and 3 are enabled, then the data coming out of FIFO0 will be from 1,2, and 3 in order?   I'll read the tags to be sure but I just want to know if this is how it should work.

    Basically, I have to take these three readings and send them to the host so that they can be formatted into a CSV file for upload to analysis software.  Knowing they are always in this order, will make some of that programming easier.  Although I don't usually assume anything.  I know it can be dangerous to do so.

  • Hello Walter,

    That is correct. Reference AM335x TRM, Chapter "Touchscreen Controller", Section "Operational Modes" for more information.

    Regards,

    Nick