I2C driver by PRU

I want to manage the I2C1 module by the PRU, in order to interface some I/O expanders (MCP23017 by Microchip).
I use may own “cape” without the plug-n’ play eeprom (one of the next steps will be adding management for DCAN0 and DCAN1 so i’ll need these pins too…).
So, at present, there are just 2 MCP23017 connected to the P9.17 and P9.18.

I load the cape-universal into slots and then I use configure-pin command to set P9.17 and P9.18 as i2c

I’ve started from an example into the am335x_pru_package-master and wrote my own C PRU loader.
Very simple, it just:

  • loads the PRU code
  • init the data exhanged with the PRU
  • start the PRU
  • wait for ESC key press
  • signal to the PRU to stop
  • wait for the PRU stop
  • exit.

Also the assembly PRU code is simple:

  • init I2C1 module (by writing registers PSC, SCLL, SCLH, CON)
  • init 1st I/O expander as 16 inputs (even if at power on it’s already set as input)
  • init 2nd I/O expander as 16 outputs
  • cicle reading status of inputs from 1st expander and echoing to the outputs of 2nd expander
  • exit cycle and halt when receive stop flag from the loader

for send and receive I2C messages I use register SA, CNT, DATA and CON.

That’s very simple and linear… pity, it doesn’t work.

I didn’t see any activity at all in P9.17 and P9.18.

The PRU code is surely running, as I add a cycle counter and show it in the loader while it’s waiting for ESC keypress, and also the PRU code correctly stops at the loader command.

I was expecting that the PRU code stalls if I2C bus doesn’t work, as there are waiting cycles both for STOP condition or for CNT reaching zero (depending on the write or read message sending).

But it seems running, and running very fast also: the cycle counter is incremented to a very fast rate (over 550 kcycles/s) that’s not compatible with the correct executing of I2C sequences (I’ve setted the module for 400Kbps rate… so the PRU cycle it’s even faster than a single I2C bit time!).

I’m surely doing something wrong, but I cant fugure what.

Any idea?

Suggestions?

Inserisci qui il codice...
.origin 0
.entrypoint START

#include “iic_ioexp.hp”

//costanti per l’accesso al modulo I2C1
#define I2C1_BASE C2 //base registri I2C1 nella tabella costanti
#define I2C_SYSC 0x10 //offset del registro I2C_SYSC
#define I2C_STAT_RAW 0x24 //offset del registro I2C_STATUS_RAW
#define I2C_SYSS 0x90 //offset del registro I2C_SYSS
#define I2C_CNT 0x98 //offset del registro I2C_CNT
#define I2C_DATA 0x9C //offset del registro I2C_DATA
#define I2C_CON 0xA4 //offset del registro I2C_CON
#define I2C_SA 0xAC //offset del registro I2C_SA
#define I2C_PSC 0xB0 //offset del registro I2C_PSC
#define I2C_SCLL 0xB4 //offset del registro I2C_SCLL
#define I2C_SCLH 0xB8 //offset del registro I2C_SCLH

#define I2C_CMD_ENABLE 0x8400 //modulo I2C abilitato come master
#define I2C_CMD_TX 0x0200 //modulo I2C in trasmissione
#define I2C_CMD_RX 0x0000 //modulo I2C in ricezione
#define I2C_CMD_START 0x0001 //modulo I2C richiesta generazione sequenza START
#define I2C_CMD_STOP 0x0002 //modulo I2C richiesta generazione sequenza STOP

//costanti per l’accesso all’I/O expander MCP23017
#define IO_EXP0 0x20 //7bit I2C address dell’I/O expander 0 (ingressi)
#define IO_EXP1 0x21 //7bit I2C address dell’I/O expander 1 (uscite)

#define IO_EXP_IODIRA 0x00 //indirizzo registro IODIRA dell’I/O expander
#define IO_EXP_GPIOA 0x12 //indirizzo registro GPIOA dell’I/O expander

//======================================================================

//macro che attende fine sequenza verificando generazione seqeunza di STOP
.macro I2C_WAIT_BY_STOP
_CHECK:
LBCO r1.w0, I2C1_BASE, I2C_CON, 2
QBBS _CHECK, r1.t1
.endm

//macro che attende fine sequenza verificando generazione seqeunza di STOP
.macro I2C_WAIT_BY_COUNT
_CHECK:
LBCO r1.w0, I2C1_BASE, I2C_CNT, 2
QBNE _CHECK, r1.w0, 0
.endm

