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 [email protected] EBB-GPIO-Example-00A0.dts >> EBB-GPIO-Example-00A0.dtbo
反编译成设备树文本
$ dtc -I dts -O dts [email protected] 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Ω,接下如下: