Welcome
Below is the code which shows how to read and write integers between the host ARM to and from shared memory inside the PRU. Thanks to Texane (Fabien le Mentec) and Owen who shared their code.
Prerequisites are:
- A Beaglebone of course
- The uio driver working, (see this post)
- The c compiler from TI (which still can be found stand-alone on the TI website)
- And some other files: AM335x_PRU.cmd (pick the right one, see remark below), bin.cmd
For convenience, a git repository has been created to download all code at once. This also enables further improvements by the community.
PRU C Code
The PRU code has now been updated with a much simpler code several times. Note the direct memory pointer, pointing to the first shared memory segment. See also this page describing some of the essentials of using c code for programming the PRU.
#include "pru_cfg.h" #define PRU_SHARED_MEM_ADDR 0x00012000 int main(void) { // direct pointer to memory address volatile int* shared_mem = (volatile int *) PRU_SHARED_MEM_ADDR; // enable OCP CT_CFG.SYSCFG_bit.STANDBY_INIT = 0; while(shared_mem[0]!=0xCAFEBABE) {} shared_mem[1]=0xDEADBEEF; while(1) {} __halt(); return 0; }
More brief explanation of what's going on in this code here could be at place. The PRU_SHARED_MEM_ADDR definition of 0x00012000 can be traced back to the pru reference manual, where it defines the start location of the shared memory segment. Next in the code, the volatile declaration of the shared_mem pointer is needed, as the memory content can be changed outside of what the code itself is doing. So in this case, the ARM host can update the memory, without the PRU knowing it. Depending on how compiler optimization is set (see the makefile below), it is possible the compiler optimizes the code to not poll the memory location over and over again, as it might think it useless to do when it did not change in the PRU code. The volatile keyword prevents these optimization errors. Next is the OCP enabling (see elsewhere), and further along in the code is the polling of the first memory location, and when it's condition is met, putting a value on the next memory location.
Host C Code
The host code makes use of the prussdrv or uio library to load the PRU code into memory, and also writes to values to shared memory, which is in return read by the PRU code. The code has also seen some changes in time, this is as of writing the latest version. Note the somewhat obscure 2048 offset, which is explained in the link in the code.
#include <stdio.h> #include <stdint.h> #include <unistd.h> #include <stdlib.h> #include "prussdrv.h" #include "pruss_intc_mapping.h" #define PRU_NUM 1 // define which pru is used #define SHM_OFFSET 2048 // http://www.embedded-things.com/bbb/understanding-bbb-pru-shared-memory-access/ int pru_init(void) { tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA; prussdrv_init(); if (prussdrv_open(PRU_EVTOUT_0)) { return -1; } else { prussdrv_pruintc_init(&pruss_intc_initdata); return 0; } } void pru_load(int pru_num, char* datafile, char* codefile) { // load datafile in PRU memory prussdrv_load_datafile(pru_num, datafile); // load and execute codefile in PRU prussdrv_exec_program(pru_num, codefile); } void pru_stop(int pru_num) { prussdrv_pru_disable(pru_num); prussdrv_exit(); } volatile int32_t* init_prumem() { volatile int32_t* p; prussdrv_map_prumem(PRUSS0_SHARED_DATARAM, (void**)&p); return p+SHM_OFFSET; } int main(void) { // initialize the PRU printf("pruss driver init (%i)\n", pru_init()); // load the PRU code (consisting of both code and data bin file). pru_load(PRU_NUM, "pru_data.bin", "pru_code.bin"); // get the memory pointer to the shared data segment volatile unsigned int* pruDataMem = init_prumem(); printf("sending CAFEBABE to PRU\n"); // write to shared data memory pruDataMem[0]=0xCAFEBABE; sleep(1); // read from shared data memory printf("PRU replies: %X\n", pruDataMem[1]); pru_stop(PRU_NUM); return 0; }
The pru_init and pru_load are fairly straightforward implementations which can be found on the TI site explaining the prussdrv driver. Note that the latter is a generic implementation of a PRU code and data loader. Please also beware of some other implementations where prussdrv_exec_program is modified in case the PRU code is not started at the beginning of PRU memory. This has to do with the absence of a specific instruction in the AM335x_PRU.cmd file, so please check or use the provided command file in the git code, which ensures the _c_int00 (or start of the code) starts at location 0x0. The init_prumem is the function which returns a pointer to the shared memory segment with the somewhat obscure offset of 2048. See the reference in the code for an explanation of this. Further up in the code, after initializing the PRU via the pru_init() function, loading the PRU code, also here the volatile declaration of the pointer variable pointing to the shared memory segment. The rest of the code should explain itself.
Makefile
The Makefile is also given here. Pay attention here how to name all the files and ensure all's there.
pru_options = --silicon_version=2 --hardware_mac=on -i/usr/include/arm-linux-gnueabihf/include -i/usr/include/arm-linux-gnueabihf/lib pru_compiler = /usr/bin/clpru pru_hex_converter = /usr/bin/hexpru all: exports host host: pru_data.bin pru_code.bin host.c gcc -std=gnu11 host.c -o host -lprussdrv -lrt # # Here's the PRU code generation part # exports: @export PRU_SDK_DIR=/usr @export PRU_CGT_DIR=/usr/include/arm-linux-gnueabihf pru.obj: pru.c $(pru_compiler) $(pru_options) --opt_level=off -c pru.c pru.elf: pru.obj $(pru_compiler) $(pru_options) -z pru.obj -llibc.a -m pru.map -o pru.elf AM335x_PRU.cmd --quiet pru_code.bin pru_data.bin: bin.cmd pru.elf $(pru_hex_converter) bin.cmd ./pru.elf --quiet clean: rm pru.obj rm pru.elf rm pru.map
Feedback?
Again, see the git repository to download all needed files and modify / branch if you have some improvements. Feel free to comment or give feedback on the beaglebone.org forum post about this code.