1 项目需求
使用PRU控制一个LED实现呼吸灯,并且使用一个按键按下后切换闪烁的速度
2 完成的功能及达到的性能
2.1LED可以实现呼吸灯的效果
LED灯的io接口为P9_31,按键的io接口为P9_25。使用了面包板和杜邦线来进行实验,原理图见下。
实际测量中,发现LED的电阻不能太大,1K电阻下LED的亮度非常小,几乎看不出来呼吸灯的变化,所以使用到了330Ω的电阻。另外,VCC和GND由开发板上的P9_3和P9_2提供,VCC为3.3v。
有很多例程点亮LED灯是使用的IO口来驱动,个人不推荐这样做。虽然BBB板子IO口驱动的能力比一些开发板如51单片机而言强,但是难免会损伤IO口。个人习惯使用灌电流的方式,所以点灯的方式会与例程不同,在代码节会详解。
2.2呼吸灯可以设置不同的速度
我使用的是模拟pwm输出,PWM的周期为1ms。呼吸灯的最大时间和按键改变的步进精度都可以在程序中设置,之后将在代码节详解。视频中演示的是最大时间5s,步进精度1s的呼吸灯。
为了方便观察,当呼吸灯时间为0时,LED的状态是关闭。连续摁下按键超过最大值后,便会返回关闭状态。
2.3整体功能总结
当给PRU烧录程序时,LED处于关闭状态。摁下摁键后,LED灯开始闪烁,此时的闪烁速度最快。再摁下摁键后,如果不松开摁键,LED灯会保持关闭状态。松开摁键后,之前的LED呼吸灯会停止,执行下一个呼吸灯,此时的呼吸灯速度会变慢。继续摁下摁键会重复上述操作,直到到达呼吸灯的最慢速度。在此之后,再次摁下按键会返回一开始关闭的状态。
3 软件程序
软件程序分成.sh文件和.c文件,.sh文件用来配置IO口为输入输出,.c文件用来编写整体流程的代码。
3.1验证代码
官方的例程中有可以验证IO口输入输出正确的代码,在pru-cookbook-code/05blocks/input.pru0.c下,运行.sh文件
cd pru-cookbook-code/05blocks
source input_setup.sh
此时会将P9_25设置为输入,P9_31设置为输出。
再运行.c文件
make
将其分别连接到按键和LED上,便可以通过按键控制LED灯的亮灭。如果能够实现效果则证明IO口的输入输出没有问题。
3.2sh文件代码
可以直接使用上述例程中的.sh文件
#!/bin/bash
#
export TARGET=my_file.pru0
echo TARGET=$TARGET
# Configure the PRU pins based on which Beagle is running
machine=$(awk '{print $NF}' /proc/device-tree/model)
echo -n $machine
if [ $machine = "Black" ]; then
echo " Found"
config-pin P9_31 pruout
config-pin -q P9_31
config-pin P9_25 pruin
config-pin -q P9_25
elif [ $machine = "Blue" ]; then
echo " Found"
pins=""
elif [ $machine = "PocketBeagle" ]; then
echo " Found"
config-pin P1_36 pruout
config-pin -q P1_36
config-pin P1_29 pruin
config-pin -q P1_29
else
echo " Not Found"
pins=""
fi
如果需要自己设置IO口,则可以将P9_31和P9_25进行替换即可。
3.3c文件代码
主要是由几个函数组成
A.延迟函数
官方的库中只有延迟一个时钟周期的代码,我们可以自己写一个延迟1us的函数。PRU的频率是200MHZ,因此,1us即是200个时钟周期。
void delay_us(uint32_t us)
{
uint32_t i;
for(i = 0; i<us;i++)
__delay_cycles(200);
}
B.按键检测代码
可以仿照例程中的按键进行编写,需要注意的是,按键检测需要在LED呼吸灯运行中进行扫描,如果在呼吸灯程序外边进行检测,那么只有当一个呼吸灯完成的时候才可以进行按键检测,这样检测时间会非常短,容易检测不到按键。但是在呼吸灯程序中进行按键检测就又会产生一个问题,就是检测太快容易检测到多个按键摁下。这种情况下我进行了下面描述程序效果:等待摁键被松开,然后再延迟10ms。这样就可以避免重复检测。
另外当按键被检测到后,时间寄存器precision_now会增加,到达最大值就会返回0。这也就是上面一节描述的效果。最大精度可以自己设置,我设置的5。
#define key_precision 5
void key_scan(void)
{
if((__R31&sw) == sw)
{
while((__R31&sw) == sw)__R30 |= led;;
delay_us(10000);
precision_now ++;
if(precision_now > key_precision)
precision_now = 0;
stop_flag = 1;
}
}
C.PWM程序
PWM程序非常简单,就是在一个周期内,前面的时间让灯开,后面的时间让灯灭。我为了方便理解,使用到了百分比来进行控制。程序中的cycle_time 就是我设置的时间,其单位为us。
#define cycle_time 1000
void led_pwm(float percent)
{
uint32_t time_on,time_off;
time_on = cycle_time *percent;
time_off = cycle_time - time_on;
__R30 |= led;
delay_us(time_on);
__R30 &= ~led;
delay_us(time_off);
}
D.呼吸灯程序
呼吸灯程序中,依然使用百分比来作为输入。由于呼吸灯有两段,分别是从亮到暗,从暗到亮,所以程序中的for循环有两段。另外,程序中最需要注意的是stop_flag这个标志位。这个标志位用来结束当前的呼吸灯程序。当发现呼吸灯的速度改变的时候,这个标志位就会被置位,这样呼吸灯程序就会退出。为了防止其影响下一个呼吸灯程序,就需要在最后将其归零。最后是按键扫描程序,他不仅仅需要在呼吸灯的运行中进行扫描,也需要在两个for循环前面进行扫描,这是因为,当速度为0,即不执行呼吸灯程序时,for循环就不会执行,所以需要在外面调用一次来防止这种情况。
void led_flash(float speed)
{
uint32_t speed_time = (speed *max_speed_time)/2;
float percent_now;
uint32_t index = 0;
key_scan();
for(index = 0;index<speed_time;index ++)
{
percent_now = (float)index/speed_time;
led_pwm(percent_now);
key_scan();
if(stop_flag == 1)break;
}
for(;index!=0;index--)
{
percent_now = (float)index/speed_time;
led_pwm(percent_now);
key_scan();
if(stop_flag == 1)break;
}
if(stop_flag == 1)
{
stop_flag =0;
__R30 |= led;
}
}
E.主函数
主函数没有多少内容,就是开始的时候将位进行初始化,然后执行呼吸灯程序即可。
void main(void)
{
/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
__R30 |= led;
while(1)
{
led_flash(precision_now/key_precision);
}
}
4 实物展示
5 未来的计划建议
PRU只是BBB板子的一小部分内容,其LIUNX系统的强大功能尚没有学习,包括联网等都需要学习。另外,LED呼吸灯是非常简单的程序,其并没有体现PRU中的实时性的优点。未来有机会的继续学习。