- 2024-12-22
-
加入了学习《【Follow me第二季第3期】》,观看 【Follow me第二季第3期】全部任务
- 2024-12-12
-
加入了学习《【Follow me第二季第3期】》,观看 【Follow me第二季第3期】扩展任务信号发生器
-
上传了资料:
Follow me 第二季第3期 源码
- 2024-12-11
-
发表了主题帖:
【Follow me第二季第3期】项目汇总
本帖最后由 一只小跳帽 于 2024-12-22 17:09 编辑
【Follow me第二季第3期】任务整合贴
物料介绍
EK-RA6M5
RA6M5 MCU 群组评估套件
EK-RA6M5 评估套件可通过灵活配置软件包 (FSP) 和 e2 studio IDE,帮助用户对RA6M5 MCU 群组的特性进行无缝评估,并对嵌入系统应用程序进行开发。用户可利用丰富的板载功能以及自选的热门生态系统插件来将丰富创意变为现实。
一下任务所有使用的材料仅为EK-RA6M5
任务说明和开发板介绍视频
入门任务(必做):搭建环境,下载调试示例程序,Blink,按键
【Follow me第二季第3期】入门任务 - DigiKey得捷技术专区 - 电子工程世界-论坛
环境配置
1、下载e2 studio IDE 并且安装指定的灵活配置软件包 (FSP)
2、打开软件新建项目 开发板选择EK-RA6M5 新建不带操作系统只初始化完成的示例
3、在hal_entry.c 中编写主函数
4、configuration.xml 中配置端口和软件包
入门任务 Blink
流程图
代码如下
void hal_entry (void)
{
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
R_IOPORT_Open(&g_ioport_ctrl, g_ioport.p_cfg);
while(1)
{
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_02_PIN_07, BSP_IO_LEVEL_HIGH);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_02_PIN_06, BSP_IO_LEVEL_HIGH);
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_02_PIN_07, BSP_IO_LEVEL_LOW);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_02_PIN_06, BSP_IO_LEVEL_LOW);
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
}
}
实现结果如下
[localvideo]d4a51633dca946d9565e86cfb3f18e5f[/localvideo]
入门任务 按键
确定基本结构
我们要实现一个按钮来反转LED的结构 首先要实现按钮的输入检测
但是我们如何来实现对LED的反转
1、首先要创建一个基本变量记录此时LED的输出状态
2、再读取按钮的状态 通过原理图知道按钮按下我们读取到的是低电平
3、在读取 到按钮为低电平时 我们要对存储LED灯的状态的变量进行反转
4、我们还需要知道按钮何时被抬起
5、抬起后对LED灯的状态进行反转
实现基本流程图
代码实现如下
//滤波延时
//读取按键状态
bsp_io_level_t Read_Button()
{
bsp_io_level_t button_value = BSP_IO_LEVEL_HIGH ;
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_05, &button_value);
return button_value;
}
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
bsp_io_level_t LED_value = BSP_IO_LEVEL_HIGH ;
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
R_IOPORT_Open(&g_ioport_ctrl, &g_ioport.p_cfg);
while(1)
{
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_08, LED_value);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_07, LED_value);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_06, LED_value);
if(BSP_IO_LEVEL_LOW==Read_Button())
{
R_BSP_SoftwareDelay(50,BSP_DELAY_UNITS_MILLISECONDS);
if(BSP_IO_LEVEL_LOW==Read_Button())
{
//判断为真的按下 进行状态反转
if(BSP_IO_LEVEL_HIGH==LED_value)
{
LED_value=BSP_IO_LEVEL_LOW;
}else
{
LED_value=BSP_IO_LEVEL_HIGH;
}
while (BSP_IO_LEVEL_HIGH != Read_Button());
}
}
}
}
结果如下
[localvideo]4fb4ee4448a6efd63a310009f91a3b5e[/localvideo]
基础任务(必做):quad-spi flash和octo-spi flash配置及读写速度测试;DAC配置生成波形及性能测试;
【Follow me第二季第3期】基础任务quad-spi 和octo-spi flash对比和 DAC生成波形 - DigiKey得捷技术专区 - 电子工程世界-论坛
确定基本结构
要实现quad-spi flash和octo-spi flash配置及读写速度测试
1、要进行对两个协议有充分认识
2、使用官方的示例进行读写
3、实验方法为将两个flash进行读和写同一块大小的内存
4、最后将读写的时间打印出来
流程图框架
主要代码
sprintf(s_print_buffer, "\r\n\r\nGenerated a text block of %2lu KB in SRAM\r\n", block_size_actual);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console((void*)s_print_buffer);
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)
"\r\nWriting the text block to external Quad-SPI and Octo-SPI flash memories...\r\n");
uint32_t ospi_performance_write_result = 0;
uint32_t ospi_performance_read_result = 0;
uint32_t timer_frequency;
R_GPT_InfoGet(g_memory_performance.p_ctrl, &timer_info);
timer_frequency = timer_info.clock_frequency;
ospi_performance_test (block_size_actual, &ospi_performance_write_result, &ospi_performance_read_result);
/* Multiply uSec calcs by 100, to avoid losses due to small results in integer maths
* Scaled to fit within uint32_t */
ospi_write_result = ((100000000 / timer_frequency) * ospi_performance_write_result) / 100;
qspi_write_result = ((100000000 / timer_frequency) * qspi_write_test(block_size_actual)) / 100;
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"Writing to flash completed\r\n");
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\nReading the text block from external Quad-SPI and Octo-SPI flash memories...\r\n");
ospi_read_result = ((100000000 / timer_frequency) * ospi_performance_read_result) / 100;
qspi_read_result = ((100000000 / timer_frequency) * qspi_read_test(block_size_actual)) / 100;
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"Reading from flash completed\r\n");
R_GPT_Close(g_memory_performance.p_ctrl);
完成结果如下
DAC配置生成波形及性能测试
确定基本结构
1、首先为了生成正弦波 因此需要通过数学函数生成一个sin发生器
2、配置DAC生成输出
3、将发生器的数值放到DAC中进行输出
4、使用一个示波器查看生成的波形是否符合自己的预期
流程图
主要代码
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;
}
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
float a=0;
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
//打开DAC模块
R_DAC_Open(&g_dac0_ctrl, &g_dac0_cfg);
R_DAC_Start(&g_dac0_ctrl);
while(1)
{
a+= 0.1;
if(a>2*PI)
a=0;
float out=map_float(sin(2*a),-1,1,0,4096);
R_BSP_SoftwareDelay(20,BSP_DELAY_UNITS_MILLISECONDS);
R_DAC_Write(&g_dac0_ctrl, (uint16_t)out);
// out = sin(a);
}
}
结果如下
进阶任务(必做):示例程序中新增命令打印信息
【Follow me第二季第3期】进阶任务:示例程序中新增命令打印信息 - DigiKey得捷技术专区 - 电子工程世界-论坛
分析过程
1、通过分析示例 得到基本上菜单为一个数组结构体前面为显示的内容后面为进行的方法
2、添加一个方法 方法的内容为接收传入的字符然后把字符打印出来
3、将上面的方法添加到主界面数组中
流程图如下
主要代码为
static char_t s_print_buffer[BUFFER_LINE_LENGTH] = {};
void user_print()
{
int8_t c = -1;
print_to_console("请输入需要打印的数据");
print_to_console((uint8_t *)"\r\n");
while ((0 != c))
{
c = input_from_console ();
sprintf(s_print_buffer,"%c",c);
print_to_console(s_print_buffer);
}
}
主界面代码为
/* Table of menu functions */
static st_menu_fn_tbl_t s_menu_items[] =
{
{"Kit Information" , kis_display_menu},
{"Web Server" , eth_emb_display_menu},
{"Network Name Lookup" , eth_www_display_menu},
{"Quad-SPI and Octo-SPI Speed Comparison" , ext_display_menu},
{"Cryptography and USB High speed (MSC)" , enc_display_menu},
{"User Print", user_print },
{"User DAC", DAC_main },
{"Next Steps", ns_display_menu },
{"", NULL }
};
结果如下
扩展任务:设计一个类似信号发生器功能的例程
【Follow me第二季第3期】扩展任务信号发生器 - DigiKey得捷技术专区 - 电子工程世界-论坛
这个任务就是把我们之前的一些内容组合起来 比如第三个任务的输入字符 第二个任务的DAC输出和 spi的存储
思路拓展
1、设置界面 输入对应字符的功能如生成正弦波形 生成斜坡 将值存储到内存中 将值打印出并且输出历史波形
2、设置正弦波形和斜坡波形生成器
3、将值存储到内存中的函数
4、最后将配置好的值输出到DAC的函数
流程图
主要的代码
enum DAC_MODE
{
SIN_MODE, OBL_MODE, ADC_MODE
};
enum DAC_MODE MODE = OBL_MODE;
#define PI 3.14
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;
}
//将波形通过DAC输出
void DAC_output()
{
R_DAC_Open (&g_dac0_ctrl, &g_dac0_cfg);
R_DAC_Start (&g_dac0_ctrl);
float a = 0;
float out;
while (1)
{
switch (MODE)
{
case SIN_MODE:
{
a += 0.1;
if (a > 2 * PI)
a = 0;
out = map_float (sin (2 * a), -1, 1, 0, 4096);
}
break;
case OBL_MODE:
{
a += 1;
if (a >= 4096)
a = 0;
out = a;
}
break;
case ADC_MODE:
{
a += 1;
if (a >= 5000)
{
a = 0;
}
out= flash_value[(int)a];
}
// int f= flash_value[a];
break;
}
R_BSP_SoftwareDelay (20, BSP_DELAY_UNITS_MILLISECONDS);
R_DAC_Write (&g_dac0_ctrl, (uint16_t) out);
}
}
//获取到ACD的值并且存储
void get_ADC_save()
{
R_ADC_Open (&g_adc_ctrl, &g_adc_cfg);
R_ADC_ScanCfg (&g_adc_ctrl, &g_adc_channel_cfg);
R_ADC_ScanStart (&g_adc_ctrl);
static uint16_t g_adc_data;
char print_str[100] = "";
uint16_t ADC_value[5001];
adc_status_t p_status;
print_to_console ("现在采集AD值并且打印\r\n");
for (int i = 0; i < 5000; i++)
{
R_ADC_Read (&g_adc_ctrl, ADC_CHANNEL_0, &g_adc_data);
ADC_value[i] = g_adc_data;
sprintf (print_str, "%d\r\n", g_adc_data);
print_to_console (print_str);
R_BSP_SoftwareDelay (20, BSP_DELAY_UNITS_MILLISECONDS);
}
//保存到flash中
saveToFlash (ADC_value);
print_to_console("存储完成");
}
//将flash中的值读出来
void get_flash()
{
readFromFlash (flash_value);
}
void print_flash()
{
char print_str[100] = "";
get_flash ();
print_to_console ("现在打印flase\r\n");
for (int i = 0; i < 5000; i++)
{
sprintf (print_str, "%d\r\n", flash_value[i]);
print_to_console (print_str);
R_BSP_SoftwareDelay (20, BSP_DELAY_UNITS_MILLISECONDS);
}
}
void DAC_main()
{
int8_t c = -1;
print_to_console ("下面是本产品功能:\r\n");
print_to_console ("1. 将ADC采集的数据存储到flash中\r\n");
print_to_console ("2. 打印flash中的数据\r\n");
print_to_console ("3. 将设置的数据通过DAC输出\r\n");
print_to_console ("4. 设置输出的波形为斜波 ");
print_to_console ("5. 设置输出的波形为正弦波 ");
print_to_console ("6. 设置输出的波形为flash ");
while ((0 != c))
{
c = input_from_console ();
if (0 != c)
{
/* Cast, as compiler will assume calc is int */
c = (int8_t) (c - '0');
g_selected_menu = c;
if ((c > 0) && (c <= 6))
{
switch (c)
{
case 1:
{
get_ADC_save ();
break;
}
case 2:
{
print_flash ();
}
break;
case 3:
{
DAC_output ();
}
break;
case 4:
{
MODE = OBL_MODE;
print_to_console ("现在实现为斜坡模式");
}
break;
case 5:
{
MODE = SIN_MODE;
print_to_console ("现在实现为正弦模式");
}
break;
case 6:
{
MODE = ADC_MODE;
print_to_console ("现在实现为ADC模式");
}
break;
}
}
}
}
}
结果如下
活动心得
在试用过程中,我发现EK-RA6M5开发板不仅易于上手,而且扩展性极佳。其内置的丰富外设接口,使得我能够轻松连接各种传感器和执行器,进行多样化的项目开发。同时,开发板所支持的多种通信协议,也让我在数据传输和交互方面更加得心应手。
此外,EK-RA6M5开发板的文档和社区资源也非常丰富,这对于初学者来说无疑是一个巨大的福音。在遇到问题时,我能够迅速查阅相关资料或向社区求助,从而快速解决问题。
总的来说,这次EK-RA6M5开发板的试用经历让我收获颇丰。它不仅提升了我的开发技能,还为我未来的项目开发提供了更多的可能性。我相信,这款开发板将会成为我未来开发工作中的得力助手。
所有任务完成视频
[localvideo]353174e2f9470f58bfe9eef0b1c7e7d6[/localvideo]
源码
download.eeworld.com.cn/detail/eew_ljd6R2/635242
- 2024-12-08
-
发表了主题帖:
【Follow me第二季第3期】基础任务quad-spi 和octo-spi flash对比和 DAC生成波形
本帖最后由 一只小跳帽 于 2024-12-8 21:16 编辑
今天来完成第二个基础任务首先完成quad-spi 和octo-spi flash
下面首先介绍一下quad-spi
Quad-SPI(也称为QSPI)是一种在现代微控制器中广泛采用的外围设备接口,主要用于与外部闪存芯片进行高速通信。以下是对Quad-SPI的详细介绍:
一、基本概念
Quad-SPI是一个串行接口,它通过四条数据线(I0、I1、I2、I3)实现数据的读取、写入和擦除操作。与传统的SPI接口相比,Quad-SPI提供了更高的数据传输速率和更大的带宽,特别适用于需要处理大量内存密集型数据的应用场景,如多媒体处理和片上内存不足的情况。
二、工作原理
Quad-SPI接口的数据线是双向的,可以根据需要动态配置为输入或输出。在数据传输过程中,Quad-SPI接口通过时钟信号控制数据的同步传输。每个时钟周期可以传输多个比特(通常是4个比特),从而大大提高了数据传输速率。
Quad-SPI的通信过程通常包括以下几个阶段:指令阶段、地址阶段、交替字节阶段、空指令周期阶段和数据阶段。在指令阶段,Quad-SPI接口发送一个8位指令到闪存芯片,指定要执行的操作类型。在地址阶段,发送1~4字节的地址信息,指示操作的目标位置。交替字节阶段用于发送控制操作模式的字节。空指令周期阶段则用于准备数据线的传输方向。最后,在数据阶段,Quad-SPI接口可以从闪存芯片接收或向其发送任意数量的字节。
三、关键特性
双倍数据速率模式:Quad-SPI支持双倍数据速率模式,即在每个时钟周期的上升沿和下降沿都传输数据,从而实现了数据传输速率的加倍。但需要注意的是,这个功能需要微控制器和闪存芯片都支持才能实现。
XIP功能:XIP(eXecute In Place)是Quad-SPI的一个重要特性,它允许微控制器直接从外部闪存执行代码而无需先将其复制到内部存储器。这大大提高了代码执行的效率,特别是在代码大小较大、无法存储在片上存储器中的情况下。
多设备连接:多个Quad-SPI设备可以链接到同一个Quad-SPI接口上,并通过芯片选择引脚来选择特定的设备进行通信。这增加了系统的灵活性和可扩展性。
四、应用领域
Quad-SPI接口因其高速、高效和灵活的特性而被广泛应用于各种嵌入式系统中。特别是在需要处理大量数据、对存储性能有较高要求的场景中,如智能手机、平板电脑、数码相机等消费电子产品中,Quad-SPI接口发挥着至关重要的作用。
五、总结
Quad-SPI是一种功能强大、灵活高效的串行接口,它通过四条数据线实现了与外部闪存芯片的高速通信。其支持的双倍数据速率模式、XIP功能和多设备连接特性使其在各种嵌入式系统中得到了广泛应用。随着技术的不断发展,Quad-SPI接口的性能和功能也将不断提升,为嵌入式系统的设计和开发提供更多的可能性。
我们这款开发板中有一个Quad-SPI flash
原理图如下
下面再了解一下 octo-spi
Octo-SPI(也称作OctoSPI或OSPI)是Quad-SPI(Quad Serial Peripheral Interface,四线串行外设接口)的升级版,是一种高速串行接口技术,它通过使用八条数据线(而不是Quad-SPI的四条)来进一步提高数据传输速率和带宽。以下是对Octo-SPI的详细介绍:
一、基本概念与特点
高速数据传输:Octo-SPI通过增加数据线的数量,实现了更高的数据传输速率,适用于需要处理大量数据的应用场景。
并行通信:与Quad-SPI类似,Octo-SPI也采用并行通信方式,但数据线数量加倍,从而提高了通信效率。
向下兼容:Octo-SPI接口通常也支持Single-SPI、Dual-SPI和Quad-SPI模式,这意味着它可以与这些较低版本的SPI接口设备兼容。
内存映射模式:在Octo-SPI接口中,外部存储器可以像内部内存一样被访问,这提高了系统的总线主控器(如DMA)在CPU停机的低功耗模式下自动访问外部内存的能力。
二、工作原理
Octo-SPI的工作原理与Quad-SPI相似,但数据传输能力更强。它通常包括指令阶段、地址阶段、数据阶段等,通过时钟信号同步数据的传输。在每个时钟周期,Octo-SPI可以传输多达8个比特的数据,从而实现了更高的数据传输速率。
三、关键特性
高吞吐量:由于使用了八条数据线,Octo-SPI能够提供比Quad-SPI更高的数据吞吐量,适用于需要快速访问大量数据的场景。
灵活性:Octo-SPI接口的配置非常灵活,可以根据需要启用或禁用每个数据线,以适应不同的数据传输需求。
支持多种存储器类型:Octo-SPI接口可以连接多种类型的外部存储器,如Flash存储器、PSRAM等,为系统设计提供了更多的选择。
四、应用领域
Octo-SPI接口因其高速、高效和灵活的特性而被广泛应用于各种高性能嵌入式系统中。特别是在需要处理大量数据、对存储性能有较高要求的场景中,如智能手机、平板电脑、数码相机、物联网设备等,Octo-SPI接口发挥着至关重要的作用。
五、发展趋势
随着技术的不断发展,Octo-SPI接口的性能和功能也在不断提升。例如,一些最新的Octo-SPI接口支持更高的时钟频率和更大的数据传输量,同时还提供了更多的配置选项和更强大的错误检测与纠正功能。这些改进使得Octo-SPI接口在高性能嵌入式系统中的应用更加广泛和深入。
总的来说,Octo-SPI作为一种高速串行接口技术,以其高效的数据传输能力和灵活的配置选项,在高性能嵌入式系统中发挥着越来越重要的作用。
我们这款开发板中有一个Octo-SPI flash
原理图如下
这里我们看到Octo-SPI 使用了更多的线 因此按照道理来说他的传输速率应该是更高的 下面我们来尝试一下对他们进行测试
由于我们对存储芯片的不熟悉因此我们使用官方的历程进行分析但是我们也要去读懂这个代码是如何实现的下面进行分析代码的实现的过程和实际的运行结果
首先我从官网下载了官方的历程并且导入工程
我们查看分析后看到这个是一个快速入门的例子 首先有一个菜单main函数我们看到里面的文件
这个应该是一个主菜单 前面对应的是字符串解释应该是在终端输入的后面是对应执行的函数看到第四个就是我们所需要的两种flash速度的对比
我们进入其中对应的函数查看
ospi_performance_test (block_size_actual, &ospi_performance_write_result, &ospi_performance_read_result);
/* Multiply uSec calcs by 100, to avoid losses due to small results in integer maths
* Scaled to fit within uint32_t */
ospi_write_result = ((100000000 / timer_frequency) * ospi_performance_write_result) / 100;
qspi_write_result = ((100000000 / timer_frequency) * qspi_write_test(block_size_actual)) / 100;
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"Writing to flash completed\r\n");
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\nReading the text block from external Quad-SPI and Octo-SPI flash memories...\r\n");
ospi_read_result = ((100000000 / timer_frequency) * ospi_performance_read_result) / 100;
qspi_read_result = ((100000000 / timer_frequency) * qspi_read_test(block_size_actual)) / 100;
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"Reading from flash completed\r\n");
R_GPT_Close(g_memory_performance.p_ctrl);
其中最主要的代码就是这一部分
qspi_write_test(block_size_actual)
qspi_read_test(block_size_actual))
ospi_performance_test (block_size_actual, &ospi_performance_write_result, &ospi_performance_read_result);
这两个函数就是其中测试的内容使用两种方法去测试我们再去分析每一个方法
ospi_performance_test
void ospi_performance_test(uint32_t data_size,
uint32_t *ospi_performance_write_result,
uint32_t *ospi_performance_read_result)
{
fsp_err_t err;
uint32_t i = 1;
if (R_CGC_Open (g_cgc.p_ctrl, g_cgc.p_cfg) != FSP_SUCCESS)
{
__asm("bkpt");
}
while (i)
{
err = R_OSPI_Open(g_ospi.p_ctrl, g_ospi.p_cfg);
if (FSP_SUCCESS != err)
{
__asm("bkpt");
}
#if HIGH_SPEED_MODE
configure_dopi_ospi();
ospi_test_wait_until_wip();
#endif
*ospi_performance_write_result = write_dopi_ospi(data_size);
ospi_test_wait_until_wip();
*ospi_performance_read_result = read_dopi_ospi(data_size);
ospi_test_wait_until_wip();
erase_dopi_ospi();
ospi_test_wait_until_wip();
#if HIGH_SPEED_MODE
configure_spi_ospi();
ospi_test_wait_until_wip();
#endif
err = R_OSPI_Close(g_ospi.p_ctrl);
if (FSP_SUCCESS != err)
{
__asm("bkpt");
}
i--;
}
}
这个中就是使用
*ospi_performance_write_result = write_dopi_ospi(data_size);
ospi_test_wait_until_wip();
*ospi_performance_read_result = read_dopi_ospi(data_size);
ospi_test_wait_until_wip();
先对块进行写入再进行读出进行计算各自时间
qspi_write_test
static uint32_t qspi_write_test(uint32_t block_size)
{
fsp_err_t fsp_err;
uint32_t qspi_write_result = 0;
timer_status_t status = {};
fsp_err_t err = FSP_SUCCESS;
spi_flash_protocol_t current_spi_mode;
/* Convert from kB */
block_size *= 1024;
/* The comms mode is EXTENDED_SPI by default */
current_spi_mode = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* initialise the QSPI, and change mode to that set in FSP */
err = qpi_init();
if (FSP_SUCCESS == err)
{
/* The comms mode has changed. So if recovering, this new mode required */
current_spi_mode = g_qspi_cfg.spi_protocol;
}
uint32_t page_write_count = 0;
uint8_t * p_mem_addr;
/* Cast to req type */
p_mem_addr = (uint8_t *)QSPI_DEVICE_START_ADDRESS;
while (((page_write_count * SECTOR_SIZE) < block_size)
&& ( FSP_SUCCESS == err ) )
{
/* Erase Flash for one sector */
err = R_QSPI_Erase(&g_qspi_ctrl, p_mem_addr, SECTOR_SIZE);
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "R_QSPI_Erase Failed\r\n");
}
else
{
err = get_flash_status();
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "Failed to get status for QSPI operation\r\n");
}
/* Verify the erased block data */
uint32_t count;
for (count = 0; count < SECTOR_SIZE; count++ )
{
if (DEFAULT_MEM_VAL != p_mem_addr[count])
{
/* Verification failed, perhaps the ERASE failed */
err = FSP_ERR_NOT_ERASED;
}
}
}
p_mem_addr += SECTOR_SIZE;
page_write_count++;
}
/* Start the test timer */
fsp_err = R_GPT_Start(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
/* Cast to req type */
p_mem_addr = (uint8_t *)QSPI_DEVICE_START_ADDRESS;
page_write_count = 0;
while (((page_write_count * PAGE_WRITE_SIZE) < block_size)
&& (FSP_SUCCESS == err))
{
if (FSP_SUCCESS == err)
{
/* Write data to QSPI Flash */
/* Each block begins one character shifted along the source text. To avoid regular striping in memory */
err = R_QSPI_Write(&g_qspi_ctrl, &(sp_source[page_write_count]), p_mem_addr, PAGE_WRITE_SIZE);
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "R_QSPI_Write Failed\r\n");
}
else
{
err = get_flash_status();
if (FSP_SUCCESS != err)
{
sprintf(s_print_buffer, "Failed to get status for QSPI operation\r\n");
}
}
}
p_mem_addr += PAGE_WRITE_SIZE;
page_write_count++;
}
/* close QSPI module */
deinit_qspi(current_spi_mode);
fsp_err = R_GPT_Stop(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
fsp_err = R_GPT_StatusGet(g_memory_performance.p_ctrl, &status);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
fsp_err = R_GPT_Reset(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
qspi_write_result = status.counter;
return (qspi_write_result);
}
/**********************************************************************************************************************
End of function qspi_write_test
static uint32_t qspi_read_test(uint32_t block_size)
{
fsp_err_t fsp_err;
fsp_err_t err = FSP_SUCCESS;
uint32_t qspi_read_result = 0;
timer_status_t status = {};
spi_flash_protocol_t current_spi_mode;
uint8_t * p_dma_read_buffer;
uint32_t page_read_count;
uint8_t * p_mem_addr;
/* Convert from kB */
block_size *= 1024;
p_dma_read_buffer = pvPortMalloc(block_size);
if (NULL == p_dma_read_buffer)
{
HeapStats_t pxHeapStats;
vPortGetHeapStats(&pxHeapStats);
sprintf(s_print_buffer, "\r\nQSPI malloc operation Failed - Max free mem: %dbytes\r\n",
pxHeapStats.xSizeOfLargestFreeBlockInBytes);
/* Verification failed, perhaps the ERASE failed */
err = FSP_ERR_NOT_ERASED;
}
/* The comms mode of the FLASH device is EXTENDED_SPI by default */
current_spi_mode = SPI_FLASH_PROTOCOL_EXTENDED_SPI;
/* initialise the QSPI, and change mode to that set in FSP */
err = qpi_init();
if (FSP_SUCCESS == err)
{
/* The comms mode has changed. So if recovering, this new mode required */
current_spi_mode = g_qspi_cfg.spi_protocol;
}
/* Start the test timer */
fsp_err = R_GPT_Start(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
page_read_count = 0;
/* cast to req type */
p_mem_addr = (uint8_t *)QSPI_DEVICE_START_ADDRESS;
while (((page_read_count * PAGE_WRITE_SIZE) < block_size)
&& (FSP_SUCCESS == err))
{
/* Verify the written data */
/* Each block begins one character shifted along the source text. To avoid regular striping in memory */
if ((fsp_err_t) (memcmp (p_mem_addr, &(sp_source[page_read_count]), PAGE_WRITE_SIZE)) != FSP_SUCCESS)
{
err = FSP_ERR_NOT_ERASED;
sprintf(s_print_buffer, "\r\nQSPI operation Failed -> Data read does not match with written data\r\n");
}
p_mem_addr += PAGE_WRITE_SIZE;
page_read_count++;
}
fsp_err = R_GPT_Stop(g_memory_performance.p_ctrl);
/* close QSPI module */
deinit_qspi(current_spi_mode);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
fsp_err = R_GPT_StatusGet(g_memory_performance.p_ctrl, &status);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
fsp_err = R_GPT_Reset(g_memory_performance.p_ctrl);
/* Handle error */
if (FSP_SUCCESS != fsp_err)
{
/* Fatal error */
SYSTEM_ERROR
}
qspi_read_result = status.counter;
vPortFree(p_dma_read_buffer);
return (qspi_read_result);
}
/**********************************************************************************************************************
End of function qspi_read_test
这两个函数就是分别进行读和写
我们只需要知道这么多就可以了下面进行运行和测试
下载进行后使用USB-fs的端口进行打印和交互
输入 4
输入64
这里可以看到 Octa-Spi 确实比较快与我们开头的猜测是想符合的
下面完成第二个任务DAC配置生成波形及性能测试;
这里我使用的方法是使用我们的开发板进行输出DAC的正弦波 再使用第二期的arduino R4 的开发板进行采集输出波形并且进行打印查看波形
首先新建工程并且进行配置
打开后发现有红色代表冲突我们查看这个端口P014是被配置为 是什么功能
这里为模拟输入我们改为DAC输出
将ADC0 的第12通道改为None即可
加入DAC的栈
对其进行设置
选择默认即可
代码如下
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;
}
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
float a=0;
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
//打开DAC模块
R_DAC_Open(&g_dac0_ctrl, &g_dac0_cfg);
R_DAC_Start(&g_dac0_ctrl);
while(1)
{
a+= 0.1;
if(a>4096)
a=0;
float out=map_float(sin(2*a),-1,1,0,4096);
// R_BSP_SoftwareDelay(50,BSP_DELAY_UNITS_MILLISECONDS);
R_DAC_Write(&g_dac0_ctrl, (uint16_t)out);
// out = sin(a);
}
}
我们查看打印出来的波形
波形是这种有规律的杂波是因为我们使用的ARDUINO R4 的性能没有我们这个开发板的强这个从侧面印证了我们DAC的强大为了看出波形我们加入延迟函数
void hal_entry(void)
{
/* TODO: add your own code here */
float a=0;
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
//打开DAC模块
R_DAC_Open(&g_dac0_ctrl, &g_dac0_cfg);
R_DAC_Start(&g_dac0_ctrl);
while(1)
{
a+= 0.1;
if(a>4096)
a=0;
float out=map_float(sin(2*a),-1,1,0,4096);
R_BSP_SoftwareDelay(20,BSP_DELAY_UNITS_MILLISECONDS);
R_DAC_Write(&g_dac0_ctrl, (uint16_t)out);
// out = sin(a);
}
}
波形就很有规律了
下面是本个实验的完整视频具体的过程和细节大家可以查看视频
-
发表了主题帖:
【Follow me第二季第3期】进阶任务:示例程序中新增命令打印信息
本帖最后由 一只小跳帽 于 2024-12-8 21:03 编辑
本个任务较为简单 首先打开示例 我们上个任务已经分析过主要是有一个主界面然后再进入主界面后方的对应的函数我们就可以仿照进行增加但是也要分析一下执行的过程下面首先是对主界面数组的分析
* Function Name: main_display_menu
* Description : .
* Return Value : The main menu controller.
*********************************************************************************************************************/
int8_t main_display_menu(void)
{
int8_t c = -1;
int8_t menu_limit = 0;
sprintf (s_print_buffer, "%s%s", gp_clear_screen, gp_cursor_home);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console((void*)s_print_buffer);
sprintf (s_print_buffer, MODULE_NAME, FULL_NAME);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console((void*)s_print_buffer);
sprintf (s_print_buffer, SUB_OPTIONS);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console((void*)s_print_buffer);
for (int8_t test_active = 0; NULL != s_menu_items[test_active].p_func; test_active++ )
{
sprintf (s_print_buffer, "\r\n %d. %s", (test_active + 1), s_menu_items[menu_limit++ ].p_name);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console((void*)s_print_buffer);
}
/* ignoring -Wpointer-sign is OK for a constant string */
print_to_console((uint8_t *)"\r\n");
while ((0 != c))
{
c = input_from_console ();
if (0 != c)
{
/* Cast, as compiler will assume calc is int */
c = (int8_t) (c - '0');
g_selected_menu = c;
if ((c > 0) && (c <= menu_limit))
{
s_menu_items[c - 1].p_func ();
break;
}
}
}
/* Cast, as compiler will assume calc is int */
return ((int8_t) (c - '0'));
}
/**********************************************************************************************************************
End of function main_display_menu
这里可以看到首先进行遍历数组将数组中的字符串进行打印
for (int8_t test_active = 0; NULL != s_menu_items[test_active].p_func; test_active++ )
{
sprintf (s_print_buffer, "\r\n %d. %s", (test_active + 1), s_menu_items[menu_limit++ ].p_name);
/* ignoring -Wpointer-sign is OK when treating signed char_t array as as unsigned */
print_to_console((void*)s_print_buffer);
}
再在下面进行接收执行对应数组中的方法
while ((0 != c))
{
c = input_from_console ();
if (0 != c)
{
/* Cast, as compiler will assume calc is int */
c = (int8_t) (c - '0');
g_selected_menu = c;
if ((c > 0) && (c <= menu_limit))
{
s_menu_items[c - 1].p_func ();
break;
}
}
}
下面我们增加自己的打印方法
新建一个.c和.h文件
就为user_print
/*
* user_print.c
*
* Created on: 2024年12月8日
* Author: li
*/
#include "user_print.h"
#include <stdio.h>
#include <string.h>
#include "FreeRTOS.h"
#include "FreeRTOSConfig.h"
#include "semphr.h"
#include "queue.h"
#include "task.h"
#include "common_init.h"
#include "common_data.h"
#include "common_utils.h"
static char_t s_print_buffer[BUFFER_LINE_LENGTH] = {};
void user_print()
{
int8_t c = -1;
print_to_console("请输入需要打印的数据");
print_to_console((uint8_t *)"\r\n");
while ((0 != c))
{
c = input_from_console ();
sprintf(s_print_buffer,"%c",c);
print_to_console(s_print_buffer);
}
}
这里的作用就是将输入的字符再次打印出来我们可以看到如下效果
就是将本次打印的字符串再次打印出来
本次任务就完成了这个对于我们完成最后的任务很有帮助
下面的视频为本次任务的完整视频更多的细节大家可以查看下面的视频
-
发表了主题帖:
【Follow me第二季第3期】扩展任务信号发生器
本帖最后由 一只小跳帽 于 2024-12-8 21:04 编辑
今天实现最为困难的信号发生器
我设置了这几种功能
采集ADC的值并且保存到flash中可以进行DAC复现输出
首先实现ADC采集的功能代码如下
R_ADC_Open (&g_adc_ctrl, &g_adc_cfg);
R_ADC_ScanCfg (&g_adc_ctrl, &g_adc_channel_cfg);
static uint16_t g_adc_data;
char print_str[100] = "";
uint16_t ADC_value[5001];
print_to_console ("现在采集AD值并且打印\r\n");
for (int i = 0; i < 5000; i++)
{
R_ADC_Read (&g_adc_ctrl, ADC_CHANNEL_0, &g_adc_data);
ADC_value[i] = g_adc_data;
sprintf (print_str, "%d\r\n", g_adc_data);
print_to_console (print_str);
R_BSP_SoftwareDelay (20, BSP_DELAY_UNITS_MILLISECONDS);
}
//保存到flash中
saveToFlash (ADC_value);
print_to_console("存储完成");
最后再加入存储到flash中的代码即可
功能就是循环保存数据5000个
下面我们看下实际的效果 连接图如下端口为p000
采集到的数据如下
输入1
可以采到值并且保存
下面看斜波和正弦波的触发和上一个任务的代码是相同的
代码如下
void DAC_output()
{
R_DAC_Open (&g_dac0_ctrl, &g_dac0_cfg);
R_DAC_Start (&g_dac0_ctrl);
float a = 0;
float out;
while (1)
{
switch (MODE)
{
case SIN_MODE:
{
a += 0.1;
if (a > 2 * PI)
a = 0;
out = map_float (sin (2 * a), -1, 1, 0, 4096);
}
break;
case OBL_MODE:
{
a += 1;
if (a >= 4096)
a = 0;
out = a;
}
break;
case ADC_MODE:
{
a += 1;
if (a >= 5000)
{
a = 0;
}
out= flash_value[(int)a];
}
// int f= flash_value[a];
break;
}
R_BSP_SoftwareDelay (20, BSP_DELAY_UNITS_MILLISECONDS);
R_DAC_Write (&g_dac0_ctrl, (uint16_t) out);
}
}
使用ARDUINR4 进行查看波形
下面尝试将flash中的通过DAC输出
这里的打印一直为gnd后续我们可以再次查看
好的本个代码大致完成更详细的可以查看下方的视频
- 2024-12-07
-
发表了主题帖:
【Follow me第二季第3期】入门任务
本帖最后由 一只小跳帽 于 2024-12-8 21:13 编辑
瑞萨MCU 的安装环境为官方的 e2 studio 搭建涉及多个步骤和组件,以下是一个详细的分享:
e2 studio (简称为 e2 或 e2s)是瑞萨电子的一款包含代码开发、构建和调试的开发工具。 e2 studio 基于开源Eclipse IDE和与之相关的C/C++开发工具(CDT)。 e2 studio 托管了瑞萨的FSP灵活配置软件包,这是一个用于支持瑞萨MCU开发的固件库。 通过使用FSP库,我们可以轻松配置和管理瑞萨MCU,从而轻松实现复杂的应用程序。
一、准备工作
下载必要的软件包:
从瑞萨官方网站或相关资源下载最新的RA Pack和RASC(RA Smart Configurator)。e2 studio 的下载页面为:E² 单间公寓 |瑞萨电子 下载最新版即可 但是需要先登录才能进行下载
二、安装e2 studio
安装e2 studio
解压缩下载的安装包。
双击运行安装程序,等待安装程序加载完毕。
选择安装路径(如有需要),并勾选同意许可协议。
选择在开始菜单创建快捷方式,然后点击“Install”开始安装。
安装完成后,在开始菜单或桌面上找到e2 studio 的快捷方式。
三、安装和配置其他开发工具
安装FSP(Flexible Software Package):
FSP是瑞萨提供的一套灵活的软件开发包,它包含了各种外设驱动和中间件。
从瑞萨官方网站下载FSP安装包,并按照提示完成安装过程。
在你的开发环境中配置FSP,以便使用其中的外设驱动和中间件。
五、创建和编译项目
创建新项目:
在你的IDE(如MDK、Keil等)中,创建一个新的瑞萨MCU项目。
指定项目名称、开发板型号、芯片型号和工具链等参数。这里可以选择版本 开发板的型号 和使用的工具链 还有调试方式 为板载的jlink
配置项目:
根据你的需求,配置项目的时钟、引脚分配、外设驱动等参数。
使用FSP配置工具或RASC来简化配置过程。
双击 configuration.xml 即可进行图形化的配置
这里的 clocks 可以配置时钟 pins 可以配置引脚 等功能
编译和调试:
编译你的项目,确保没有错误。
使用 上方的锤子进行编译 使用虫子按钮进行debug和download
使用IDE提供的调试工具进行调试,查看寄存器和外设的状态。
六、烧录程序到开发板
连接开发板:
使用USB线或其他连接方式将你的开发板连接到计算机上。
烧录程序:
在IDE中选择适当的烧录工具(如J-Link、dap-link等)。
配置烧录参数,如烧录速度、烧录地址等。
开始烧录程序,并等待烧录完成。
验证程序:
烧录完成后,断开开发板与计算机的连接。
给开发板上电,观察LED指示灯或其他外设的状态,以验证程序是否正确运行。
通过以上步骤,可以成功地搭建瑞萨MCU的安装环境,并开始进行开发工作。请注意,不同型号的瑞萨MCU和开发板可能具有不同的配置要求,因此在实际操作中需要根据具体情况进行调整。
下面完成入门的第二个任务 Blink
一、准备工作
硬件准备:
一块瑞萨MCU开发板(EK-RA6M5)。
一根USB连接线(用于连接开发板和计算机)
二、新建工程
选择Rensesas RA 进行新建
输入创建的工程名字 这里选择输入Blink
这里选择对应的开发板就好了
然后一直下一步就好啦
三、编写代码
使用Smart Configurator配置引脚:
打开Smart Configurator,创建一个新的工程。
选择你的MCU型号和开发板。
在引脚配置中,找到与LED相连的引脚,并将其配置为输出模式。
这里我查找原理图查看我们的LED灯连接到008 007 006 三个引脚
按照下方的图进行配置 为输出 推挽 进行生成代码
编写LED闪烁逻辑:
这里我们需要在hal_entry.c 文件中写自己的逻辑但是本质上还是在main函数中写的
在对应的函数中写主要逻辑
下面就是打开对应功能 并且使用官方的延迟函数进行延时 同时在延时的前后使用官方对端口进行打开和关闭对应的代码如下
void hal_entry(void)
{
/* TODO: add your own code here */
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
//打开对应功能
R_IOPORT_Open(&g_ioport_ctrl, &g_ioport.p_cfg);
//写主循环
while(1)
{
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_08, BSP_IO_LEVEL_HIGH);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_07, BSP_IO_LEVEL_HIGH);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_06, BSP_IO_LEVEL_HIGH);
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_08, BSP_IO_LEVEL_LOW);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_07, BSP_IO_LEVEL_LOW);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_06, BSP_IO_LEVEL_LOW);
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
}
}
这里R_BSP_SoftwareDelay() 函数第一个参数为1 第二个参数 为秒 因此就是一秒组合 下面进行下载和验证 编译使用上方说明的小锤子 下载使用上方的虫子图标下载完成后我们查看现象如下所示
[localvideo]45c1b1fc30c715506a069a0987e9dbfb[/localvideo]
下面完成入门的第三个任务 按键
其实任务前面是相同的创建工程下面直接进行到配置阶段
首先通过原理图查看按键的引脚
我们使用引脚1 005 我门做一个程序为按下一次按钮将LED灯的状态进行反转
下面进行配置
配置为输入模式 不使用中断 上拉输入 我们分析原理图当接收到高时为未按下状态 当为低时为按下状态
代码如下
//读取按键状态
bsp_io_level_t Read_Button()
{
bsp_io_level_t button_value = BSP_IO_LEVEL_HIGH ;
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_05, &button_value);
return button_value;
}
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
bsp_io_level_t LED_value = BSP_IO_LEVEL_HIGH ;
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
R_IOPORT_Open(&g_ioport_ctrl, &g_ioport.p_cfg);
while(1)
{
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_08, LED_value);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_07, LED_value);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_06, LED_value);
if(BSP_IO_LEVEL_LOW==Read_Button())
{
R_BSP_SoftwareDelay(50,BSP_DELAY_UNITS_MILLISECONDS);
if(BSP_IO_LEVEL_LOW==Read_Button())
{
//判断为真的按下 进行状态反转
if(BSP_IO_LEVEL_HIGH==LED_value)
{
LED_value=BSP_IO_LEVEL_LOW;
}else
{
LED_value=BSP_IO_LEVEL_HIGH;
}
while (BSP_IO_LEVEL_HIGH != Read_Button());
}
}
}
}
逻辑为当检测到按钮输入为低时进行滤波过了50ms还为低时此时表示按钮为真正的按下 将LED灯的状态进行改变 并且等待按钮抬起将改变后的值写入到LED灯中 最后的效果如下所示
[localvideo]4ba0d563ba436ad674f943bc804d61f6[/localvideo]
总结
通过本次学习,我对瑞萨的GPIO操作有了更深入的理解。在未来,我计划继续深入学习微控制器的其他功能,如串口通信、ADC(模数转换)等,并尝试将这些知识应用到实际的项目中。同时,更好的完成后面的任务
下面是本次整个任务的录像视频解说 有一些很细节的地方我将在视频中和大家一起分享
- 2024-10-16
-
加入了学习《【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印》,观看 【Follow me第二季第2期】+通过外部SHT40温湿度传感器,上传温湿度到HA
-
加入了学习《【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印》,观看 【Follow me第二季第2期】Arduino Uno R4 WiFi 通过MQTT连入Home Assistant
- 2024-09-23
-
加入了学习《【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印》,观看 【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印
-
回复了主题帖:
【Follow me第二季第2期】Arduino Uno R4 WiFi 通过MQTT连入Home Assistant
Jacktang 发表于 2024-9-23 07:28
容器技术来安装和运行Home Assistant这个看来是有技巧的
主要是安装后配置文件太麻烦了 还是自发现比较适合实际应用
- 2024-09-22
-
发表了主题帖:
【Follow me第二季第2期】 汇总提交帖:全部任务
本帖最后由 一只小跳帽 于 2024-10-18 22:11 编辑
大家好!很高兴能与大家分享关于我们申请到的Arduino UNO R4 WiFi开发板的硬件资源。这款开发板在保留了Arduino UNO R3大部分接口的基础上,进行了一些升级并增加了新的功能。
与R3的兼容性:它保留了与R3相同的大部分接口,这意味着我们可以很方便地在R3和R4之间进行切换,同时兼容大部分为R3设计的扩展板和项目。
新增CAN通信接口:最显著的是增加了CAN(Controller Area Network)通信接口。CAN是一种常用的工业通信协议,广泛应用于汽车、工业控制等领域。通过增加CAN接口,我们可以模拟更多的功能,实现与各种CAN设备的通信和控制。
实现模拟输出:这款开发板还增加了DAC(Digital-to-Analog Converter,数字到模拟转换器)接口,实现了模拟输出功能。这使得Arduino UNO R4 WiFi不仅能够处理数字信号,还能处理模拟信号,进一步扩展了其应用范围。
总的来说,Arduino UNO R4 WiFi开发板在保留了原有功能的基础上,通过增加CAN通信接口和DAC接口,提供了更丰富的硬件资源和更广泛的应用场景。无论是对于初学者还是对于需要进行复杂项目开发的用户来说,都是一个非常不错的选择。
任务一开发板硬件介绍和实现任务一 LED灯闪烁和串口打印
对应论坛帖:【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印 https://bbs.eeworld.com.cn/thread-1292136-1-1.html
所需硬件
本个任务之需要我们一块arduinoR4Wifi 开发板
整体的思路就是初始化外设并且在循环中对灯或者外设进行取反
首先打印字符串
流程图:
代码
void setup() {
// 初始化串口通信,设置波特率为9600
Serial.begin(9600);
}
void loop() {
// 每隔一秒打印一次“Hello EEWorld!”
Serial.println("Hello EEWorld!");
delay(1000);
}
演示:
第二个是LED灯闪烁
流程图:
代码:
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
演示:
下面的视频为这个项目的整个过程
任务二驱动12x8点阵LED;用DAC生成正弦波并放大采集
对应论坛帖:【Follow me第二季第2期】实现任务二 驱动12x8点阵LED;用DAC生成正弦波并放大采集 https://bbs.eeworld.com.cn/thread-1293083-1-1.html
今天我们将着手完成任务二的第一个关键任务——驱动一个由12x8 LED灯组成的点阵屏。
所需硬件
也是只需要一个ARDUINOR4WIFI 开发板和一些导线
Arduino官方特别为点阵屏开发了一套高效的API,通过引入
Arduino_LED_Matrix.h
库,开发者可以便捷地控制点阵屏。首先,你需要创建一个点阵屏对象,例如命名为
ledMatrix
,以
ArduinoLEDMatrix
整体思路就是使用这个对象进行画图
流程图:
代码:
#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配置好再配置外设的放大器最后再使用ADC不断采集放大出来的信号并且进行打印最后使用电脑串口工具查看
本个项目使用的软件为
实物链截图
流程图
代码:
#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;
}
结果演示:
中间是更改K和B值的改变下面的视频为详细教程和结果和一些其他的不太好纸面介绍的知识
任务三
通过Wi-Fi,利用MQTT协议接入到开源的智能家居平台HA(HomeAssistant)
对应论坛帖:【Follow me第二季第2期】Arduino Uno R4 WiFi 通过MQTT连入Home Assistant https://bbs.eeworld.com.cn/thread-1294158-1-1.html
这个任务在于是否能够理解mqtt协议和部署HomeAssistant 实现思路为首先链接WiFi然后再使用MQTT协议去连接我们配置好的家具平台
物料展示
用到的硬件为一个ARDUINOR4WIFI 开发板
软件为虚拟机和MQTT客户端
流程图
代码:
//#include <WiFiS3.h>
#include <PubSubClient.h>
#include <WiFi.h>
#define ARDUINOJSON_SLOT_ID_SIZE 1
#define ARDUINOJSON_STRING_LENGTH_SIZE 1
#define ARDUINOJSON_USE_DOUBLE 0
#define ARDUINOJSON_USE_LONG_LONG 0
#include <ArduinoJson.h>
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "aaaa";
const char* password = "aaaaaaaa";
const char* mqttServer = "broker.emqx.io";
IPAddress server(192,168,31 ,200);
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
void setup() {
Serial.begin(9600);
// 连接WiFi
connectWifi();
// 设置MQTT服务器和端口号
mqttClient.setServer(server, 1883);
// 连接MQTT服务器
connectMQTTServer();
Register_temp();
Register_hum();
}
void loop() {
if (mqttClient.connected()) { // 如果开发板成功连接服务器
mqttClient.loop(); // 保持客户端心跳
} else { // 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
void connectMQTTServer(){
String clientId = "UNOR4";
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
void connectWifi(){
WiFi.begin(ssid, password);
//等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
Serial.println(WiFi.localIP());
}
void Register_temp()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomT/config";
String output;
JsonDocument doc;
doc["device_class"] = "temperature";
doc["name"] = "Temperature";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["value_template"] = "{{ value_json.temperature}}";
doc["unique_id"] = "temp01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Register_hum()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomH/config";
String output;
JsonDocument doc;
doc["device_class"] = "humidity";
doc["name"] = "Humidity";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["unit_of_measurement"] = "%";
doc["value_template"] = "{{ value_json.humidity}}";
doc["unique_id"] = "hum01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
结果:
本结果图片不好展示使用视频展示看下方视频
任务四
通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据
对应论坛帖:【Follow me第二季第2期】+通过外部SHT40温湿度传感器,上传温湿度到HA https://bbs.eeworld.com.cn/thread-1294457-1-1.html
这个任务难点是如何获取SHT40 并且理解如何上传mqtt数据 本任务使用任务三思路即可完成
物料展示:
使用的硬件有
ARDUINOR4WIFI SHT40温湿度传感器 一个 一个Qwiic 线缆
流程图如下
代码:
//#include <WiFiS3.h>
#include <PubSubClient.h>
#include "Adafruit_SHT4x.h"
#include <Wire.h>
#include <WiFi.h>
#include "aaa.h"
#define ARDUINOJSON_SLOT_ID_SIZE 1
#define ARDUINOJSON_STRING_LENGTH_SIZE 1
#define ARDUINOJSON_USE_DOUBLE 0
#define ARDUINOJSON_USE_LONG_LONG 0
#include <ArduinoJson.h>
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* mqttServer = "broker.emqx.io";
IPAddress server(192,168,31 ,200);
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
void setup() {
Serial.begin(9600);
// 连接WiFi
connectWifi();
if (! sht4.begin(&Wire1 )) {
Serial.println("Couldn't find SHT4x");
while (1) delay(1);
}
// 设置MQTT服务器和端口号
mqttClient.setServer(server, 1883);
// 连接MQTT服务器
connectMQTTServer();
Register_temp();
Register_hum();
}
void loop() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
Serial.print("Temperature: "); Serial.print(temp.temperature); Serial.println(" degrees C");
Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); Serial.println("% rH");
if (mqttClient.connected()) { // 如果开发板成功连接服务器
Update_Value(temp.temperature,humidity.relative_humidity);
} else { // 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
void connectMQTTServer(){
String clientId = "UNOR4";
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
void connectWifi(){
WiFi.begin(ssid, password);
//等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
Serial.println(WiFi.localIP());
}
void Register_temp()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomT/config";
String output;
JsonDocument doc;
doc["device_class"] = "temperature";
doc["name"] = "Temperature";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["value_template"] = "{{ value_json.temperature}}";
doc["unique_id"] = "temp01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Register_hum()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomH/config";
String output;
JsonDocument doc;
doc["device_class"] = "humidity";
doc["name"] = "Humidity";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["unit_of_measurement"] = "%";
doc["value_template"] = "{{ value_json.humidity}}";
doc["unique_id"] = "hum01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Update_Value(float temp,float hum)
{
String topics="homeassistant/sensor/sensorroom/state";
JsonDocument doc;
doc["temperature"] = temp;
doc["humidity"] = hum;
String output;
doc.shrinkToFit(); // optional
serializeJson(doc, output);
if(mqttClient.publish(topics.c_str(), output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
结果展示:
结果也使用视频进行展示
感想
试用Arduino的经历是一次宝贵的学习之旅。它不仅让我掌握了电子技术和编程知识,更重要的是激发了我的创造力和想象力。我相信,在未来的日子里,Arduino将继续陪伴我探索未知、实现梦想。对于所有对电子技术和创新感兴趣的人来说,Arduino无疑是一个值得尝试和深入学习的平台。
代码链接
【Follow me第二季第2期】 所有任务代码-嵌入式开发相关资料下载-EEWORLD下载中心
-
发表了主题帖:
【Follow me第二季第2期】+通过外部SHT40温湿度传感器,上传温湿度到HA
本帖最后由 一只小跳帽 于 2024-9-22 22:31 编辑
今天来完成第四个任务通过外部SHT40温湿度传感器,上传温湿度到HA,通过HA面板显示数据
首先完成对SHT40 的采集
SHT4X是业内知名的Sensirion公司推出的第四代数字温湿度传感器系列。传承了盛思锐湿度和温度传感器在业界有口皆碑的质量和可靠性,SHT40 可在广阔的测量范围内提供始终如一的高精确度,为用户带来最佳的性价比。 SHT40凭借宽电源电压范围 (3.3 – 5 V) 和顶尖精度(±1.8% 相对湿度,±0.2℃),它拥有更出色的灵活性。 SHT40可以测量 0 至 100% 的相对湿度以及 -40℃ 至 125℃ 的温度,可以达到 ±1.8%的湿度精度和 ±0.2℃温度精度。 SHT40集成可变功率加热器,在冷凝环境也可以正常工作。 凭借 3.3 V 至 5 V 的宽电源电压以及低于0.15mA的低功耗模式,SHTC40非常适合移动和电池驱动型应用,可以非常容易的集成到智能楼宇、天气站、仓库存储、养殖、孵化等应用场景中
首先获取温湿度数据这里要注意的是我们使用的 是Qwiic Connector 所以使用的是IIC2端口因此很多例子我们不能使用比如Adafruit_SHT4x 库中例子他就是使用的是IIC1 的接口
下面我们首先使用官方的历程进行读取数据
/***************************************************
This is an example for the SHT4x Humidity & Temp Sensor
Designed specifically to work with the SHT4x sensor from Adafruit
----> https://www.adafruit.com/products/4885
These sensors use I2C to communicate, 2 pins are required to
interface
****************************************************/
#include <Wire.h>
#include "Adafruit_SHT4x.h"
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
void setup() {
Serial.begin(115200);
while (!Serial)
delay(10); // will pause Zero, Leonardo, etc until serial console opens
Serial.println("Adafruit SHT4x test");
if (! sht4.begin(&Wire1)) {
Serial.println("Couldn't find SHT4x");
while (1) delay(1);
}
Serial.println("Found SHT4x sensor");
Serial.print("Serial number 0x");
Serial.println(sht4.readSerial(), HEX);
// You can have 3 different precisions, higher precision takes longer
sht4.setPrecision(SHT4X_HIGH_PRECISION);
switch (sht4.getPrecision()) {
case SHT4X_HIGH_PRECISION:
Serial.println("High precision");
break;
case SHT4X_MED_PRECISION:
Serial.println("Med precision");
break;
case SHT4X_LOW_PRECISION:
Serial.println("Low precision");
break;
}
// You can have 6 different heater settings
// higher heat and longer times uses more power
// and reads will take longer too!
sht4.setHeater(SHT4X_NO_HEATER);
switch (sht4.getHeater()) {
case SHT4X_NO_HEATER:
Serial.println("No heater");
break;
case SHT4X_HIGH_HEATER_1S:
Serial.println("High heat for 1 second");
break;
case SHT4X_HIGH_HEATER_100MS:
Serial.println("High heat for 0.1 second");
break;
case SHT4X_MED_HEATER_1S:
Serial.println("Medium heat for 1 second");
break;
case SHT4X_MED_HEATER_100MS:
Serial.println("Medium heat for 0.1 second");
break;
case SHT4X_LOW_HEATER_1S:
Serial.println("Low heat for 1 second");
break;
case SHT4X_LOW_HEATER_100MS:
Serial.println("Low heat for 0.1 second");
break;
}
}
void loop() {
sensors_event_t humidity, temp;
uint32_t timestamp = millis();
sht4.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data
timestamp = millis() - timestamp;
Serial.print("Temperature: "); Serial.print(temp.temperature); Serial.println(" degrees C");
Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); Serial.println("% rH");
Serial.print("Read duration (ms): ");
Serial.println(timestamp);
delay(1000);
}
我们查看串口打印的数据
Read duration (ms): 10
Temperature: 24.83 degrees C
Humidity: 54.25% rH
Read duration (ms): 10
串口正确的读到了温湿度
下面我们再结合上一个连接mqtt的例子进行上传数据这里我们上次设置的上传数据 的主题是homeassistant/sensor/sensorroom/state
那这里我们就再加一个函数用于上传数据
void Update_Value(float temp,float hum)
{
String topics="homeassistant/sensor/sensorroom/state";
JsonDocument doc;
doc["temperature"] = temp;
doc["humidity"] = hum;
String output;
doc.shrinkToFit(); // optional
serializeJson(doc, output);
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
这样我们就可以完整的上传我们需要的温湿度了下面我们来看一下结果
我们看到已经上传上去了 下面是完整的代码
//#include <WiFiS3.h>
#include <PubSubClient.h>
#include "Adafruit_SHT4x.h"
#include <Wire.h>
#include <WiFi.h>
#include "aaa.h"
#define ARDUINOJSON_SLOT_ID_SIZE 1
#define ARDUINOJSON_STRING_LENGTH_SIZE 1
#define ARDUINOJSON_USE_DOUBLE 0
#define ARDUINOJSON_USE_LONG_LONG 0
#include <ArduinoJson.h>
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* mqttServer = "broker.emqx.io";
IPAddress server(192,168,31 ,200);
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
void setup() {
Serial.begin(9600);
// 连接WiFi
connectWifi();
if (! sht4.begin(&Wire1 )) {
Serial.println("Couldn't find SHT4x");
while (1) delay(1);
}
// 设置MQTT服务器和端口号
mqttClient.setServer(server, 1883);
// 连接MQTT服务器
connectMQTTServer();
Register_temp();
Register_hum();
}
void loop() {
sensors_event_t humidity, temp;
sht4.getEvent(&humidity, &temp);
Serial.print("Temperature: "); Serial.print(temp.temperature); Serial.println(" degrees C");
Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); Serial.println("% rH");
if (mqttClient.connected()) { // 如果开发板成功连接服务器
Update_Value(temp.temperature,humidity.relative_humidity);
} else { // 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
void connectMQTTServer(){
String clientId = "UNOR4";
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
void connectWifi(){
WiFi.begin(ssid, password);
//等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
Serial.println(WiFi.localIP());
}
void Register_temp()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomT/config";
String output;
JsonDocument doc;
doc["device_class"] = "temperature";
doc["name"] = "Temperature";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["value_template"] = "{{ value_json.temperature}}";
doc["unique_id"] = "temp01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Register_hum()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomH/config";
String output;
JsonDocument doc;
doc["device_class"] = "humidity";
doc["name"] = "Humidity";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["unit_of_measurement"] = "%";
doc["value_template"] = "{{ value_json.humidity}}";
doc["unique_id"] = "hum01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Update_Value(float temp,float hum)
{
String topics="homeassistant/sensor/sensorroom/state";
JsonDocument doc;
doc["temperature"] = temp;
doc["humidity"] = hum;
String output;
doc.shrinkToFit(); // optional
serializeJson(doc, output);
if(mqttClient.publish(topics.c_str(), output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
下方的视频为详细讲解
-
上传了资料:
【Follow me第二季第2期】 所有任务代码
- 2024-09-18
-
发表了主题帖:
【Follow me第二季第2期】Arduino Uno R4 WiFi 通过MQTT连入Home Assistant
本帖最后由 一只小跳帽 于 2024-9-22 14:52 编辑
今天,我们将细致而有序地完成一个项目——搭建Home Assistant平台,并通过MQTT协议实现设备接入。整个过程将分为四个清晰、连贯的步骤,确保每一步都精准无误,助力您成功构建智能家居生态系统。
第一步:安装Home Assistant
首先,我们将着手安装Home Assistant。这一步骤是构建智能家居中枢的关键,Home Assistant以其强大的集成能力和灵活的自定义选项而闻名,能够轻松管理家中的各种智能设备。我们将按照官方指南或选择适合您操作系统的安装方法,确保Home Assistant能够稳定运行在您的设备上。
第二步:部署MQTT服务器
紧接着,我们将安装并配置MQTT服务器。MQTT,即消息队列遥测传输,是一种轻量级的、基于发布/订阅模式的消息协议,非常适合物联网(IoT)设备的通信。我们将选择一个稳定可靠的MQTT服务器软件(如Mosquitto),并根据实际需求进行配置,确保它能够高效、安全地处理来自智能家居设备的消息。
第三步:将Home Assistant连接到MQTT服务器
在成功部署MQTT服务器后,下一步是将Home Assistant与之连接起来。这一步骤至关重要,因为它将使得Home Assistant能够接收并处理来自MQTT服务器的消息,进而实现对智能家居设备的控制。我们将通过Home Assistant的配置界面,添加MQTT集成,并填写MQTT服务器的相关信息,完成连接设置。
第四步:使用MQTT客户端进行连接测试
为了确保MQTT服务器的正常运行以及Home Assistant与MQTT服务器的成功连接,我们将使用MQTT客户端工具进行连接测试。这一步骤不仅验证了通信链路的通畅性,还让我们有机会熟悉MQTT的基本操作,为后续的设备接入打下坚实基础。
最后一步:使用Arduino R4 WiFi开发板接入系统
作为整个项目的亮点,我们将使用Arduino R4 WiFi开发板,通过编程使其能够通过MQTT协议与Home Assistant进行通信。通过编写相应的代码,Arduino R4 WiFi将能够发送状态信息至MQTT服务器,并接收来自Home Assistant的控制指令,实现智能家居设备的智能化控制。这一步骤将充分展示MQTT在物联网应用中的强大潜力。
首先,我们将通过安装Linux虚拟机来启动我们的Home Assistant部署之旅,这里特别选择Ubuntu系统作为我们的基础平台。Ubuntu以其完善的生态系统、广泛的用户群体以及丰富的解决方案而著称,这使得它成为部署智能家居控制中心——Home Assistant的理想选择。
在准备阶段,我们将确保Ubuntu虚拟机被正确安装并配置,以便为后续步骤提供一个稳定、可靠的环境。通过选择Ubuntu,我们不仅能够享受到其强大的功能和灵活性,还能轻松获取到来自全球开发者和社区的支持与帮助,这对于解决在部署过程中可能遇到的各种问题至关重要。
接下来,我们将利用容器技术来安装和运行Home Assistant。容器化部署以其轻量级、可移植性和易于管理的特点,在现代软件开发和运维中得到了广泛应用。通过容器,我们可以将Home Assistant及其依赖项封装在一个独立的虚拟环境中,从而避免与系统其他部分的潜在冲突,并确保其稳定运行。
综上所述,选择Ubuntu系统作为Linux虚拟机的基础,并结合容器技术来安装Home Assistant,将为我们提供一个强大、灵活且易于维护的智能家居控制中心解决方案。
下面进行安装docker
#安装前先卸载操作系统默认安装的docker,
sudo apt-get remove docker docker-engine docker.io containerd runc
#安装必要支持
sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release
#添加 Docker 官方 GPG key (可能国内现在访问会存在问题)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 阿里源(推荐使用阿里的gpg KEY)
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
#添加 apt 源:
#Docker官方源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
#阿里apt源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
#更新源
sudo apt update
sudo apt-get update
#安装最新版本的Docker
sudo apt install docker-ce docker-ce-cli containerd.io
#等待安装完成
#查看Docker版本
sudo docker version
#查看Docker运行状态
sudo systemctl status docker
现在安装好docker 下面安装docker版的Home Assistant
docker search homeassistant
2、拉取docker镜像
docker pull homeassistant/home-assistant:latest
3、创建容器并运行
docker run -d --name="hass" -v /path/to/config:/config -v /etc/localtime:/etc/localtime:ro -p 8123:8123 --net
-d: 表示在后台运行容器。
--name my_home_assistant_instance: 指定容器的名称为 "my_home_assistant_instance",你可以根据需要修改。
-v /path/to/config:/config: 将本地配置目录映射到容器内的 /config 目录。确保将 /path/to/config 替换为你的 Home Assistant 配置目录的实际路径。
-v /etc/localtime:/etc/localtime:ro: 将主机的时间配置映射到容器内,保持容器和主机时间同步。
--net=host: 使用主机网络模式,使得容器可以直接使用主机的网络配
-p:映射端口(容器内的端口直接映射到本地主机端口最后便是刚才下载的镜像了,运行该容器。
注:这里启动docker容器之后每次启动docker容器都会数据初始化建议-v进行数据挂载
docker 查看并启动容器
//查看容器信息
docker ps -a
//docker 使用<container_id>启动容器
docker start <container_id>
//使用容器名称启动--name 参数指定容器的名称为 "my_container_instance"。my_image:tag 是你要启动的 Docker 镜像的名称和标签。
docker run --name my_container_instance my_image:tag
docker stop <container_id>
通过上面看到我们已经创建 了Home Assistant 的镜像下面进行启动
这里我们已经启动了Home Assistant 下面我们使用浏览器进行打开首先使用ifconfig 查看ip再使用端口8123 进行访问
现在安装好了 但是还没有mqtt服务因此不能使用mqtt连接下面安装mqtt服务器
1 认识EMQX
1.1 EMQX 简介
EMQX 是一款开源的大规模分布式 MQTT 消息服务器,功能丰富,专为物联网和实时通信应用而设计。EMQX 5.0 单集群支持 MQTT 并发连接数高达 1 亿条,单服务器的传输与处理吞吐量可达每秒百万级 MQTT 消息,同时保证毫秒级的低时延。
EMQX 支持多种协议,包括 MQTT (3.1、3.1.1 和 5.0)、HTTP、QUIC 和 WebSocket 等,保证各种网络环境和硬件设备的可访问性。EMQX 还提供了全面的 SSL/TLS 功能支持,比如双向认证以及多种身份验证机制,为物联网设备和应用程序提供可靠和高效的通信基础设施。
1.2 EMQX 版本类型
EMQX 有 4 种部署模式,包括两种云服务模式(EMQX Cloud Serverless 和 EMQX Cloud 专有版)和两种自托管模式(EMQX 开源版 和 EMQX 企业版)。以下表格列出了这些部署模式的对比,以帮助您根据业务需求进行选择。想进一步了解具体的功能对比,参考功能对比。
对于非企业级应用,使用EMQX开源版即可,本文也是以该版本作为Demo,介绍其搭建和使用方法。
2 Ubuntu搭建EMQX 平台
2.1 下载和安装
2.1.1 下载
下载地址:
https://www.emqx.io/zh/downloads
打开网站,选择安装环境:
2.1.2 安装
使用如下三个步骤在Ubuntu上安装EMQX:
Step 1: 从软件链接源下载软件包,并装载安装环境
curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash
下载完成后,可以看见如下log:
Step-2: 安装软件
sudo apt-get install emqx
Step-3: 运行软件
执行如下代码,如果没有任何信息打印出来,说明EMAX已经正常启动了
sudo systemctl start emqx
2.2 查看运行端口
和EMQX相关的端口有如下这些:
端口号 介绍
1883 MQTT 协议端口
8883 MQTT/SSL 端口
8083 MQTT/WebSocket 端口
8080 HTTP API 端口
18083 Dashboard 管理控制台端口
查看EMQX的运行端口:
netstat -ap | grep 18083
查看MQTT 协议端口:
netstat -ap | grep 1883
3 运行Dashboard 管理控制台
要在第三方终端上运行Dashboard 管理控制台,必须保证18083端口运行被访问,那么怎么做呢?检查该端口是否被允许外网访问。
3.1 查看Ubuntu上的防火墙
首先确保ufw 已经安装在当前的Ubuntu系统中,如果没有安装,使用如下命令:
sudo apt update
sudo apt install ufw
一种最简单的方式就是关闭防火墙,如果允许外网访问,不建议这样做:
sudo ufw disable
3.2 运行Dashboard 管理控制台
step-1: 查看当前Ubuntu主机的ip,使用命令:
ifconfig
执行命令后,终端会打印当前主机所有网卡相关的IP信息,找到主机的实际ip,然后在第三方电脑主机上登录
step-2: 登录服务器
打开浏览器,输入相应IP和端口号,就能打开网页了。举个例子,以笔者本人测试主机为例
192.168.1.11:18083
如果安装成功,可以看见如下页面,说明EMQX可以正常工作了。
第三步使用 Home Assistant 连接到mqtt 服务器
在设置的设备与服务中搜索mqtt 进行配置 ip地址就是自己本地的ip 端口使用的是mqtt统一端口1883 密码和用户名使用自己设置的就可以设置成功后就可以看到集群中连接的设备和订阅的主题
这个就是Home Assistant 的设备
下面我们先使用mqtt的客户端连接到mqtt服务器 这里我使用的是mqtt客户端工具
使用这个工具我们可以很简单的模拟我们的开发板如何配置mqtt和发送的主题
首先我们先配置端口ip地址然后进行连接
然后点击连接在浏览器上查看节点是否连接成功
这里看到已经有两个节点了有一个就是我们刚刚连接的客户端
下面我们要把这个设备连接到我们的 Home Assistant 中这里很多人选择直接配置设备文件但是我发现这种方法十分的不麻烦而且也不利于我们后续的设备自己连接函数需要我们一个一个的配置下面我使用的方法是使用设置自动的添加到我们这个系统中 只需要向特定的主题发送对应的内容就可以下面我来试一下
这里我们选择配置成我们下一个任务的 温湿度传感器类型进行发现
这里看到我们已经成功的添加了一个设备并且配置他的两个状态为 一个是温度一个是湿度下面我们对其进行发送状态
这个已经有数值了 下面我们先通过开发板连接上mqtt 下一节我们再上传报文
首先我们使用PubSubClient 库进行连接下面简单介绍一下连接的流程首先创建一个实体类
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
后续的所有操作都是使用的这个实体
下一步进行连接到mqtt服务器
void connectMQTTServer(){
String clientId = "UNOR4";
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
后面再进行学习上面的mqtt客户端发送给服务器的主题和内容进行发送让Home Assistant 自发现的方式
注意我后面因为网关的原因自己进行配置了一次网络
void Register_temp()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomT/config";
String output;
JsonDocument doc;
doc["device_class"] = "temperature";
doc["name"] = "Temperature";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["value_template"] = "{{ value_json.temperature}}";
doc["unique_id"] = "temp01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Register_hum()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomH/config";
String output;
JsonDocument doc;
doc["device_class"] = "humidity";
doc["name"] = "Humidity";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["unit_of_measurement"] = "%";
doc["value_template"] = "{{ value_json.humidity}}";
doc["unique_id"] = "hum01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
运行后已经连接上了
到这里已经完全了下面是整个的代码
//#include <WiFiS3.h>
#include <PubSubClient.h>
#include <WiFi.h>
#define ARDUINOJSON_SLOT_ID_SIZE 1
#define ARDUINOJSON_STRING_LENGTH_SIZE 1
#define ARDUINOJSON_USE_DOUBLE 0
#define ARDUINOJSON_USE_LONG_LONG 0
#include <ArduinoJson.h>
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "aaaa";
const char* password = "aaaaaaaa";
const char* mqttServer = "broker.emqx.io";
IPAddress server(192,168,31 ,200);
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
void setup() {
Serial.begin(9600);
// 连接WiFi
connectWifi();
// 设置MQTT服务器和端口号
mqttClient.setServer(server, 1883);
// 连接MQTT服务器
connectMQTTServer();
Register_temp();
Register_hum();
}
void loop() {
if (mqttClient.connected()) { // 如果开发板成功连接服务器
mqttClient.loop(); // 保持客户端心跳
} else { // 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
void connectMQTTServer(){
String clientId = "UNOR4";
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.println("Server Address: ");
Serial.println(mqttServer);
Serial.println("ClientId:");
Serial.println(clientId);
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
void connectWifi(){
WiFi.begin(ssid, password);
//等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
Serial.println(WiFi.localIP());
}
void Register_temp()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomT/config";
String output;
JsonDocument doc;
doc["device_class"] = "temperature";
doc["name"] = "Temperature";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["value_template"] = "{{ value_json.temperature}}";
doc["unique_id"] = "temp01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
void Register_hum()
{
// Stream& output;
const char * topics="homeassistant/sensor/sensorBedroomH/config";
String output;
JsonDocument doc;
doc["device_class"] = "humidity";
doc["name"] = "Humidity";
doc["state_topic"] = "homeassistant/sensor/sensorroom/state";
doc["unit_of_measurement"] = "%";
doc["value_template"] = "{{ value_json.humidity}}";
doc["unique_id"] = "hum01ae";
JsonObject device = doc["device"].to<JsonObject>();
device["identifiers"][0] = "room01";
device["name"] = "room";
doc.shrinkToFit(); // optional
serializeJson(doc, output);
Serial.println(output);
Serial.println(output.length());
if(mqttClient.publish(topics, output.c_str())){
Serial.println("Publish topic:");Serial.println(topics);
Serial.println("Publish message:");Serial.println(output);
} else {
Serial.println("Message Publish Failed.");
}
}
- 2024-09-07
-
发表了主题帖:
【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值的改变下面的视频为详细教程和结果和一些其他的不太好纸面介绍的知识
-
加入了学习《【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印》,观看 【Follow me第二季第2期】实现任务二 驱动12x8点阵LED;用DAC生成正弦波并放大采集
- 2024-09-02
-
回复了主题帖:
【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印
秦天qintian0303 发表于 2024-9-1 18:16
自带串口调试还是不错的,用外部串口调试工具应该也可以吧?
外部也可以但是要注意的是这个和以往的r3是不同的以往USBa和rt串口都是serial但是r4是typec是serial 端口可能是serial1这个还没确定需要试一下看但是我想的最好还是使用debug但是我还没有实现后续成功了在这里分享
- 2024-09-01
-
发表了主题帖:
【Follow me第二季第2期】+开发板硬件介绍和实现任务一 LED灯闪烁和串口打印
本帖最后由 eew_ljd6R2 于 2024-9-1 15:14 编辑
大家好!很高兴能与大家分享关于我们申请到的Arduino UNO R4 WiFi开发板的硬件资源。这款开发板在保留了Arduino UNO R3大部分接口的基础上,进行了一些升级并增加了新的功能。
与R3的兼容性:它保留了与R3相同的大部分接口,这意味着我们可以很方便地在R3和R4之间进行切换,同时兼容大部分为R3设计的扩展板和项目。
新增CAN通信接口:最显著的是增加了CAN(Controller Area Network)通信接口。CAN是一种常用的工业通信协议,广泛应用于汽车、工业控制等领域。通过增加CAN接口,我们可以模拟更多的功能,实现与各种CAN设备的通信和控制。
实现模拟输出:这款开发板还增加了DAC(Digital-to-Analog Converter,数字到模拟转换器)接口,实现了模拟输出功能。这使得Arduino UNO R4 WiFi不仅能够处理数字信号,还能处理模拟信号,进一步扩展了其应用范围。
总的来说,Arduino UNO R4 WiFi开发板在保留了原有功能的基础上,通过增加CAN通信接口和DAC接口,提供了更丰富的硬件资源和更广泛的应用场景。无论是对于初学者还是对于需要进行复杂项目开发的用户来说,都是一个非常不错的选择。
下面我们通过原理图来详细查看。本次Arduino UNO R4 WiFi开发板增加了USB Type-C接口,这是一个重要的升级。通过USB Type-C接口,开发板可以实现直接模拟串口进行通信,这与以往将P1和P2引出到USB A型接口的设计有所不同。因此,在设计时我们需要格外注意这一点。
另外,新增加的LED矩阵也是本次升级的一个亮点。这个LED矩阵使得开发板可以显示更多的信息,为用户提供了更加丰富的视觉反馈和交互体验。
总的来说,通过原理图我们可以清晰地看到Arduino UNO R4 WiFi开发板在硬件资源上的升级和增加的新功能。这些改进不仅提升了开发板的性能,还扩展了其应用场景,为用户带来了更多的便利和可能性。
在关注Arduino UNO R4 WiFi开发板时,我们特别注意到其上的瑞萨MCU是5V芯片,而ESP32则是3.3V芯片。由于这两种芯片的电压要求不同,因此需要使用电平转换芯片来进行通信,以确保信号的稳定传输。此外,这两个芯片之间的通信是通过串口来实现的,这使得它们能够方便地交换数据和信息。在设计时,我们需要特别注意电平转换芯片的选择和使用,以确保开发板的正常工作和稳定性。
下面我们来完成本次任务1:blink。通过原理图,我们可以看到使用13端口来控制黄色的LED灯进行闪烁。
首先,我们需要配置13端口为输出模式,这样我们才能通过它来控制LED灯的亮灭。在Arduino编程中,我们可以使用pinMode()函数来设置端口模式,使用digitalWrite()函数来控制端口的电平。
接下来,在loop()循环中,我们使用delay()函数进行延迟,先延迟1000毫秒(即1秒),然后点亮LED灯;再延迟1000毫秒,熄灭LED灯。这样,LED灯就会以1秒的间隔进行闪烁。
以下是实现该功能的Arduino代码示例:
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
效果如下
接下来我们来完成第二个任务:打印“Hello EEWorld!”。这个任务相对简单,我们只需要在loop()循环中每隔一秒通过串口打印一次即可。
首先,我们需要确保已经通过USB Type-C接口将开发板连接到电脑,并在Arduino IDE中选择正确的串口。
然后,在setup()函数中,我们使用Serial.begin()函数来初始化串口通信,设置波特率为9600(或其他你想要的波特率)。
在loop()函数中,我们使用Serial.println()函数来打印“Hello EEWorld!”,并使用delay()函数来每隔一秒打印一次。
以下是实现该功能的Arduino代码示例:
void setup() {
// 初始化串口通信,设置波特率为9600
Serial.begin(9600);
}
void loop() {
// 每隔一秒打印一次“Hello EEWorld!”
Serial.println("Hello EEWorld!");
delay(1000);
}
将上述代码上传到Arduino UNO R4 WiFi开发板后,打开Arduino IDE的串口监视器,设置正确的波特率,你就可以看到每隔一秒就打印一次“Hello EEWorld!”了。这样,我们就完成了第二个任务。
下面我们把两个都结合起来代码如下
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println("Hello EEWorld!");
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
初次接触Arduino R4开发板,我深感其魅力无穷。这款开发板不仅继承了Arduino系列一贯的易用性和灵活性,还在硬件资源上进行了显著的升级和扩展。
在使用过程中,我深刻体会到了Arduino R4的强大功能。新增的USB Type-C接口让我能够更方便地与电脑进行通信和数据传输,而无需担心接口不兼容的问题。同时,开发板上的LED矩阵也为我提供了丰富的视觉反馈,使得调试和交互变得更加直观和便捷。
在编程方面,Arduino R4同样表现出色。我使用了Arduino IDE进行代码编写和上传,整个过程非常顺畅。通过简单的代码,我就能够控制开发板上的各种硬件资源,实现各种有趣的功能和效果。
当然,初次使用Arduino R4也遇到了一些挑战。比如,我需要适应新的硬件资源和接口,了解它们的工作原理和使用方法。但是,通过查阅资料和不断实践,我逐渐掌握了这些新知识,并成功完成了多个有趣的项目。
总的来说,初次使用Arduino R4开发板是一次非常愉快和充实的经历。我不仅学到了很多新知识,还感受到了Arduino系列开发板的无限魅力和潜力。我相信,在未来的学习和实践中,我会更加深入地了解和掌握这款开发板,创造出更多有趣和实用的项目。
下面的视频为这个项目的整个过程