Beagelebone通过PRU-ICSS使用HCSR-04超声波传感器

使用PRU的时候必须要禁用HDMI,才能正确加载设备树
本次的使用的是pru0,设备树加载所用的引脚"P9.11", "P9.13", "P9.27", "P9.28", "pru0";
       0x070 0x07  // P9_11 MODE7 | OUTPUT | GPIO pull-down
       0x074 0x27  // P9_13 MODE7 | INPUT | GPIO pull-down

       0x1a4 0x05  // P9_27 pr1_pru0_pru_r30_5, MODE5 | OUTPUT | PRU | $PIN105
       0x19c 0x26  // P9_28 pr1_pru0_pru_r31_3, MODE6 | INPUT | PRU | $PIN103

1. 到官网上或者github上下载设置好的文件,通过以下步骤即可加载成功

设置加载设备树的环境变量
$ export SLOTS=/sys/devices/bone_capemgr.9/slots
$ export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins
$ source ~/.profile

$ sudo cp EBB-PRU-Example-00A0.dtbo /lib/firmware
$ sudo sh -c "echo EBB-PRU-Example > $SLOTS"
$ sudo cat $PINS | grep '103\|105'
就会看到0x05和0x26已经被加载

2 .或者通过反编译dtbo,修改之后再编译,然后加载的方法。

编译成设备树二进制文件
$ dtc -I dts -O dtb -@ EBB-GPIO-Example-00A0.dts >> EBB-GPIO-Example-00A0.dtbo
反编译成设备树文本
$ dtc -I dts -O dts -@ EBB-GPIO-Example-00A0.dtbo > EBB-GPIO-Example-00A0.dts

REG31称为PRU事件寄存器(r31), 当对其进行写操作的时候,会产生触发中断控制器(INTC), 通过写入事件编号(0~31)到寄存器的低5位( PRU VEC4:0]),并设置第5位( PRU VEC VALID)为高电平,这样输出事件就可以发送到 Linux主机。具体的代码如下:

MOV R31 b0, PRUO R31 VEC VALID I PRU EVTOUT 0

使能OCP主口的方法:

LBCO    r0, C4, 4, 4     // load SYSCFG reg into r0 (use c4 const addr)
CLR     r0, r0, 4        // clear bit 4 (STANDBY_INIT)
SBCO    r0, C4, 4, 4     // store the modified r0 back at the load addr

汇编代码举例 :

LBCO     R2, C2, 5, 8 //从C2+5 的地址读取8 字节到R2
SBCO     R2, C2, 5, 8 //将R2的数据写到C2+5 开始的地址。
SBBO     r2, r1, 0, 4 // 将r2的数据写到r1+0 开始的地址
LBBO     r6, r5, 0, 4 // 加载r5地址中的数据放到r6中
QBBC     MAINLOOP, r6.t31 // 判断是否置位,也就是如果没有按按钮,则继续MAINLOOP

Linux非抢占式的特征意味着用户态程序不能通过GPIO端口来访问超声波传感器.而使用UART接口的超声波传感器,因为使用的是
微处理器,所以价格要昂贵的多.所以一般使用PRU来连接超声波传感器.超声波传感器的原理:10μs的触发脉冲传送到传感器的“Trig”出入端,之后传感器的“Echo”的输出端返回一个脉冲,它的宽度相当于超声波传感器与障碍物距离(如果障碍物不在有效范围内,脉冲宽度大于150μs到25ms、38ms)

创建ultrasonic.p文件,内容如下:

// PRUSS program to drive a HC-SR04 sensor and store the output in memory
// that can be read by a Linux userspace program when an interrupt is sent
// pruss程序驱动HC-SR04传感器并将输出存储在存储器中.当发送中断时,Linux用户空间程序可以读取它

 
.origin 0              
.entrypoint START      

#define TRIGGER_PULSE_US    10
#define INS_PER_US          200
#define INS_PER_LOOP        2
#define TRIGGER_COUNT       (TRIGGER_PULSE_US * INS_PER_US) / INS_PER_LOOP
#define SAMPLE_DELAY_1MS    (1000 * INS_PER_US) / INS_PER_LOOP
#define PRU0_R31_VEC_VALID  32;
#define PRU_EVTOUT_0	    3
#define PRU_EVTOUT_1	    4

// 将 r0 用于所有临时存储(重复使用多次), r1储存样本数, r2储存trig脉宽, r3存储echo脉宽
START:
   MOV    r0, 0x00000000        // 样本存储地址
   LBBO   r1, r0, 0, 4          // 加载到r1
   MOV    r0, 0x00000004        // 采样的延时
   LBBO   r2, r0, 0, 4          // 加载到r2

MAINLOOP:
   MOV    r0, TRIGGER_COUNT     // 存储trigger脉宽
   SET    r30.t5                // 置位

TRIGGERING:                     
   SUB    r0, r0, 1             // 延时 10us
   QBNE   TRIGGERING, r0, 0     // 循环直到trigger脉冲延时结束
   CLR    r30.t5                // 延时结束,triger置零,此时Trig脉冲已近发送出去了

   MOV    r3, 0                 // 清除计数器r3, 将存储 echo 的脉宽
   WBS    r31.t3                // 等待echo变高

COUNTING:
   ADD    r3, r3, 1             // 开始计数(测量echo脉冲宽度)r3 += 1
   QBBS   COUNTING, r31.t3      // 循环直到echo变低

   MOV    r0, 0x00000008        // 此时echo变低了-将值写入共享内存
   SBBO   r3, r0, 0, 4          // 先储存在r0中

//////////////////////////////////////////////////////////////////////////////////////
   SUB    r1, r1, 1             // 又进行了一次样本迭代,所以从迭代次数中减去1
   MOV    r0, r2                // 两次迭代之间需要延迟
SAMPLEDELAY:			
   SUB    r0, r0, 1             // 做这个循环r2次(每次延迟1毫秒)
   MOV	  r4, SAMPLE_DELAY_1MS  // 加载1ms延迟到r4
DELAY1MS:
   SUB	  r4, r4, 1             
   QBNE   DELAY1MS, r4, 0       // 循环直到1ms结束
   QBNE   SAMPLEDELAY, r0, 0    // 重复循环直到延时结束
////////////////////////////////////////////////////////////////////////////////////// 

   MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_1	// 生成中断以更新主机上的显示
   QBNE   MAINLOOP, r1, 0       // 如果迭代次数不为0,则继续循环

END:
   MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0
   HALT

创建ultrasonic.c文件,内容如下: 

/* 
PRUSS program to drive a HC-SR04 sensor and display the sensor output in Linux userspace by sending an interrupt.
pruss程序驱动HC-SR04传感器并将输出存储在存储器中.当发送中断时,Linux用户空间程序可以读取它
*/

#include <stdio.h>
#include <stdlib.h>
#include <prussdrv.h>
#include <pruss_intc_mapping.h>
#include <pthread.h>
#include <unistd.h>

#define PRU_NUM 0

static void *pru0DataMemory;
static unsigned int *pru0DataMemory_int;

// 子线程则循环等着中断事件1,无数次执行下面的代码
void *threadFunction(void *value){
   do {
      int notimes = prussdrv_pru_wait_event (PRU_EVTOUT_1);
      unsigned int raw_distance = *(pru0DataMemory_int+2);
      float distin = ((float)raw_distance / (100 * 148));
      float distcm = ((float)raw_distance / (100 * 58));
      printf("Distance is %f inches (%f cm) \r", distin, distcm);
      prussdrv_pru_clear_event (PRU_EVTOUT_1, PRU0_ARM_INTERRUPT);
   } while (1);
}

