上一篇博客为了实现延时特定时间(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 heremov r7, 0x111 // 当把这个值写入IEP从0开始的4个偏移 就会使能定时器mov r5,IEP // r5保存IEP的地址ldi r6,0 // 当把0写入IEP从0开始的4个偏移 就会停止定时器ldi r3,0 // IEP的初值设为0sbbo r3,r5,r6,4 // 停止定时器ldi r6,0xC // 当把1写入IEP从0xC开始的4个偏移 就会清除所有位ldi r4,1sbbo r4,r5,r6,4 // 清除所有位TIMERSTART:sbbo r7,r5,0,4 // 设能IEP并开始计数PWMCONTROL:MOV r9, 50 // 50的占空比SET r30.t5 // 输出引脚 P9_27 highSIGNAL_HIGH:MOV r0, r10 // 延迟因子DELAY_HIGH:SUB r0, r0, 1QBNE DELAY_HIGH, r0, 0 SUB r9, r9, 1QBNE SIGNAL_HIGH, r9, 0 // 延迟高电平MOV r9, 50 // 50的占空比CLR r30.t5 // 输出引脚 P9_27 lowSIGNAL_LOW:MOV r0, r10 // 延迟因子DELAY_LOW:SUB r0, r0, 1QBNE DELAY_LOW, r0, 0SUB r9, r9, 1QBNE 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 // 使计数器的数据为0END:MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0HALT
程序输出结果如下,考虑到进程的切换和寄存器的赋值,时间是能对的上的,然而使用示波器来观察的时候,使用示波器的Normal模式,然后执行程序,发现PWM只发送了2ms左右,查阅资料发现是因为示波器的存储深度选择了自动,采样率设置成了最高,因此示波器最多只能捕捉大约2ms的波形,点击acquire,调低采样率,再次执行即可。