之前的博客为了实现延时特定时间(4ms)并在这段时间内产生PWM波形,使用了两种方法,第一种通过计数的方式,比较low;第二种使用PRU的工业级定时器IEP,时钟频率200MHZ,使用也很简单。   但是随着实验的进行,因为是六自由度的机械臂,计划使用三块beaglebone来控制,也就是一块板子控制两个伺服电机,beaglebone自带两个PRU核,PRU1和PRU2,每个核都具有31个寄存器, 所以基本的PWM信号产生和电机方向信号产生的汇编代码完全一样,唯一不一样的就是IEP定时器只有一个,PRU1和PRU0共用,想要实现动态计数,就必须在寻找一个类似与IEP功能的东西,经过一天的AM335X手册洗礼,终于找到了beaglebone的普通定时器DMTIMER。     想要使用DM定时器,必须要使能OCP主口。DM定时器一共有8个,其中DMTIMER0 和 DMTIMER3到7 默认非使能状态,DMTIMER1是一个比较特别的计时器,使用方法也不太一样,而DMTIMER2被默认使能,使用的时钟频率是24MHZ,简单起见,直接使用系统默认的DMTIMER2。如下所示,DMTIMER0使用默认的时钟频率是32KHZ,应该可以将默认失踪频率换成100MHZ的,不深究了。    

对于DMTIMER2,它记录的数据类似于时间戳(这点我没有细看),两次读取做差值就可以得到经过的时间,再与24MHZ对应的4ms系统嘀嗒数做比较即可。4ms对应200MHZ的滴答数是4000*1000/5,所以24MHZ对应的就是4000*1000 / 5 * 24 /200 = 96000

  如果不想使用DMTIMER2这种需要做差的数据,直接使用类似IEP的从0开始的计时器,需要对TLDR,TCLR,TCRR这三个寄存器进行操作,并且使能DMTIMER3~7.具体参考AM335X手册。     下面上代码: 使用了两个PRU核,所以需要重新修改设备树文件,如下,新增pru1对应的两个p8插口:  

/dts-v1/;
/plugin/;
 
/ {
   compatible = "ti,beaglebone", "ti,beaglebone-black";
   part-number = "EBB-PRU-Example";
   version = "00A0";
   /* This overlay uses the following resources */
   exclusive-use =
         "P9.11", "P9.13", "P9.27", "P9.31", "P8.28", "P8.29",
         "pru0", "pru1";
   fragment@0 {
      target = <&am33xx_pinmux>;
      __overlay__ {
         gpio_pins: pinmux_gpio_pins {         // The GPIO pins
            pinctrl-single,pins = <
               0x070 0x07  // P9_11 MODE7 | OUTPUT | GPIO pull-down
               0x074 0x27  // P9_13 MODE7 | INPUT | GPIO pull-down
            >;
         };
         pru_pru_pins: pinmux_pru_pru_pins {   // The PRU pin modes
            pinctrl-single,pins = <
               0x1a4 0x05  // P9_27 pr1_pru0_pru_r30_5, MODE5 | OUTPUT | PRU
               0x190 0x05  // P9_31 pr1_pru0_pru_r30_0, MODE5 | OUTPUT | PRU
               0x0e8 0x05  // P8_28 pr1_pru1_pru_r30_10  MODE5 | OUTPUT | PRU
               0x0e4 0x05  // p8_29 pr1_pru1_pru_r30_9, MODE5 | OUTPUT | PRU
            >;
         };
      };
   };
 
   fragment@1 {         // Enable the PRUSS
      target = <&pruss>;
      __overlay__ {
         status = "okay";
         pinctrl-names = "default";
         pinctrl-0 = <&pru_pru_pins>;
      };
   };
 
   fragment@2 {         // Enable the GPIOs
      target = <&ocp>;
      __overlay__ {
         gpio_helper {
            compatible = "gpio-of-helper";
            status = "okay";
            pinctrl-names = "default";
            pinctrl-0 = <&gpio_pins>;
         };
      };
   };
};

  修改客户端文件redwall_arm_client.cpp,增加PRU1控制部分,其他部分完全一样,只不过调用了两个bin文件,分别控制两个电机  

#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <iostream>
 
/* 使用vector数组 */
#include <vector>
#include <algorithm>
 
/* PRU指令 */
#include <prussdrv.h>
#include <pruss_intc_mapping.h>
 
#define PORT 7788
#define ADDR "192.168.7.1"
#define K 1000
#define DELAY_US 4000 // Max. value = 21474836 us
#define TICKS_IEP ((DELAY_US / 5) * 1000)   // 200MHZ
#define TICKS_DMT 96000                     // 24MHZ
 
/* 使用的是PRU0 */
#define PRU_NUM0  0
#define PRU_NUM1  1
 
void *lumbar_and_big_arm_motor(void *)
{
    while(1)
    {
        usleep(1000);
        if(v_lumbar.size() == vector_len_)
        {
            cout<< "插补规划的数组长度: "<< vector_len_<<endl;
            tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
            prussdrv_init ();
            prussdrv_open (PRU_EVTOUT_0);
            prussdrv_open (PRU_EVTOUT_1);
            prussdrv_pruintc_init(&pruss_intc_initdata);
 
            // 存储周期数组,谐波减速器的减速比是50,速度数组的单位是弧度每秒
            // n = K*f = K / T;注意单位!!!
            // 周期单位是ns,延迟因子单位是us
            // 现在要储存负数了,不能使用unsigned int了,反正范围也不溢出
            int lumbar_period[vector_len_];
            int big_arm_period[vector_len_];
            for (int i=0; i<vector_len_; i++){
                // 转换成延时因子
                // lumbar_period[i] = int(K / v_lumbar[i] * 0.001);     // PRU稳定循环开销1.6u,计算周期的时候需要考虑
                if(i%10==0)
                {
                    lumbar_period[i] = 0;
                    big_arm_period[i] = 0;
                }
                else
                {
                    lumbar_period[i] = i%2==0?-20:10;
                    big_arm_period[i] = i%2==0?-8:18;
                }
            }
 
            // 映射内存
            static void *pru0DataMemory;
            static int *pru0DataMemory_int;
            prussdrv_map_prumem(PRUSS0_PRU0_DATARAM, &pru0DataMemory);
            pru0DataMemory_int = (int *) pru0DataMemory;
            static void *pru1DataMemory;
            static int *pru1DataMemory_int;
            prussdrv_map_prumem(PRUSS0_PRU1_DATARAM, &pru1DataMemory);
            pru1DataMemory_int = (int *) pru1DataMemory;
 
            // 数据写入PRU内核空间
            *(pru0DataMemory_int) = TICKS_IEP;          //4ms
            *(pru0DataMemory_int+1) = vector_len_;  //number of samples
            *(pru1DataMemory_int) = TICKS_DMT;          //4ms
            *(pru1DataMemory_int+1) = vector_len_;  //number of samples
            for (int i=0; i< vector_len_; i++)
            {
                *(pru0DataMemory_int+2+i) = lumbar_period[i];
                *(pru1DataMemory_int+2+i) = big_arm_period[i];
            }
 
            // PRU开始时间
            struct timeval start_lumbar;
            gettimeofday(&start_lumbar,NULL);
 
            // 加载并执行 PRU 程序
            prussdrv_exec_program (PRU_NUM0, "./redwall_arm_lumbar.bin");
            prussdrv_exec_program (PRU_NUM1, "./redwall_arm_big_arm.bin");
 
            // 等待来自pru的事件完成,返回pru 事件号
            //prussdrv_pru_wait_event (PRU_EVTOUT_0);
            prussdrv_pru_wait_event (PRU_EVTOUT_1);
 
            // pru结束时间
            struct timeval end_lumbar;
            gettimeofday(&end_lumbar,NULL);
 
            double diff;
            diff = end_lumbar.tv_sec -start_lumbar.tv_sec + (end_lumbar.tv_usec - start_lumbar.tv_usec)*0.000001;
            cout<< "lumbar 和 big_arm 程序已完成,历时约 "<< diff << "秒!" << endl;
 
            // 清空数组
            p_lumbar.clear();
            v_lumbar.clear();
            a_lumbar.clear();
            p_big_arm.clear();
            v_big_arm.clear();
            a_big_arm.clear();
            time_from_start.clear();
 
            // 初始化数组长度
            vector_len_ = -1;
 
            // 禁用pru并关闭内存映射
            prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU0_ARM_INTERRUPT);
            prussdrv_pru_clear_event (PRU_EVTOUT_1, PRU1_ARM_INTERRUPT);
            prussdrv_pru_disable(PRU_NUM0);
            prussdrv_pru_disable(PRU_NUM1);
            prussdrv_exit ();
        }
    }
}

  第一个bin文件使用的是IEP控制时间,新增的第二个bin使用DMTIMER2控制时间,代码的逻辑都是一样的:  

// PRUSS program to output a simple PWM signal at fixed sample rate (100)
// Output is r30.10 (P8_28) and r30.9 (P8_29)
 
 
.origin 0
.entrypoint START
 
#define PRU1_R31_VEC_VALID 32    // 允许程序完成通知
#define PRU_EVTOUT_1    4        // 发送回的事件号
 
 
// Power, reset, and clock management (PRCM)
#define CM_PER 0x44e00000  // Clock module for peripherals
#define CM_DPLL 0x44e00500 // Clock module for phase-locked loops
#define CONTROL_MODULE 0x44e10000 // Control module (AM335x Tech Ref sec. 9)
 
//offsets
#define CM_PER_L4LS_CLKSTCTRL 0x00  // L4 clock state control register?
#define CM_PER_TIMER2_CLKCTRL 0x80  // timer activity registers...
#define CLKSEL_TIMER2_CLK 0x08      // timer clock source registers..
#define DMTIMER2 0x48040000
 
// offsets  (from sec. 20.1.5 of ARM 335x Technical Reference)
#define TCLR 0x38           // timer control register
#define TCRR 0x3c           // timer counter register
#define TLDR 0x40           // timer load register
#define TTGR 0x44           // timer trigger register
 
 
START:
    // 使能OCP主口
	LBCO	r0, c4, 4, 4
	CLR 	r0, r0, 4
	SBCO	r0, c4, 4, 4
 
    // r0 保存数组元素地址, r1 保存滴答数(4ms), r2 保存数组长度
    // r3 保存延迟因子, r4 保存占空比50
    MOV	r0, 0x00000000
    LBBO	r1, r0, 0, 4
    MOV	r0, 0x00000004
    LBBO	r2, r0, 0, 4    // r2 == 1或者2 说明数组执行完毕
    MOV	r0, 0x00000008
 
CONFIGUETIMER:
	mov	r5, CM_DPLL
	mov	r6, 0x00000001
	sbbo	r6, r5, CLKSEL_TIMER2_CLK, 4  // Set source to CLK_M_OSC
 
	mov	r5, CM_PER
	mov	r6, 0x00000002
	sbbo	r6, r5, CM_PER_TIMER2_CLKCTRL, 4 // Enable DMTIMER2
 
    mov r5, DMTIMER2
    LBBO    r7,r5,TCRR,4        // 获取初始值
 
CONFIGUEPWM:
	ADD	r0, r0, 4           // 跳过第一个速度为0的点
	SUB	r2, r2, 1           // r2 自减
 
    LBBO	r3, r0, 0, 4    // 获取此时速度对应的延迟因子
 
    LBBO    r7,r5,TCRR,4        // 获取初始值
    QBEQ    IFSPEEDZERO, r3, 0      // 判断r3是否为0
    LSR     r12,r3,31
    QBGT    GPIOHIGH,r12, 1  // r3表示速度为正方向
    JMP     GPIOLOW
 
IFSPEEDZERO:
    LBBO    r8,r5,TCRR,4            // 读取timer数值
    SUB     r8,r8,r7                // 这里和IEP有点区别
    QBLT	IFSPEEDZERO, r1, r8     // 执行IFSPEEDZERO, 除非 超时
    QBNE    CONFIGUEPWM, r2, 2     // 下一个点
    JMP     END                    // 如果数据执行完毕了,直接跳转到结束
 
GPIOHIGH:
    SET r30.t9
    JMP     TIMERSTART
GPIOLOW:
    NOT r3,r3
    ADD r3,r3,1
    CLR r30.t9
 
TIMERSTART:
    LBBO    r7,r5,TCRR,4        // 获取初始值
 
PWMCONTROL:
    MOV	r4, 50              // 占空比50
	SET	r30.t10	            // 输出引脚 P9_27 high
SIGNAL_HIGH:
	MOV	r10, r3             // 延迟因子
DELAY_HIGH:
	SUB	r10, r10, 1
	QBNE	DELAY_HIGH, r10, 0
	SUB	r4, r4, 1
	QBNE	SIGNAL_HIGH, r4, 0
	MOV	r4, 50             // 占空比50
	CLR	r30.t10	           // 输出引脚 P9_27 low
SIGNAL_LOW:
	MOV	r10, r3            // 延迟因子
DELAY_LOW:
	SUB	r10, r10, 1
	QBNE	DELAY_LOW, r10, 0
	SUB	r4, r4, 1
	QBNE	SIGNAL_LOW, r4, 0
 
DELAYON:
    LBBO    r8,r5,TCRR,4            // 读取timer数值
    SUB     r8,r8,r7
    QBLT	PWMCONTROL, r1, r8     // 执行PWMCONTROL, 除非 超时
 
TIMERSTOP:
    QBNE    CONFIGUEPWM, r2, 2      // r2 == 1或者2 说明数组执行完毕
 
END:
	MOV	R31.b0, PRU1_R31_VEC_VALID | PRU_EVTOUT_1
	HALT
 

  上述程序小写部分是增加修改的地方,本来使用DMTIMER2不要使能和设置时钟频率的,直接读取即可,但是考虑到以后可能会用到其他的DMTIMER,所以特地记录一下。运行程序,示波器伺候,波形长度还是4ms,还是相当精确的。     下面两张图说明了由于先启动 PRU0,所以第二个波形要比第一个波形晚启动一定的时间,大约是240us。