//======================================================================

START:
// clear that bit
LBCO r0, C4, 4, 4
CLR r0, r0, 4
SBCO r0, C4, 4, 4

//------------------------------------------------------------------
//configurazione modulo I2C1

//reset del modulo I2C1
MOV r1.w0, 0x0002
SBCO r1.w0, I2C1_BASE, I2C_SYSC, 2

MOV r1.w0, 0x0000
SBCO r1.w0, I2C1_BASE, I2C_SYSC, 2

// //attesa fine reset modulo
//_WAIT_RDONE:
// LBCO r1, I2C1_BASE, I2C_SYSS, 4
// QBBC _WAIT_RDONE, r1.t0

//configura prescaler e durata SCL H/L per avere 400kHz
MOV r1.b0, 4 //prescaler=4+1 → ICLK=SCLK/prescaler=100/5=20MHz (nel reference manual raccomandano circa 24Mhz)
SBCO r1.b0, I2C1_BASE, I2C_PSC, 1

MOV r1.b0, 18 //durata SCL L=18+7 → tLOW=1/ICLK*(SCLL+7)=1/20E6*(18+7)=1.25us
SBCO r1.b0, I2C1_BASE, I2C_SCLL, 1

MOV r1.b0, 20 //durata SCL H=20+5 → tHIGH=1/ICLK*(SCLH+5)=1/20E6*(20+5)=1.25us
SBCO r1.b0, I2C1_BASE, I2C_SCLH, 1

//abilita modulo
MOV r1.w0, I2C_CMD_ENABLE
SBCO r1.w0, I2C1_BASE, I2C_CON, 2

//------------------------------------------------------------------
//inizializzazione IOEXP 0

//indirizzo slave
MOV r1.w0, IO_EXP0
SBCO r1.w0, I2C1_BASE, I2C_SA, 2

//n. byte da spedire
MOV r1.w0, 3
SBCO r1.w0, I2C1_BASE, I2C_CNT, 2

//riempie FIFO
MOV r1, IO_EXP_IODIRA | 0x00FFFF00 //tutti i pin come ingressi (in realta’ e’ gia’ cosi’ dal POR)
SBCO r1.b0, I2C1_BASE, I2C_DATA, 1
SBCO r1.b1, I2C1_BASE, I2C_DATA, 1
SBCO r1.b2, I2C1_BASE, I2C_DATA, 1

//attesa bus free
_WAIT_BB:
LBCO r1, I2C1_BASE, I2C_STAT_RAW, 4
QBBS _WAIT_BB, r1.t12

//comando scrittura
MOV r1.w0, I2C_CMD_ENABLE | I2C_CMD_TX | I2C_CMD_START | I2C_CMD_STOP
SBCO r1.w0, I2C1_BASE, I2C_CON, 2

I2C_WAIT_BY_STOP

//------------------------------------------------------------------
//inizializzazione IOEXP 1

//indirizzo slave
MOV r1.w0, IO_EXP1
SBCO r1.w0, I2C1_BASE, I2C_SA, 2

//n. byte da spedire gia’ impostato

//riempie FIFO
MOV r1, IO_EXP_IODIRA | 0x00000000 //tutti pin come uscita
SBCO r1.b0, I2C1_BASE, I2C_DATA, 1
SBCO r1.b1, I2C1_BASE, I2C_DATA, 1
SBCO r1.b2, I2C1_BASE, I2C_DATA, 1

//comando scrittura
MOV r1.w0, I2C_CMD_ENABLE | I2C_CMD_TX | I2C_CMD_START | I2C_CMD_STOP
SBCO r1.w0, I2C1_BASE, I2C_CON, 2

I2C_WAIT_BY_STOP

//------------------------------------------------------------------
//ciclo rinfresco I/O

_LOOP:

//------------------------------------------------------------------
//legge ingressi

//indirizzo slave
MOV r1.w0, IO_EXP0
SBCO r1.w0, I2C1_BASE, I2C_SA, 2

//n. byte da spedire
MOV r1.w0, 1
SBCO r1.w0, I2C1_BASE, I2C_CNT, 2

//riempie FIFO
MOV r1, IO_EXP_GPIOA
SBCO r1.b0, I2C1_BASE, I2C_DATA, 1

