Best way to build a large array for access by PRU0?

Hello,

I’ve been working with PRU0 on the Beaglebone Black for a couple weeks now (only a beginner) and have made some hopeful progress towards developing a real time system that will change gpio logic states based on given location data from a pupil tracking system.

So far I’ve been using the examples given in the BeagleScope project (found here) to program the PRU using pru_rpmsg, and I’m currently using: Linux beaglebone 4.4.113-ti-r147 #1 SMP Tue Feb 20 20:58:22 UTC 2018 armv7l GNU/Linux

Things are going well!
But I wanted to ask the community about the simplest way to build a large 1-D array (look up table of ~310,000 indexes of just integers) that can be accessed by PRU0 on the fly to know how to set the gpios when it receives a new set of pupil location values. Previously I’ve built this array in userspace from an existing .txt file that is read byte by byte from a C script that builds the array, but I need that array to be accessible from PRU0. What is the simplest way to do this?

I’ve looked into trying to build the array in DDR memory (which could in theory be accessed by the PRU using address pointers), but allocating that kind of space to be available to a user space program looks pretty complicated (especially to ME who has never done any kernel space programming). I’m hoping that someone out there can give me inspiration for a simpler way to get this array to PRU0.

Please get back to me soon! Thank you for reading

When you allocate the array from user space the memory may be not continuous. To get a single block, you have to allocate from kernel space.

The most easy way: drop rproc, instead use libprussdrv. The uio_pruss kernel driver allocates external memory, which you can fill by the C code and read from the PRU code. (Note: PRU access to ARM memory isn’t as fast as DRam or SRam, needs at least 3 cycles - AFAIR.)

When you allocate the array from user space the memory may be not continuous. To get a single block, you have to allocate from kernel space.

This is not a true statement. The kernel uses virtual memory just like user space does. The memory is only contiguous in physical memory if you use kmalloc. If you use vmalloc, the memory can be fragmented in physical memory.

Anyway, this has nothing to do with rproc vs uio. You can do this with both.

Regards,
John

Oh, yes. John is right (as always). Here’s my corrected statement:

When you allocate the array from user space the memory may be not continuous. To get a single block, you have to allocate from kernel space by kmalloc.

Note @John:

A user dealing with real world problems doesn’t want to learn about kernel driver details. When he can solve a problem by a simple command line like

sudo modprobe uio_pruss extram_pool_sz=0x12500

he’ll prefer that solution.
``

Hi TJF,

I love the work you do and the advise you give. My only purpose was to remove any confusion :wink:

Regards,
John

Hi TJF,

I love the work you do and the advise you give. My only purpose was to remove any confusion :wink:

Regards,
John

If that is a fact, why don’t you replace phrasing like

This is not a true statement.

by wording like

I’d like to complete the statement.

Hello! Yes you are right! Something was wrong with my client and I only just now saw all of these replies!

I don’t understand why, but I was not receiving emails with updates on this post of mine :confused: I was getting email updates with daily summaries of other questions getting answered right away and I was beginning to feel discouraged. But somehow I just found all of these ! Thank you for all the replies everyone.

As far as using kmalloc and vmalloc inside kernel space, it definitely looks challenging, but kernel space programming is something I think I will want to learn someday so I’m not too intimidated to consider it. I read Derek Molloy’s 3 articles about loadable kernel modules on the beaglebone, and I thought they were really well done, but I wasn’t sure if I really knew everything I needed to know to make a kernel module that uses kmalloc/vmalloc. Does a kernel module that allocates this kind of memory in external ram need to happen in the boot up process? or will a run-time Loadable Kernel Module be able to do it? Any one have any good resources for a beginner to learn kernel space memory allocation?

I was originally trying to program the PRU using libprussdrv, but I was unable to make it work on 4.9… :frowning: prussdrv_open() would always fail me no matter what I tried. If anyone could tell me what I was doing wrong, I would definitely go back to libprussdrv because it made more intuitive sense to me than pru_rproc, but as of now pru_rproc (using 4.4) is the only way I’ve been able to get at my beaglebone’s PRUs! So I gotta ask, is there anything like the “sudo modprobe uio_pruss extram_pool_sz=0x12500”
solution that would work with pru_rproc? I definitely think that kind of command is idealic, I’m willing to switch to uio_pruss if I can use it and it works… but is there anything similar that works with pru_rproc? because that would save me a lot o f time and backtracking.

Thank you so much for reading!
Evan

Hello,

Thank you for all the help!

I have finally been able to get this array constructed by a user space C program into the external DDR ram, and access the array directly from PRU0 using uio_pruss! I wanted to give an update on what I did for anyone else who might try to do something similar in the future.

TJF and the others were right, I could not find an easy way to do this using rproc, but with uio_pruss it is fairly straight forward. While the suggestions made to help me get this going were surely valid, I found what seemed to be the simplest way for me using the example PRU_memAccess_DDR_PRUsharedRAM found in the am335x_pru_package which I just cloned from here.

I was unable to do this before because I did not set up my beaglebone for uio_pruss correctly, and would always get errors when using commands like prussdrv_open(). But from the suggestions in this post I was able to figure out what I was doing wrong, and got uio_pruss set up properly so that I could run this example. The example uses a c script to mmap() a block of memory in DDR and stores 3 values in the first 3 addresses of the mapped memory, then it programs the pru with an assembly file that is compiled with pasm, to simply configure OCP master port, configure one programmable pointer to point at the DDR memory where the 3 values are, and configure another programmable pointer to point to the pru’s shared RAM. The pru loads in the three values into 3 of its 32 registers, and then places them in the first 3 address of its 12kB shared RAM. Then the pru halts after interrupting the host c program that it finished, and the c program checks the 3 registers in the shared RAM to see if they are the same values that it originally stored in DDR RAM.

