Funpack - S3-5 - BeagleBoneBlack - 模拟赛车转速灯(RacingRPM)
该项目使用了BeagleBoneBlack,实现了模拟赛车转速灯(RacingRPM)的设计,它的主要功能为:实时显示赛车游戏里车辆的转速信息。
标签
嵌入式
BeagleBoneBlack
模拟赛车
arilink
更新2025-01-13
18

项目背景

首先由衷的祝愿电子森林发展的越来越好,自从参与Funpack项目以来,接触了越来越多的开发板,也拓宽了很多知识面,所以无需犹豫,果断参与第四期。

第四期的主题是点灯,具体到BeagleBone Black(以下简称BBB)上使用AM335的PRU模块点灯,本项目是在此基础上把只会亮灭的LED变成一组RGB灯,这一组RGB灯刚好可以用作模拟赛车的转速指示灯。

项目详情

BBB使用AM335作为主控,主控通过网口与局域网上的上位机进行通讯,上位机通过UDP协议获取赛车游戏中车辆的遥测数据,并打包发给BBB,BBB上的AM335通过内置的PRU模块驱动WS2812灯带,并根据遥测数据中发动机的转速更新灯带显示,项目框图如下:

设计思路

  1. 上位机通过UDP与游戏建立通讯,获取到游戏中赛车的实时遥测数据
  2. BBB有网口,可以与上位机在局域网内建立通讯
  3. 上位机和BBB之间建立UDP通讯,将遥测数据下发到BBB
  4. AM335内部的PRU模块可以创造出符合WS2812时序的波形,驱动WS2812显示
  5. AM335的内核可以与PRU模块通过RPMsg通讯,用于更新WS2812的显示状态

硬件介绍

芯片介绍

IMG-Funpack 4 - BBB - RacingRPM-20241207000924-1.png

上图为BBB的主控芯片AM335的功能框图,AM335的主要特点如下:

  1. 高达 1GHz Sitara™ ARM® Cortex®-A8 32 位精简指令集计算机 (RISC) 处理器
  2. 片上存储器(共享 L3 RAM)
  3. 外部存储器接口 (EMIF)
  4. 通用存储器控制器 (GPMC)
  5. 可编程实时单元子系统和工业通信子系统 (PRU-ICSS)
  6. SGX530 3D 图形引擎
  7. ....

开发板介绍

IMG-Funpack 4 - BBB - RacingRPM-20241207000924-2.png

IMG-Funpack 4 - BBB - RacingRPM-20241207000924-3.png

BBB采用6层板 布局规整,走线清晰

软件流程

IMG-Funpack 4 - BBB - RacingRPM-20241207000924-4.png

如上图所示,BBB在上电后,首先启动PRU模块,PRU模块完成配置,等待主程序的指令,然后通过代码中预设的IP地址和端口号与上位机建立通讯,上位机会下发游戏中实时的车辆遥测数据,BBB接收到遥测数据后解析并获取到更新WS2812所需的转速灯带信息,并通过RPMsg模块发送给PRU,PRU模块根据主程序的指令调整WS2812的显示状态,以实现模拟赛车转速灯的效果。

开发流程

开发过程主要分为基础知识学习,PRU驱动WS2812,AM335与上位机通讯,联调四个主要步骤,具体内容如下:

基础知识

PRU

对于嵌入式开发而言,目前接触比较多的主控类芯片有MPU,MCU,和FPGA三类,其中MPU主要运行Linux系统,来做一些复杂的应用比如音视频编解码,网络通讯等。而MCU主要做一些实时控制,会使用RTOS作为操作系统,而BBB上的主控芯片则是把这两种芯片结合起来形成的SOC,PRU模块在AM335芯片上就是一个可编程实时控制单元,这也就是PRU的由来。
PRU 有独立于 ARM 处理器运行的 32 位内核,因此可以对它们进行编程,使其快速响应输入并产生非常精确的时序输出。

WS2812

WS2812/WS2812B LED 使用 24 位来表示绿色、红色和蓝色值。数据线上的位由高脉冲和低脉冲编码。


IMG-Funpack 4 - BBB - RacingRPM-20241207000924-5.png

对于 WS2812B,“0” 的编码方式是先用 0.35 µs 的高脉冲,然后是 0.90 µs 的低脉冲。“1” 的编码方式是先用 0.90 µs 的高脉冲,然后是 0.35 µs 的低脉冲。高脉冲和低脉冲的总长度始终为 1.25 µs。

发送超过 24 位的数据会将位移位到下一条 led 线路。当线路保持低位至少 50 µs(称为“重置”条件)时,位会从移位寄存器输入到 pwm 驱动器。

发送单个 LED 的数据需要 24 × 1.25 µs + 50 µs = 80 µs。发送 8 个 LED 的数据需要:8 × 24 × 1.25 µs + 50 µs = 290 µs

IPC核间通讯

/dev/rpmsg_pru30是一个设备驱动程序,它允许 ARM 与 PRU 通信。

PRU驱动WS2812

使用PRU驱动WS2812实际上就是通过反转IO来满足WS2812规格书所要求的时序,每完成一次反转就代表完成了一个bit的发送,每8个bit代表RGB的一种颜色分量,每24个bit代表一个灯珠的完整控制。具体代码如下:

////////////////////////////////////////
// neopixelRpmsg.c
// Uses rpmsg to control the NeoPixels via /dev/rpmsg_pru30 on the ARM
// Usage: echo index R G B > /dev/rpmsg_pru30 to set the color at the given index
// echo -1 0 0 0 > /dev/rpmsg_pro30 to update the string
// echo 0 0xf0 0 0 > /dev/rpmsg_pru30 Turns pixel 0 to Red
// neopixelRainbow.py to display moving rainbow pattern
// Wiring: The NeoPixel Data In goes to P9_29, the plus lead to P9_3 or P9_4
// and the ground to P9_1 or P9_2. If you have more then 40 some
// NeoPixels you will need and external supply.
// Setup: config_pin P9_29 pruout
// See:
// PRU: pru0
////////////////////////////////////////
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h> // atoi
#include <string.h>
#include <pru_cfg.h>
#include <pru_intc.h>
#include <rsc_types.h>
#include <pru_rpmsg.h>
#include "resource_table_0.h"
#include "prugpio.h"

volatile register uint32_t __R30;
volatile register uint32_t __R31;

/* Host-0 Interrupt sets bit 30 in register R31 */
#define HOST_INT ((uint32_t) 1 << 30)

/* The PRU-ICSS system events used for RPMsg are defined in the Linux device tree
* PRU0 uses system event 16 (To ARM) and 17 (From ARM)
* PRU1 uses system event 18 (To ARM) and 19 (From ARM)
* Be sure to change the values in resource_table_0.h too.
*/
#define TO_ARM_HOST 16
#define FROM_ARM_HOST 17

/*
* Using the name 'rpmsg-pru' will probe the rpmsg_pru driver found
* at linux-x.y.z/drivers/rpmsg/rpmsg_pru.c
*/
#define CHAN_NAME "rpmsg-pru"
#define CHAN_DESC "Channel 30"
#define CHAN_PORT 30

/*
* Used to make sure the Linux drivers are ready for RPMsg communication
* Found at linux-x.y.z/include/uapi/linux/virtio_config.h
*/
#define VIRTIO_CONFIG_S_DRIVER_OK 4

char payload[RPMSG_BUF_SIZE];

#define STR_LEN 15
#define oneCyclesOn 700/5 // Stay on for 700ns
#define oneCyclesOff 600/5
#define zeroCyclesOn 350/5
#define zeroCyclesOff 800/5
#define resetCycles 51000/5 // Must be at least 50u, use 51u
#define out 1 // Bit number to output on

#define SPEED 20000000/5 // Time to wait between updates

uint32_t color[STR_LEN]; // green, red, blue

/*
* main.c
*/
void main(void)
{
struct pru_rpmsg_transport transport;
uint16_t src, dst, len;
volatile uint8_t *status;
// Select which pins to output to. These are all on pru1_1
uint32_t gpio = P9_27;

uint8_t r, g, b;
int i, j;
// Set everything to background
for(i=0; i<STR_LEN; i++) {
color[i] = 0x010000;
}

/* Allow OCP master port access by the PRU so the PRU can read external memories */
CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;

/* Clear the status of the PRU-ICSS system event that the ARM will use to 'kick' us */
CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;

/* Make sure the Linux drivers are ready for RPMsg communication */
status = &resourceTable.rpmsg_vdev.status;
while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK));

/* Initialize the RPMsg transport structure */
pru_rpmsg_init(&transport, &resourceTable.rpmsg_vring0, &resourceTable.rpmsg_vring1, TO_ARM_HOST, FROM_ARM_HOST);

