1 项目介绍
BeagleBone® Black开发板的主控为TI的AM3358。该芯片中不仅有一个ARM Cortex-A8处理器芯片,还有两个可编程实时单元(PRU)。PRU 是独立于 ARM 处理器运行的32位处理器内核,因此可以对它们进行编程,使其快速响应输入并产生非常精确的时序输出。基于此,使用PRU控制一个LED实现呼吸灯,并且使用一个按键按下后切换闪烁的速度。
设计的基本指标如下:
- 按键控制呼吸灯模式
- 模式1:呼吸间隔500ms
- 模式2:呼吸间隔1s
- 模式3:呼吸间隔1.5s
- 可以通过按键关闭呼吸灯
开发板:BeagleBone® Black开发板
PC操作系统:Windows 11
板载操作系统:debian
2 硬件描述
BeagleBone® Black开发板板载有四个LED灯。给电路板通电,它就会开始启动过程。当电路板开始启动时,LED将按顺序亮起,如下图15所示。状态LED亮起需要几秒钟,所以请耐心等待。在启动Linux内核时,LED将以不规则的方式闪烁。
其有两排排针,P8和P9。P8的1、2线为GND线。P9的1,2为GND;3,4是3.3V直流电压;5,6是5V电压;7,8是系统电压。由于我们要实现用PRU点灯,所以关键是了解PRU可以直接使用的引脚。
观察手册:https://docs.beagleboard.org/boards/beaglebone/black/ch07.html
可以使用的PRU引脚很多。但是需要注意的是,标注R30
的引脚为输出引脚,标注R31
的引脚为输入引脚。
这里选择P9_27
作为输入按键的输入引脚。使用P9_30
作为按键的输出引脚。
使用面包板和辅助板搭建LED和按键。其基本电路原理图如下所示。
P9_27接在按键和上拉电阻之间,使其默认状态为高电平,按下状态为低电平。P9_30接在LED端,当其为高电平时,可以驱动LED发光。
3 软件描述
3.1 软件整体流程
软件需要根据P9_27的输入以及当前呼吸状态,使PRU中产生占空比递增或者递减的方波。
软件的基本逻辑如下图所示。启动程序后,首先判断 P9_27 电平是否发生改变。如果发生改变,则判断当前 P9_27 电平是否为高电平。若是高电平,时间存储变量增加 500;若不是高电平,则判断 Led 高电平存储变量是否为 0,若是 0 则关闭 LED 灯。如果 Led 高电平时间存储变量大于 1500,则将其归零。之后,根据占空比变量的大小来切换 LED 灯的亮暗状态:占空比变量最大时切换为渐暗状态,占空比变量最小时切换为渐亮状态。在渐亮状态下,占空比变量递增;在渐暗状态下,占空比变量递减。最后,执行 1ms 的 LED 亮灭控制。从而实现LED的呼吸灯控制。
编写主函数代码如下:
// led action
void main(void)
{
uint32_t ledSta[] = {0};
uint32_t hlstate = 0;
int32_t htime = 0;
/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
/* Initial cnt */
while(1) {
// key
key_scan_fcn(ledSta, KEY_STATE);
if(ledSta[0]==0){
LED3_OFF;
}else{
if(!hlstate){
htime++;
if(htime > ledSta[0]) {
htime = ledSta[0];
hlstate = 1;
}
} else{
htime--;
if(htime < 0){
htime = 0;
hlstate = 0;
}
}
led_hl_action(htime,ledSta[0]-htime);
}
}
}
3.2 关键功能函数
任意时间延迟函数
由于PRU中内核时钟频率为200MHz,且__delay_cycles(value);
函数为延迟value个时钟周期。使value=200实现1us的延时。
// delay us FCN
void my_delay_us(uint32_t us){
uint32_t i=0;
for ( i = 0; i < us; i++) {
__delay_cycles(200);
}
}
Led闪烁函数
使Led按照指定占空比闪烁一次。其中highUs
为高电平持续的时间,单位为微秒。lowUs
为低电平持续的时间,单位为微秒。
// led action
void led_hl_action(uint32_t highUs,uint32_t lowUs){
LED3_ON;
my_delay_us(highUs);
LED3_OFF;
my_delay_us(lowUs);
}
按键判断函数
该函数的参量有ledSta
和keyCs
,ledSta
是指针变量,用来回传Led高电平持续时间存储变量。keyCs
是当前按键状态。第二行声明了一个静态变量 keyLs
,并初始化为 1。静态变量在函数调用之间会保留其值,这意味着每次调用 key_scan_fcn
函数时,keyLs
的值将保持上一次调用结束时的值。第三行检查 keyLs
(上一次存储的按键状态)是否与 keyCs
(当前按键状态)不同。如果不同,说明按键状态发生了变化。让后根据keyLs
的值判断是否进行下一步动作。若按键按下,则会更新 ledSta
所指向的状态变量的值,将其增加500,如果该值超过1500则重置为0。同时,函数会更新存储的按键状态,以便下一次调用时进行比较。
// Key scan fcn
void key_scan_fcn(uint32_t *ledSta, uint8_t keyCs){
static uint8_t keyLs = 1;
if(keyLs != keyCs) {
if(keyLs){
*ledSta += 500;
if(*ledSta > 1500)
*ledSta = 0;
}
keyLs = keyCs;
}
}
3.3 PRU编译方式
- LED不规则闪烁后在网页输入192.168.2.7进入claud9中的debian操作系统的交互界面。
- 新建文件夹并任意命名
- 在文件夹中创建
setup.sh
文件,并编写如下sell代码。
#!/bin/bash
export TARGET=pru_pwm.pru0
echo TARGET=$TARGET
config-pin P9_27 pruin
config-pin -q P9_27
config-pin P9_30 pruout
config-pin -q P9_30
- 在命令行中运行
source setup.sh
- 看到如下结果表示成功
TARGET=pru_pwm.pru0
Current mode for P9_27 is: pruin
Current mode for P9_27 is: pruin
Current mode for P9_30 is: pruout
Current mode for P9_30 is: pruout
- 第3步骤中,第一行表明了我们编写的.c文件必须是
pru_pwm.pru0.c
。创建该.c文件。 - 在
pru_pwm.pru0.c
添加代码。
#include <stdint.h>
#include <pru_cfg.h>
#include "resource_table_empty.h"
#include "prugpio.h"
volatile register uint32_t __R30;
volatile register uint32_t __R31;
- 编写功能代码,代码段见附件。
- 创建Makefile文件,并写入代码:
include /var/lib/cloud9/common/Makefile
- 在
pru_pwm.pru0.c
界面中点击交互界面上方按键 - 运行成功进行测试
4 功能展示
见视频结尾:【Funpack3-5 基于BeagleBone® Black开发板实现的PRU按键控制呼吸灯】
5 心得体会
学习AM3358并尝试使用其内部的PRU来实现按键控制呼吸灯的过程中,我收获了很多新知识。我第一次知道了PRU的作用以及如何使用,其在实时性要求较高的波形控制任务方面是一个非常强大的工具。而且我也学习了一些简单shell脚本命令和linux编程的基础知识。这次经历不仅让我在技术上取得了进步,更激发了我对嵌入式系统开发的浓厚兴趣。我期待着在未来能够利用AM3358的更多功能,探索其在更多复杂场景下的应用,例如工业控制、机器人和物联网等领域。