//comando scrittura
MOV r1.w0, I2C_CMD_ENABLE | I2C_CMD_TX | I2C_CMD_START
SBCO r1.w0, I2C1_BASE, I2C_CON, 2

I2C_WAIT_BY_COUNT

//n. byte da ricevere
MOV r1.w0, 2
SBCO r1.w0, I2C1_BASE, I2C_CNT, 2

//comando lettura
MOV r1.w0, I2C_CMD_ENABLE | I2C_CMD_RX | I2C_CMD_START | I2C_CMD_STOP
SBCO r1.w0, I2C1_BASE, I2C_CON, 2

I2C_WAIT_BY_STOP

//legge i byte ricevuti
LBCO r2.b1, I2C1_BASE, I2C_DATA, 1
LBCO r2.b2, I2C1_BASE, I2C_DATA, 1

//------------------------------------------------------------------
//scrive uscite

//indirizzo slave
MOV r1.w0, IO_EXP1
SBCO r1.w0, I2C1_BASE, I2C_SA, 2

//n. byte da spedire
MOV r1.w0, 3
SBCO r1.w0, I2C1_BASE, I2C_CNT, 2

//riempie FIFO
MOV r2.b0, IO_EXP_GPIOA
SBCO r2.b0, I2C1_BASE, I2C_DATA, 1
SBCO r2.b0, I2C1_BASE, I2C_DATA, 1
SBCO r2.b0, I2C1_BASE, I2C_DATA, 1

//comando scrittura
MOV r1.w0, I2C_CMD_ENABLE | I2C_CMD_TX | I2C_CMD_START | I2C_CMD_STOP
SBCO r1.w0, I2C1_BASE, I2C_CON, 2

I2C_WAIT_BY_STOP

//------------------------------------------------------------------
LBCO r2, CONST_PRUDRAM, 0, 4
ADD r2, r2, 1
SBCO r2, CONST_PRUDRAM, 0, 4

//------------------------------------------------------------------
//esce dal ciclo e se il flag di uscita nella ram condivisa e’ azzerato
LBCO r2, CONST_PRUDRAM, 4, 1
QBNE _LOOP, r2.b0, 0

_EXIT:
//invia all’host la notifica di programma completato
MOV R31.b0, PRU0_ARM_INTERRUPT+16

HALT

I’ve also posted this in I2C topic, the solution is there:

http://beagleboard.org/Community/Forums/?place=msg%2Fbeagleboard%2FDAXyYJOrDIc%2FDZ8WKkRWaC0J

Hi,

I’m interested by what you have done. I want to use i2c to read analog value from a component.

The first solution that I found was to bitbang the i2c. But you, you use the i2c driver which is nice.

Could you give us the peace of asm code that you use to interface with the MCP23017. And if you have the c code also it would be great.

( if possible, also the part of you managed to activate the module by writing MODULEMODE field into register CM_PER_I2C1_CLKCTRL register and also the code that wait for IDLEST field to confirm that module is ready).

Micka,

I’ve also posted this in I2C topic, the solution is there:

http://beagleboard.org/Community/Forums/?place=msg%2Fbeagleboard%2FDAXyYJOrDIc%2FDZ8WKkRWaC0J

I want to manage the I2C1 module by the PRU, in order to interface some I/O expanders (MCP23017 by Microchip).
I use may own “cape” without the plug-n’ play eeprom (one of the next steps will be adding management for DCAN0 and DCAN1 so i’ll need these pins too…).
So, at present, there are just 2 MCP23017 connected to the P9.17 and P9.18.

I load the cape-universal into slots and then I use configure-pin command to set P9.17 and P9.18 as i2c

I’ve started from an example into the am335x_pru_package-master and wrote my own C PRU loader.
Very simple, it just:

loads the PRU codeinit the data exhanged with the PRUstart the PRUwait for ESC key presssignal to the PRU to stopwait for the PRU stopexit.

Also the assembly PRU code is simple:

init I2C1 module (by writing registers PSC, SCLL, SCLH, CON)init 1st I/O expander as 16 inputs (even if at power on it’s already set as input)init 2nd I/O expander as 16 outputscicle reading status of inputs from 1st expander and echoing to the outputs of 2nd expanderexit cycle and halt when receive stop flag from the loader

for send and receive I2C messages I use register SA, CNT, DATA and CON.

That’s very simple and linear… pity, it doesn’t work.

I didn’t see any activity at all in P9.17 and P9.18.

