上一篇博客为了实现延时特定时间(4ms)并在这段时间内产生PWM波形,方法是通过计算PWM的单次循环时间(PWM的周期),然后计算出循环次数,使用计数器计数,每次循环判断计数器的值是否等于循环次数。这种方法比较简单,对于不熟悉PRU 的我来说比较好用,对于只改变占空比不改变周期,延时时间不变的波形很容易实现,因为PWM的周期和延时是不变的,所以循环次数也不会改变,轮询检测就搞定了。 但是随着实验的进行,我发现仅仅变占空比满足不了需求,虽然延时时间并没有改变(还是4ms),但是每经过4ms,周期变化了,所以需要改变循环次数,虽然复杂了点还是可以用上述方法解决的,最终打败我的是因为计算的循环次数不一定是整数,每次取整就会带来一定的误差(虽然很小)。 想了想,因为单片机是有两个定时器T0和T1,可以通过超时中断来实现产生特定时间的PWM波形,那么beaglebone的PRU是否也提供了类似的定时器呢,没办法只能继续啃am335的手册,终于发现了Industrial Ethernet Peripheral (IEP)。 (IEP) is intended to do the hardware work required for industrial ethernetfunctions. The IEP module features an industrial ethernet timer with eight compare events. IEP旨在完成工业以太网所需的硬件工作功能。iep模块具有一个工业以太网定时器,具有八个比较事件。默认频率是200MHZ,意味着执行一条指令的时间是5ns。其余详细的细节参考am335x PruReferenceGuide。下面详细介绍代码实现:
#include <stdio.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <iostream>
#include "prussdrv.h"
#include <pruss_intc_mapping.h>
#define DELAY_US 4000u // 4000us 延时 最大值为 21474836 us
#define TICKS ((DELAY_US / 5) * 1000) // 单条指令5ns,转换成系统滴答数
#define PRU_NUM 0 // 使用PRU0
using namespace std;
int main(void)
{
tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
prussdrv_init();
prussdrv_open(PRU_EVTOUT_0);
prussdrv_pruintc_init( &pruss_intc_initdata);
static void *pru0DataMemory;
static unsigned int *pru0DataMemory_int;
prussdrv_map_prumem(PRUSS0_PRU0_DATARAM, &pru0DataMemory);
pru0DataMemory_int = (unsigned int *) pru0DataMemory;
// 数据写入PRU内核空间
unsigned int sampletimestep = TICKS; // 4ms的滴答数
*(pru0DataMemory_int) = sampletimestep;
unsigned int numbersamples = 10; // 延时因子暂设,会大约11us周期的波
*(pru0DataMemory_int+1) = numbersamples;
// PRU开始时间
struct timeval start;
gettimeofday(&start,NULL);
prussdrv_exec_program (PRU_NUM, "./PRU_industrialEthernetTimer.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;
}
.origin 0
.entrypoint start
#define PRU0_R31_VEC_VALID 32
#define PRU_EVTOUT_0 3 // 完成事件为3
#define IEP 0x2E000 // 使用0x2E000常量寄存器 作为 IEP 寄存器
start:
MOV r0, 0x00000000
LBBO r1, r0, 0, 4 // r1 寄存器保存滴答数
MOV r0, 0x00000004
LBBO r10, r0, 0, 4 // r10 寄存器保存延迟因子
// 定时器timer需要设置这三位,具体可参考手册
// GLOBAL_CONFIG 、
// 0x1 to enable、
// 0x10 to set default increment、
// 0x100 to set compensation increment、 compensation is not used here
mov r7, 0x111 // 当把这个值写入IEP从0开始的4个偏移 就会使能定时器
mov r5,IEP // r5保存IEP的地址
ldi r6,0 // 当把0写入IEP从0开始的4个偏移 就会停止定时器
ldi r3,0 // IEP的初值设为0
sbbo r3,r5,r6,4 // 停止定时器
ldi r6,0xC // 当把1写入IEP从0xC开始的4个偏移 就会清除所有位
ldi r4,1
sbbo r4,r5,r6,4 // 清除所有位
TIMERSTART:
sbbo r7,r5,0,4 // 设能IEP并开始计数
PWMCONTROL:
MOV r9, 50 // 50的占空比
SET r30.t5 // 输出引脚 P9_27 high
SIGNAL_HIGH:
MOV r0, r10 // 延迟因子
DELAY_HIGH:
SUB r0, r0, 1
QBNE DELAY_HIGH, r0, 0
SUB r9, r9, 1
QBNE SIGNAL_HIGH, r9, 0 // 延迟高电平
MOV r9, 50 // 50的占空比
CLR r30.t5 // 输出引脚 P9_27 low
SIGNAL_LOW:
MOV r0, r10 // 延迟因子
DELAY_LOW:
SUB r0, r0, 1
QBNE DELAY_LOW, r0, 0
SUB r9, r9, 1
QBNE SIGNAL_LOW, r9, 0 // 延迟低电平
lbbo r8,r5,0xC,4 // 获取timer计数器的值
QBLT PWMCONTROL, r1, r8 // 执行PWMCONTROL, 除非 超时
TIMERSTOP:
sbbo r3,r5,0,4 // 停止timer计数器
sbbo r3,r5,0xC,4 // 使计数器的数据为0
END:
MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0
HALT
程序输出结果如下,考虑到进程的切换和寄存器的赋值,时间是能对的上的,然而使用示波器来观察的时候,使用示波器的Normal模式,然后执行程序,发现PWM只发送了2ms左右,查阅资料发现是因为示波器的存储深度选择了自动,采样率设置成了最高,因此示波器最多只能捕捉大约2ms的波形,点击acquire,调低采样率,再次执行即可。