项目背景
很高兴参加funpack第三季第五期的活动,本次活动的开发板是BeagleBone® Black。
功能介绍
任务描述
- 使用PRU控制一个LED实现呼吸灯
- 使用一个按键按下后切换呼吸灯闪烁的速度
硬件介绍
BeagleBone® Black是一种微型计算机,非常适合学习和制作电子原型,板卡处理器选用的是 TI 公司 AM3358 芯片, 处理器集成了高达 1GHz 的 ARM Cortex™ A8 内核,板载512MB DDR3L内存,4GB eMMC闪存,TPS65217C电源管理,支持JTAG调试,miniUSB和直流电源供电。它配备了丰富的外设,使用户能够体验处理器的强大功能,还提供了许多接口,用于开发拓展的电路板,例如,提供了USB 2.0主/客户端,10/100M以太网,UART串行端口,HDMI、LCD接口,46针扩展接口,配备4个用户LED。
设计思路
主要困难
从任务描述中可以看出,实现该任务有两个核心功能:
- 构建基础呼吸灯模块
- 外部按键触发,调整呼吸灯频率
解决思路
BeagleBone® Black既有运行 Linux 的 ARM 处理器,又有可编程实时单元(PRU)。PRU 具有 32 位内核,独立于 ARM 处理器运行,因此可以对其进行编程,使其对输入做出快速响应,并产生非常精确的定时输出。一般情况下,完成呼吸灯的操作是在Linux系统上编程的,但是为了熟悉PRU的操作,本次呼吸灯代码将利用PRU完成编程。
可编程实时单元(Programmable Real-time Unit SubSystem,PRUSS),是Cotex A8内核中的一个子系统,它可运行在1/2CPU时钟频率下,具有本地的指令和数据RAM,并可寻址访问整个片上系统资源。PRU模块接口是由两个内部寄存器R30和R31构成。R31为PRU的通用输入端(GPI)中断控制器(INTC)提供接口。通过读取R31的值便可获得GPI引脚以及INTC的状态信息。写R31则可为PRU系统产生事件信息。R30为PRU模块的通用输出端(GPO)的状态接口。
对于当前BeagleBone® Black系统版本,可能对某些IO口没有很好的支持,查看配置端口映射向量表的头文件,选择端口 P9_27 作为PRU控制LED的输出端口,选择端口 P9_30 作为按键的输入端口。
核心功能一
呼吸灯的原理很简单,一般来说,呼吸灯是采用 PWM (脉冲宽度调制)的方式,在固定的频率下,通过调整占空比来控制 LED 灯亮度的变化,即在同样小时间段内,小灯亮的时间依次增加到最大后再依次减小,从而实现渐亮到渐灭的“呼吸”效果。
但是由于PRU没有PWM的控制功能,使用IO口模拟PWM,控制IO口输出电平翻转的时长即可。具体来说,选择固定的频率,调节占空比,在同一频率下占空比越高,led越亮,反之led越暗。实现方法是使用一个占空比变量,逐步增加或减少这个变量的值。LED灯的亮度由占空比控制,当占空比达到最大值时,亮度开始逐渐减小;当占空比降到最小值时,亮度又逐渐增加。
核心功能二
呼吸灯控制部分主要用于检测按钮的按下事件,并通过改变过改变亮度变化的延迟时间来调节呼吸灯的速度。如果检测到按钮被按下,延长灯亮和灯灭之间的时间,从而使呼吸灯的频率变慢。当延迟时间达到一定上限后会被重置为初始值。
软件流程图
功能展示
核心配置及代码片段
根据官方文档配置好系统后,下载PRU示例代码,本项目功能将在示例代码的基础上实现。
cd /opt/source
git clone https://git.beagleboard.org/beagleboard/pru-cookbook-code
cd pru-cookbook-code
sudo ./install.sh
查看BeagleBone的引脚复用系统
debian@BeagleBone:/opt/source/pru-cookbook-code/02start$ sudo ls /sys/devices/platform/ocp/
40300000.sram 4a000000.interconnect ocp:P8_09_pinmux ocp:P8_19_pinmux ocp:P9_19_pinmux ocp:P9_42_pinmux
44c00000.interconnect 4b144400.interconnect ocp:P8_10_pinmux ocp:P8_26_pinmux ocp:P9_20_pinmux ocp:P9_91_pinmux
47400000.target-module 4c000000.emif ocp:P8_11_pinmux ocp:P9_11_pinmux ocp:P9_21_pinmux ocp:P9_92_pinmux
478102fc.target-module 53100100.target-module ocp:P8_12_pinmux ocp:P9_12_pinmux ocp:P9_22_pinmux ocp:cape-universal
47c00000.interconnect 53500080.target-module ocp:P8_13_pinmux ocp:P9_13_pinmux ocp:P9_23_pinmux of_node
48000000.interconnect 5600fe00.target-module ocp:P8_14_pinmux ocp:P9_14_pinmux ocp:P9_24_pinmux power
49000000.target-module driver_override ocp:P8_15_pinmux ocp:P9_15_pinmux ocp:P9_26_pinmux subsystem
49800000.target-module modalias ocp:P8_16_pinmux ocp:P9_16_pinmux ocp:P9_27_pinmux supplier:platform:fixedregulator0
49900000.target-module ocp:P8_07_pinmux ocp:P8_17_pinmux ocp:P9_17_pinmux ocp:P9_30_pinmux uevent
49a00000.target-module ocp:P8_08_pinmux ocp:P8_18_pinmux ocp:P9_18_pinmux ocp:P9_41_pinmux
查看pru-cookbook-code/common/prugpio.h中的配置
// R30 output bits on pru0
#define P9_31 (1<<0)
#define P9_29 (1<<1)
#define P9_30 (1<<2)
#define P9_28 (1<<3)
#define P9_92 (1<<4)
#define P9_27 (1<<5)
#define P9_91 (1<<6)
#define P9_25 (1<<7)
综上,查看引脚复用端口和PRU端口重合部分,选择端口 P9_27 作为PRU控制LED的输出端口,连接LED灯,选择端口 P9_30 作为按键的输入端口,连接按键。
配置引脚
config-pin P9_27 pruout
config-pin P9_30 pruin
呼吸灯控制代码实现breath_led.pru0.c
#include <stdint.h>
#include <pru_cfg.h>
#include "resource_table_empty.h"
#include "prugpio.h"
volatile register unsigned int __R30;
volatile register unsigned int __R31;
#define BREATH_GPIO_B P9_27
#define BUTTON_GPIO P9_30
#define PWM_MAX 500 // PWM 的最大周期
#define STEP 2 // 亮度增加/减少的步长
// 初始的 DELAY_MULTIPLIER 值
unsigned int DELAY_MULTIPLIER = 20;
// 按钮状态
int last_button_state = 0; // 默认为低电平
// 自定义延时函数
void custom_delay(unsigned int cycles) {
volatile unsigned int i;
for (i = 0; i < cycles; i++) {
__delay_cycles(1); // PRU 自带的短延时
}
}
// 软件 PWM 呼吸灯函数
void software_pwm_breathing(int duty_cycle) {
__R30 |= BREATH_GPIO_B; // 输出高电平
// __R30 &= ~BREATH_GPIO_R; // 输出低电平
custom_delay(duty_cycle * DELAY_MULTIPLIER); // 按占空比延时
// __R30 |= BREATH_GPIO_R; // 输出高电平
__R30 &= ~BREATH_GPIO_B; // 输出低电平
custom_delay((PWM_MAX - duty_cycle) * DELAY_MULTIPLIER); // 剩余时间延时
}
// 检查按钮按下和释放状态
void check_button() {
int button_state = __R31 & BUTTON_GPIO; // 读取按钮状态
// 检查按键是否从释放变为按下 (上升沿检测)
if ((__R31 & BUTTON_GPIO) && !last_button_state) {
// 每次按下按钮后,增加 DELAY_MULTIPLIER 的值
DELAY_MULTIPLIER += 30;
if (DELAY_MULTIPLIER > 140) {
DELAY_MULTIPLIER = 20; // 超过 140 后重置为 20
}
}
// 记录当前按钮状态,便于下次检测
last_button_state = button_state;
}
void main(void) {
int duty_cycle = 0;
int increasing = 1; // 亮度增加标志
/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0
while (1) {
// 调整占空比,模拟呼吸效果
software_pwm_breathing(duty_cycle);
// 检查按钮状态并更新 DELAY_MULTIPLIER
check_button();
// 根据当前状态增加或减少占空比
if (increasing) {
duty_cycle += STEP;
if (duty_cycle >= PWM_MAX) {
increasing = 0; // 到达最大亮度,开始变暗
}
} else {
duty_cycle -= STEP;
if (duty_cycle <= 0) {
increasing = 1; // 到达最暗,开始变亮
}
}
}
}
运行程序
make TARGET=breath_led.pru0
实现效果
具体演示效果请观看演示视频
总结
本项目依托BeagleBone® Black平台,完成呼吸灯频率调节任务。在此过程中阅读BeagleBone® Black使用手册数据手册和PRU驱动代码,分别实现呼吸灯闪烁和外部按键控制呼吸灯功能,最终顺利完成任务。