The PRU code is surely running, as I add a cycle counter and show it in the loader while it’s waiting for ESC keypress, and also the PRU code correctly stops at the loader command.

I was expecting that the PRU code stalls if I2C bus doesn’t work, as there are waiting cycles both for STOP condition or for CNT reaching zero (depending on the write or read message sending).

But it seems running, and running very fast also: the cycle counter is incremented to a very fast rate (over 550 kcycles/s) that’s not compatible with the correct executing of I2C sequences (I’ve setted the module for 400Kbps rate… so the PRU cycle it’s even faster than a single I2C bit time!).

I’m surely doing something wrong, but I cant fugure what.

Any idea?

Suggestions?

Inserisci qui il codice…

.origin 0
.entrypoint START

#include “iic_ioexp.hp”

//costanti per l’accesso al modulo I2C1
#define I2C1_BASE C2 //base registri I2C1 nella tabella costanti
#define I2C_SYSC 0x10 //offset del registro I2C_SYSC
#define I2C_STAT_RAW 0x24 //offset del registro I2C_STATUS_RAW
#define I2C_SYSS 0x90 //offset del registro I2C_SYSS
#define I2C_CNT 0x98 //offset del registro I2C_CNT
#define I2C_DATA 0x9C //offset del registro I2C_DATA
#define I2C_CON 0xA4 //offset del registro I2C_CON
#define I2C_SA 0xAC //offset del registro I2C_SA
#define I2C_PSC 0xB0 //offset del registro I2C_PSC
#define I2C_SCLL 0xB4 //offset del registro I2C_SCLL
#define I2C_SCLH 0xB8 //offset del registro I2C_SCLH

#define I2C_CMD_ENABLE 0x8400 //modulo I2C abilitato come master
#define I2C_CMD_TX 0x0200 //modulo I2C in trasmissione
#define I2C_CMD_RX 0x0000 //modulo I2C in ricezione
#define I2C_CMD_START 0x0001 //modulo I2C richiesta generazione sequenza START
#define I2C_CMD_STOP 0x0002 //modulo I2C richiesta generazione sequenza STOP

//costanti per l’accesso all’I/O expander MCP23017
#define IO_EXP0 0x20 //7bit I2C address dell’I/O expander 0 (ingressi)
#define IO_EXP1 0x21 //7bit I2C address dell’I/O expander 1 (uscite)

#define IO_EXP_IODIRA 0x00 //indirizzo registro IODIRA dell’I/O expander
#define IO_EXP_GPIOA 0x12 //indirizzo registro GPIOA dell’I/O expander

//======================================================================

//macro che attende fine sequenza verificando generazione seqeunza di STOP
.macro I2C_WAIT_BY_STOP
_CHECK:
LBCO r1.w0, I2C1_BASE, I2C_CON, 2
QBBS _CHECK, r1.t1
.endm

//macro che attende fine sequenza verificando generazione seqeunza di STOP
.macro I2C_WAIT_BY_COUNT
_CHECK:
LBCO r1.w0, I2C1_BASE, I2C_CNT, 2
QBNE _CHECK, r1.w0, 0
.endm

//======================================================================

START:
// clear that bit
LBCO r0,

on

Hi Micka,

I do not think he is using and driver. When speaking of the I2C module, I believe hes speaking of the physical on chip module. But this . . .

#define I2C1_BASE C2 //base registri I2C1 nella tabella

translated from Italian to English . . . I2C1 base registers in the table which seems to me he is setting up the I2C hardware module directly through it’s registers in memory. But the other link, he pasted I do not know if you saw it or not http://beagleboard.org/Community/Forums/?place=msg%2Fbeagleboard%2FDAXyYJOrDIc%2FDZ8WKkRWaC0J he talks about the problem being solved and he was not bringing the hardware module out of reset, which is similar to how the ADC module works.

Wish I could help you more, but I know nearly nothing about I2C. I know what it is, and vaguely how it’s done, but have never used I2C . . .

By the way, I keep seeing stuff like “C2” in PASM assembly in regard to the PRU’s. Wish I could figure out what it is . . . Seems to be some sort of constant “register” ? And there is more than just C2, but I have not found any reference to those yet :confused:

Ah, ok got it. Page 25 of the PRU reference manual table 9

2 I2C1 0x4802_A000 == base address for i2c1

Hi Micka,