I was able to modify this example to mmap() a larger block of memory, and store the ~300,000 integer bytes into the DDR registers, then have the PRU check all of them and confirm that it is as it should be, then it loops and does what it needs to do set its GPIOs appropriately at very high frequency! It works great!

Some things I had to do to set up the executable:

  1. set up Beaglebone to enable uio_pruss
    -(This worked on several different linux images that I tried it on, but currently I’ve installed 4.4.88-ti-r125) in the uEnv.txt file, I added this line “dtb=am335x-boneblack-emmc-overlay.dtb”, and uncommented these lines “disable_uboot_overlay_video=1 disable_uboot_overlay_audio=1” but most importantly comment out “#uboot_overlay_pru=/lib/firmware/AM335X-PRU-RPROC-4-4-TI-00A0.dtbo” and uncomment “uboot_overlay_pru=/lib/firmware/AM335X-PRU-UIO-00A0.dtbo”. Then after rebooting you should see this if you enter lsmod | grep pru :
    uio_pruss 4629 0
    uio 11100 2 uio_pruss,uio_pdrv_genirq
    pru_rproc 15879 0
    pruss 12346 1 pru_rproc
    pruss_intc 9009 1 pru_rproc

-If there are any GPIOs that you want to give to the PRU, find the pins that will work using the BBB reference manual, and simply config-pin them for use by pru. for example " config-pin P9_31 pruout ", check that it worked with “config-pin -q P9_31”, it should print “P9_31 Mode: pruout”

  1. set up everything for the make files in the am335x_pru_package to work: (this only needs to be done once)
    This is example tutorial was very helpful, but I had to do a few things slightly differently:
    -before anything, type " export CROSS_COMPILE= "
    -cd to /am335x_pru_package/pru_sw/app_loader/interface and enter " make ". This creates 4 files - libprussdrv.a libprussdrvd.a libprussdrvd.so libprussdrv.so, copy them all into /usr/lib and enter " ldconfig "
    -cd to /am335x_pru_package/pru_sw/utils/pasm_source and enter " source ./linuxbuild " . This creates the pasm executable one directory up in /am335x_pru_package/pru_sw/utils , copy pasm into /usr/bin , and make sure it works by just typing " pasm " with no arguments. If it’s done correctly you will get a usage instruction statement because no arguments were given.

  2. now that the uio_pruss is working, and am335x_pru_package has been correctly “built”, the examples can be used to do all sorts of cool stuff by modifying .c scripts, .p (assembly) scripts, and .hp (assembly header) scripts. To compile an example or modified example do this:
    -cd /am335x_pru_package/pru_sw/example_apps and run first “make clean” and then “make”. All the examples will be compiled.
    -inside /am335x_pru_package/pru_sw/example_apps/bin are all the compiled binary files created from each example’s assembly code. Copy the one you want into /am335x_pru_package/pru_sw/example_apps//obj
    -compile the .o and .bin files within /obj by linking with a command like "gcc .o -L…/…/…/app_loader/lib -lprussdrv -lpthread -o myexecutable.out
    -This makes an executable myexecutable.out in the same /obj folder, which is ready to run with ./myexecutable.out !!! All done!

If anyone wants to know more about the ways I modified the .c script to mmap() a larger DDR memory block, or the .p script which tells the pru how to deal with a much larger memory block, I can also post code about that stuff as well.

I hope this is helpful to any beginners out there like me!
Thanks again for the help,
Evan

Hi Evan, thanks for the guide!

TJF and the others were right, I could not find an easy way to do this using rproc, but with uio_pruss it is fairly straight forward. While the suggestions made to help me get this going were surely valid, I found what seemed to be the simplest way for me using the example PRU_memAccess_DDR_PRUsharedRAM found in the am335x_pru_package which I just cloned from here.

I was unable to do this before because I did not set up my beaglebone for uio_pruss correctly, and would always get errors when using commands like prussdrv_open(). But from the suggestions in this post I was able to figure out what I was doing wrong, and got uio_pruss set up properly so that I could run this example. The example uses a c script to mmap() a block of memory in DDR and stores 3 values in the first 3 addresses of the mapped memory, then it programs the pru with an assembly file that is compiled with pasm, to simply configure OCP master port, configure one programmable pointer to point at the DDR memory where the 3 values are, and configure another programmable pointer to point to the pru’s shared RAM. The pru loads in the three values into 3 of its 32 registers, and then places them in the first 3 address of its 12kB shared RAM. Then the pru halts after interrupting the host c program that it finished, and the c program checks the 3 registers in the shared RAM to see if they are the same values that it originally stored in DDR RAM.

AFAIK, it’s not garantied that an mmap block of memory is contingouos. Have a look at mmap - Wikipedia

When you need a reliable solution, you’ve to use the external memory allocated from kernel space by the uio_pruss driver (as mentioned above)!

`
prussdrv_map_extmem(&ERam)
ESize = prussdrv_extmem_size()
EAddr = prussdrv_get_phys_addr(ERam)

`