Синхронизация вывода BeagleBone GPIO с PRU (TI AM335x)

Я использую один из блоков PRU на AM335x для управления 4 контактами GPIO на BeagleBone (GPIO1_2, GPIO1_3, GPIO1_6, GPIO1_7), и я хочу синхронизировать переходы границ (мой полный исходный код находится внизу).

С Beaglebone для установки выходного HI на контакте вы устанавливаете соответствующий бит в 1 по адресу 0x4804c194, затем, чтобы установить его в LO, вы устанавливаете бит в 1 по адресу 0x4804c190. Таким образом, мой ассемблерный код PRU сначала устанавливает выходные биты HI, а затем устанавливает выходные биты LO:

 MOV r4, GPIO1 | GPIO_CLEARDATAOUT
 MOV r5, GPIO1 | GPIO_SETDATAOUT    
 ...
 ...
//Loop the following:
MAIN_LOOP:
    LBCO r2, CONST_PRUDRAM, r1, 8//Read in LO and HI data into r2/r3
    SBBO r3, r5, 0, 1  //Write HI data
    SBBO r2, r4, 0, 1  //Write LO data
    ADD r1, r1, 8
    QBEQ EXIT, r1, 112  //Done?  Exit
    QBA MAIN_LOOP

из-за того, сколько циклов требуется для запуска каждого из них, период LO значительно длиннее, чем HI (50 нс против 110 нс). К сожалению, я слишком новичок, чтобы публиковать изображения, вот ссылка на скриншот логического анализатора из предыдущего кода

Чтобы выровнять тайм-аут, я чередую установку битов HI и LO так, чтобы периоды были равны по 80 нс, но переходы HI и LO смещены друг от друга на 80 нс:

 MOV r4, GPIO1 | GPIO_CLEARDATAOUT
 MOV r5, GPIO1 | GPIO_SETDATAOUT    
 ...
 ...
//Loop the following:
MAIN_LOOP:
    LBCO r2, CONST_PRUDRAM, r1, 8 //Read in LO and HI data into r2/r3
    SBBO r3, r5, 0, 1  //Write HI data
    SBBO r2, r4, 0, 1  //Write LO data
    ADD r1, r1, 8
    QBEQ EXIT, r1, 112
    QBA MAIN_LOOP2

MAIN_LOOP2:
    LBCO r2, CONST_PRUDRAM, r1, 8 //Read in LO and HI data into r2/r3
    SBBO r2, r4, 0, 1  //Write LO data
    SBBO r3, r5, 0, 1  //Write HI data
    ADD r1, r1, 8
    QBEQ EXIT, r1, 112
    QBA MAIN_LOOP

Здесь также показан скриншот предыдущего кода с помощью логического анализатора.

Итак, мой вопрос: как я могу заставить переходы краев происходить одновременно? т.е. если вы сравните GPIO1_6 и GPIO_7, в центре скриншота будет 200 нс, когда GPIO1_7 перешел в LO, а затем 50 нс ДО, GPIO1_6 перешел в HI, я бы хотел, чтобы они оба перешли одновременно. Я не против замедлить его, чтобы добиться этого.

Вот мой исходный код:

Файл: main.p

.origin 0
.entrypoint START

#include "main.hp"

#define GPIO1 0x4804c000
#define PINMUX 0x44E10800

#define GPIO_CLEARDATAOUT 0x190
#define GPIO_SETDATAOUT 0x194
#define GPIO_DIRECTION 0x134
#define GPIO_DIRECTION2 0x142


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

    //TODO SET the pin(s) direction to OUTPUT, currently sets ALL bits to output
    MOV r4, GPIO1 | GPIO_DIRECTION
    MOV r7, 0x00000000
    SBBO r7, r4, 0, 4
    MOV r4, GPIO1 | GPIO_DIRECTION2
    SBBO r7, r4, 0, 4

    //TODO SET the pins to GPIO Mode aka MODE 7, i.e. GPIO1_6 to mode GPIO1_6

    MOV r4, GPIO1 | GPIO_CLEARDATAOUT
    MOV r5, GPIO1 | GPIO_SETDATAOUT

    //Read in number of patterns into R20
    LBCO r20, CONST_PRUDRAM, 0, 4

    //Set R1 to 4bytes
    MOV r1, 32

MAIN_LOOP:
    //Read pin data into r2/r3
    LBCO r2, CONST_PRUDRAM, r1, 8
    //Set Pin outputs by writing to the GPIO1 memory
    //SBBO r2, r4, 0, 8
    SBBO r3, r5, 0, 1
    SBBO r2, r4, 0, 1
    //Increment Pin Data to next 8 bytes
    ADD r1, r1, 8
    //Check if done, after 80bytes
    QBEQ EXIT, r1, 112
    QBA MAIN_LOOP2
    //QBA MAIN_LOOP //To get first screenshot, comment line before & uncomment this

