How to acess shared memory between the PRU and Beagle Bone Black

Hi,

I need your help,

I am developing an application on the beagle bone for reading data for an SPI slave. For that, I have to use the PRU since the data transmission is up to 15 KHz.

Following this tutorial: https://markayoder.github.io/PRUCookbook/ , in the Chapter 5 - Memory allocation, it is explained how memory allocation works on the PRU, as well as how to store variables on specific memory addresses. But he doesn’t mention how do I get access to those memory addresses from a host code.

I did some researching on the internet and everything I can find is examples of getting access to specific memory adresses using the prussdrv, which I think no longer work in the latest Debian images ( I tried to use it and it trhowed an error from the very beginning when trying to open the drive (prussdrv_open()) . Can anyone give me an inkling on how can I achieve this?

Ps: Here’s an example using the prussdrv : http://catch22.eu/beaglebone/beaglebone-pru-ipc/

Best regards,
Fred Gomes

Can anyone explain to me why the below example does not work? I could confirm on the PRU side that I am writing correctly in the 0x10000 address, however, I can’t get the written value in the beagle bone side. It’s quite confusing to me, since if the memory is shared it should work.

PRU code:

#include <stdint.h>
#include <pru_cfg.h>
#include “resource_table_empty.h”

#define PRU_SHARED_MEM_ADDR 0x00010000

void main(void)
{
// enable OCP
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
volatile int* buffer = (volatile int *) PRU_SHARED_MEM_ADDR;
buffer[0] = 0xED;

/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port → Shared memory */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
}

Host C++ code:

#include <unistd.h>
#include <stdio.h>
#include <inttypes.h>
#include <prussdrv.h>
#include <pruss_intc_mapping.h>

using namespace std;

#define PRU_SHARED_MEM_ADDR 0x00010000

int main(int argc, char **argv)
{

volatile int* buffer = (volatile int *)PRU_SHARED_MEM_ADDR;

printf(“Memory address: %x\n”, &buffer);

return(0);
}

Regards,
Fred Gomes

<fred.p.gomes92@gmail.com> escreveu no dia terça, 20/11/2018 à(s) 17:13:

On the C-side, you need to find the address that maps the physical to logical address. The function “mmap” does that.
volatile int *buffer = (volatile int *)mmao( PRU_SHARED_ADDRESS, …
Check the man pages for how to use mmap.
Chad

Thank you very much for your answer to Chad,

I’ve tried this piece of code:

Arm side:

#define DDR_BASEADDR 0x10000
#define OFFSET_DDR 0x00

int main(int argc, char **argv)
{

int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);

if (mem_fd == -1){
printf(“Err”);
return 0;
}

volatile void *ddrMapp = NULL;
volatile void *DDR_regaddr1 = NULL;

ddrMapp = mmap(0, 0x0FFFFFFF, PROT_WRITE | PROT_READ, MAP_SHARED, mem_fd, DDR_BASEADDR);

DDR_regaddr1 = ddrMapp + OFFSET_DDR;

printf(“PRU address: %X\n”, &DDR_regaddr1);

return(0);
}

PRU side:

#include <stdint.h>
#include <pru_cfg.h>
#include “resource_table_empty.h”

#define PRU_SHARED_MEM_ADDR 0x00010000

void main(void)
{
// enable OCP
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
volatile int* buffer = (volatile int *) PRU_SHARED_MEM_ADDR;
buffer[0] = 0xED;

/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port → Shared memory */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
}

And the value returned from the printf is 0xBEFFFAFC , rather than 0x000000ED. Maybe I am pointing to the wrong physical address, do you know what am I doing wrong here?

Thank you very much,
Fred Gomes

Chad Baker <cmbaker3@gmail.com> escreveu no dia quarta, 21/11/2018 à(s) 16:55:

Hi Fred,

The problem is that
DDR_BASEADDR != PRU_SHARED_MEM_ADDR
Ie. The shared memory from the PRU’s view is 0x10000, but from the Arm host it is 0x4A300000.

Mark explains pru/host shared memory in a later part of chapter 5: https://markayoder.github.io/PRUCookbook/05blocks/blocks.html#_solution_4
“Controlling the PWM Frequency”

Thanks,
Alex

Thank you very much for your answer to Chad,

I’ve tried this piece of code:

Arm side:

#define DDR_BASEADDR 0x10000

0x10000 is the location only the PRU has for shared memory.

Change this to the value that the Arm sees in terms of PRU shared memory location.

1 Like

Hi Alex,

Thank you very much for your tip. I could already solve the problem.

Fred Gomes

Alex Bagehot <ceeaspb@gmail.com> escreveu no dia quinta, 22/11/2018 à(s) 13:46:

Very interesting. I could not make the SPI say a single word. :frowning: I had some leftover space

in a CPLD so I implemented a serial->par converter and now I read in my ADC bytewise

over R30 7 downto 0 in PRU 1. I now also get the 100 MBit/sec I originally wanted instead of 48 Mbit/s only.

The memory access works here this way:

ARM side:

int dev_mem_fd;
volatile int *shared_ram;
volatile int *pru_ram; // the 8 KB local data = 2 KWORDS

...

#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

 // map the shared ram into our virtual memory
 dev\_mem\_fd = open\(&quot;/dev/mem&quot;,O\_RDWR | O\_SYNC\);

 // mmap params:
 // addres where the kernel creates the mapping\. NULL= do as you like
 // size of the mapped region  12288 = 12 KBytes
 // protection
 // flags
 // file descriptor of /dev/mem
 // pru\-base  = 0x4A30\_0000, shared ram starts at \+0x10000

 shared\_ram = \(int \*\) mmap\(NULL, 12\*1024, PROT\_READ | PROT\_WRITE, MAP\_SHARED,
             dev\_mem\_fd, 0x4A300000 \+ 0x10000\);
 if \(\-1 == \(int\)shared\_ram\) panic\(&quot;could not mmap\(\) shared PRU ram&quot;\);

 // both PRU local data rams together
 pru\_ram = \(int \*\) mmap\(NULL, 2\*8\*1024, PROT\_READ | PROT\_WRITE, MAP\_SHARED,
             dev\_mem\_fd, 0x4A300000 \+ 0x00000\);
 if \(\-1 == \(int\)pru\_ram\) panic\(&quot;could not mmap\(\) local PRU rams&quot;\);

...
void copy_pru_ram_to_file(char *fn){
FILE * phyle;
int i, j;
phyle = fopen(fn, "w"); // FIXME return value
fprintf(phyle, "%s\n", fn);
fprintf(phyle, "byte index word index content all hex\n\n");
fprintf(phyle, "stack lowest and data nil pointer\n");
for (i=0; i< 4*1024; i++){ // 2 * 8 KB are 4 Kwords
if (0x100/4 == i) { fprintf(phyle, "\nstack highest - heap lowest\n"); }
else if (0x200/4 == i) { fprintf(phyle, "\nheap highest\n"); }
fprintf(phyle, "%8x %8x = %8x\n", 4*i, i, pru_ram[i]);
}
fclose(phyle); // FIXME return value
}

I access the few words that I need in the shared RAM simply as

#define COMMAND 5 /* offset for integers */

shared_ram[COMMAND] = .....

...

// something like this may be needed to configure the SPI pins:

 if \(system\(&quot;/usr/bin/config\-pin  p8\.45 pruin  2&gt; spitest\.log&quot;\)\) return 1;    // PRU1\.0      q0
 if \(system\(&quot;/usr/bin/config\-pin  p8\.46 pruin  2&gt; spitest\.log&quot;\)\) return 1;    // PRU1\.1      q1
 if \(system\(&quot;/usr/bin/config\-pin  p8\.43 pruin  2&gt; spitest\.log&quot;\)\) return 1;    // PRU1\.2      q2

...

...

PRU side: (probably you don't need everything)

#include <stdint.h>
#include <pru_cfg.h>
#include "resource_table_empty.h"

typedef struct {
int revision; // 0
int filler1 [3];
int sysconfig; // 0x10
int filler2[3];
int eoi; // 0x20
int irqstatus_raw_o;
int irqstatus_raw_1;
int irqstatus_0;
int irqstatus_1;
int irqstatus_set_0;
int irqstatus_set_1;
int irqstatus_clr_0;
int irqstatus_clr_1;
int irqwaken_0;
int irqwaken_1; // 0x48
int filler3[0x32];
int sysstatus; // 0x114
int filler4[0x06];
int ctrl; // 0x130
int oe;
int datain;
int dataout;
int leveldetect_0;
int leveldetect_1;
int risingdetect;
int fallingdetect;
int debounceEnable;
int debouncingtime; // 0x154
int filler5[0x0e];
int cleardataout; // 0x190
int setdataout; // 0x194
} gpioregs;

typedef struct { // the order MATTERS!!!
int revision; // 0
char filler1 [0x10c];
int sysconfig; // 0x110
int sysstatus; // 0x114
int irqstatus; // 0x118
int irqenable; // 0x11c
char filler2[4];
int syst; // 0x124
int modulctrl; // 0x128

 int ch0conf;            // 0X12C
 int ch0stat;            // 0x130
 int ch0ctrl;            // 0x134
 int tx0;                // 0x138   ch0 fifo transmit buffer register
 int rx0;                // 0x13c   ch0 fifo receive  buffer register

 int ch1conf;            // 0x140
 int ch1stat;
 int ch1ctrl;
 int tx1;
 int rx1;

 int ch2conf;            // 0x154
 int ch2stat;
 int ch2ctrl;
 int tx2;
 int rx2;

 int ch3conf;            // 0x168
 int ch3stat;
 int ch3ctrl;
 int tx3;
 int rx3;

 int xferlevel;            // 0x17c
 int daftx;                // 0x180  DMA address aligned FIFO transmitter reg
 char filler3\[28\];
 int dafrx;                // 0x1A0

} spiregs;

volatile spiregs * const spi0 = (volatile spiregs *) 0x48030000; // constants are stored at PRU-RAM offset 0x100
volatile spiregs * const spi1 = (volatile spiregs *) 0x481A0000; // offset 104

volatile gpioregs * const gpio0 = (volatile gpioregs *) 0x44E07000; // offset 108
volatile gpioregs * const gpio1 = (volatile gpioregs *) 0x4804C000; // user LEDs live here
volatile gpioregs * const gpio2 = (volatile gpioregs *) 0x481AC000;
volatile gpioregs * const gpio3 = (volatile gpioregs *) 0x481AE000;

// bit positions of the user LEDs in the GPIO 1 block. Calling them USER LEDs is outright wrong.
// You can use them anyway. Just don't get irritated when you don't expect them to light.

// USER0 is the heartbeat indicator from the Linux kernel.
// USER1 turns on when the SD card is being accessed
// USER2 is an activity indicator. It turns on when the kernel is not in the idle loop.
// USER3 turns on when the onboard eMMC is being accessed.

#define USR_LED0 (1<<21)
#define USR_LED1 (1<<22)
#define USR_LED2 (1<<23)
#define USR_LED3 (1<<24)

// GPIO, Clearing or setting takes about 40 nsec
#define PROG_ENA (1<<2)
#define USE_CHAN_B (1<<4)

// per default, SPI gets no power nor clock.
// spi clock control registers in power, reset & clock management
// CM_PER clock module peripheral registers

volatile unsigned int *const cm_per_spi0_clk_ctrl = (volatile unsigned int *)(0x44E00000 + 0x4c);
volatile unsigned int *const cm_per_spi1_clk_ctrl = (volatile unsigned int *)(0x44E00000 + 0x50);

volatile register unsigned int __R30; // CPU register R30 connects directly to some output pins
volatile register unsigned int __R31; // CPU register R31 connects directly to some input pins

// some highly visible data to find out where the C compiler stores it's stuff:
// variables in main end up on the stack. We only have 0x100 bytes by default.
// global variables are on the heap. Stack and heap are on the bottom of the PRU data RAM.

volatile int heapmarker = 0x22222222;
volatile char *bla = "HEAP @ @ @ ";
int i;
volatile int *pipo_pointer;
int pipo_offset;

// my communication registers between ARM CPU and the PRU
// The ARM deposits command codes in *command

volatile int * const command = (volatile int *) (0x10000 + 4*COMMAND); // start of shared data ram is 0x10000 PRU-local
volatile int * const status = (volatile int *) (0x10000 + 4*STATUS);
volatile int * const errcode = (volatile int *) (0x10000 + 4*ERRCODE);

....

void main(void) {
// so you can find it in a memory dump:
volatile int stackmarker = 0x11111111; // That ends up in PRUram, offset 0F8.
// The more variables one declares, the lower this sinks.
int buffered_cmnd;
int i;
volatile int stackmarker2 = 0x11111112;

 // Clear SYSCFG\[STANDBY\_INIT\] to enable OCP master port access by the PRU
 // so that the PRU can access external memories\. Each PRU has one OCP master port,
 // There is also an OCP slave port so that the ARM CPU can access the PRU local bus\.
 // OCP means open core protocol, a hardware module interface spec\.
 // I have the gut feeling that it may take some clocks to be effective\. \(race condition?\)

 CT\_CFG\.SYSCFG\_bit\.STANDBY\_INIT = 0;

 \* \(int \*\) 0        = 0xdeadbeef;            // the NIL pointer, that is pru data ram offset 0, stack segment
 shared\_ram\[0\]    = 0x01010101;            // That works\.

 \*status          = STAT\_INITIALIZING;      // until init is done
 \*command        = CMND\_NONE;
 \*ping\_full = 0;
 \*pong\_full = 0;
 gpio0\-&gt;setdataout     = USE\_CHAN\_B;            // use 24 bit ADC channel
 gpio0\-&gt;cleardataout    = PROG\_ENA;

.....

somewhere in my PRU command interpreter:

(someone else had problems accessing the LEDs from PRU)

        case CMND\_BLINK\_FAST:
         // USR3 LED is the one of the block of 4  closest to Ethernet connector
         // We do not have the user LEDs exclusively, this one also lights shortly for EMMC accesses\.

             \*status  = STAT\_BUSY;
             \*test1  = 999;            // I had doubts about the control flow \.\.\.  :\-\(
             for\(i=0; i&lt;500; i\+\+\) \{

                 if \(\*command == CMND\_ABORT\) break;
                 gpio1\-&gt;setdataout = USR\_LED3;
                 us\_delay\(50000\);                // 50 msec

                 gpio1\-&gt;cleardataout = USR\_LED3;
                 us\_delay\(50000\);
             \}

             \*status  = STAT\_RDY\_FOR\_CMND;
             \*command = CMND\_NONE;
             break;

Hope it helps & best regards,

Gerhard

Thank you very much for the feedback, Gerard. I already could get access to the shared memory and have tackled the problem.

However, I think I have not expressed myself correctly. I implemented an SPI Slave interface in the PRU because the Kernel driver only allows that as Master. But I have both SPI Master (using SPI1 / SPI0 pins) implemented and working, if you need some help on this matter I can do that :slight_smile: . The SPI slave is another mode in which I need to catch data from the sensor, which is the master.

Regards
Fred Gomes.

Gerhard Hoffmann <ghf@hoffmann-hochfrequenz.de> escreveu no dia quinta, 22/11/2018 à(s) 23:34: