Funpack3-5: 基于BeagleBone Black开发板的PRU控制呼吸灯系统
该项目使用了BeagleBone Black开发板、clpru开发工具,实现了LED控制系统的设计,它的主要功能为:使用PRU读取按钮状态和共享内存数据,通过按钮和网页联动PRU控制LED灯的闪烁模式和闪烁频率。
标签
Funpack活动
呼吸灯
PRU
Beaglebone
clr
更新2025-01-13
72

一、 项目介绍

BeagleBone Black 搭载的 AM3358 中内置了可编程实时单元(Programmable Real-time Unit, PRU)为实时控制提供了强大的支持,本项目旨在充分利用PRU的低延迟特性,结合网页控制接口,实现一个可以自定义控制的LED呼吸灯系统。

项目目标

  1. 使用BeagleBone BlackPRU实现LED的呼吸灯效果,通过控制PWM输出改变LED的亮度,模拟呼吸效果。
  2. 配置一个物理按键,实现按键按下后切换LED呼吸灯的闪烁速度(例如慢速、中速、快速等不同频率)。
  3. 构建一个简单的网页控制界面,通过该网页可以控制PRU的内存,从而实现对LED状态(开/关)和闪烁速度的实时控制。
  4. 综合上述目标,实现同时使用按钮和网页控制LED闪烁速度。


设计思路

任务一:点灯!PRU控制LED呼吸灯

使用pru控制LED的亮灭,通过占空比的变化来模拟呼吸灯效果。通过PWM模拟多种函数波形达到不同的呼吸灯效果,计算占空比值时,可选择以下函数曲线,例如正弦函数、反Gamma曲线等,调整LED亮度变化的平滑程度。同时需要在pru操作LED的间隙检测按钮状态,并在按下和释放检测中进行消抖处理。


根据传入的参数,检测按钮是否被按下或释放同时之前没有被触发过,同时进行2ms的消抖,释放按钮后使用下一种模式。

根据传入的当前循环数、总半周期数和使用的模式,计算当前的占空比并映射到查找表。过多的复杂数学计算可能会导致最终生成的程序超过pru内存限制导致编译失败,预计算的查找表可以提升运行速度。


软件流程图:

image.png

任务二:在系统中建立一个网页,并且与LED联动,使用网线连接到设备上时,可以从网页中控制LED的开关与闪烁

AM335XPRU具有两个独立的处理单元,每个处理单元有独立的内存区域但可互相访问,还有一个共享的内存区域,可以被linux系统读写。利用这个特性,通过pythonflask库实现前端读取传入的指令,并通过 /dev/mem”改写指定的共享内存地址,pru程序读取到内存数据后改变闪烁速率或模式。


软件流程图:

image.png

任务三:

综合任务二和三,使用pru控制LED的亮灭,通过占空比的变化来模拟呼吸灯效果,同时需要在pru操作LED的间隙检测按钮状态,并在按下和释放检测中进行消抖处理,以及读取pru共享内存,检测模式和速度参数是否改变。通过pythonflask库实现前端,根据传入的http指令读取或改写pru内存,从而控制小灯。


软件流程图:

image.png

呼吸灯效果:

image.pngimage.png

网页端与按钮同时控制效果,按下按钮或网页端切换模式

image.pngimage.png

网页前端设置每0.5s获取一次状态数据并更新显示

image.png


实现过程

1.配置开发环境

通过config-pin查询需要使用的pin是否被其它功能占用(如:config-pin -i p9.28

),默认设置下,HDMIAUDIO功能占用了对应的pin,无法更改为PRU功能,需要在"/boot/uEnv.txt"取消disable的注释


如果编译环境有问题,在ti官网下载pru cgt安装包 https://www.ti.com/tool/PRU-CGT#downloads


2. 可以通过三种方法设置网络:

a.通过usb连接电脑可以使用电脑代理来连接网络,导出proxy环境设置,无需其它配置,如:
export https_proxy=https://192.168.7.1:10809