With a few tweaks, you can use the drivers from Staterware[1] and use the PRU C compiler. My advice is to use Starterware on the CortexA8 to get familiar with the examples, utilities and driver code. Next you want to pull out just the driver and utility code you need and put it into one file, compile with the PRU C compiler and fix errors and warnings. Don’t forget to add CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;[2] to the start of main().

I do this all with a JTAG emulator. Not sure how you will be able to debug this code otherwise.

[1] http://www.ti.com/tool/starterware-sitara

[2] https://git.ti.com/pru-software-support-package

Regards,
John

I do this all with a JTAG emulator. Not sure how you will be able to debug this code otherwise

If you’re speaking of debugging PRU code. You can emulate the PRU code from userland C with /dev/mem/, and mmap() until the code works properly, then do the same thing on the PRU’s.

By the way, I keep seeing stuff like "C2" in PASM assembly in regard to the
PRU's. Wish I could figure out what it is . . . Seems to be some sort of
constant "register" ? And there is more than just C2, but I have not found
any reference to those yet :confused:

I know I2C pretty well, and have written drivers for it, just not for
this chip.

If you wish, email me privately and we can talk should you feel the
need.

NXP has some very good tutorials in their I2C chips, try a PCA9551 or
a PCA9634 as chips that have a decent tutorial on how I2C works.

Harvey

Ok, thx for the tips.

I’ve CCS & starterware installed. I found the I2C driver. But how do you create a PRU project for the beaglebone black ?

In the CCS App center, the PRU compiler is installed, but I can’t find how to create a PRU project ?

Micka,

I found that :

http://www.element14.com/community/community/designcenter/single-board-computers/next-gen_beaglebone//blog/2014/04/30/bbb–pru-c-compiler

Ok, thx for the tips.

I’ve CCS & starterware installed. I found the I2C driver. But how do you create a PRU project for the beaglebone black ?

In the CCS App center, the PRU compiler is installed, but I can’t find how to create a PRU project ?

I believe there is a proper TI wiki that covers the ti-cgt compiler, how to set it up, how to use it, example code, and all that. I’d give you a link, if I could find it . . .maybe I’ll be able to find it quickly . .

I believe everything you need to setup, and use it is all accessible from this link: http://processors.wiki.ti.com/index.php/PRU-ICSS

Ok, the problem is that in a PRU project you don’t have access to the I2C function that you have in the starterware :’( .

I guess that I need to code in ASM then ^^ .

Hi Micka,

Follow this tutorial:

http://processors.wiki.ti.com/index.php/PRU_Training:_Hands-on_Labs

Regards,
John

Yeah that link John gave out seems good. I’ve been meaning to follow it myself for a couple weeks now. But I’ve been doing other things waiting for remoteproc / rpmsg to settle down.

I really like the idea of remoteproc / rpmsg, it seems very intuitive / natural.

Also Micka, there is some github project that uses starterware too . . . demonstrating how to use starterware inside a ti_cgt C compiler project . . . I do not remember which it is, so may not be able to find a link to it . . .

Here is one such project, although I’m not sure this is the one I was thinking of

https://github.com/BeaglePilot/PRUSS-C

the starterware stuff is located in the PRUSS_LIB sub directory.

A few pieces of advise on how to get these examples to work. Make sure to boot with the custom MLO provided with Starterware as this sets up memory and clocks required by Starterware. Also don’t forget to use the GEL file when launching CCSV6. I also had to add a watchdog disable to the GEL file or else BBB just keeps on rebooting. I have attached my custom GEL file.

AM335X_beagleboneblack.gel (51 KB)

Ah ! Since John is mentioning starterware + bare metal, I must mention that it is not necessary to run bare metal in order to use the starterware driver files. The github project above that I linked to is an example of how to do that(also).

This is not to say that you can not , or should not run bare metal. I’m just putting it out there that it is not a requirement.

Yeah, I agree. Running Starterware as bare metal is a real pain because so many subsystems have to be configured correctly before anything works. If you can run this from within Linux, then you have the added benefits of knowing that clocks, memory, etc have already been setup for you. Be careful when running the drivers, because they do try to setup clocks/memory/bus and that may interfere with the Linux kernel. I haven’t tried this so I have no idea. One other complication is Linux uses an MMU so the peripherals memory map will not match the TRM. For example, MCSPI0 is located at 0xFA030000, not 0x48030000 and EDMA is located at 0xFB000000, not 0x49000000

Regards,
John