1. 项目简介
项目旨在设计并实现一个嵌入式系统,该系统能够通过 BeagleBone® Black 开发板的可编程实时单元(PRU)控制 LED 实现呼吸灯效果,同时通过按键切换 LED 闪烁速度。该项目涉及硬件设计(包括 LED 和按键电路设计)、嵌入式系统开发(基于 PRU 的 PWM 信号生成)、按键输入的信号处理以及系统调试等多个方面。
代码文件已上传附件。
2. 项目设计思路
2.1 总体设计
本项目的总体设计思路是通过 BeagleBone® Black 的 PRU 模块实现高精度的 LED 呼吸灯控制,并通过 GPIO 按键实现速度切换功能。系统总体架构分为两部分:PRU 控制端和按键响应模块。
- PRU 控制端:
PRU 独立运行并生成 PWM 信号,直接控制连接的 LED,使其亮度实现平滑的逐渐增减,形成呼吸灯效果。PRU 的高实时性保证了 PWM 信号的精确性,确保呼吸灯亮度变化的平滑性和视觉效果。
- 按键响应与LED模块:
按键通过 GPIO 接口连接至 PRU,用于检测用户的输入操作。系统通过软件消抖处理来确保按键输入的可靠性,并在按键触发时调整 PRU 的 PWM 参数,实现呼吸灯闪烁速度的切换。按键响应模块的设计保证了系统对用户输入的实时响应。这种模块化的设计方法,结合了 PRU 的实时性能和按键模块的交互能力,通过按键与 LED 呼吸灯的联动,形成了功能高效且稳定的嵌入式实时控制系统。
本项目系统设计如下
3. 硬件设计与介绍
3.1 LED驱动电路
LED驱动电路如下:
在该电路中,P9_30为板卡信号输出网口,使用开发板生成的3.3V电压供电,并作为逻辑电平的参考电压。当该端口电平为低电平时,LED正向导通发亮;当该端口为高电平时,LED截止熄灭。使用低电平使能,可以有效避免环境中的噪声信号对LED工作的影响。
3.2 按键电路
按键部分电路如下:
在该电路中,P9_27为板卡信号输入网口,使用开发板生成的3.3V电压供电,并作为逻辑电平的参考电压。按键按下时该端口电平为高电平;按键松开时该端口电平为低电平。使用102瓷片电容作为滤波电容。当端口信号出现下降沿跳变时,表明按键经历了按下到松开的过程,可以将其作为按键触发的标志。
4.程序设计
4.1 呼吸灯逻辑与呼吸灯速度控制思路
首先,本项目通过连续改变LED灯端口的PWM波脉宽实现对LED灯亮度的连续控制。PWM生成函数见下文。
呼吸灯的亮度和时间的关系可以描述为:
其中D为t时刻占空比,D_min,D_max为最小、最大占空比,T为呼吸灯频率。改变呼吸灯速度可以看作对周期T的控制,当使用累加更新脉宽的控制方式时,周期和步长stp的关系为:
其中t为单位时间。
4.2 软件流程图及其说明
程序的运行主要分为3个步骤:按键状态检测,占空比累加步长更新。
在主循环中,上述3个步骤依次进行。各部分代码如下。
4.3 按键状态检测
此段代码实现了对按键端口的下降沿检测,表明按键经历了按下-松开的过程,可以看作是一个指令单元。使用全局变量记录按键上一时刻的状态,将其与当前时刻的状态比较即可获得动作结果。程序如下:
uint8_t key_scan()
{
if (__R31 & P9_27) \\ Key is pressed
{
key_state = 1;
}
else if (key_state == 1 && !(__R31 & P9_27)) \\ Key is released
{
key_state = 0;
return 1;
}
return 0; \\ None
}
4.4 PWM控制与阻塞延时实现
阻塞延迟部分代码如下
#define MS_1 200000
#define US_1 200
void delay_ms(uint16_t ms)
{
int i=0;
for (i=0;i<ms;i++)
__delay_cycles(MS_1);
}
void delay_us(uint16_t us)
{
int i=0;
for (i=0;i<us;i++)
__delay_cycles(US_1);
}
该代码利用历程自带的__cycle__delay()函数实现阻塞延迟,由于该函数只接受常量输入,因此需要使用for loop控制延时时间。
依据5.1中提到的公式,编写了PWM发生程序如下
void USR1_Blink(uint16_t period, uint16_t pw)
{
__R30 = P9_30; // LED Off
delay_us(pw);
__R30 = 0; // LED On
delay_us(period-pw);
}
4.5 呼吸灯实现
呼吸灯部分代码如下:
/* Global Paras */
int16_t pw = 10;
uint16_t dir = 0;
uint16_t stp = 1;
uint16_t stp_dir = 1;
uint8_t key_state = 0;
void USR1_Breath(uint16_t stp)
{
uint16_t period = 2000;
if (dir)
pw += stp;
else
pw -= stp;
if (pw <= stp)
{
pw = stp;
dir = 1;
}
else if (pw >=period)
{
pw = period;
dir = 0;
}
USR1_Blink(period,pw);
}
本程序通过使脉宽在每个序列累加或递减特定步长从而实现LED亮度的逐渐变化。步长作为函数形参输入。
4.6 基础配置与主函数
本文所提及的代码需要对硬件进行初始化,方能正常工作,代码如下:
% 引脚外设初始化
$ config-pin p9_27 pruin
$ config-pin p9_30 pruout
setup.sh脚本:
#!/bin/bash
export TARGET=MyBlink.pru0
echo TARGET=$TARGET
echo none > /sys/class/leds/beaglebone\:green\:usr1/trigger
echo none > /sys/class/leds/beaglebone\:green\:usr2/trigger
主函数代码如下:
// Filename is MyBlink.pru0.c
void main(void) {
// Points to the GPIO port that is used
/* Clear SYSCFG[STANDBY_INIT] to enable OCP master port */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
gpio1[GPIO_SETDATAOUT] = USR1;
while(1) {
if (key_scan())
{
gpio1[GPIO_SETDATAOUT] = USR1;
if(stp_dir == 1)
stp += 4;
}
else
gpio1[GPIO_CLEARDATAOUT] = USR1;
if (stp > 20)
{
stp = 4;
}
USR1_Breath(stp);
}
__halt();
}
该函数通过循环执行5.2的流程完成任务。
5. 功能展示
呼吸灯效果详见视频。为了验证按键和呼吸灯频率的关系,按键按下次数和呼吸灯周期的关系:
按键按下次数 | 步长(STP) | 呼吸灯周期(s) |
---|---|---|
0 | 4 | 2 |
1 | 8 | 1 |
2 | 12 | 0.5 |
3 | 16 | 0.25 |
4 | 20 | 0.125 |
5 | 4 | 2 |
... | ... | ... |
5. 总结与展望
本项目成功实现了利用Beaglebone Black开发板,使用PRU模块,实现了IO口基础读写操作。本项目通过使用下降沿检测进行按键扫描,通过改变呼吸灯占空比累加步长改变呼吸灯闪烁频率。BBB是一个潜力十足的开发板,在未来我将会继续进行如下探索:使用来自网页的指令对LED进行控制;探索该开发板在物联网、ROS节点通讯中的应用。
10. 解决的问题与心得体会
对于Linux系统的开发板,在过去的学习探索中,我主要基于RaspberryPi开发板,探索其在具有视觉、SLAM功能的无人小车中,作为控制平台的作用。惭愧的是我居然从来没有直接对Pin进行操作。在完成此次活动的任务中,这一点不足也确实对我造成了一些困扰。
最初我随便挑选了一个IO口(P9_14),然后没有经过任何初始化就直接进行驱动,显然不会有任何结果。经过学习这篇博客,我发现了需要对io进行初始化,此文使用了P9_31实现IO输出。当我照猫画虎把端口号改变为P9_14时,进行如下报错:
debian@beaglebone:/var/lib/cloud9$ config-pin p9_14 pruout
ERROR: write() to /sys/devices/platform/ocp/ocp:P9_14_pinmux/state failed, No such device
起初我以为是驱动文件不全——根本没有这个引脚的配置文件,这显然很荒唐。随后我才意识到不一定所有IO口都连接PRU外设,这一特性在STM32十分常见,例如不是所有IO都有TIM_OC功能。
随后我通过该指令查询了P9_14引脚,指令与输出如下:
debian@beaglebone:/var/lib/cloud9$ config-pin -l p9_14
Available modes for P9_14 are: default gpio gpio_pu gpio_pd gpio_input pwm
正如我的猜测,该引脚并不具备PRU的模式。进一步学习该博客我得知:BBB板载的IO口中具有pruin/pruout模式的pin如下:
本项目使用的时pru0外设,由表可知引脚与寄存器的映射关系。在实际实验中并非所有具有pruin/pruout模式的引脚都可以进行配置,当该引脚同时映射其他外设时,则无法通过简单的配置指令进行配置。
显而易见的,对于一个3年前就可以在一个陌生平台随手写一个呼吸灯测试程序的我,本次任务难度并不大,但是本次任务也给了我一个尝试过去忽略的但也值得学习的地方。在过去我一直将这一类Linux开发板视作是一个计算机,但在查询关于本开发板资料时我发现该开发板居然可以做两轮自平衡机器人,这对我而言实实在在刷新了对芯片、操作系统和实时性的认识。我一向希望通过MCU、MPU等平台做出一些有新意的创新,此次经历让我深刻意识到,相比于硬件平台性能,真正限制我们的,真正创造不凡的是创意本身。
实在惭愧,再一次压着DDL完成任务。相比于暑假,我要面对的除了实验结果的不理想甚至令人费解外,还要面对不少的课程项目与考试,每周一次的在8:00的组会 ... ... 我第一次打开板子测试功能完整性是在9月,并没有注意到板载了完整PRU历程和cookbook,documents,我总以为接触一个完全不熟悉的外设需要耗费我至少一周的时间,不幸的是我并没有这样大块的时间。我总在想,下次组会后我就开始,忙完这个project就开始,论文还缺数据这个先搁置搁置... ...随着DDL临近,我才不得不面对这样一个事实:因为我的拖延(抑或是现实的限制)我几乎要放弃本次任务,直到我抱着试一试的心态在一个深夜登陆了192.163.7.2,详细的例程与文档让我很快对这个外设有了完整了解。于是我几乎飞速的写出了再熟悉不过的呼吸灯。一切进展都加速了,就在DDL的前一周。我发现借助完善的文档,教程和例程,仅利用碎片时间就可以完成任务。我想或许很多事情也有着同样的特质:你很难在工作之余找到完整大块的,不想娱乐的时间做事情,但碎片时间就足以把事情做好!可能生活就是这样一件事情:你必须求解出一个你最满意的答案,但是你不得不面对无穷无尽的限制——它可以来自生活的方方面面。你不断地想寻找一个解析解,一个连续的在任何点都有意义的函数,可事实是有的时候在一些必须面对的限制中,一个解析的结果并不容易求解,但是你却可以通过不断的“迭代拟合”找到和你目标的“残差”最小的数值解!给Board连接笔记本是一次小的迭代,打开192.168.7.2是一次小的迭代,RUN示例程序也是一次小的迭代,但正是无数次小的迭代最终完成了曾经我觉得十分困难的事情,或许不那么优雅但也足够好。虽然本次任务——按键与电灯,看起来也是一次很小的迭代,但在未来如果有了我提到的足以创造不凡的创意以及为了实现创意进行的进一步迭代迭代与迭代,这样拟合的结果也许离“令人满意”的残差也没有那么大!
感谢Funpack活动提供的宝贵的学习和实操机会以及实验平台,感谢交流群里无数大佬提供的宝贵启迪。本文在仓促之间完成,疏漏在所难免,如有技术问题未尽事宜,请务必联系我(董 1464465962@qq.com)做修正或补充。