PRU programming from within bare-metal or Starterware application

There are many information about PRU usage out there but most of them expect to have a Linux-system running which initialises PRU. In case somebody wants to utilitise it out of a bare-metal or Starterware application one is lost in the dark. PRU-registers are documentend in AM3358 TRM but creating working code out of these specifications is not an easy thing. So after it is working for me I’ll provide all information here.

To create the code running on PRU I’m using TI’s C-compiler from http://software-dl.ti.com/codegen/non-esd/downloads/download.htm#PRU

Compilation of the source-file “main.c” is done by calling

clpru --silicon_version=3 -I/opt/ti/ccsv6/tools/compiler/ti-cgt-pru_2.1.0/include/ -I/opt/ti/ccsv6/tools/compiler/ti-cgt-pru_2.1.0/lib/ \ -o4 --opt_for_speed=5 main.c -z AM3359_PRU.cmd -o PRU_tests.out -m PRU_tests.map

This generates an ELF-file PRU_tests.out and a linker map file PRU_tests.map which is needed to evaluate the start-address of the PRU-code later.

Next split the ELF file into two separate sections for text (read-only, executable program code) and data (readable and writable but not executable):

hexpru bin.cmd PRU_tests.out

This results in two files text.bin and data.bin which later have to be loaded into instruction- and data-RAM of PRU. Now one can start with the code running on AM3358 that initialises PRU0. First some definitions are necessary that contain register-addresses:

`

#define HWREG(x) (*((volatile unsigned int *)(x)))

#define CM_PER_PRU_ICSS_CLKCTRL (0x000000E8u) // set to 0x00000002 to enable/wake up

#define CM_PER_PRU_ICSS_CLKSTCTRL (0x00000140u) // unset to 0x00000002 to enable/wake up
#define CM_PER_PRU_ICSS_CLKSTCTRL_UART_GCLK (0x00000040u) // activate UART clock
#define CM_PER_PRU_ICSS_CLKSTCTRL_IEP_GCLK (0x00000020u) // activate IEP clock
#define CM_PER_PRU_ICSS_CLKSTCTRL_OCP_GCLK (0x00000010u) // activate OCP clock

#define PRU_PHYS_BASE_CTRL 0x0000

#define PRUSS_CFG_BASE_SYSCFG 0x0004
#define PRUSS_CFG_BASE_GPCFG0 0x0008
#define PRUSS_CFG_BASE_GPCFG1 0x000C
#define PRUSS_CFG_BASE_CGR 0x0010
#define PRUSS_CFG_BASE_PIN_MX 0x0040
#define PRUSS_CFG_BASE_PMAO 0x0028
`

And the code that initialises PRU’s required clocks, copies text.bin and data.bin into related RAM-areas, specifies start-address taken out of MAP file and starts the whole thing:

`
volatile int i=0;

// reset the PRU, this may not be necessary in case of initial start-up
HWREG(SOC_PRM_PER_REGS)|=0x00000002;
while ((HWREG(SOC_PRM_PER_REGS) & 0x00000002)==0); //wait until reset was done
HWREG(SOC_PRM_PER_REGS)&=0xFFFFFFFD; // clear reset bit

// wake-up and nebale PRU, enable OCP-clock (mandatory)
// UART and IEP clock have to be enabled here too when needed
HWREG(SOC_CM_PER_REGS+CM_PER_PRU_ICSS_CLKCTRL)=0x00000002;
HWREG(SOC_CM_PER_REGS+CM_PER_PRU_ICSS_CLKSTCTRL)=(CM_PER_PRU_ICSS_CLKSTCTRL_OCP_GCLK);

// have a short delay before next step
while (i<10000)
{
i++;
}
HWREG(PRUSS_CFG_BASE+PRUSS_CFG_BASE_SYSCFG)=(0x00000005);
while ((HWREG(PRUSS_CFG_BASE+PRUSS_CFG_BASE_SYSCFG) & 0x00000020)!=0); // check wait state bit

// copy text and data into PRU0 instruction and data RAM
memcpy((void*)PRU0IRAM_PHYS_BASE,(void*)text_bin,sizeof(text_bin));
memcpy((void*)DATARAM0_PHYS_BASE,(void*)data_bin,sizeof(data_bin));

// set start address and execute
HWREG(PRU0CONTROL_PHYS_BASE+PRU_PHYS_BASE_CTRL)|=0x04200000; // set start address
HWREG(PRU0CONTROL_PHYS_BASE+PRU_PHYS_BASE_CTRL)|=0x00000002; // execute

`

The delay before writing 0x00000005 into SYSCFG-register is necessary for some reason, I don’t know if this is a good solution or if there is a wait-/ready-bit to be checked somewhere.

The start-address where execution of PRU-code has to begin can be found in created MAP-file. There an entry

00000420 _c_int00_noinit_noargs_noexit

can be found. _c_int00_noinit_noargs_noexit is the entry point for code generated with TI’s C-compiler and 00000420 is it’s address. It has to be shifted up by 16 bit and written into CTRL-register.

Any comments, ideas, improvements are welcome! And feel free to use this code without any restrictions.

As additional information: when using a linker command file (AM3359_PRU.cmd) with following content, entry point function is always located at address 0x0000 and it is not necessary to do the address initialisation described in last step above:

`
-cr
-stack 0x100
-heap 0x100

MEMORY
{
PAGE 0:
PRUIMEM: o = 0x00000000 l = 0x00001000 /* 8kB PRU0 Instruction RAM /
PAGE 1:
PRUDMEM: o = 0x00000000 l = 0x00001000 /
8kB PRU Data RAM 0 */
}

SECTIONS
{
GROUP: load = PRUIMEM
{
.text:_c_int00* :
.text :
}
.stack > PRUDMEM, PAGE 1
.bss > PRUDMEM, PAGE 1
.cio > PRUDMEM, PAGE 1
.const > PRUDMEM, PAGE 1
.data > PRUDMEM, PAGE 1
.switch > PRUDMEM, PAGE 1
.sysmem > PRUDMEM, PAGE 1
.cinit > PRUDMEM, PAGE 1
}
`

Hi Karl,
This is useful information. But can you tell how to actually load text_bin and data_bin into compiler?? Are you creating static table hardcoded into .c file or there is some easier way to do this?

Michal,

this is up to you. You can load binaries from files during runtime or convert the binaries to header data and use them as arrays directly.

I’m using second variant.

Karl

Hi

Another i use the following comand : ./hexpru ../bin.cmd
<path_directory_my_project/project.out>. This create 2 binary file:
text.bin and data.bin. Now, what i do to run my program in AM3359?

These two files belong to this part of code:

   // copy text and data into PRU0 instruction and data RAM
   memcpy((void*)PRU0IRAM_PHYS_BASE,(void*)text_bin,sizeof(text_bin));
   memcpy((void*)DATARAM0_PHYS_BASE,(void*)data_bin,sizeof(data_bin));

It is up to you how you load them, you can read them from flash and load
them into PRU memory or you can convert them into a headerfile containing a
large char-array which then is copied into PRU instruction/data RAM.

Hi,

where i create this file and how compile this file? Can you give me an example?

Thanks

Fabio

Please read the beginning of this thread, there exactly this is described!

Hi Karl,

thanks for your help. I write my code to run in PRU1 and i generate two file text.bin and data.bin. After i write file loader.c IN this file i write this:

#define HWREG(x) (*((volatile unsigned int *)(x)))
#define SOC_PRCM_REGS (0x44E00000)
#define SOC_PRM_PER_REGS (SOC_PRCM_REGS + 0xC00)
#define SOC_CM_PER_REGS (SOC_PRCM_REGS + 0)
#define PRUSS_CFG_BASE 0x4a326000
#define PRU0CONTROL_PHYS_BASE 0x4a322000
#define PRU0IRAM_PHYS_BASE 0x4a334000
#define DATARAM0_PHYS_BASE 0x01C30000
#define PRU_PHYS_BASE_CTRL 0x0000
#define CM_PER_PRU_ICSS_CLKCTRL (0x000000E8u) // set to 0x00000002 to enable/wake up
#define CM_PER_PRU_ICSS_CLKSTCTRL (0x00000140u) // unset to 0x00000002 to enable/wake up
#define CM_PER_PRU_ICSS_CLKSTCTRL_UART_GCLK (0x00000040u) // activate UART clock
#define CM_PER_PRU_ICSS_CLKSTCTRL_IEP_GCLK (0x00000020u) // activate IEP clock
#define CM_PER_PRU_ICSS_CLKSTCTRL_OCP_GCLK (0x00000010u) // activate OCP clock
#define PRUSS_CFG_BASE_SYSCFG 0x0004
#define PRUSS_CFG_BASE_GPCFG0 0x0008
#define PRUSS_CFG_BASE_GPCFG1 0x000C
#define PRUSS_CFG_BASE_CGR 0x0010
#define PRUSS_CFG_BASE_PIN_MX 0x0040
#define PRUSS_CFG_BASE_PMAO 0x0028

int main(])
{
volatile int i=0;

// reset the PRU, this may not be necessary in case of initial start-up
HWREG(SOC_PRM_PER_REGS)|=0x00000002;
while ((HWREG(SOC_PRM_PER_REGS) & 0x00000002) == 0); //wait until reset was done
HWREG(SOC_PRM_PER_REGS)&=0xFFFFFFFD; // clear reset bit

// wake-up and nebale PRU, enable OCP-clock (mandatory)
// UART and IEP clock have to be enabled here too when needed
HWREG(SOC_CM_PER_REGS+CM_PER_PRU_ICSS_CLKCTRL)=0x00000002;
HWREG(SOC_CM_PER_REGS+CM_PER_PRU_ICSS_CLKSTCTRL)=(CM_PER_PRU_ICSS_CLKSTCTRL_OCP_GCLK);

// have a short delay before next step
while (i<10000)
{
i++;
}

HWREG(PRUSS_CFG_BASE+PRUSS_CFG_BASE_SYSCFG)=(0x00000005);
while ((HWREG(PRUSS_CFG_BASE+PRUSS_CFG_BASE_SYSCFG) & 0x00000020)!=0); // check wait state bit

// copy text and data into PRU0 instruction and data RAM
memcpy((void*)PRU0IRAM_PHYS_BASE, (void*)text_bin, sizeof(text_bin));
memcpy((void*)DATARAM0_PHYS_BASE, (void*)data_bin, sizeof(data_bin));

// set start address and execute
HWREG(PRU0CONTROL_PHYS_BASE+PRU_PHYS_BASE_CTRL)|=0x04200000; // set start address
HWREG(PRU0CONTROL_PHYS_BASE+PRU_PHYS_BASE_CTRL)|=0x00000002; // execute

}

i have some question:

  1. Why you write in code text_bin, data_bin and not text.bin, data.bin?

  2. What this the command to compile this file(loader.c)?

  3. This file loader.c and text.bin and data.bin must be in the same directory?

  4. This file is correct or there are a errors?

Thanks

Fabio

Karl,
I am trying the similar configuration of programming PRU of AM3359 of beaglebone black. The ARM OS is QNX and hence I need to write the starter ware to work under QNX. I have followed the same procedure as specified, but I am getting into problem in enabling the PRU-ICSS module in CM_PER_PRU_ICSS_CLKCTRL. After writing a value of 0x02, I still not getting the module enabled. Before and after setting to 0x02, the value of the register is 0x40002. I know I am using the correct register since if I write 0x00, the read back value is 0x70000 which is correct. Is there anything else to be enabled/checked? I am debugging the code & hence there is sufficient delay.
Please suggest the reason for this kind of behavior. (I know the card is working since I have testing PRU using Angstrom, where it works fine!!)

Thanks,
Geetha

Sorry, I don’t understand what you are doing: are you using StarterWare or QNX? Only one is possible, both can’t work together.

When it is starterware: please check out the code posted earlier in this thread, it works smoothly with StarterWare.