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.

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: