Funpack3-5 基于BeagleBone® Black的LED呼吸灯
该项目使用了BeagleBone® Black,实现了PRU上的LED呼吸灯的设计,它的主要功能为:按键可控呼吸灯速度。
标签
Funpack活动
嵌入式
Arm
C
raincorn
更新2025-01-13
38

Funpack3-5 基于BeagleBone® Black的LED呼吸灯

BeagleBone® Black是一款基于TI AM3358的嵌入式开发板,该芯片的原理框图如下图所示。该芯片包含一颗主频高达1GHz的ARM Cortex-A8内核与两颗可编程实时处理器(Programmable Real-Time Units,PRU),还包括独立的显示单元与众多外设。借助PRU的实时特性,使得其非常适合用做工业领域的控制与协议栈实现,以实现对工业以太网(Profinet、EtherCAT、EtherNET等协议)的接入。

本设计拟通过AM3358的PRU,通过读写GPIO来实现按键控制呼吸灯速度的功能。开发平台直接使用Cloud9,其中按键输入为P9-30,呼吸灯输出为P9-27,程序运行在PRU0上。下面将详细该项目的具体实现过程,包含硬件上按键与LED的电路搭建,与软件上对PRU IO读写与呼吸灯控制逻辑的实现。

硬件

参考BeagleBone Black开发板原理图可知,该开发板有P8 P9两个扩展排座。其中P8端口多被板卡上的LCD、MMC等外设复用,由于需要使用PRU0进行控制,因此在P9上选取两个GPIO端口用于读写。

参考开发板手册P69页的Table7.2 Expansion Header P9 Pinout,选取P9-27作为输出,对应的PRU0端口为pr1_pru0_pru_r30_5。同样地选取P9-30为输入,对应的PRU0端口为pr1_pru0_pru_r31_2。在后续的软件代码中将会将这两个寄存器映通过宏定义的方式,定义到P9-27/30。

image.png

其中P9-27连接到LED灯,GPIO输出高电平时点亮;P9-30连接到按键,按键没有按下时通过上拉电阻拉低,为低电平。按键按下后端口上的电压为R2 R3对VCC的分压,为高电平。

软件

软件部分的实现主要分为两个部分,即呼吸灯的实现与控制逻辑。其中呼吸灯的实现主要通过可变PWM占空比来实现亮度的渐变,借助软件延迟实现占空比可调。控制逻辑包括按键的读取与呼吸灯速度参数的改变。软件的具体流程图如下图所示:

bbb.drawio.png

对PRU而言,GPIO的操作本质上是对R30(输出)与R31寄存器的读写。SDK在prugpio.h中定义了扩展端口P9上IO与PRU相关寄存器的对应关系,其中P9-27对应的是PRU0的Bit5,P9-30对应的是PRU0的Bit2。在进行读写操作时只需要将R30/R31与该值相与,即可提取出该端口上的IO电平。

// R30/R31 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)

呼吸灯通过可变PWM占空比实现,当PWM频率高到肉眼无法察觉时便可以忽略开关闪烁。当占空比越高时灯越亮,占空比越低灯越暗,叠加变暗与变亮两个过程就可以实现连续的呼吸灯效果,其具体过程的示意图如下所示。

image.png

具体代码描述如下,该函数传入一个speed用来控制流水灯的速度。在第一个循环时,PWM占空比随着变量i的累加逐渐变小,灯由亮变暗。第二个循环时变量递减,灯由暗变亮。

// breath led, the speed range is 1~10
void breath_led(int speed) {
   uint32_t i;
   for (i = 0; i < 1000; i++) {
       __R30 |= led; // LED -> 1
       delay((1000-i)/speed);
       __R30 &= ~led; // LED -> 0
       delay(i/speed);
  }
   for (i = 1000; i > 0; i--) {
       __R30 |= led; // LED -> 1
       delay((1000-i)/speed);
       __R30 &= ~led; // LED -> 0
       delay(i/speed);
  }
}

其中延迟函数如下所示,借助内联汇编__asm__(" nop");使用软件延迟实现,一个nop指令对应一个时钟周期的长度。考虑到涉及循环周期的计算,实际的延迟时间略大于设定时间。

// delay by nop
void delay(uint32_t delay_us) {
uint32_t i;

   double cycle_time_s = 1.0 / (double)PRU_CLK_FREQ;
   // us -> s
   double delay_s = (double)delay_us * 1e-6;
   // cal the clock cycles
   uint32_t cycle_count = (uint32_t)(delay_s / cycle_time_s);
   // delay by one clock cycle
   for (i = 0; i < cycle_count; i++) {
       __asm__(" nop");
  }
}

控制逻辑包含对按键的读取与流水灯参数的下发,当按键按下时R31对应的Bit位置高,此时保存流水灯速度的变量speed_level以1为步进累加,最后将速度参数传入流水灯函数以控制速度。

while(1) {
if(__R31 & key){
speed_level ++;
if(speed_level > 3){
speed_level = 1;
}
}
breath_led(speed_level*3);
}

总结

AM3358是工业界较为常用的一款处理器,Coretx A8+PRU的组合使得其可以兼顾高性能与高实时性,非常适合将对实时性要求较高的任务卸载到PRU以提高响应速率,传统任务继续使用高性能Coretx A8来运行以提高效率。一个典型的引用场景是工业现场PLC设备的组网,TI官方提供了运行在PRU上的Profinet/EtherCAT协议栈,非常适合进行实时的机械控制。

在本项目中,我们有机会借助BeagleBone Black来一窥AM3358上PRU的开发流程,为后续应用的选型评估提供了相当有价值的参考。

附录

////////////////////////////////////////
// blinkR30.pru0.c
// Blinks LEDs wired to P9_29 (and others) by writing register R30 on the PRU
// Wiring: P9_29 connects to the plus lead of an LED. The negative lead of the
// LED goes to a 220 Ohm resistor. The other lead of the resistor goes
// to ground.
// Setup: config-pin P9_27 pruout config-pin P9_30 pruin
// See: prugpio.h to see which pins attach to R30
// PRU: pru0
////////////////////////////////////////
#include <stdint.h>
#include <pru_cfg.h>
#include "resource_table_empty.h"
#include "prugpio.h"

#define PRU_CLK_FREQ 200000000

volatile register unsigned int __R30;
volatile register unsigned int __R31;

uint32_t led = P9_27;
uint32_t key = P9_30;
uint32_t speed_level = 1;

// delay by nop
void delay(uint32_t delay_us) {
uint32_t i;

   double cycle_time_s = 1.0 / (double)PRU_CLK_FREQ;
   // 将微秒转换为秒
   double delay_s = (double)delay_us * 1e-6;
   // 算出需要的时钟周期数
   uint32_t cycle_count = (uint32_t)(delay_s / cycle_time_s);
   // 使用nop实现延迟
   for (i = 0; i < cycle_count; i++) {
       __asm__(" nop");
  }
}

// breath led, the speed range is 1~10
void breath_led(int speed) {
   uint32_t i;
   for (i = 0; i < 1000; i++) {
       __R30 |= led; // LED -> 1
       delay((1000-i)/speed);
       __R30 &= ~led; // LED -> 0
       delay(i/speed);
  }
   for (i = 1000; i > 0; i--) {
       __R30 |= led; // LED -> 1
       delay((1000-i)/speed);
       __R30 &= ~led; // LED -> 0
       delay(i/speed);
  }
}

void main(void) {

// Clear SYSCFG[STANDBY_INIT] to enable OCP master port
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;

while(1) {
if(__R31 & key){
speed_level ++;
if(speed_level > 3){
speed_level = 1;
}
}
breath_led(speed_level*3);
}
__halt();
}

// Sets pinmux
#pragma DATA_SECTION(init_pins, ".init_pins")
#pragma RETAIN(init_pins)
const char init_pins[] =  
"/sys/devices/platform/ocp/ocp:P9_27_pinmux/state\0pruout\0" \
"/sys/devices/platform/ocp/ocp:P9_30_pinmux/state\0pruin\0" \
"\0\0";


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