完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
每个PRU都连接着一个OCP主口,它允许访问linux主机设备对应的内存地址。此功能允许PRU控制通用GPIO的输入和输出状态。PRU可访问Linux主机内存,但是访问速度要慢上好几倍,因为内存访问需要路由到外部的PRU-ICSS,在通过PRU-ICSS接口从/OCP从口接收返回结果。
首先测试用 PRU 通过/OCP主口访问 通用 GPIO 口。 设备树覆盖层如下,用示波器连接beaglebone的GND和P9_11,板子开机发现示波器一直显示的是高电平,如下操作将环境变量写入文件,这样就不用每次开机手动加载环境变量了。 对于通用GPIO口,模式和地址如下查询手册即可。对于增强型GPIO口,0x05表示输出模式,0x26表示输入模式。 _ __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 0x19c 0x26 // P9_28 pr1_pru0_pru_r31_3, MODE6 | INPUT | PRU >; }; }; $ vim ./bashrc export SLOTS=/sys/devices/bone_capemgr.9/slots export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins $ sudo sh -c "echo EBB-PRU-Example > $SLOTS" 加载好设备树之后,发现示波器上的高电平变成了低电平,说明设备树加载成功。 使用如下主程序测试motor_direction.cpp: #include #include #include #include #include #include "prussdrv.h" #include #define DELAY_US 4000 // Max. value = 21474836 us #define TICKS ((DELAY_US / 5) * 1000) #define PRU_NUM 0 using namespace std; int main(void) { if(getuid()!=0){ printf("必须使用root权限,否则会提示段错误n"); } tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA; prussdrv_init(); prussdrv_open(PRU_EVTOUT_0); prussdrv_pruintc_init( &pruss_intc_initdata); // PRU开始时间 struct timeval start; gettimeofday(&start,NULL); prussdrv_exec_program (PRU_NUM, "./motor_direction.bin"); prussdrv_pru_wait_event (PRU_EVTOUT_0); // pru结束时间 struct timeval end; gettimeofday(&end,NULL); double diff; diff = end.tv_sec -start.tv_sec + (end.tv_usec - start.tv_usec)*0.000001; cout<< "EBB PRU程序已完成,历时约 "<< diff << "秒!" << endl; // prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU0_ARM_INTERRUPT); prussdrv_pru_disable(PRU_NUM); prussdrv_exit (); return 0; } PRU程序如下motor_direction.p: // 选择gpio0_30和gpio0_31 对应 p9_11(OUT)和p9_13(IN) .origin 0 .entrypoint ENABLEOCP #define DELAY_US 4000 // Max. value = 21474836 us #define TICKS ((DELAY_US / 5) * 1000) #define PRU0_R31_VEC_VALID 32 #define PRU_EVTOUT_0 3 #define GPIO0 0x44e07000 // GPIO 0 See the AM335x TRM,Table 2.2 Peripheral Map #define GPIO1 0x4804c000 // GPIO 1 #define GPIO2 0x481ac000 // GPIO 2 #define GPIO3 0x481ae000 // GPIO 3 #define GPIO_CLEARDATA 0x190 // for clearing the GPIO registers, See the TRM section 25.4.1 #define GPIO_DATAOUT 0x194 // for setting the GPIO registers #define GPIO_DATAIN 0x138 // to read the register data read from GPIO pins #define GPIO0_30 1<<30 // P9_11 gpio0[30] Output - bit 30 #define GPIO0_31 1<<31 // P9_13 gpio0[31] Input - bit 31 ENABLEOCP: // c4表示常量表的入口地址4,即ROU_ICSS CFG地址,加上4偏移量就可以访问SYSCFG寄存器 LBCO r0, C4, 4, 4 // load SYSCFG reg into r0 (use c4 const addr) 加载C4地址 CLR r0, r0, 4 // clear bit 4 (STANDBY_INIT) enable OCP master ports SBCO r0, C4, 4, 4 // store the modified r0 back at the load addr 将处理好的再写回C4 GPIOOUThigh: // P9_11 为 out,并且一直是高电平(GPIO_DATAOUT) MOV r1, GPIO0 | GPIO_DATAOUT // 基址 | 偏移地址加载gpio并设置 MOV r2, GPIO0_30 // 将 GPIO0_30 的输出状态写入 r2 SBBO r2, r1, 0, 4 // 将r2的数据写到r1+0 开始的4个字节地址 MOV r0, TICKS DELAYON: SUB r0, r0, 1 QBNE DELAYON, r0, 0 GPIOOUTlow: // P9_11 为 out,并且一直是低电平(GPIO_CLEARDATA) MOV r1, GPIO0 | GPIO_CLEARDATA // 加载 GPIO 并清除数据 MOV r2, GPIO0_30 // 将 GPIO0_30 的输出状态写入 r2 SBBO r2, r1, 0, 4 // 将r2的数据写到r1+0 开始的4个字节地址 MOV r0, TICKS DELAYOFF: SUB r0, r0, 1 QBNE DELAYOFF, r0, 0 //GPIOOUTIN: // MOV r5, GPIO0 | GPIO_DATAIN // 加载 GPIO 并 检测数据的输入 // LBBO r6, r5, 0, 4 // 加载r5中的数据放到r6中 // QBBC MAINLOOP, r6.t31 // 判断是否置位,也就是如果没有按按钮,则继续MAINLOOP END: MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0 HALT 因为在电机正反转实验当中,我只需要在电机速度大于0 的时候,让电机正传,只需要让特定GPio口持续输出高电平,速度小于0的时候,gpio输出持续输出低电平即可。所以将上述GPIP输入模式注释掉。上述程序事先的功能是让GPIO在TICK次滴答时间内先输出高电平,然后接下来TICKS次滴答时间内输出高电平,然后结束程序。这个切换时间在几毫秒之内,所以想要用电压表可能很难观测的到(增长延时时间则能清楚观测),因为机械臂规划的速度时间间隔在4ms,所以还是使用示波器来观察,调节到Normal模式,执行程序,发现示波器电压根本没有触发。 尝试手动加载GPIO: root@beaglebone:~# cd /sys/class/gpio/ root@beaglebone:/sys/class/gpio# ls export gpiochip0 gpiochip32 gpiochip64 gpiochip96 unexport root@beaglebone:/sys/class/gpio# echo 30 > export root@beaglebone:/sys/class/gpio# ls export gpio30 gpiochip0 gpiochip32 gpiochip64 gpiochip96 unexport root@beaglebone:/sys/class/gpio# cd gpio30 root@beaglebone:/sys/class/gpio/gpio30# ls active_low direction edge power subsystem uevent value root@beaglebone:/sys/class/gpio/gpio30# cat direction in root@beaglebone:/sys/class/gpio/gpio30# echo out > direction root@beaglebone:/sys/class/gpio/gpio30# cat value 0 再次执行程序,这次观测到了目标的波形,这说明使用PRU访问gpio也需要先将gpio口写入export。 $ reboot 再次从头开始,这次先加载gpio $ sudo sh -c "echo EBB-PRU-Example > $SLOTS" $ sudo sh -c "echo 30 >/sys/class/gpio/export" $ sudo sh -c "echo out >/sys/class/gpio/gpio30/direction" $ g++ motor_direction.cpp -o motor_direction -lpthread -lprussdrv $ pasm -b motor_direction.p $ sudo ./motor_direction 使用通用型GPIO不仅需要加载设备树,还需要手动写入模式,而且速度相对与增强型GPIO速度要慢上不少。既然都是输出,虽然不过仅仅是高低电平,但是增强型GPIO还是有用武之地的。 现在测试使用PRU内部的增强型GPIO(EGP)。 修改上述设备树覆盖层如下,如果使用的输入输出口比较多,可以参考下面的这些。然后重新编译加载: pru_pru_pins: pinmux_pru_pru_pins { // The PRU pin modes pinctrl-single,pins = < 0x190 0x05 // P9_31 pr1_pru0_pru_r30_0, MODE7 | OUTPUT | PRU pr0 out spi sclk 0x194 0x05 // P9_29 pr1_pru0_pru_r30_1, MODE7 | OUTPUT | PRU pr0 out spi MOSI 0x198 0x05 // P9_30 pr1_pru0_pru_r30_2, MODE7 | OUTPUT | PRU pr0 out spi sync 0x19c 0x05 // P9_28 pr1_pru0_pru_r30_3, MODE7 | OUTPUT | PRU pr0 in spi CONV 0x1ac 0x26 // P9_25 pr1_pru0_pru_r31_7, MODE6 | INPUT | PRU pr0 out spi MISO 0x1a4 0x05 // P9_27 pr1_pru0_pru_r30_5, MODE7 | OUTPUT | PRU pr0 in spi sclk 0x1a8 0x26 // P9_41 pr1_pru0_pru_r31_6, MODE6 | INPUT | PRU pr0 in spi MISO 0x1a0 0x3e // P9_42 ... 25 and 27 are mucked up on the switch circuit 0x0a0 0x05 // P8_45 pr1_pru1_pru_r30_0, MODE7 | OUTPUT | PRU pr1 out spi sclk 0x0a4 0x05 // P8_46 pr1_pru1_pru_r30_1, MODE7 | OUTPUT | PRU pr1 out spi MOSI 0x0a8 0x05 // P8_43 pr1_pru1_pru_r30_2, MODE7 | OUTPUT | PRU pr1 out spi sync 0x0ac 0x05 // P8_44 pr1_pru1_pru_r30_3, MODE7 | OUTPUT | PRU pr1 in spi sclk 0x0b8 0x05 // P8_39 pr1_pru1_pru_r30_6, MODE7 | OUTPUT | PRU pr1 in spi CONV 0x0b4 0x26 // P8_42 pr1_pru1_pru_r31_5, MODE6 | INPUT | PRU pr1 in spi MISO 0x0bc 0x26 // P8_40 pr1_pru1_pru_r31_7, MODE6 | INPUT | PRU pr1 out spi MISO 0x0b0 0x26 // P8_41 0x0e0 0x26 // P8_27 >; 测试就暂时使用p9_27和p9_31作为PWM和GPIO方向输出(高电平正转,低电平反转)。 __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 >; }; }; 编译成设备树二进制文件 $ dtc -I dts -O dtb -@ EBB-GPIO-Example-00A0.dts >> EBB-GPIO-Example-00A0.dtbo 覆盖设备树 $ sudo cp EBB-GPIO-Example-00A0.dtbo /lib/firmware 反编译成设备树文本 $ dtc -I dts -O dts -@ EBB-GPIO-Example-00A0.dtbo > EBB-GPIO-Example-00A0.dts 使用和之前一样的加方式,这里有一点需要注意,编译的二进制文件名称必须是-00A0结尾的,也就是要和设备树的内容保持一致,这样加载才是有效的。用示波器连接beaglebone的GND和P9_31,P9_27,修改程序线程代码用于测试: void *lumbar_motor(void *) { while(1) { usleep(1000); if(v_lumbar.size() == vector_len_) { cout<< "插补规划的数组长度: "<< vector_len_< // PRUSS_INTC_INITDATA 使用的是 pruss_intc_mapping.h头文件 tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA; // 分配并初始化内存空间 prussdrv_init (); prussdrv_open (PRU_EVTOUT_0); // 映射 PRU 的中断 prussdrv_pruintc_init(&pruss_intc_initdata); // 存储周期数组,谐波减速器的减速比是50,速度数组的单位是弧度每秒 srand((unsigned int)time(NULL)); // n = K*f = K / T;注意单位!!! // 周期单位是ns,延迟因子单位是us // 现在要储存负数了,不能使用unsigned int了,反正范围也不溢出 int lumbar_period[vector_len_]; for (int i=0; i // lumbar_period = int(K / v_lumbar * 0.001); // PRU稳定循环开销1.6u,计算周期的时候需要考虑 //lumbar_period = rand()% 20 - 10; //if(lumbar_period==0){ // lumbar_period = -10; //} lumbar_period = i%2==0?10:-10; // 测试用例,交替更换正负号,方便观察 } // 映射内存 static void *pru0DataMemory; static int *pru0DataMemory_int; prussdrv_map_prumem(PRUSS0_PRU0_DATARAM, &pru0DataMemory); pru0DataMemory_int = (int *) pru0DataMemory; // 数据写入PRU内核空间 *(pru0DataMemory_int) = TICKS; //4ms *(pru0DataMemory_int+1) = vector_len_; //number of samples for (int i=0; i< vector_len_; i++) { *(pru0DataMemory_int+2+i) = lumbar_period; } // PRU开始时间 struct timeval start; gettimeofday(&start,NULL); // 加载并执行 PRU 程序 prussdrv_exec_program (PRU_NUM, "./redwall_arm_client.bin"); // 等待来自pru的事件完成,返回pru 事件号 int n = prussdrv_pru_wait_event (PRU_EVTOUT_0); // pru结束时间 struct timeval end; gettimeofday(&end,NULL); double diff; diff = end.tv_sec -start.tv_sec + (end.tv_usec - start.tv_usec)*0.000001; cout<< "EBB PRU程序已完成,历时约 "<< diff << "秒!" << endl; // 清空数组 p_lumbar.clear(); v_lumbar.clear(); a_lumbar.clear(); time_from_start.clear(); // 初始化数组长度 vector_len_ = -1; // 禁用pru并关闭内存映射 prussdrv_pru_disable(PRU_NUM); prussdrv_exit (); } } } 考虑到速度的正负,需要将原本的无符号unsigned int 转换成有符号整形 int,它们都是占4个字节的,唯一不同的是无符号在寄存器的表示方式是最高位是0对应正数,最高位为1对应负数,最高位作为符号位会导致最大值范围变小,不过操作过程并不会溢出,所以不考虑了。 当速度为负的时候,转换得到的延迟因子也是负,不能通过比较延迟因子和0的大小来判断正负,而是要判断保存延迟因子的二进制数的最高位是0还是1,我通过右移操作实现判断正负。 汇编中可以通过获取绝对值或者对二进制表示的负数取反+1得到其相反数。 // PRUSS program to output a simple PWM signal at fixed sample rate (100) // Output is r30.5 (P9_27) and r30.0 (P9_31) .origin 0 .entrypoint START #define PRU0_R31_VEC_VALID 32 // 允许程序完成通知 #define PRU_EVTOUT_0 3 // 发送回的事件号 #define IEP 0x2E000 // IEP使用常量寄存器 START: // r0 保存数组元素地址, r1 保存滴答数(4ms), r2 保存数组长度 // r3 保存延迟因子, r4 保存占空比50 // r5 保存IEP地址, r6 保存IEP作用地址, r7 保存使能IEP的参数, r8 保存使不能IEP的参数, r9 保存清空操作参数 // r10 临时寄存器,主要用于暂存r3 // r11 保存读取的timer数值,r12用来存储右移的值 MOV r0, 0x00000000 LBBO r1, r0, 0, 4 MOV r0, 0x00000004 LBBO r2, r0, 0, 4 // r2 == 1或者2 说明数组执行完毕 MOV r0, 0x00000008 CONFIGUETIMER: // GLOBAL_CONFIG 0x1 to enable // 0x10 to set default increment // 0x100 to set compensation increment MOV r5, IEP LDI r6, 0 // 使不能作用地址 MOV r7, 0x111 MOV r8, 0 LDI r9, 1 SBBO r8,r5,r6,4 // 使不能 LDI r6, 0xC // 清空作用地址 SBBO r9,r5,r6,4 // clear bit CONFIGUEPWM: ADD r0, r0, 4 // 跳过第一个速度为0的点 SUB r2, r2, 1 // r2 自减 LBBO r3, r0, 0, 4 // 获取此时速度对应的延迟因子 LSR r12,r3,31 // 对r3中的数进行右移操作,右移31位得到最高位的数值,1为负,0为正 QBGT GPIOHIGH,r12, 1 // r3表示速度为正方向 JMP GPIOLOW GPIOHIGH: // p9_31 输出高电平 SET r30.t0 JMP TIMERSTART GPIOLOW: // p9_31 输出低电平 NOT r3,r3 // 取反+1得到负数的相反数 ADD r3,r3,1 CLR r30.t0 TIMERSTART: ***bo r7,r5,0,4 // 使能IEP并且开始计数 PWMCONTROL: MOV r4, 50 // 占空比50 SET r30.t5 // 输出引脚 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.t5 // 输出引脚 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 r11,r5,0xC,4 // 读取timer数值 QBLT PWMCONTROL, r1, r11 // 执行PWMCONTROL, 除非 超时 TIMERSTOP: SBBO r8,r5,0,4 // 停止计数,并停止IEP SBBO r8,r5,0xC,4 // 使计数器的数据为0 QBNE CONFIGUEPWM, r2, 2 // r2 == 1或者2 说明数组执行完毕 END: MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0 HALT ----------------------------------------------分割线-------------------------------------------------- int lumbar_period[vector_len_]; for (int i=0; i // lumbar_period = int(K / v_lumbar * 0.001); // PRU稳定循环开销1.6u,计算周期的时候需要考虑 if(i%10==0) { lumbar_period = 0; } else { lumbar_period = i%2==0?-10:10; } } // PRUSS program to output a simple PWM signal at fixed sample rate (100) // Output is r30.5 (P9_27) and r30.0 (P9_31) .origin 0 .entrypoint START #define PRU0_R31_VEC_VALID 32 // 允许程序完成通知 #define PRU_EVTOUT_0 3 // 发送回的事件号 #define IEP 0x2E000 // IEP使用常量寄存器 START: // r0 保存数组元素地址, r1 保存滴答数(4ms), r2 保存数组长度 // r3 保存延迟因子, r4 保存占空比50 // r5 保存IEP地址, r6 保存IEP作用地址, r7 保存使能IEP的参数, r8 保存使不能IEP的参数, r9 保存清空操作参数 // r10 临时寄存器,主要用于暂存r3 // r11 保存读取的timer数值,r12用来存储右移的值 MOV r0, 0x00000000 LBBO r1, r0, 0, 4 MOV r0, 0x00000004 LBBO r2, r0, 0, 4 // r2 == 1或者2 说明数组执行完毕 MOV r0, 0x00000008 CONFIGUETIMER: // GLOBAL_CONFIG 0x1 to enable // 0x10 to set default increment // 0x100 to set compensation increment MOV r5, IEP LDI r6, 0 // 使不能作用地址 MOV r7, 0x111 MOV r8, 0 LDI r9, 1 SBBO r8,r5,r6,4 // 使不能 LDI r6, 0xC // 清空作用地址 SBBO r9,r5,r6,4 // clear bit CONFIGUEPWM: ADD r0, r0, 4 // 跳过第一个速度为0的点 SUB r2, r2, 1 // r2 自减 LBBO r3, r0, 0, 4 // 获取此时速度对应的延迟因子 QBEQ IFSPEEDZERO, r3, 0 // 判断r3是否为0 LSR r12,r3,31 QBGT GPIOHIGH,r12, 1 // r3表示速度为正方向 JMP GPIOLOW IFSPEEDZERO: SBBO r7,r5,0,4 // 使能IEP并且开始计数 LBBO r11,r5,0xC,4 // 读取timer数值 QBLT IFSPEEDZERO, r1, r11 // 执行IFSPEEDZERO, 除非 超时 SBBO r8,r5,0,4 // 停止计数,并停止IEP SBBO r8,r5,0xC,4 // 使计数器的数据为0 QBNE CONFIGUEPWM, r2, 2 // 下一个点 JMP END // 如果数据执行完毕了,直接跳转到结束 GPIOHIGH: SET r30.t0 JMP TIMERSTART GPIOLOW: NOT r3,r3 ADD r3,r3,1 CLR r30.t0 TIMERSTART: SBBO r7,r5,0,4 // 使能IEP并且开始计数 PWMCONTROL: MOV r4, 50 // 占空比50 SET r30.t5 // 输出引脚 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.t5 // 输出引脚 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 r11,r5,0xC,4 // 读取timer数值 QBLT PWMCONTROL, r1, r11 // 执行PWMCONTROL, 除非 超时 TIMERSTOP: SBBO r8,r5,0,4 // 停止计数,并停止IEP SBBO r8,r5,0xC,4 // 使计数器的数据为0 QBNE CONFIGUEPWM, r2, 2 // r2 == 1或者2 说明数组执行完毕 END: MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0 HALT |
|
|
|
只有小组成员才能发言,加入小组>>
2431 浏览 0 评论
9078 浏览 4 评论
36741 浏览 19 评论
5020 浏览 0 评论
24701 浏览 34 评论
1512浏览 2评论
1730浏览 1评论
2169浏览 1评论
1538浏览 0评论
509浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 10:28 , Processed in 1.255380 second(s), Total 75, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号