export http_proxy=https://192.168.7.1:10809


b.设置网关以及网络分享:

bbb上设置默认网关为usb(电脑),sudo ip route add default via 192.168.7.1,或修改"/etc/systemd/network/usb0.network",在[Network]下添加Gateway=192.168.7.1,持久化网络配置。

在电脑中将正在使用的网络共享给和bbb通过usb连接的网络并且设置与bbb连接的网络,确保IP地址配置正确

image.png

c.直接使用网线连接


3. 连接bbbLED、按钮


正确连接电源,LED#22绿/#32红)和按钮(#13)集成在X-STM32MP-MSP01拓展板上,按钮和LED处于高电平,使用时需注意电流方向。连接的两个pru接口,一个输入PRU0_R31_16(P9.28)连接按钮,另一个输出PRU0_R30_5(P9.27)连接LED
image.png

连接完成后需要设置连接的pin为对应的功能,如:

config-pin p9.27 pruout //led

config-pin p9.28 pruin //按钮


4. 具体代码实现

4.1 循环延时函数

void delayCycle(uint32_t cycle)  {
    uint32_t step = 0;
    for (step = 0; step < cycle; step++)  {
        __delay_cycles(2000); //about 0.01ms
    }
}

__delay_cycles()函数只支持常数参数,通过循环控制占空时长。


4.2 按钮状态检查

void check(uint8_t press)
{
if(press){
if (!(__R31 && button) && (!buttonState)) {
// Button pressed, debounce
__delay_cycles(1000000); // 5ms debounce at 200MHz
if (!(__R31 && button)) {
// Confirmed press, change frequency
buttonState = 1;
}
}
}
else{
if (((__R31 && button)) && buttonState) {
// Button released, debounce
__delay_cycles(1000000); // 5ms debounce at 200MHz
if (__R31 && button) {
buttonState = 0;
(*mode_mem) ++;
if (*mode_mem == 5) {*mode_mem = 0; }
}
}
}
}

检测按钮是否被按下或释放,并进行5ms的除抖。只有当检测到按钮被按下且之前没有被按下,改写按钮状态为1;检测到按钮之前没有被按下(按钮状态为1),且被释放,改写按钮状态为0并增加模式值增加模式值到5时改写为0返回。


4.3 占空比查表

uint8_t duty(uint32_t z, uint32_t period_rd, uint8_t algo)
{
float ratio = (float)z / period_rd;
uint8_t duty = (uint8_t)(ratio * 255);
if(algo == 2){ // sinf(0.5 * PI * z / period_rd)
duty = sin_lut[duty];
}
if(algo == 3){ // 1 - cosf(0.5 * PI * z / period_rd)
duty = sinr_lut[duty];
}
if(algo == 4){ // gamma2.2
duty = gamma_lut[duty];
}
return duty;
}

根据传入的模式查询并计算占空时长。


4.4 pwm函数

void pwm(uint32_t period_rd, uint8_t mode)
{
uint32_t z = 0;
uint8_t duty0;
for (z=0; z<period_rd; z++) {
__R30 &= ~led;
duty0 = duty(z, period_rd, *mode_mem);
delayCycle(duty0);
__R30 ^= led;
delayCycle(255 - duty0);
check(z % 2);
if (*mode_mem==0) {return;}
}
for (z=0; z<period_rd; z++) {
__R30 |= led;
duty0 = duty(z, period_rd, *mode_mem);
delayCycle(duty0);
__R30 ^= led;
delayCycle(255 - duty0);
check(z % 2);
if (*mode_mem==0) {return;}
}
}

根据传入的参数对LED进行pwm控制,交替检测按钮状态,检测到模式值为0时返回。


4.5 网页后端实现

使用flask库作为后端接收客户端的请求,/get_current_value 接受前端的 GET 请求,返回PRU共享内存中当前的模式值和轮次值;/update_mem:接受前端的 POST 请求,用于更新PRU共享内存中的模式值或轮次值;使用 mmap 通过 /dev/mem 直接读写PRU共享内存。 GET 请求 "http://192.168.7.2:9000" 时,返回 static 目录下的index.html