/* Create the RPMsg channel between the PRU and ARM user space using the transport structure. */
while (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS);
while (1) {
/* Check bit 30 of register R31 to see if the ARM has kicked us */
if (__R31 & HOST_INT) {
/* Clear the event status */
CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;
/* Receive all available messages, multiple messages can be sent per kick */
while (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS) {
char *ret; // rest of payload after front character is removed
int index; // index of LED to control
// Input format is: index red green blue
index = atoi(payload);
// Update the array, but don't write it out.
if((index >=0) & (index < STR_LEN)) {
ret = strchr(payload, ' '); // Skip over index
r = strtol(&ret[1], NULL, 0);
ret = strchr(&ret[1], ' '); // Skip over r, etc.
g = strtol(&ret[1], NULL, 0);
ret = strchr(&ret[1], ' ');
b = strtol(&ret[1], NULL, 0);

color[index] = (g<<16)|(r<<8)|b; // String wants GRB
}
// When index is -1, send the array to the LED string
if(index == -1) {
// Output the string
for(j=0; j<STR_LEN; j++) {
// Cycle through each bit
for(i=23; i>=0; i--) {
if(color[j] & (0x1<<i)) {
__R30 |= gpio; // Set the GPIO pin to 1
__delay_cycles(oneCyclesOn-1);
__R30 &= ~gpio; // Clear the GPIO pin
__delay_cycles(oneCyclesOff-14);
} else {
__R30 |= gpio; // Set the GPIO pin to 1
__delay_cycles(zeroCyclesOn-1);
__R30 &= ~gpio; // Clear the GPIO pin
__delay_cycles(zeroCyclesOff-14);
}
}
}
// Send Reset
__R30 &= ~gpio; // Clear the GPIO pin
__delay_cycles(resetCycles);

// Wait
// __delay_cycles(SPEED);
}

}
}
}
}
// 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" \
"\0\0";

AM335与上位机通讯

AM335使用Ethernet与上位机通讯,通过Python脚本启动UDP服务器,上位机在获取到遥测数据后会通过BBB的IP和端口号下发数据,BBB接收到数据后会进行解析。

#!/usr/bin/python3
# ////////////////////////////////////////
# // rpm_led_host.py
# // UDisplays a moving rainbow pattern on the NeoPixels
# // Usage: Receive vehicle telemetry data via UDP and update the speedometer light.
# // Wiring: See neopixelRpmsg.c for wiring
# // Setup: Run neopixelRpmsg.c on the PRU
# // See:
# // PRU: Runs on ARM
# ////////////////////////////////////////
import socket
import struct

total_len = 15
rpm_led_num = 12

# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定地址和端口
server_address = ('192.168.10.66', 8888)
sock.bind(server_address)

try:
# print(f"Listening to UDP on {server_address}...") # 无限循环来接收数据
while True:
data, client_address = sock.recvfrom(1024) # Buffer size is 1024 bytes
STRING_FORMATER = 'bbHHH'
# print(f"Received message: {data.hex()} from {client_address}")
gear,served,led_num,speed,rpm = struct.unpack(STRING_FORMATER, data) #python需要做字节对齐类似5 7长度的会报错 因此加入了一个served Byte
# print(f"Gear:{gear} Speed:{speed} RPM:{rpm} LEDS:{led_num}")
update_rpm_leds(led_num)
# 对数据进行处理(如回显)
# sock.sendto(data, client_address)
finally:
print("Closing socket...")
sock.close()

通过遥测数据更新WS2812

BBB接收到数据后解析,并通过RPMsg通知PRU更新WS2812的显示效果,具体代码如下:

def update_rpm_leds(led_number):
# Open a file
fo = open("/dev/rpmsg_pru30", "wb", 0) # Write binary unbuffered
for i in range(0, total_len):
bit = (led_number >> i) & 1 # 右移i位并与1进行位与操作以获取第i位
if (bit) == 1:
if i < 5:
r = 10;
g = 0;
b = 0;
elif 5 <= i < 10:
r = 0;
g = 10;
b = 0;
else:
r = 3;
g = 0;
b = 12;
else:
r = 0;
g = 0;
b = 0;
fo.write("%d %d %d %d\n".encode("utf-8") % (i, r, g, b))
# print(f"LED{i} is {bit}")
fo.write("-1 0 0 0\n".encode("utf-8"))
fo.close()

注意事项

python需要做字节对齐 类似5 7长度的会报错 所以需要加入了一个served Byte保证python脚本可以正确解析上位机发来的结构体数据



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