int  main (void)
{
   if(getuid()!=0){
      printf("必须使用root权限执行.\n");
      exit(EXIT_FAILURE);
   }
   // 创建一个子线程用来处理中断请求
   // 它能处理的中断(PRU_EVTOUT_1)与用于通知程序即将终止的中断(PRU_EVTOUT_0)有所不同
   pthread_t thread;
   tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
   prussdrv_init ();
   // PRU_EVTOUT_1中断主要是通知Linux主机程序,PRU程序已经把新的测量结果存放在内存中。
   prussdrv_open (PRU_EVTOUT_0);
   prussdrv_open (PRU_EVTOUT_1);
   prussdrv_pruintc_init(&pruss_intc_initdata);
   // 将数据复制到pru内存-另一种方法
   prussdrv_map_prumem(PRUSS0_PRU0_DATARAM, &pru0DataMemory);
   pru0DataMemory_int = (unsigned int *) pru0DataMemory;

   // 使用第一个4字节作为样本数空间
   *pru0DataMemory_int = 500;
   // 使用第二个4字节作为采样延迟(毫秒)
   *(pru0DataMemory_int+1) = 100;   // 样本之间相差2ms

   prussdrv_exec_program (PRU_NUM, "./ultrasonic.bin");
   if(pthread_create(&thread, NULL, &threadFunction, NULL)){
       printf("创建子线程失败!");
   }

   // 主线程应该就等着中断事件0,然后执行下面代码一次
   int n = prussdrv_pru_wait_event (PRU_EVTOUT_0);
   printf("PRU程序完成,事件编号为:%d.\n", n);
   printf("内存中的数据是:\n");
   printf("-使用的样本数为 %d.\n", *pru0DataMemory_int);
   printf("-使用的延时是 %d.\n", *(pru0DataMemory_int+1));
   unsigned int raw_distance = *(pru0DataMemory_int+2);
   printf("-最后的距离结果是 %d.\n", raw_distance);
   // 原始距离以10ns样本为单位
   // 以英寸为单位的距离 = time (ms) / 148 (根据数据表)
   float distin = ((float)raw_distance / (100 * 148));
   float distcm = ((float)raw_distance / (100 * 58));
   printf("-- A distance of %f inches (%f cm).\n", distin, distcm);

   prussdrv_pru_disable(PRU_NUM);
   prussdrv_exit ();
   return EXIT_SUCCESS;
}

编译运行即可 

$ pasm -b ultrasonic.p
$ gcc ultrasonic.c -o ultrasonic -lpthread -lprussdrv
$ sudo ./ultrasonic

代码执行的步骤大致如下:


1.初始化的同时,设置共享内存
2.主循环开始
3.发送脉冲到输出引脚(P9_27),设置该引脚为高电平,使用代码保持10μs后。在切换为低电平。
4.输入引脚(P9_28)一直处于低电平状态,如果突然被拉高时(应为echo接受到返回的脉冲),“宽度”定时器立即启动计数,当该引脚再次变为低电平时,定时器停止
5.宽度定时器的计数值写入共享内存之后,引发中断事件1,更新主机上的显示.则Linux用户空间程序可以读取它,主循环再次开始直到样本迭代次数为0,触发中断事件0,整个程序结束

代码:
代码给出了中断处理函数的源代码,该函数单独创建一个线程,用来处理中断请求,它能处理的中断(PRU_EVTOUT_1)与用于通知程序即将终止的中断(PRU_EVTOUT_0)有所不同。PRU_EVTOUT_1中断主要是通知Linux主机程序,PRU程序已经把新的测量结果存放在内存中。pruss程序驱动HC-SR04传感器并将输出存储在存储器中.当发送中断时,Linux用户空间程序可以读取它

 .p和.c代码的联系
*pru0DataMemory_int = 500;    # 使用第一个4字节作为样本数空间,                                            对应     MOV    r0, 0x00000000       
*(pru0DataMemory_int+1) = 100;  # 使用第二个4字节作为采样延迟1ms,所以样本之间相差2ms,对应     MOV    r0, 0x00000004
printf("-最后的距离结果是 %d.\n",  *(pru0DataMemory_int+2));                                                       对应     MOV    r0, 0x00000008
MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_1    // 生成中断以更新主机上的显示

代码部署完毕,接下来就是硬件接线了,由于beaglebone的PRU引脚能接受的最大电压是3.3V, 而超声波传感器的输出电压是5V的,所以需要使用逻辑电平转换,使用手册上的接线方法如下:

 结果一直无法成功,最好采取串电阻的方式成功读取数据,电阻的大小是1kΩ,接下如下:

猜你喜欢

转载自blog.csdn.net/qq_34935373/article/details/90297070