from flask import Flask, request, jsonify
import mmap
import os

app = Flask(__name__)

PRU_SHARED_MEM_ADDR_MODE = 0x00010000
PRU_SHARED_MEM_ADDR_ROUND = 0x00010004
PRU_LEN = 0x80000
PRU_ADDR = 0x4A300000

def read_shared_memory(address):
with open("/dev/mem", "r+b") as f:
mem = mmap.mmap(f.fileno(), PRU_LEN, offset=PRU_ADDR)
mem.seek(address)
value = int.from_bytes(mem.read(4), byteorder='little')
mem.close()
return value

def write_shared_memory(address, value):
with open("/dev/mem", "r+b") as f:
mem = mmap.mmap(f.fileno(), PRU_LEN, offset=PRU_ADDR)
mem.seek(address)
mem.write(value.to_bytes(4, byteorder='little'))
mem.close()

@app.route('/update_mem', methods=['POST'])
def update_mem():
try:
mode = request.form.get('mode')
round_delta = request.form.get('round')

if mode is not None:
mode = int(mode)
if not (0 <= mode <= 4):
return jsonify({"error": "Mode value must be between 0 and 4"}), 400

current_mode = read_shared_memory(PRU_SHARED_MEM_ADDR_MODE)
write_shared_memory(PRU_SHARED_MEM_ADDR_MODE, mode)

return jsonify({
"message": "Mode updated",
"previous_mode": current_mode,
"new_mode": mode
})

if round_delta is not None:
round_delta = int(round_delta)
if round_delta not in [-1, 1]:
return jsonify({"error": "Round delta must be -1 or 1"}), 400

current_round = read_shared_memory(PRU_SHARED_MEM_ADDR_ROUND)
new_round = current_round + round_delta

if not (0 <= new_round <= 4):
return jsonify({"error": "Round value out of range. Must remain between 0 and 4."}), 400

write_shared_memory(PRU_SHARED_MEM_ADDR_ROUND, new_round)

return jsonify({
"message": "Round updated",
"previous_round": current_round,
"new_round": new_round
})

return jsonify({"error": "No valid data provided. Specify either 'mode' or 'round'."}), 400

except ValueError:
return jsonify({"error": "Invalid data provided. Mode must be an integer, and Round delta must be -1 or 1."}), 400
except Exception as e:
return jsonify({"error": str(e)}), 500

@app.route('/')
def index():
return app.send_static_file('index.html')

@app.route('/get_current_value', methods=['GET'])
def get_current_value():
try:
current_mode = read_shared_memory(PRU_SHARED_MEM_ADDR_MODE)
current_round = read_shared_memory(PRU_SHARED_MEM_ADDR_ROUND)
return jsonify({"current_mode": current_mode, "current_round": current_round})
except Exception as e:
return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)


4.6 运行

编译好固件并加载到pru中启动:

make
make install_PRU0

如果有问题,手动输入命令,注意替换生成的固件位置:

echo stop | sudo tee /sys/class/remoteproc/remoteproc1/state //停止PRU0运行
make clean
make
sudo cp /root/code/ledUltimate/gen/ledUltimate.out /lib/firmware/am335x-pru0-fw //加载固件到PRU0
echo start | sudo tee /sys/class/remoteproc/remoteproc1/state //运行PRU0

运行后端:

source /root/code/venv/bin/activate		//加载安装好相关库的python虚拟环境
python3 /root/code/venv/web.py //运行后端

image.pngimage.png

网页每隔0.5s获取一次内存数据,

未来的计划建议

PRU还有许多有意思的特性有待发掘,如还可以通过RPMsg使PRU和Linux内核的双向通信;同时使用两个pru核心并协作完成复杂任务;工业级实时通信等。




附件下载
SourceCode.zip
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号