之前的博客为了实现延时特定时间(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 pinspinctrl-single,pins = <0x070 0x07 // P9_11 MODE7 | OUTPUT | GPIO pull-down0x074 0x27 // P9_13 MODE7 | INPUT | GPIO pull-down>;};pru_pru_pins: pinmux_pru_pru_pins { // The PRU pin modespinctrl-single,pins = <0x1a4 0x05 // P9_27 pr1_pru0_pru_r30_5, MODE5 | OUTPUT | PRU0x190 0x05 // P9_31 pr1_pru0_pru_r30_0, MODE5 | OUTPUT | PRU0x0e8 0x05 // P8_28 pr1_pru1_pru_r30_10 MODE5 | OUTPUT | PRU0x0e4 0x05 // p8_29 pr1_pru1_pru_r30_9, MODE5 | OUTPUT | PRU>;};};};fragment@1 { // Enable the PRUSStarget = <&pruss>;__overlay__ {status = "okay";pinctrl-names = "default";pinctrl-0 = <&pru_pru_pins>;};};fragment@2 { // Enable the GPIOstarget = <&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 1void *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 samplesfor (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 registerSTART:// 使能OCP主口LBCO r0, c4, 4, 4CLR r0, r0, 4SBCO r0, c4, 4, 4// r0 保存数组元素地址, r1 保存滴答数(4ms), r2 保存数组长度// r3 保存延迟因子, r4 保存占空比50MOV r0, 0x00000000LBBO r1, r0, 0, 4MOV r0, 0x00000004LBBO r2, r0, 0, 4 // r2 == 1或者2 说明数组执行完毕MOV r0, 0x00000008CONFIGUETIMER:mov r5, CM_DPLLmov r6, 0x00000001sbbo r6, r5, CLKSEL_TIMER2_CLK, 4 // Set source to CLK_M_OSCmov r5, CM_PERmov r6, 0x00000002sbbo r6, r5, CM_PER_TIMER2_CLKCTRL, 4 // Enable DMTIMER2mov r5, DMTIMER2LBBO 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是否为0LSR r12,r3,31QBGT GPIOHIGH,r12, 1 // r3表示速度为正方向JMP GPIOLOWIFSPEEDZERO: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.t9JMP TIMERSTART
GPIOLOW:NOT r3,r3ADD r3,r3,1CLR r30.t9TIMERSTART:LBBO r7,r5,TCRR,4 // 获取初始值PWMCONTROL:MOV r4, 50 // 占空比50SET r30.t10 // 输出引脚 P9_27 high
SIGNAL_HIGH:MOV r10, r3 // 延迟因子
DELAY_HIGH:SUB r10, r10, 1QBNE DELAY_HIGH, r10, 0SUB r4, r4, 1QBNE SIGNAL_HIGH, r4, 0MOV r4, 50 // 占空比50CLR r30.t10 // 输出引脚 P9_27 low
SIGNAL_LOW:MOV r10, r3 // 延迟因子
DELAY_LOW:SUB r10, r10, 1QBNE DELAY_LOW, r10, 0SUB r4, r4, 1QBNE SIGNAL_LOW, r4, 0DELAYON:LBBO r8,r5,TCRR,4 // 读取timer数值SUB r8,r8,r7QBLT PWMCONTROL, r1, r8 // 执行PWMCONTROL, 除非 超时TIMERSTOP:QBNE CONFIGUEPWM, r2, 2 // r2 == 1或者2 说明数组执行完毕END:MOV R31.b0, PRU1_R31_VEC_VALID | PRU_EVTOUT_1HALT
上述程序小写部分是增加修改的地方,本来使用DMTIMER2不要使能和设置时钟频率的,直接读取即可,但是考虑到以后可能会用到其他的DMTIMER,所以特地记录一下。运行程序,示波器伺候,波形长度还是4ms,还是相当精确的。
下面两张图说明了由于先启动 PRU0,所以第二个波形要比第一个波形晚启动一定的时间,大约是240us。