【Follow me第二季第2期】实现任务二 驱动12x8点阵LED;用DAC生成正弦波并放大采集
[复制链接]
本帖最后由 eew_ljd6R2 于 2024-9-7 23:16 编辑
大家好,
今天我们将着手完成任务二的第一个关键任务——驱动一个由12x8 LED灯组成的点阵屏。
点阵屏的巧妙之处:
- 高效控制:该点阵屏仅通过10个管脚就能实现全面控制,这得益于其独特的点阵布局和复用技术。
- 硬件设计:从硬件原理图上,我们可以清晰看到,通过特定的电路逻辑,如当管脚7为高电平而管脚3为低电平时,能够点亮LED1;反之,则能点亮LED2。
刷新策略:
- 逐列刷新:为了有效减少管脚需求并实现流畅的视觉效果,我们将采用逐列刷新的策略。这意味着我们将以极快的速度依次激活每一列(在此场景下,我们将其视为“行”以符合点阵屏的常见操作方式),并在激活的同时,通过其他管脚设置相应的列(行)数据。
- 视觉暂留:由于人眼的视觉暂留效应,这种高速但有序的刷新过程将使得LED的点亮看起来是连续的,而非闪烁的。
动画效果实现:
- 帧数据设计:为了创造动画效果,我们将设计一系列帧数据,每帧数据代表点阵屏上LED的一种特定点亮状态。
- 显示控制:通过依次显示这些帧,并精确控制每帧之间的显示时间间隔,我们就能够呈现出动态变化的图像,即动画效果。
总结:
通过巧妙利用点阵屏的复用技术和高速刷新策略,我们成功地实现了仅用10个管脚对12x8 LED点阵屏的全面控制
Arduino官方特别为点阵屏开发了一套高效的API,通过引入Arduino_LED_Matrix.h 库,开发者可以便捷地控制点阵屏。首先,你需要创建一个点阵屏对象,例如命名为ledMatrix ,以ArduinoLEDMatrix 类实例化:
cpp复制代码
|
ArduinoLEDMatrix ledMatrix; |
接下来,进行初始化设置,准备控制一个12x8的点阵屏。这个库允许你使用一个12*8的二维数组来直接控制屏幕上的每个LED,其中数组元素为1时表示LED点亮,为0时表示熄灭。
通过这种方式,在程序中动态地修改这个二维数组中的0和1,即可轻松实现不同画面的展示。这种API的设计极大地简化了点阵屏的控制逻辑,使得开发者能够更专注于创意和内容的实现,而非底层的硬件操作。
总结来说,Arduino官方提供的Arduino_LED_Matrix 库为点阵屏开发带来了前所未有的便利,通过简单的API调用和直观的二维数组控制,即可实现复杂多变的显示效果。
官方还开发了一个小的插件可以进行创作动画大家可以通过下方的视频进行查看使用方法
下面是我的这个 任务的代码大致内容过程为通过工具设置一个动画并且加载进去
#include "Arduino_LED_Matrix.h"
#include <stdint.h>
#include "animation.h"
ArduinoLEDMatrix matrix;
void setup() {
// put your setup code here, to run once:
matrix.loadSequence(animation);
matrix.begin();
// turn on autoscroll to avoid calling next() to show the next frame; the parameter is in milliseconds
// matrix.autoscroll(300);
matrix.play(true);
}
void loop() {
// put your main code here, to run repeatedly:
}
const uint32_t animation[][4] = {
{
0xfff00000,
0x0,
0x0,
66
},
{
0xfff00,
0x0,
0x0,
66
},
{
0xff,
0xf0000000,
0x0,
66
},
{
0x0,
0xfff0000,
0x0,
66
},
{
0x0,
0xfff0,
0x0,
66
},
{
0x0,
0xf,
0xff000000,
66
},
{
0x0,
0x0,
0xfff000,
66
},
{
0x0,
0x0,
0xfff,
66
}
};
下面完成第二个任务首先使用DAC产生一个正弦波
DAC,全称Digital-to-Analog Converter,即数字模拟转换器,是一种电子设备或电路,其核心功能是将数字信号转换为相应的模拟信号。在现代电子系统中,DAC扮演了至关重要的角色,它是连接数字电路与模拟电路的桥梁,为数字系统与模拟系统之间的数据交互提供了基础。
DAC(Digital-to-Analog Converter,数模转换器)和PWM(Pulse Width Modulation,脉冲宽度调制)在电子系统中扮演着不同的角色,它们之间存在显著的区别。以下是DAC和PWM之间的主要区别:
1. 功能与原理
- DAC:DAC是一种电子设备或电路,用于将数字信号转换为模拟信号。它接收离散的数字信号(如二进制代码),并将其转换为连续可变的模拟信号(如电压或电流)。DAC广泛应用于音频设备、控制系统、仪器仪表等领域,作为数字电路与模拟电路之间的桥梁。
- PWM:PWM是一种通过调节信号的占空比来模拟连续模拟信号的技术。PWM信号是方波,其频率固定,但占空比(即高电平持续的时间与周期的比值)可变。通过调整占空比,PWM可以模拟出不同幅度的模拟信号。PWM广泛应用于高功率转换效率的电源、马达控制、音频放大等领域。
2. 输出特性
- DAC:DAC的输出是连续的模拟信号,其幅度可以在一定范围内连续变化。DAC的输出信号质量高,能够还原音频的细节和动态特性,适用于需要高保真度音频输出的场合。
- PWM:PWM的输出是方波信号,其幅度本身并不连续变化,但通过滤波等处理可以转换为近似的模拟信号。PWM的输出效率高,功耗低,适用于需要高效率低功耗的音频驱动等场合。
3. 应用领域
- DAC:DAC广泛应用于音频设备(如CD播放器、功放)、控制系统(如工业自动化控制)、仪器仪表(如示波器、频谱分析仪)等领域。在这些领域中,DAC负责将数字信号转换为模拟信号以供后续处理或输出。
- PWM:PWM广泛应用于高功率转换效率的电源(如开关电源)、马达控制(如电机驱动器)、音频放大(如数字音频放大器)等领域。在这些领域中,PWM通过调节占空比来控制功率元件的通断,从而实现高效能的功率转换或音频放大。
4. 精度与分辨率
- DAC:DAC的精度和分辨率取决于其位数(如8位、12位、16位等)。位数越高,DAC能够输出的模拟信号值数量越多,精度和分辨率也就越高。
- PWM:PWM的精度和分辨率受到其频率和占空比调节精度的限制。虽然通过提高PWM的频率和占空比调节精度可以提高其模拟精度,但通常难以达到DAC那样的高精度和高分辨率。
综上所述,DAC和PWM在功能、原理、输出特性、应用领域以及精度与分辨率等方面都存在显著的区别。
在R3版本中,所有输出均基于PWM技术实现。然而,在R4版本中,我们引入了DAC(数字模拟转换器)于A引脚,尽管其输出调用的函数仍为analogWrite() ,但实质上提供了与PWM不同的模拟信号生成机制。
下面我们来分析一下这个函数
void analogWrite(pin_size_t pinNumber, int value)
{
#if (DAC12_HOWMANY > 0) || (DAC8_HOWMANY > 0)
if (IS_DAC(pinNumber)) {
auto cfg_dac = getPinCfgs(pinNumber, PIN_CFG_REQ_DAC);
if(IS_DAC_8BIT(cfg_dac[0])) {
#if DAC8_HOWMANY > 0
if(GET_CHANNEL(cfg_dac[0]) < DAC8_HOWMANY) {
_dac8[GET_CHANNEL(cfg_dac[0])].analogWrite(value);
}
#endif
}
else {
if(GET_CHANNEL(cfg_dac[0]) < DAC12_HOWMANY) {
_dac12[GET_CHANNEL(cfg_dac[0])].analogWrite(value);
}
}
return;
}
这里我们可以看出使用IS_DAC()来进行判断是否为DAC引脚如果是的话使用DAC输出方式如果不是的话使用普通的PWM方式
因此,接下来我们可以利用 `sin()` 函数来生成一个正弦波,并使用变量 `a` 来作为一个因子。根据我们在高中所学的知识,正弦波的函数形式为 `f(x) = a * sin(kx + b)` 。其中,`a` 表示振幅,在此我们暂且固定不变。那么,我们可以通过改变 `k` 和 `b` 的值,分别来调整正弦波的周期和相位。接下来,我们将通过实时改变这两个参数的值,来实现对所打印函数图形的实时更新。现在,让我们着手实现这一部分。
生成DAC正弦波的代码
analogWriteResolution(12);
// put your main code here, to run repeatedly:
a+=0.01;
if(a>2*PI)
{
a=0;
}
float out=map_float(sin(K*a+B),-1,1,0,4096);
analogWrite(DAC, out);
在这里需要特别注意的是,`analogWriteResolution()` 函数会改变 DAC(数模转换器)的精度。关于这部分的详细说明,我会在下面的视频中进行阐述。此外,`map_float` 函数与 `map` 函数的作用是相似的。由于 `sin` 函数的输出范围是 -1 到 1,因此我们需要将其映射到 0 到 4096 的区间内。
下面我们进行配置运算放大器 opamp 有三个输入口一个+输入一个-输入一个输出 + 为A1 -为A2 输出为A3 官方给了一个典型电路和计算公式
这里我们进行使用一种典型电路及输入就是输出连接方法为输出连接-输入
这里配置为高速模式
OPAMP.begin(OPAMP_SPEED_HIGHSPEED);
还有最后就是读取ADC并且打印这里我使用的A5 这个就很简单了接收并且打印
int reading = analogRead(A5);
//a=map(reading,0,4096,0,2*3.1415926);
Serial.println(reading);
接下来,我将为大家介绍如何改变 `k` 和 `b` 的值。我所采用的方法是通过串口发送数据,数据格式为“A:15”。在接收到字符串后,我会对其进行解析,以提取冒号前后的数据,从而得到 `A` 和 `15`,进而相应地更改对应的值。
代码如下
String str="";
if (Serial.available() > 0) {
// 读取一个字节的数据,并将其转换为字符后添加到receivedString中
// 注意:这里假设我们不知道字符串何时结束,因此持续读取直到超时或检测到特定的结束字符
char incomingByte = Serial.read();
// 检查是否是字符串的结束符(例如换行符'\n')
// 注意:根据你的发送方,结束符可能不同
if (incomingByte == '\n') {
// 如果是结束符,则不将其添加到receivedString中,而是直接处理字符串
// 在这里,我们只是简单地打印它
parseString(receivedString);
// 清空receivedString,为接收下一个字符串做准备
receivedString = "";
} else {
// 如果不是结束符,则将其添加到receivedString中
receivedString += incomingByte;
}
}
void parseString(String input) {
// 查找冒号在字符串中的位置
int colonIndex = input.indexOf(':');
if (colonIndex == -1) {
Serial.println("Input format is incorrect. Missing colon.");
return;
}
// 提取标识符(冒号之前的部分)
String identifier = input.substring(0, colonIndex);
// 提取数字(冒号之后的部分),注意substring的第二个参数是结束索引(不包含),所以我们需要+1
String numberStr = input.substring(colonIndex + 1);
int number = numberStr.toInt(); // 将字符串转换为整数
if(identifier=="K")
{
K=number;
}
if(identifier=="B")
{
B=number;
}
}
完整的代码
#include <OPAMP.h>
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
OPAMP.begin(OPAMP_SPEED_HIGHSPEED);
analogReadResolution(14);
analogWriteResolution(12);
}
double a=0;
String receivedString = "";
int K=1;
int B=0;
void loop() {
// put your main code here, to run repeatedly:
a+=0.01;
if(a>2*PI)
{
a=0;
}
float out=map_float(sin(K*a+B),-1,1,0,4096);
analogWrite(DAC, out);
// Serial.println(out);
int reading = analogRead(A5);
//a=map(reading,0,4096,0,2*3.1415926);
Serial.println(reading);
//Serial.print(",");
//Serial.println(out);
String str="";
if (Serial.available() > 0) {
// 读取一个字节的数据,并将其转换为字符后添加到receivedString中
// 注意:这里假设我们不知道字符串何时结束,因此持续读取直到超时或检测到特定的结束字符
char incomingByte = Serial.read();
// 检查是否是字符串的结束符(例如换行符'\n')
// 注意:根据你的发送方,结束符可能不同
if (incomingByte == '\n') {
// 如果是结束符,则不将其添加到receivedString中,而是直接处理字符串
// 在这里,我们只是简单地打印它
parseString(receivedString);
// 清空receivedString,为接收下一个字符串做准备
receivedString = "";
} else {
// 如果不是结束符,则将其添加到receivedString中
receivedString += incomingByte;
}
}
}
//AB
void parseString(String input) {
// 查找冒号在字符串中的位置
int colonIndex = input.indexOf(':');
if (colonIndex == -1) {
Serial.println("Input format is incorrect. Missing colon.");
return;
}
// 提取标识符(冒号之前的部分)
String identifier = input.substring(0, colonIndex);
// 提取数字(冒号之后的部分),注意substring的第二个参数是结束索引(不包含),所以我们需要+1
String numberStr = input.substring(colonIndex + 1);
int number = numberStr.toInt(); // 将字符串转换为整数
if(identifier=="K")
{
K=number;
}
if(identifier=="B")
{
B=number;
}
}
float map_float(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
最后的结果图片我使用的是VOFA串口助手打印的因为arduino的串口示波器更改有点麻烦
中间是更改K和B值的改变下面的视频为详细教程和结果和一些其他的不太好纸面介绍的知识
|