MAIN_LOOP2:
    //Read pin data into r2/r3
    LBCO r2, CONST_PRUDRAM, r1, 8
    //Set Pin outputs by writing to the GPIO1 memory
    //SBBO r2, r4, 0, 8
    SBBO r2, r4, 0, 1
    SBBO r3, r5, 0, 1
    //Increment Pin Data to next 8 bytes
    ADD r1, r1, 8
    //Check if done, after 80bytes
    QBEQ EXIT, r1, 112
    QBA MAIN_LOOP

EXIT:

#ifdef AM33XX
    // Send notification to Host for program completion
    MOV R31.b0, PRU0_ARM_INTERRUPT+16
#else
    MOV R31.b0, PRU0_ARM_INTERRUPT
#endif
HALT

Файл main.c:

#include <stdio.h>
// Driver header file
#include <prussdrv.h>
#include <pruss_intc_mapping.h>

#define PRU_NUM         0
#define AM33XX

static int LOCAL_exampleInit ();

static void *pruDataMem;
static unsigned int *pruDataMem_int;

int main (void)
{
    unsigned int pindata[12];
    unsigned int pinmask = 0;
    int j = 0;

    unsigned int ret, i;
    tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;

    /* Initialize the PRU */
    printf("\nINFO: Starting %s.\r\n", "main");
    prussdrv_init ();

    /* Open PRU Interrupt */
    ret = prussdrv_open(PRU_EVTOUT_0);
    if (ret)
    {
        printf("prussdrv_open open failed\n");
        return (ret);
    }

    /* Get the interrupt initialized */
    prussdrv_pruintc_init(&pruss_intc_initdata);

    /* Initialize memory */
    printf("\tINFO: Initializing.\r\n");
    LOCAL_Init();

    pruDataMem_int[0] = 10; //ignored

    //Load up the pin data
    pruDataMem_int[4] = 0x88;
    pruDataMem_int[5] = 0x44;

    pruDataMem_int[6] = 0x44;
    pruDataMem_int[7] = 0x88;
    pruDataMem_int[8] = 0x88;
    pruDataMem_int[9] = 0x44;

    pruDataMem_int[10] = 0x44;
    pruDataMem_int[11] = 0x88;
    pruDataMem_int[12] = 0x88;
    pruDataMem_int[13] = 0x44;

    pruDataMem_int[14] = 0x44;
    pruDataMem_int[15] = 0x88;
    pruDataMem_int[16] = 0x88;
    pruDataMem_int[17] = 0x44;

    pruDataMem_int[18] = 0x44;
    pruDataMem_int[19] = 0x88;
    pruDataMem_int[20] = 0x88;
    pruDataMem_int[21] = 0x44;
    pruDataMem_int[22] = 0x44;
    pruDataMem_int[23] = 0x88;

    printf("\tINFO: Executing PRU.\r\n");
    prussdrv_exec_program (PRU_NUM, "main.bin");

    // Wait until PRU0 has finished execution

    printf("\tINFO: Waiting for HALT command.\r\n");
    prussdrv_pru_wait_event (PRU_EVTOUT_0);
    printf("\tINFO: PRU completed transfer.\r\n");
    prussdrv_pru_clear_event (PRU0_ARM_INTERRUPT);

    // Disable PRU and close memory mapping
    prussdrv_pru_disable (PRU_NUM);
    prussdrv_exit ();

    return(0);
 }

static int LOCAL_Init ()
{
    prussdrv_map_prumem (PRUSS0_PRU0_DATARAM, &pruDataMem);
    pruDataMem_int = (unsigned int) pruDataMem;

    pruDataMem_int[0] = 0x00;
    pruDataMem_int[1] = 0x00;
    pruDataMem_int[2] = 0x00;
    pruDataMem_int[3] = 0x00;
    return(0);
}

Файл main.hp:

#ifndef _main_HP_
#define _main_HP_
#define AM33XX

#ifdef AM33XX

// Refer to this mapping in the file - \prussdrv\include\pruss_intc_mapping.h
#define PRU0_PRU1_INTERRUPT     17
#define PRU1_PRU0_INTERRUPT     18
#define PRU0_ARM_INTERRUPT      19
#define PRU1_ARM_INTERRUPT      20
#define ARM_PRU0_INTERRUPT      21
#define ARM_PRU1_INTERRUPT      22

#define CONST_PRUDRAM   C24
#define CONST_SHAREDRAM C28
#define CONST_L3RAM     C30
#define CONST_DDR       C31

// Address for the Constant table Programmable Pointer Register 0(CTPPR_0)
#define CTBIR_0         0x22020
// Address for the Constant table Programmable Pointer Register 0(CTPPR_0)
#define CTBIR_1         0x22024

