- 2025-03-25
-
回复了主题帖:
测评入围名单: 国民技术高性能MCU N32H487开发板
个人信息无误,确认可以完成测评分享计划
- 2025-03-17
-
回复了主题帖:
【MCXA156开发板测评】学习笔记08(低功耗测试性能补充)
Jacktang 发表于 2025-3-16 09:13
低功耗测试性能补充比较认真,测到了功耗低到2.1uA
感觉还是很适合做低功耗的产品的
- 2025-03-14
-
发表了主题帖:
【MCXA156开发板测评】学习笔记08(低功耗测试性能补充)
针对这个低功耗问题的补充:
https://bbs.eeworld.com.cn/thread-1304418-1-1.html
前面已经进行代码说明:
## 低功耗测试补充
当程序运行时,测试功耗为7ma左右:


切换到睡眠模式。实测为1.67mA;

切换到深度睡眠模式,实测为30uA;

切换到关机模式,实测为29uA;

切换到深度下电模式,功耗低到2.1uA。貌似不错;

深度下电模式的功耗已经是非常低,还是比较适合做低功耗的相关产品
-
回复了主题帖:
嵌入式Rust修炼营入围名单来啦,一起度过嵌入式Rust学习时光的小伙伴集合啦~
个人信息无误
- 2025-02-25
-
回复了主题帖:
嵌入式Rust修炼营:动手写串口烧录工具和MCU例程,Rust达人Hunter直播带你入门Rust
-参与理由&个人编程基础:
从事嵌入式软件开发,已在本群多次测评相关开发板,这次想学习下使用Rust开发;
-查看修炼任务和活动时间表,预估可以跟着完成几级任务(初级、中级、高级):
本人根据修炼任务和活动时间表,预估可以跟着完成初级、中级、高级任务。
-如探索过Rust,请说明Rust学习过程遇到难点,希望在参与活动中收获什么?
本人在前期了解到,Rust性能和C/C++相当,Rust学习难点:所有权机制、生命周期管理、严格借用检查器带来初期理解障碍。活动期待:深入掌握内存安全设计,提升异步编程实战能力,积累高性能系统开发经验
- 2025-02-24
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记09(串口屏应用开发)
秦天qintian0303 发表于 2025-2-24 08:20
浪费了,树莓派直接自己写高清屏多爽
下一期,慢慢来,用LVGL
- 2025-02-23
-
发表了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记09(串口屏应用开发)
手头有一款闲置串口屏,其型号为,是一款7寸800\*480分辨率的电容触摸屏。这款屏幕是支持6~36V供电的,自带3个串口输出,分别是UART2,UART4。其中UART2和UART4是[RS232](http://mp.weixin.qq.com/s?__biz=MjM5OTQ3NjQ1MQ==&mid=2647663764&idx=2&sn=5243f78beb8873d0769e095e83ff9b94&chksm=bf1e69188869e00e2df0affc59a029289b826037397aba0aa1a0d63ff9df5a113ad46070c66f&scene=21#wechat_redirect)接口的,UART2也作为系统默认的调试串口;


参数信息:

结合树莓派5进行应用开发:
## 概述
在物联网(IoT)应用中,传感器数据的采集和显示是常见的任务。本文将介绍如何使用树莓派5获取温湿度数据并将其通过迪文串口屏实时显示。通过DHT22温湿度传感器获取环境温湿度数据,每隔3秒将数据上传并通过串口屏展示。本项目不仅展示了如何与DHT22传感器交互,还涵盖了如何通过串口通信与外部设备进行数据传输。
## 硬件设计
### 1. 树莓派5
树莓派5是一款强大的单板计算机,提供了丰富的GPIO接口,用于连接传感器、外设等。在本项目中,树莓派5将作为主控制器,负责获取传感器数据并通过串口将数据发送给外部设备。
### 2. DHT22温湿度传感器
DHT22是一款常见的温湿度传感器,它通过数字信号提供温度和湿度数据。在本项目中,DHT22传感器连接到树莓派的GPIO 4号引脚,用于实时采集环境的温湿度信息。
### 3. 迪文串口屏
迪文串口屏是一款支持串口通信的LCD屏幕,可以接收来自树莓派的串口数据并显示。这种屏幕广泛用于嵌入式开发中,具有简洁易用的特点。在本项目中,它将用来实时显示温湿度数据,确保用户可以轻松查看环境信息。
## 软件设计
### 1. Python环境搭建
首先,我们需要在树莓派上安装Python环境和所需的库。使用Python来与DHT22传感器进行交互,并通过串口发送数据到迪文串口屏。以下是所需安装的Python库:
```python
pip install Adafruit_DHT pyserial
```
- **Adafruit_DHT**:用于与DHT22传感器进行通信,读取温湿度数据。
- **pyserial**:用于与串口屏进行通信,发送数据。
### 2. Python代码解析
下面是Python代码的核心部分:
```python
python复制编辑import time
import Adafruit_DHT
import serial
# DHT22 传感器设置
DHT_SENSOR = Adafruit_DHT.DHT22
DHT_PIN = 4 # 连接到 GPIO 4
# 串口设置
SERIAL_PORT = '/dev/ttyS0' # 根据你的串口屏设置修改
BAUD_RATE = 9600
# 初始化串口
ser = serial.Serial(SERIAL_PORT, BAUD_RATE)
try:
while True:
# 获取温湿度数据
humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN)
# 检查数据是否有效
if humidity is not None and temperature is not None:
# 分别格式化数据并发送
temp_data = "T:{:.1f}\n".format(temperature)
hum_data = "H:{:.1f}\n".format(humidity)
# 打印到控制台
print(temp_data.strip())
print(hum_data.strip())
# 发送数据到串口屏
ser.write(temp_data.encode('utf-8'))
time.sleep(1) # 确保发送间隔
ser.write(hum_data.encode('utf-8'))
else:
print("获取数据失败,正在重试...")
time.sleep(3) # 每 3 秒获取一次数据
except KeyboardInterrupt:
print("程序中断,关闭串口...")
finally:
ser.close() # 关闭串口
```
#### 代码解析
- **DHT22读取数据**:使用`Adafruit_DHT.read_retry()`方法获取温湿度数据。如果数据成功读取,则返回温度和湿度值;否则返回`None`,并会自动重试。
- **串口通信**:通过`pyserial`库初始化串口,配置正确的串口号(`/dev/ttyS0`)和波特率(9600)。每次获取到数据后,格式化为字符串并发送到串口屏。
- **数据上传间隔**:每3秒获取一次温湿度数据,并通过串口屏显示,确保数据展示实时更新。
### 3. 串口屏显示
串口屏通过接收到的数据进行显示。在本项目中,每次树莓派发送的数据格式为:
- 温度数据:`T:温度值`
- 湿度数据:`H:湿度值`
例如,当温度为22.5°C,湿度为60.1%时,串口屏会显示:

## 结果描述
在测试过程中,树莓派成功读取到DHT22传感器的数据,并通过串口将数据传输到迪文串口屏。每隔3秒,屏幕上会实时更新温度和湿度信息,确保用户能够随时查看环境的变化。
- **温度数据**:显示为“`T:22”,表示当前环境的温度。
- **湿度数据**:显示为“`H:60`”,表示当前环境的湿度。
通过串口屏的显示,用户可以轻松监控环境的温湿度变化,数据准确、实时。
## 总结
本文介绍了如何使用树莓派5与DHT22传感器配合,采集温湿度数据并通过串口屏进行显示。通过Python代码,树莓派能够每3秒采集一次数据并将其通过串口上传到显示屏。这一项目展示了树莓派与传感器及外部显示设备的集成应用,具有较高的实际应用价值,适用于环境监控、智能家居等场景。
未来可以进一步优化此系统,例如加入数据存储、异常报警等功能,进一步提升系统的智能化水平。
- 2025-02-22
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记08(部署YOLOv5进行目标检测)
waterman 发表于 2025-2-22 18:09
推理过程耗时快0.5秒了,如果视频流推理的话会不会很卡?树莓派能部署量化后的模型吗?量化模型推理应该会 ...
会的,还有很多没测试,后续有空后再好好搞
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记08(部署YOLOv5进行目标检测)
秦天qintian0303 发表于 2025-2-22 14:15
YOLOv5模型是已经训练好的模型还是那种比较通用的模型?
算是通用的模型,自己还没训练,后续再好好玩
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记08(部署YOLOv5进行目标检测)
eew_Eu6WaC 发表于 2025-2-22 13:53
树莓派还是挺好用的,YOLOv5模型的识别的准确度咋样呢?
当前摄像头精确度还是弱了点,后续还要优化
-
发表了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记08(部署YOLOv5进行目标检测)
随着物体检测技术的不断发展,YOLOv5(You Only Look Once version 5)作为一种高效、快速的物体检测模型,广泛应用于实时物体识别任务。树莓派5凭借其较高的性价比和较强的计算能力,成为了嵌入式设备中理想的物体检测平台。本文将介绍如何在树莓派5上部署YOLOv5模型,完成物体检测任务。我们将从YOLOv5概述、部署方案、硬件设计、软件设计、测试方案等方面进行详细讨论。
### 1. YOLOv5概述
YOLOv5是由Ultralytics开发的最新一代物体检测模型,它是YOLO(You Only Look Once)系列的第五个版本。与前几代YOLO模型相比,YOLOv5在速度、精度和易用性上都有显著提升。YOLOv5采用了深度学习中的卷积神经网络(CNN)架构,能够从图片中实时检测多个物体。
YOLOv5模型的主要优势包括:
- **实时性**:可以在较低的延迟下进行物体检测,适合嵌入式设备。
- **高精度**:对于常见物体,如人、车、动物等,检测效果非常准确。
- **易于训练和部署**:Ultralytics提供了简便的API和训练框架,支持自定义数据集和预训练模型。
### 2. 部署方案
YOLOv5的部署方案需要考虑以下几个方面:硬件设计、软件环境配置和模型的优化。树莓派5是一款性价比高的单板计算机,能够胜任小型物体检测任务,支持Python环境和深度学习库(如PyTorch)。部署方案可以概括为以下几个步骤:
1. **硬件选型**:选择树莓派5作为硬件平台,并配备适量的内存和存储。
2. **操作系统配置**:在树莓派5上安装Raspberry Pi OS,确保支持Python环境、PyTorch和其他依赖库。
3. **模型优化**:根据树莓派的计算能力,使用YOLOv5小型模型(如YOLOv5n)进行部署,以确保推理速度和效率。
### 3. 硬件设计
为了实现YOLOv5的高效部署,树莓派5的硬件设计需满足以下要求:
- **处理器**:树莓派5采用四核ARM Cortex-A72 CPU,具有较强的计算能力,能够运行YOLOv5模型。
- **内存**:4GB或8GB RAM,以确保足够的内存空间进行数据处理和模型推理。
- **存储**:使用较大的MicroSD卡(32GB或更大),用来存储操作系统、模型和数据。
- **接口**:通过USB或其他方式连接摄像头进行实时数据采集,或使用本地图片进行检测。
### 4. 软件设计
在树莓派5上部署YOLOv5的关键软件组件包括操作系统、Python环境、PyTorch框架、YOLOv5模型及其依赖。具体步骤如下:
1. **操作系统安装**:
- 安装Raspberry Pi OS。
- 通过树莓派的终端进行基本设置,如网络连接、SSH启用等。
2. **Python环境配置**:
- 安装Python 3.7以上版本。
- 安装必要的Python库:
```
bash复制编辑sudo apt-get update
sudo apt-get install python3-pip
pip3 install torch torchvision
pip3 install matplotlib pillow
```
3. **YOLOv5部署**:
- 下载并加载YOLOv5模型:
```
python复制编辑import torch
from pathlib import Path
from PIL import Image
# 加载YOLOv5模型
model = torch.hub.load('ultralytics/yolov5', 'custom', path='yolov5n.pt') # 这里yolov5n.pt是你的模型路径
# 读取图片
img_path = 'test.jpg' # 本地图片路径
img = Image.open(img_path)
# 进行推理
results = model(img)
# 输出检测结果
results.print() # 打印检测结果
results.show() # 显示检测的图片
results.save(Path('output/')) # 将结果保存到output文件夹
```
4. **图像采集与预处理**:
- 通过树莓派的摄像头模块采集图像。
- 在YOLOv5推理前,对图像进行尺寸调整和颜色空间转换等预处理。
### 5. 测试结果

这是YOLOv5模型执行推理后的输出信息,具体解读如下:
1. **YOLOv5n summary: 213 layers, 1867405 parameters, 0 gradients, 4.5 GFLOPs**
- **213 layers**:模型包含213层。
- **1867405 parameters**:模型有1867405个参数(即可训练的权重)。
- **0 gradients**:没有梯度(通常在推理模式下不会计算梯度,因为推理时不进行反向传播)。
- **4.5 GFLOPs**:模型在执行推理时,每秒大约执行4.5亿次浮点运算(Giga FLOPs)。
2. **Adding AutoShape...**
- 这表示模型在推理前应用了 `AutoShape`,这通常是YOLOv5的预处理步骤,用于自动调整输入图像的大小,确保它们符合模型要求。
3. **image 1/1: 640x640 (no detections)**
- 这是处理的图像信息。模型处理了一张大小为640x640的图像。
- **(no detections)**:没有检测到任何物体。这意味着模型未能在这张图片中识别出任何目标。
4. **Speed: 13.8ms pre-process, 469.9ms inference, 2.3ms NMS per image at shape (1, 3, 640, 640)**
- **13.8ms pre-process**:图像预处理耗时13.8毫秒。
- **469.9ms inference**:推理过程耗时469.9毫秒(即模型进行物体识别的时间)。
- **2.3ms NMS**:非极大值抑制(NMS,Non-Maximum Suppression)耗时2.3毫秒,这是用来过滤重叠框的后处理步骤。
- **shape (1, 3, 640, 640)**:输入的图像尺寸是 `(1, 3, 640, 640)`,其中 `1` 表示图像的批量大小(batch size),`3` 表示RGB三个通道,`640x640`是图像的分辨率。
5. **Saved 1 image to runs/detect/exp2**
- 这表明YOLOv5已将处理后的图像保存到文件夹 `runs/detect/exp2` 中,可能包含了标注的检测结果或原始图像。
### 6. 总结
本文详细介绍了如何在树莓派5上部署YOLOv5模型进行物体检测。通过合理的硬件设计、软件配置和优化方案,我们可以利用树莓派5的计算能力来实现高效的物体检测任务。YOLOv5作为一个高效的物体检测框架,能够在嵌入式平台上完成快速、精准的物体识别工作。通过合理的测试方案,我们确保了部署过程的顺利进行,并验证了YOLOv5在树莓派5上的性能。
- 2025-02-19
-
发表了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记07(垃圾分类模型训练与推理)
## 1. 引言
垃圾分类是近年来备受关注的话题,而图像识别技术可以有效地帮助实现自动化垃圾分类。本文将介绍如何使用树莓派 5(Raspberry Pi 5)和 PyTorch 框架,构建一个基于深度学习的垃圾分类模型,并在实际场景中应用图像识别进行垃圾分类。
我们将使用预训练的 ResNet18 模型,进行微调(fine-tuning),然后训练垃圾分类模型。训练完成后,用户可以通过上传图片来进行推理,模型会预测图像中的垃圾类别。
## 2. 环境准备
### 2.1 硬件要求
- **树莓派 5(Raspberry Pi 5)**
- **摄像头(可选,用于实时拍摄图像)**
- **外部存储(SD卡或USB驱动器,用于存储训练数据和模型)**
- **显示器、键盘和鼠标**
### 2.2 软件要求
- **Raspberry Pi OS(树莓派操作系统)**
- **Python 3**
- **PyTorch**:深度学习框架,用于训练和推理。
- **OpenCV**:用于图像处理(可选)
- **torchvision**:包含预训练模型和数据集的库
### 2.3 安装必要的库
```python
pip install torch torchvision Pillow
```
## 3. 项目结构
项目包含以下主要部分:
- **数据预处理与加载**:将图像数据转换为模型可接受的格式。
- **模型定义与训练**:使用预训练的 ResNet18 模型进行微调。
- **推理功能**:加载训练好的模型并对新的图像进行分类。
训练垃圾数据集:

验证数据集:

每个文件夹就是各类垃圾类型,比如:

## 4. 代码实现
### 4.1 数据预处理与加载
首先,使用 `torchvision` 提供的 `ImageFolder` 类加载训练和测试数据。数据会经过处理,缩放为 224x224 的大小,并归一化到 ImageNet 的标准值。
```python
# 数据预处理
transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小
transforms.ToTensor(), # 将图像转换为 Tensor 格式
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), # 归一化
])
# 加载训练和测试数据
train_dataset = datasets.ImageFolder(root='/home/admin/Pictures/soft/data/train', transform=transform) # 训练集
test_dataset = datasets.ImageFolder(root='/home/admin/Pictures/soft/data/test', transform=transform) # 测试集
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) # 训练数据加载器
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False) # 测试数据加载器
```
### 4.2 模型定义与训练
我们使用预训练的 ResNet18 模型,并调整最后的全连接层,使其适应我们的垃圾分类任务(类别数等于垃圾种类的数量)。
```python
# 使用预训练的ResNet18
model = models.resnet18(pretrained=True) # 加载预训练模型
model.fc = nn.Linear(model.fc.in_features, len(train_dataset.classes)) # 修改输出层为垃圾分类的类别数
```
我们使用 **Adam 优化器** 和 **交叉熵损失函数** 来训练模型。
```python
# 使用 GPU 或 CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam 优化器
```
### 4.3 训练过程与早停机制
为了防止过拟合,我们使用早停机制。当连续多个 epoch 准确率没有提高时,停止训练。此外,若准确率达到 90%,也会提前停止。
```
python复制编辑# 训练模型
def train_model():
best_accuracy = 0.0 # 记录最佳准确率
no_improvement_count = 0 # 连续未改善次数
for epoch in range(10): # 最大训练 10 个 epoch
model.train()
running_loss = 0.0
correct = 0
total = 0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
epoch_loss = running_loss / len(train_loader)
epoch_accuracy = 100 * correct / total
print(f"Epoch {epoch+1}, Loss: {epoch_loss:.4f}, Accuracy: {epoch_accuracy:.2f}%")
if epoch_accuracy > best_accuracy:
best_accuracy = epoch_accuracy
no_improvement_count = 0 # 重置未改善计数
else:
no_improvement_count += 1
print(f"No improvement in accuracy for {no_improvement_count} epochs.")
if no_improvement_count >= 5:
print("Stopping early due to no improvement in the last 5 epochs.")
break
if epoch_accuracy >= 90:
print("Stopping early as accuracy reached 90%.")
break
# 保存模型
torch.save(model.state_dict(), "garbage_classifier.pth")
print("Model saved as 'garbage_classifier.pth'")
```
训练结果:

### 4.4 推理过程
训练完成后,模型可以加载并用于推理。通过 `infer_image` 函数,我们可以对新上传的图像进行垃圾分类预测。
```
python复制编辑# 推理函数
def infer_image(image_path):
image = Image.open(image_path) # 打开图片
image = transform(image).unsqueeze(0).to(device) # 转换为 Tensor 并添加批次维度
model.eval() # 设置为评估模式
with torch.no_grad():
output = model(image)
_, predicted = torch.max(output, 1)
classes = train_dataset.classes # 获取分类标签
print(f"Predicted class: {classes[predicted]}") # 输出预测的垃圾类别
```
推理结果:

### 4.5 主函数
用户可以选择是进行训练还是推理。
```
# 主函数
if __name__ == "__main__":
choice = input("Enter 'train' to train the model or 'infer' to run inference: ").strip().lower()
if choice == 'train':
train_model() # 训练模型
elif choice == 'infer':
image_path = input("Enter image path for inference: ").strip() # 输入图片路径
if os.path.exists(image_path):
load_model() # 加载模型
infer_image(image_path) # 推理
else:
print("Invalid image path!")
else:
print("Invalid choice! Please enter 'train' or 'infer'.")
```
## 5. 总结与展望
本文介绍了如何使用树莓派 5 和 PyTorch,构建一个基于深度学习的垃圾分类系统。我们使用了 ResNet18 作为预训练模型,并通过微调实现了垃圾分类任务。在训练过程中,我们使用了早停机制来防止过拟合并提高训练效率。完成训练后,用户可以通过上传图像来进行垃圾分类预测。未来,我们可以扩展此项目,进一步提高模型精度,使用更多种类的垃圾图像进行训练,并将其集成到智能垃圾分类系统中。
- 2025-02-17
-
发表了主题帖:
【MCXA156开发板测评】学习笔记07(reeRTOS中的消息队列使用)
# FreeRTOS 消息队列概述
FreeRTOS 消息队列是核心的进程间通信机制,允许任务以 FIFO(先进先出)的方式发送和接收数据。通过 `xQueueSend` 和 `xQueueReceive` 函数,任务之间可以传递数据,并且可以选择阻塞或非阻塞操作。该示例中使用消息队列来存储日志数据,日志任务从队列中读取这些数据并显示。
# 硬件设计
## 硬件要求:
- **Type-C USB 数据线**
- **FRDM-MCXA156 开发板**
- **个人计算机**
## 板卡设置:
不需要特殊配置,只需要确保开发板通过 USB Type-C 数据线正确连接到计算机,并且程序已经成功下载到目标板上。
## 通信接口:
使用串口终端(例如 Tera Term)查看目标板的调试输出。串口终端配置应如下:
- **波特率**:115200
- **数据位**:8
- **校验位**:无
- **停止位**:1
- **流控制**:无
# 软件设计
## 代码结构
代码分为两个主要部分:日志机制和应用程序任务。
## 日志机制
日志功能负责使用队列管理日志消息。它创建一个消息队列并创建一个任务来打印这些消息。
## 日志 API 函数:
1. **`log_add`**:将日志消息添加到队列中。
```C
c复制编辑// 向日志队列中添加一条日志消息
void log_add(char *log) {
xQueueSend(log_queue, log, 0); // 向队列发送日志消息
}
```
2. **`log_init`**:初始化日志系统,创建消息队列并创建日志任务。
```C
c复制编辑// 初始化日志系统,创建队列和日志任务
void log_init(uint32_t queue_length, uint32_t max_log_length) {
log_queue = xQueueCreate(queue_length, max_log_length); // 创建消息队列
if (log_queue != NULL) {
vQueueAddToRegistry(log_queue, "LogQ"); // 注册队列
}
// 创建日志任务
if (xTaskCreate(log_task, "log_task", configMINIMAL_STACK_SIZE + 166, NULL, tskIDLE_PRIORITY + 1, NULL) != pdPASS) {
PRINTF("任务创建失败!.\r\n");
while (1); // 如果任务创建失败,进入死循环
}
}
```
3. **`log_task`**:任务不断地从队列中接收日志消息并打印。
```C
c复制编辑// 日志任务,从队列中接收并打印日志
static void log_task(void *pvParameters) {
uint32_t counter = 0; // 日志计数器
char log[MAX_LOG_LENGTH + 1];
while (1) {
if (xQueueReceive(log_queue, log, portMAX_DELAY) != pdTRUE) {
PRINTF("接收队列失败。\r\n");
}
PRINTF("Log %d: %s\r\n", counter, log); // 打印日志
counter++;
}
}
```
## 应用程序任务
有两个任务:`write_task_1` 和 `write_task_2`,它们模拟生成日志消息,并将其发送到日志任务进行打印。每个任务生成 5 条消息。
### 任务代码:
```C
c复制编辑// 任务1,发送5条日志消息
static void write_task_1(void *pvParameters) {
char log[MAX_LOG_LENGTH + 1];
uint32_t i = 0;
for (i = 0; i < 5; i++) {
sprintf(log, "Task1 Message %d", (int)i); // 格式化日志消息
log_add(log); // 将消息添加到日志队列
taskYIELD(); // 任务让出控制权
}
vTaskSuspend(NULL); // 挂起任务
}
// 任务2,发送5条日志消息
static void write_task_2(void *pvParameters) {
char log[MAX_LOG_LENGTH + 1];
uint32_t i = 0;
for (i = 0; i < 5; i++) {
sprintf(log, "Task2 Message %d", (int)i); // 格式化日志消息
log_add(log); // 将消息添加到日志队列
taskYIELD(); // 任务让出控制权
}
vTaskSuspend(NULL); // 挂起任务
}
```
### 主函数
在 `main` 函数中,初始化了日志系统,并创建了两个任务 `write_task_1` 和 `write_task_2`。
```C
c复制编辑// 主函数
int main(void) {
BOARD_InitPins(); // 初始化引脚
BOARD_InitBootClocks(); // 初始化时钟
BOARD_InitDebugConsole(); // 初始化调试控制台
// 初始化日志系统,队列长度为10,每条日志最大长度为20字节
log_init(10, MAX_LOG_LENGTH);
// 创建任务1
if (xTaskCreate(write_task_1, "WRITE_TASK_1", configMINIMAL_STACK_SIZE + 166, NULL, tskIDLE_PRIORITY + 2, NULL) != pdPASS) {
PRINTF("任务创建失败!.\r\n");
while (1); // 如果任务创建失败,进入死循环
}
// 创建任务2
if (xTaskCreate(write_task_2, "WRITE_TASK_2", configMINIMAL_STACK_SIZE + 166, NULL, tskIDLE_PRIORITY + 2, NULL) != pdPASS) {
PRINTF("任务创建失败!.\r\n");
while (1); // 如果任务创建失败,进入死循环
}
vTaskStartScheduler(); // 启动调度器
for (;;); // 进入空循环
}
```
# 实验结果
程序烧录到开发板后,通过串口终端可以看到以下日志输出,显示 `write_task_1` 和 `write_task_2` 两个任务并发执行,并向日志任务发送消息,串口结果输出:

# 结论
通过本示例,展示了如何在 FreeRTOS 中使用消息队列实现简单的日志机制。任务通过消息队列将日志消息传递给日志任务,日志任务负责打印这些消息。该示例展示了消息传递和任务同步的基本概念,并能有效地展示多个任务如何在 FreeRTOS 中协作。
- 2025-02-15
-
发表了主题帖:
【MCXA156开发板测评】学习笔记06(reeRTOS中的互斥锁使用)
在嵌入式系统中,多个任务可能需要访问共享资源,例如终端输出。如果没有合适的同步机制,多个任务同时输出数据时,可能会出现输出内容交织混乱,影响系统的稳定性和可靠性。为了解决这一问题,我们可以使用FreeRTOS提供的互斥锁(mutex)来同步任务的访问,确保只有一个任务可以访问共享资源,从而避免输出冲突。
本文将通过一个示例,展示如何使用FreeRTOS中的互斥锁来管理任务间对共享资源(终端输出)的访问,实现任务输出的同步。
## 硬件设计
### 硬件要求
- **开发板**:FRDM-MCXA156开发板
- **连接方式**:使用Type-C USB线连接PC与开发板上的MCU-Link USB端口
- 调试控制台:使用串口调试终端
### 硬件配置
硬件的初始化通过`BOARD_InitPins()`、`BOARD_InitBootClocks()`和`BOARD_InitDebugConsole()`来完成。这些函数分别负责引脚初始化、时钟初始化和调试串口初始化,确保系统能够正常启动并输出调试信息。
## 软件设计
### 1. **任务设计**
在该示例中,我们创建了两个任务:`write_task_1`和`write_task_2`,它们负责将不同的输出信息打印到终端。任务的创建使用了FreeRTOS的`xTaskCreate()`函数,任务的堆栈大小为`configMINIMAL_STACK_SIZE + 128`,任务优先级为`tskIDLE_PRIORITY + 1`。
### 2. **互斥锁实现**
在程序开始时,我们使用`xSemaphoreCreateMutex()`创建一个互斥锁`xMutex`。每个任务在打印数据之前都会通过`xSemaphoreTake(xMutex, portMAX_DELAY)`尝试获取互斥锁。如果互斥锁已被其他任务占用,任务将阻塞,直到获取锁。获取锁后,任务可以安全地打印输出,然后通过`xSemaphoreGive(xMutex)`释放互斥锁,允许其他任务访问。
### 3. **任务输出与任务让渡**
每个任务在打印输出时,使用了`taskYIELD()`函数。`taskYIELD()`使得当前任务自愿放弃CPU控制,允许其他任务运行。通过任务让渡,两个任务的输出会交替进行,但由于互斥锁的保护,输出内容不会被混淆。
### 4. **代码实现**
```C
int main(void)
{
// 创建互斥锁
xMutex = xSemaphoreCreateMutex();
// 初始化硬件
BOARD_InitPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
// 创建任务
if (xTaskCreate(write_task_1, "WRITE_TASK_1", configMINIMAL_STACK_SIZE + 128, NULL, tskIDLE_PRIORITY + 1, NULL) != pdPASS)
{
PRINTF("任务创建失败!\r\n");
while (1);
}
if (xTaskCreate(write_task_2, "WRITE_TASK_2", configMINIMAL_STACK_SIZE + 128, NULL, tskIDLE_PRIORITY + 1, NULL) != pdPASS)
{
PRINTF("任务创建失败!\r\n");
while (1);
}
// 启动调度器
vTaskStartScheduler();
for (;;);
}
static void write_task_1(void *pvParameters)
{
while (1)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY) != pdTRUE)
{
PRINTF("获取互斥锁失败。\r\n");
}
PRINTF("ABCD |");
taskYIELD();
PRINTF(" EFGH\r\n");
xSemaphoreGive(xMutex);
taskYIELD();
}
}
static void write_task_2(void *pvParameters)
{
while (1)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY) != pdTRUE)
{
PRINTF("获取互斥锁失败。\r\n");
}
PRINTF("1234 |");
taskYIELD();
PRINTF(" 5678\r\n");
xSemaphoreGive(xMutex);
taskYIELD();
}
}
```
## 测试结果
### 1. **任务输出**
在程序运行时,两个任务交替输出不同的字符串。由于互斥锁的使用,确保每次只有一个任务可以打印数据,避免了输出内容的交叉或混乱。示例输出如下:

如上所示,`write_task_1`和`write_task_2`的输出是交替进行的,每个任务的输出内容都完整且清晰。
### 2. **任务调度与同步**
由于使用了互斥锁,任务之间的输出是有序的。每个任务在输出完一部分后,会通过`taskYIELD()`让出CPU控制权,允许其他任务运行。通过这种方式,两个任务的输出得以交替进行,但始终没有混合。
## 总结
通过这个示例,我们展示了如何在FreeRTOS中使用互斥锁来管理多个任务对共享资源的访问。互斥锁确保了任务间的同步,避免了输出内容的混乱,使得多个任务能够安全地访问共享资源。在嵌入式系统中,类似的互斥机制在保证任务间协调和数据一致性方面至关重要。在实际应用中,任务间的同步不仅限于输出操作,还可以扩展到对其他共享资源(如内存、外设等)的保护。使用FreeRTOS的互斥锁可以有效地管理这些资源,避免竞态条件和数据冲突。
- 2025-02-12
-
回复了主题帖:
【MCXA156开发板测评】学习笔记05(FreeRTOS的信号量使用)
okhxyyo 发表于 2025-2-12 11:43
谢谢分享~~~
相互学习
-
发表了主题帖:
【MCXA156开发板测评】学习笔记05(FreeRTOS的信号量使用)
本帖最后由 qzc0927 于 2025-2-12 11:28 编辑
概述
在嵌入式开发中,生产者消费者模式常用于解决并发任务之间的同步问题。本文将介绍如何在NXP MCXA156开发板上使用FreeRTOS实现生产者消费者模式。通过该模式,生产者任务会生成数据,并将其交给消费者任务进行处理。在此过程中,生产者和消费者通过信号量进行同步。
硬件平台
开发板:NXP MCXA156
FreeRTOS操作系统
开发环境:MCUXpresso IDE
软件环境
FreeRTOS内核
NXP SDK和驱动库
调试串口(用于输出调试信息)
1. 硬件与软件准备
1.1 硬件连接
在这个示例中,我们使用NXP MCXA156开发板。硬件设置非常简单,重点在于通过FreeRTOS调度器管理任务的执行。
1.2 软件环境搭建
安装并配置MCUXpresso IDE。
下载并导入适用于MCXA156的NXP SDK。
确保FreeRTOS内核已经集成并配置好。
2. 代码分析
以下代码实现了一个生产者消费者模式,生产者负责产生数据,消费者负责处理数据,二者通过信号量进行同步。
2.1 主函数
```C
int main(void)
{
/* 硬件初始化 */
BOARD_InitPins(); // 初始化引脚配置
BOARD_InitBootClocks(); // 初始化系统时钟
BOARD_InitDebugConsole(); // 初始化调试串口
/* 创建生产者任务 */
if (xTaskCreate(producer_task, "PRODUCER_TASK", configMINIMAL_STACK_SIZE + 128, NULL, TASK_PRIO, NULL) != pdPASS) {
PRINTF("Task creation failed!.\r\n");
while (1) ; // 死循环处理错误
}
/* 启动调度器 */
vTaskStartScheduler();
/* 调度器启动失败时进入死循环 */
for (;;) ;
}
```
}
在主函数中,我们首先初始化了引脚、时钟和调试串口。
然后创建了一个生产者任务,并启动调度器。
2.2 生产者任务
```C
static void producer_task(void *pvParameters)
{
uint32_t i;
PRINTF("Producer_task created.\r\n");
/* 创建二进制信号量 */
xSemaphore_producer = xSemaphoreCreateBinary(); // 用于生产者等待消费者就绪
if (xSemaphore_producer == NULL) {
PRINTF("xSemaphore_producer creation failed.\r\n");
vTaskSuspend(NULL); // 挂起当前任务
}
xSemaphore_consumer = xSemaphoreCreateBinary(); // 用于消费者等待生产者就绪
if (xSemaphore_consumer == NULL) {
PRINTF("xSemaphore_consumer creation failed.\r\n");
vTaskSuspend(NULL);
}
/* 创建多个消费者任务 */
for (i = 0; i < CONSUMER_LINE_SIZE; i++) {
if (xTaskCreate(consumer_task, "CONSUMER_TASK", configMINIMAL_STACK_SIZE + 128, (void *)i, TASK_PRIO, NULL) != pdPASS) {
PRINTF("Task creation failed!.\r\n");
vTaskSuspend(NULL);
}
else {
PRINTF("Consumer_task %d created.\r\n", i);
}
}
/* 生产者主循环 */
while (1) {
/* 步骤1:通知消费者可以接收数据 */
xSemaphoreGive(xSemaphore_consumer);
/* 步骤2:等待消费者确认接收完成 */
if (xSemaphoreTake(xSemaphore_producer, portMAX_DELAY) == pdTRUE) {
PRINTF("Producer released item.\r\n");
} else {
PRINTF("Producer is waiting for customer.\r\n");
}
}
}
```
}
生产者任务首先创建了两个信号量,一个用于通知消费者开始接收数据,另一个用于等待消费者确认接收。
然后,生产者任务通过 xSemaphoreGive 和 xSemaphoreTake 控制任务之间的同步。
2.3 消费者任务
```C
static void consumer_task(void *pvParameters)
{
/* 将参数转换为消费者编号 */
int consumer_id = (int)pvParameters;
PRINTF("Consumer number: %d\r\n", consumer_id);
while (1) {
/* 步骤1:通知生产者可以发送数据 */
xSemaphoreGive(xSemaphore_producer);
/* 步骤2:等待生产者提供数据 */
if (xSemaphoreTake(xSemaphore_consumer, portMAX_DELAY) == pdTRUE) {
PRINTF("Consumer %d accepted item.\r\n", consumer_id);
} else {
PRINTF("Consumer %d is waiting for producer.\r\n", consumer_id);
}
}
}
```
每个消费者任务通过 xSemaphoreGive 向生产者通知可以开始生产数据。
通过 xSemaphoreTake 等待生产者的信号,接收数据并进行处理。
3. 信号量与同步机制
在这个例子中,信号量用于同步生产者和消费者任务之间的执行顺序。生产者和消费者通过信号量的“给”与“取”机制进行数据传递和同步。
生产者任务:通过 xSemaphoreGive(xSemaphore_consumer) 通知消费者准备好接收数据。然后,等待消费者确认通过 xSemaphoreTake(xSemaphore_producer)。
消费者任务:通过 xSemaphoreGive(xSemaphore_producer) 通知生产者准备好接收数据。然后,等待生产者提供数据,通过 xSemaphoreTake(xSemaphore_consumer)。
4. 调试与测试
使用串口调试输出,可以观察到任务的执行情况以及生产者和消费者之间的同步状态。每当任务创建、数据传递或者同步发生时,都会在串口终端输出相应的调试信息,帮助开发者确认任务是否正常运行。

5. 总结
本文通过一个简单的例子介绍了如何在NXP的MCXA156开发板上使用FreeRTOS实现生产者消费者模式。通过任务与信号量的配合,我们可以实现任务之间的同步和数据的安全传递。这种方法不仅适用于MCXA156开发板,也适用于其他基于FreeRTOS的嵌入式系统。
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记06(图像处理)
hellokitty_bean 发表于 2025-2-11 17:23
这是一只让人敬佩的手(有饱经沧桑之感)。。。。。。。向楼主致敬
哈哈哈,电子工程师的铁掌
- 2025-02-11
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记06(图像处理)
okhxyyo 发表于 2025-2-11 12:31
精彩!期待后续呀
没问题
-
发表了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记06(图像处理)
树莓派5(Raspberry Pi 5)是一款强大的单板计算机,广泛应用于物联网、嵌入式开发等领域。OV5647是一款常见的5MP摄像头传感器,广泛用于树莓派项目中。本文将介绍如何在树莓派5上使用OV5647摄像头模块拍照。
首先,确保将OV5647摄像头模块正确连接到树莓派5的CSI接口。具体步骤如下:
1. 将摄像头模块的带状电缆插入树莓派5的CSI接口。
2. 检查连接是否牢固。
**注意**:树莓派5的CSI接口与树莓派4有所不同,请确保您使用的电缆和接口匹配。
- 1.硬件连接

我的摄像头模块:

- 2.软件配置
如果使用的是最新的Raspberry Pi Camera Module 3 或者Raspberry Pi Global Shutter Camera,需要运行以下指令进行系统更新(需要联网操作).
```
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt update && sudo apt full-upgrade
```
- 安装完成后,重启系统
```
sudo reboot
```
**打开树莓派终端,并开启摄像头预览:**
```
libcamera-hello
```
这个指令会在屏幕上预览摄像头大概5秒时间,用户可以用-t 参数来设置预览的时间, 其中的单位是毫秒,如果设置为0的话就是保持一直预览。比如:
```
sudo libcamera-hello -t 0
```
看下掌纹,是不是断掌

**拍摄一张全像素的JPEG图像**
```
libcamera-jpeg -o test.jpg
```
这个拍摄指令会显示一个5秒左右的预览串口,然后拍摄一张全像素的JPEG图像,保存为test.jpg
用户可以通过-t 参数来设置预览时间,同时可以通过--width 和 --height来设置拍摄图像的分辨率。例如
```bash
sudo libcamera-jpeg -o test.jpg -t 2000 --width 640 --height 480
```

- 曝光控制
所有这些都rpicam-apps允许用户以固定的快门速度和增益运行相机。拍摄曝光时间为 20ms、增益为 1.5x 的图像,该增益将用作传感器内的模拟增益,直到达到内核传感器驱动程序允许的最大模拟增益,之后其余部分将用作数字增益。
```
rpicam-jpeg -o test.jpg -t 2000 --shutter 20000 --gain 1.5
```
Raspberry Pi 的 AEC/AGC 算法允许应用程序指定曝光补偿:能够通过给定的停止次数使图像变暗或变亮。
```bash
rpicam-jpeg --ev -0.5 -o darker.jpg
rpicam-jpeg --ev 0 -o normal.jpg
rpicam-jpeg --ev 0.5 -o brighter.jpg
```
后续进行模型训练
- 2025-02-06
-
回复了主题帖:
【Raspberry Pi 5测评】树莓派5学习笔记05(语音识别垃圾分类)
xinlian55 发表于 2025-2-6 14:58
真是一篇不错的帖子,解决了我很多没有解决的问题,非常感谢博主!
哈哈,能帮您解决问题也不错