// Address for the Constant table Programmable Pointer Register 0(CTPPR_0)
#define CTPPR_0         0x22028
// Address for the Constant table Programmable Pointer Register 1(CTPPR_1)
#define CTPPR_1         0x2202C

#else

// Refer to this mapping in the file - \prussdrv\include\pruss_intc_mapping.h
#define PRU0_PRU1_INTERRUPT     32
#define PRU1_PRU0_INTERRUPT     33
#define PRU0_ARM_INTERRUPT      34
#define PRU1_ARM_INTERRUPT      35
#define ARM_PRU0_INTERRUPT      36
#define ARM_PRU1_INTERRUPT      37

#define CONST_PRUDRAM   C3
#define CONST_HPI       C15
#define CONST_DSPL2     C28
#define CONST_L3RAM     C30
#define CONST_DDR       C31

// Address for the Constant table Programmable Pointer Register 0(CTPPR_0)
#define CTPPR_0         0x7028
// Address for the Constant table Programmable Pointer Register 1(CTPPR_1)
#define CTPPR_1         0x702C

#endif

.macro  LD32
.mparam dst,src
    LBBO    dst,src,#0x00,4
.endm

.macro  LD16
.mparam dst,src
    LBBO    dst,src,#0x00,2
.endm

.macro  LD8
.mparam dst,src
    LBBO    dst,src,#0x00,1
.endm

.macro ST32
.mparam src,dst
    SBBO    src,dst,#0x00,4
.endm

.macro ST16
.mparam src,dst
    SBBO    src,dst,#0x00,2
.endm

.macro ST8
.mparam src,dst
    SBBO    src,dst,#0x00,1
.endm

#define sp r0
#define lr r23
#define STACK_TOP       (0x2000 - 4)
#define STACK_BOTTOM    (0x2000 - 0x200)

.macro stack_init
    mov     sp, STACK_BOTTOM
.endm

.macro push
.mparam reg, cnt
    sbbo    reg, sp, 0, 4*cnt
    add     sp, sp, 4*cnt
.endm

.macro pop
.mparam reg, cnt
   sub     sp, sp, 4*cnt
    lbbo    reg, sp, 0, 4*cnt
.endm
#endif //_main_HP_

person Dave    schedule 23.07.2013    source источник


Ответы (2)


Поговорив с кем-то об этой проблеме, решение состоит в том, чтобы напрямую писать в регистр Dataout вместо использования регистров Set/Clear Dataout, тогда все переходы будут происходить одновременно:

#define GPIO_DATAOUT 0x13C

...
MOV r4, GPIO1 | GPIO_DATAOUT
 ...
 ...
//Loop the following:
MAIN_LOOP:
    LBCO r2, CONST_PRUDRAM, r1, 4//Read pin state data into r2
    SBBO r2, r4, 0, 4  //Write pin state data to Dataout 
    ADD r1, r1, 4
    QBEQ EXIT, r1, 112  //Done?  Exit
    QBA MAIN_LOOP
person Dave    schedule 24.07.2013
comment
Поскольку мы имеем дело только с GPIO, можно было бы просто МУЛЬТИФИКСИРОВАТЬ выводы в PRU, а затем писать в оба одновременно через регистр R30 (это заняло бы все 5 нс вместо 50-200) — конечно, единственный недостаток заключается в том, что контакты должны быть мультиплексированы только как выход или вход. См. этот пример: github.com/TekuConcept/PRU_Demo/ blob/master/Read-from-Pin/ - person TekuConcept; 23.06.2014

Хотя вы можете использовать регистр GPIO_DATAOUT, это имеет побочный эффект сброса всех контактов, даже тех, которые вы, возможно, не хотите менять. Однако, поскольку GPIO_CLEARDATAOUT и GPIO_SETDATAOUT соседствуют в карте памяти, вы можете писать в них обоих в одной инструкции SBBO. Вместо:

MOV r4, GPIO1 | GPIO_CLEARDATAOUT
MOV r5, GPIO1 | GPIO_SETDATAOUT    
...
LBCO r2, CONST_PRUDRAM, r1, 8//Read in LO and HI data into r2/r3
SBBO r3, r5, 0, 1  //Write HI data
SBBO r2, r4, 0, 1  //Write LO data

Вы можете сделать это так (что также экономит один регистр, поскольку вам не нужны r4 и r5):

MOV r4, GPIO1 | GPIO_CLEARDATAOUT
...
LBCO r2, CONST_PRUDRAM, r1, 8// Read in LO and HI data into r2/r3
SBBO r2, r4, 0, 8  // Write both LO and HI data in a single pass
person Hudson    schedule 03.06.2014
comment
Это действительно работает? Я бы не стал предполагать, что все 8 байтов SBBO будут постоянно проходить через межсоединение L3/L4 одновременно. Я думаю, что некоторые из них могут быть переведены немедленно, а другие - отложены, если возникнут разногласия. Есть опыт? - person bigjosh; 05.12.2020